Real-ESRGANによる動画品質改善、顔復元(ソースコードと実行結果)

画質改善前

画質改善後
Python開発環境,ライブラリ類
ここでは、最低限の事前準備について説明する。機械学習や深層学習を行う場合は、NVIDIA CUDA、Visual Studio、Cursorなどを追加でインストールすると便利である。これらについては別ページ https://www.kkaneko.jp/cc/dev/aiassist.htmlで詳しく解説しているので、必要に応じて参照してください。
Python 3.12 のインストール
インストール済みの場合は実行不要。
管理者権限でコマンドプロンプトを起動(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行する。管理者権限は、wingetの--scope machineオプションでシステム全体にソフトウェアをインストールするために必要である。
REM Python をシステム領域にインストール
winget install --scope machine --id Python.Python.3.12 -e --silent
REM Python のパス設定
set "PYTHON_PATH=C:\Program Files\Python312"
set "PYTHON_SCRIPTS_PATH=C:\Program Files\Python312\Scripts"
echo "%PATH%" | find /i "%PYTHON_PATH%" >nul
if errorlevel 1 setx PATH "%PATH%;%PYTHON_PATH%" /M >nul
echo "%PATH%" | find /i "%PYTHON_SCRIPTS_PATH%" >nul
if errorlevel 1 setx PATH "%PATH%;%PYTHON_SCRIPTS_PATH%" /M >nul
【関連する外部ページ】
Python の公式ページ: https://www.python.org/
AI エディタ Windsurf のインストール
Pythonプログラムの編集・実行には、AI エディタの利用を推奨する。ここでは,Windsurfのインストールを説明する。
管理者権限でコマンドプロンプトを起動(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行して、Windsurfをシステム全体にインストールする。管理者権限は、wingetの--scope machineオプションでシステム全体にソフトウェアをインストールするために必要となる。
winget install --scope machine Codeium.Windsurf -e --silent
【関連する外部ページ】
Windsurf の公式ページ: https://windsurf.com/
必要なライブラリのインストール
コマンドプロンプトを管理者として実行(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行する
pip install -U torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126
pip install basicsr opencv-python pillow numpy requests scikit-image
Real-ESRGAN動画品質改善、顔復元プログラム
AIの基本的な能力と動画編集応用
AIの基本的な能力の1つは、既存のデータから学習したパターンや特徴を理解し、新しい表現を生成することである。AIによる画像・動画処理技術の発展により、専門的な技術や経験がなくても、効率的な品質改善処理が可能となった。
本プログラムの技術的位置づけ
本プログラムは、Real-ESRGAN(Real-World Enhanced Super-Resolution Generative Adversarial Network)技術を用いた動画品質改善システムである。Real-ESRGANは、実世界の劣化画像に対応するため純粋な合成データで訓練された超解像度技術である。
プログラムは以下の技術を使用している。
- RRDBNet(Residual-in-Residual Dense Block Network):23層のRRDBブロックによる高品質超解像度化
- フレーム単位処理:動画を個別フレームに分解し、各フレームに超解像度処理を適用
- タイリング処理:大画像対応とVRAM制限回避のための分割処理
参考文献
[1] X. Wang, L. Xie, C. Dong, Y. Shan, "Real-ESRGAN: Training Real-World Blind Super-Resolution with Pure Synthetic Data," in Proc. IEEE/CVF International Conference on Computer Vision Workshops (ICCVW), 2021, pp. 1905-1914.
ソースコード
# プログラム名: Real-ESRGAN動画品質改善・顔復元プログラム
# 特徴技術名: Real-ESRGAN (Real-World Enhanced Super-Resolution Generative Adversarial Network)
# 出典: Wang, X., Xie, L., Dong, C., & Shan, Y. (2021). Real-ESRGAN: Training Real-World Blind Super-Resolution with Pure Synthetic Data. In Proceedings of the IEEE/CVF International Conference on Computer Vision (pp. 1905-1914).
# 特徴機能: 実世界劣化に対応した高品質超解像度化。High-order degradation modelingにより、ノイズ、ぼけ、JPEG圧縮アーティファクト等の複雑な劣化を考慮した超解像度化を実現
# 学習済みモデル: RealESRGAN_x4plus(汎用4倍超解像度、23 RRDB構造、実写画像向け)URL: https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth、RealESRGAN_x4plus_anime_6B(アニメ特化、6 RRDB構造、アニメ画像向け)URL: https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.2.4/RealESRGAN_x4plus_anime_6B.pth
# 方式設計:
# - 関連利用技術:
# * BasicSR(画像復元ツールボックス)- Real-ESRGANの基盤フレームワーク、RRDBNetアーキテクチャ提供
# * OpenCV(コンピュータビジョンライブラリ)- 動画読み込み、フレーム処理、動画出力
# * PIL(画像処理ライブラリ)- RGB/BGR色空間変換とNumPy配列との相互変換
# * FFmpeg(マルチメディア処理ツール)- 動画音声の抽出・結合
# * scikit-image(画像品質評価)- PSNR・SSIM品質指標計算
# - 入力と出力: 入力: 動画(ユーザは「0:動画ファイル,1:カメラ,2:サンプル動画」のメニューで選択.0:動画ファイルの場合はtkinterでファイル選択.1の場合はOpenCVでカメラが開く.2の場合はhttps://github.com/opencv/opencv/blob/master/samples/data/vtest.aviを使用)、出力: 処理結果が画像化できる場合にはOpenCV画面でリアルタイムに表示.OpenCV画面内に処理結果をテキストで表示.さらに,1秒間隔で,print()で処理結果を表示.プログラム終了時にprint()で表示した処理結果をresult.txtファイルに保存し,「result.txtに保存」したことをprint()で表示.
# - 処理手順:
# 1. 入力動画から音声を抽出(FFmpeg利用可能時)
# 2. 動画をフレーム単位で読み込み
# 3. 各フレームをReal-ESRGANで超解像度化
# 4. 処理済みフレームをOpenCV画面に表示
# 5. 元の音声と超解像度化動画を結合(FFmpeg利用可能時)
# 6. 品質評価指標(PSNR・SSIM)を計算
# - 前処理、後処理:
# * 色空間変換(BGR→RGB→BGR)によるReal-ESRGAN適合性確保
# * タイリング処理による大画像対応とVRAM制限回避
# * outscaleパラメータによるLANCZOS4後処理リサイズ
# - 追加処理:
# * 半精度推論(fp16)による推論効率化
# * GPU/CPUフォールバック機能による安定性確保
# * システム環境に応じた設定自動最適化
# - 調整を必要とする設定値:
# * SCALE_FACTOR(2または4、解像度向上倍率、高倍率ほど処理時間増加)
# * MODEL_NAME(RealESRGAN_x4plus/RealESRGAN_x4plus_anime_6B、画像特性による選択)
# * TILE_SIZE(自動最適化、VRAM容量に応じた調整、0で無効化)
# 将来方策: SCALE_FACTORの自動決定のため、入力動画の最初の数フレームで複数のスケール(2,4)を試行し、PSNR/SSIM値が最も高いスケールを自動選択する機能
# その他の重要事項: FFmpegが必要(音声保持機能用)。Windows環境での動作を前提とし、Linux/macOS固有機能は使用しない
# 前準備: pip install -U torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126
# pip install basicsr opencv-python pillow numpy requests scikit-image
import torch
import cv2
import numpy as np
import os
import requests
import subprocess
from PIL import Image
from pathlib import Path
from skimage.metrics import structural_similarity as ssim
from skimage.metrics import peak_signal_noise_ratio as psnr
import warnings
import tkinter as tk
from tkinter import filedialog
import urllib.request
import time
warnings.filterwarnings('ignore')
# ファイル名・パス関連の定数
WEIGHTS_DIR = 'weights'
RESULT_FILE = 'result.txt'
TEMP_AUDIO_FILE = 'temp_audio.wav'
TEMP_VIDEO_FILE = 'temp_enhanced.mp4'
OUTPUT_VIDEO_FILE = 'enhanced_output.mp4'
SAMPLE_VIDEO_FILENAME = 'vtest.avi'
# 表示関連の定数
FONT_SIZE = 1
FONT_COLOR = (0, 255, 0)
FONT_THICKNESS = 2
TEXT_POSITION = (10, 30)
# 調整可能な設定値
SCALE_FACTOR = 4 # 拡大倍率(2または4を推奨)
MODEL_NAME = 'RealESRGAN_x4plus' # モデル名("RealESRGAN_x4plus"または"RealESRGAN_x4plus_anime_6B")
# プログラム開始時の説明
print('=== Real-ESRGAN動画品質改善プログラム ===')
print('このプログラムは、Real-ESRGANを使用して動画の超解像度化を行います。')
print('ノイズ除去、ぼけ改善、JPEG圧縮アーティファクト除去などの機能があります。')
print('\n操作方法:')
print('- 動画処理中はqキーで終了できます')
print('- 処理結果はリアルタイムでOpenCV画面に表示されます')
print('- 処理完了後、品質評価結果がresult.txtに保存されます\n')
# FFmpeg利用可能性チェック
def check_ffmpeg():
try:
subprocess.run(['ffmpeg', '-version'], capture_output=True, check=True)
return True
except (subprocess.CalledProcessError, FileNotFoundError):
return False
FFMPEG_AVAILABLE = check_ffmpeg()
if not FFMPEG_AVAILABLE:
print('注意: FFmpegが見つかりません。音声は保持されません。')
print('音声保持機能を使用するには、FFmpegをインストールしてください: https://ffmpeg.org/download.html\n')
# システム環境に応じた設定の自動最適化
if torch.cuda.is_available():
gpu_memory = torch.cuda.get_device_properties(0).total_memory / 1024**3
if gpu_memory >= 8:
TILE_SIZE = 512
USE_HALF = True
elif gpu_memory >= 4:
TILE_SIZE = 256
USE_HALF = True
else:
TILE_SIZE = 128
USE_HALF = False
else:
TILE_SIZE = 64
USE_HALF = False
# モデルURL設定
MODEL_URLS = {
'RealESRGAN_x4plus': 'https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth',
'RealESRGAN_x4plus_anime_6B': 'https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.2.4/RealESRGAN_x4plus_anime_6B.pth'
}
# BasicSR代替実装用の関数
def create_rrdbnet_model(num_block):
"""BasicSRが利用できない場合のRRDBNetモデル作成"""
import torch.nn as nn
import torch.nn.functional as F
class ResidualDenseBlock(nn.Module):
def __init__(self, num_feat=64, num_grow_ch=32):
super(ResidualDenseBlock, self).__init__()
self.conv1 = nn.Conv2d(num_feat, num_grow_ch, 3, 1, 1)
self.conv2 = nn.Conv2d(num_feat + num_grow_ch, num_grow_ch, 3, 1, 1)
self.conv3 = nn.Conv2d(num_feat + 2 * num_grow_ch, num_grow_ch, 3, 1, 1)
self.conv4 = nn.Conv2d(num_feat + 3 * num_grow_ch, num_grow_ch, 3, 1, 1)
self.conv5 = nn.Conv2d(num_feat + 4 * num_grow_ch, num_feat, 3, 1, 1)
self.lrelu = nn.LeakyReLU(negative_slope=0.2, inplace=True)
def forward(self, x):
x1 = self.lrelu(self.conv1(x))
x2 = self.lrelu(self.conv2(torch.cat((x, x1), 1)))
x3 = self.lrelu(self.conv3(torch.cat((x, x1, x2), 1)))
x4 = self.lrelu(self.conv4(torch.cat((x, x1, x2, x3), 1)))
x5 = self.conv5(torch.cat((x, x1, x2, x3, x4), 1))
return x5 * 0.2 + x
class RRDB(nn.Module):
def __init__(self, num_feat, num_grow_ch=32):
super(RRDB, self).__init__()
self.rdb1 = ResidualDenseBlock(num_feat, num_grow_ch)
self.rdb2 = ResidualDenseBlock(num_feat, num_grow_ch)
self.rdb3 = ResidualDenseBlock(num_feat, num_grow_ch)
def forward(self, x):
out = self.rdb1(x)
out = self.rdb2(out)
out = self.rdb3(out)
return out * 0.2 + x
class RRDBNet(nn.Module):
def __init__(self, num_in_ch=3, num_out_ch=3, num_feat=64, num_block=23, num_grow_ch=32, scale=4):
super(RRDBNet, self).__init__()
self.scale = scale
self.conv_first = nn.Conv2d(num_in_ch, num_feat, 3, 1, 1)
self.body = nn.Sequential(*[RRDB(num_feat, num_grow_ch) for _ in range(num_block)])
self.conv_body = nn.Conv2d(num_feat, num_feat, 3, 1, 1)
self.conv_up1 = nn.Conv2d(num_feat, num_feat, 3, 1, 1)
self.conv_up2 = nn.Conv2d(num_feat, num_feat, 3, 1, 1)
self.conv_hr = nn.Conv2d(num_feat, num_feat, 3, 1, 1)
self.conv_last = nn.Conv2d(num_feat, num_out_ch, 3, 1, 1)
self.lrelu = nn.LeakyReLU(negative_slope=0.2, inplace=True)
def forward(self, x):
feat = self.conv_first(x)
body_feat = self.conv_body(self.body(feat))
feat = feat + body_feat
feat = self.lrelu(self.conv_up1(F.interpolate(feat, scale_factor=2, mode='nearest')))
feat = self.lrelu(self.conv_up2(F.interpolate(feat, scale_factor=2, mode='nearest')))
out = self.conv_last(self.lrelu(self.conv_hr(feat)))
return out
return RRDBNet(num_in_ch=3, num_out_ch=3, num_feat=64, num_block=num_block, num_grow_ch=32, scale=4)
# BasicSRのインポート試行
try:
from basicsr.archs.rrdbnet_arch import RRDBNet
from basicsr.utils.download_util import load_file_from_url
except ImportError:
# BasicSRが利用できない場合の代替実装
def load_file_from_url(url, model_dir, progress=True, file_name=None):
os.makedirs(model_dir, exist_ok=True)
if file_name is None:
file_name = url.split('/')[-1]
file_path = os.path.join(model_dir, file_name)
if os.path.exists(file_path):
return file_path
response = requests.get(url, stream=True)
with open(file_path, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
if chunk:
f.write(chunk)
return file_path
# Real-ESRGANエンハンサークラス
class RealESRGANer:
def __init__(self, scale, model_path, model, tile=0, tile_pad=10, pre_pad=0, half=True, device=None):
self.scale = scale
self.tile_size = tile
self.tile_pad = tile_pad
self.pre_pad = pre_pad
self.half = half
self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') if device is None else device
loadnet = torch.load(model_path, map_location=torch.device('cpu'))
if 'params_ema' in loadnet:
keyname = 'params_ema'
else:
keyname = 'params'
model.load_state_dict(loadnet[keyname], strict=True)
model.eval()
self.model = model.to(self.device)
if self.half and self.device.type == 'cuda':
self.model = self.model.half()
def enhance(self, img, outscale=None):
if outscale is None:
outscale = self.scale
img = img.astype(np.float32)
if np.max(img) > 256:
max_range = 65535
img = img / max_range
else:
max_range = 255
img = img / max_range
if len(img.shape) == 2:
img = np.expand_dims(img, axis=2)
if img.shape[2] == 4:
img = img[:, :, :3]
h, w = img.shape[0:2]
img = torch.from_numpy(np.transpose(img, (2, 0, 1))).float()
img = img.unsqueeze(0).to(self.device)
if self.half and self.device.type == 'cuda':
img = img.half()
# 自動GPU/CPUフォールバック機能(改善版)
try:
if torch.cuda.is_available() and self.device.type == 'cuda':
max_memory = torch.cuda.max_memory_allocated()
if max_memory > 0:
memory_used = torch.cuda.memory_allocated() / max_memory
else:
memory_used = torch.cuda.memory_allocated() / torch.cuda.get_device_properties(0).total_memory
if memory_used > 0.9:
img = img.cpu()
self.model = self.model.cpu()
self.device = torch.device('cpu')
with torch.no_grad():
output = self.model(img)
except RuntimeError as e:
if 'out of memory' in str(e).lower():
print('GPU メモリ不足、CPUに切り替えます...')
torch.cuda.empty_cache()
img = img.cpu()
self.model = self.model.cpu()
self.device = torch.device('cpu')
with torch.no_grad():
output = self.model(img)
else:
raise e
output = output.data.squeeze().float().cpu().clamp_(0, 1).numpy()
output = np.transpose(output, (1, 2, 0))
if outscale != self.scale:
output = cv2.resize(output, (int(w * outscale), int(h * outscale)), interpolation=cv2.INTER_LANCZOS4)
output = (output * max_range).round().astype(np.uint8)
return output, None
# モデル自動ダウンロード
weights_dir = Path(WEIGHTS_DIR)
weights_dir.mkdir(exist_ok=True)
model_path = weights_dir / f'{MODEL_NAME}.pth'
if not model_path.exists():
print(f'モデル {MODEL_NAME} をダウンロード中...')
url = MODEL_URLS[MODEL_NAME]
load_file_from_url(url, model_dir=str(weights_dir), progress=True, file_name=f'{MODEL_NAME}.pth')
# モデル初期化
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
if 'anime' in MODEL_NAME:
try:
model = RRDBNet(num_in_ch=3, num_out_ch=3, num_feat=64, num_block=6, num_grow_ch=32, scale=4)
except NameError:
model = create_rrdbnet_model(6)
else:
try:
model = RRDBNet(num_in_ch=3, num_out_ch=3, num_feat=64, num_block=23, num_grow_ch=32, scale=4)
except NameError:
model = create_rrdbnet_model(23)
upsampler = RealESRGANer(
scale=4,
model_path=str(model_path),
model=model,
tile=TILE_SIZE,
tile_pad=10,
pre_pad=0,
half=USE_HALF,
device=device
)
# 処理結果記録用
processing_results = []
last_print_time = time.time()
def video_processing(frame, frame_count, fps):
global last_print_time
# フレーム改善
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
enhanced_frame, _ = upsampler.enhance(np.array(frame_rgb), outscale=SCALE_FACTOR)
enhanced_frame_bgr = cv2.cvtColor(enhanced_frame, cv2.COLOR_RGB2BGR)
# 処理情報を画面に表示
info_text = f'Frame: {frame_count} | FPS: {fps:.1f} | Scale: {SCALE_FACTOR}x'
cv2.putText(enhanced_frame_bgr, info_text, TEXT_POSITION, cv2.FONT_HERSHEY_SIMPLEX,
FONT_SIZE, FONT_COLOR, FONT_THICKNESS)
# 1秒間隔でprint出力
current_time = time.time()
if current_time - last_print_time >= 1.0:
result_text = f'処理中 - フレーム: {frame_count}, 解像度: {frame.shape[1]}x{frame.shape[0]} → {enhanced_frame_bgr.shape[1]}x{enhanced_frame_bgr.shape[0]}'
print(result_text)
processing_results.append(result_text)
last_print_time = current_time
return enhanced_frame_bgr
# 入力選択
print('0: 動画ファイル')
print('1: カメラ')
print('2: サンプル動画')
choice = input('選択: ')
temp_file = None
if choice == '0':
root = tk.Tk()
root.withdraw()
path = filedialog.askopenfilename()
if not path:
exit()
cap = cv2.VideoCapture(path)
elif choice == '1':
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
elif choice == '2':
# サンプル動画ダウンロード・処理
url = 'https://github.com/opencv/opencv/raw/master/samples/data/vtest.avi'
try:
urllib.request.urlretrieve(url, SAMPLE_VIDEO_FILENAME)
temp_file = SAMPLE_VIDEO_FILENAME
cap = cv2.VideoCapture(SAMPLE_VIDEO_FILENAME)
except Exception as e:
print(f'動画のダウンロードに失敗しました: {url}')
print(f'エラー: {e}')
exit()
else:
print('無効な選択です')
exit()
# 動画情報取得
fps = int(cap.get(cv2.CAP_PROP_FPS))
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
original_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
original_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
# 音声抽出(動画ファイルの場合のみ)
has_audio = False
if choice != '1' and FFMPEG_AVAILABLE:
if choice == '0':
audio_source = path
else:
audio_source = SAMPLE_VIDEO_FILENAME
audio_cmd = ['ffmpeg', '-i', audio_source, '-vn', '-acodec', 'copy', TEMP_AUDIO_FILE, '-y']
has_audio = subprocess.run(audio_cmd, capture_output=True).returncode == 0
# 出力動画設定(動画ファイルの場合のみ)
if choice != '1':
output_width = original_width * SCALE_FACTOR
output_height = original_height * SCALE_FACTOR
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
temp_video = TEMP_VIDEO_FILE if has_audio else OUTPUT_VIDEO_FILE
out = cv2.VideoWriter(temp_video, fourcc, fps, (output_width, output_height))
print(f'\n動画処理開始')
print(f'解像度: {original_width}x{original_height} → {original_width * SCALE_FACTOR}x{original_height * SCALE_FACTOR}')
print(f'自動最適化設定: タイルサイズ={TILE_SIZE}, 半精度={USE_HALF}')
print(f'デバイス: {device}\n')
# メイン処理
frame_count = 0
try:
while True:
cap.grab()
ret, frame = cap.retrieve()
if not ret:
break
frame_count += 1
processed_frame = video_processing(frame, frame_count, fps)
# リアルタイム表示
cv2.imshow('Real-ESRGAN Enhanced Video', processed_frame)
# 動画ファイルへの書き込み(カメラ以外)
if choice != '1':
out.write(processed_frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
finally:
cap.release()
cv2.destroyAllWindows()
if choice != '1':
out.release()
if temp_file:
os.remove(temp_file)
# 音声結合(動画ファイルの場合のみ)
if choice != '1' and has_audio and FFMPEG_AVAILABLE:
merge_cmd = ['ffmpeg', '-i', temp_video, '-i', TEMP_AUDIO_FILE, '-c:v', 'copy', '-c:a', 'aac', OUTPUT_VIDEO_FILE, '-y']
if subprocess.run(merge_cmd, capture_output=True).returncode == 0:
os.remove(temp_video)
os.remove(TEMP_AUDIO_FILE)
print('音声結合完了')
else:
if temp_video != OUTPUT_VIDEO_FILE:
os.rename(temp_video, OUTPUT_VIDEO_FILE)
if os.path.exists(TEMP_AUDIO_FILE):
os.remove(TEMP_AUDIO_FILE)
print('音声結合失敗、動画のみ保存')
# 品質評価(動画ファイルの場合のみ)
if choice != '1' and os.path.exists(OUTPUT_VIDEO_FILE):
print('\n品質評価中...')
if choice == '0':
original_path = path
else:
original_path = SAMPLE_VIDEO_FILENAME
cap_original = cv2.VideoCapture(original_path)
cap_enhanced = cv2.VideoCapture(OUTPUT_VIDEO_FILE)
sample_frames = min(10, total_frames)
frame_indices = np.linspace(0, total_frames-1, sample_frames, dtype=int)
psnr_values = []
ssim_values = []
for frame_idx in frame_indices:
cap_original.set(cv2.CAP_PROP_POS_FRAMES, frame_idx)
cap_enhanced.set(cv2.CAP_PROP_POS_FRAMES, frame_idx)
ret1, frame_orig = cap_original.read()
ret2, frame_enh = cap_enhanced.read()
if ret1 and ret2:
original_resized = cv2.resize(frame_orig, (frame_enh.shape[1], frame_enh.shape[0]))
psnr_val = psnr(original_resized, frame_enh, data_range=255)
ssim_val = ssim(original_resized, frame_enh, channel_axis=2)
psnr_values.append(psnr_val)
ssim_values.append(ssim_val)
cap_original.release()
cap_enhanced.release()
if psnr_values:
avg_psnr = np.mean(psnr_values)
avg_ssim = np.mean(ssim_values)
quality_result = f'\n品質評価結果:\n平均PSNR: {avg_psnr:.2f} dB\n平均SSIM: {avg_ssim:.4f}'
print(quality_result)
processing_results.append(quality_result)
# 結果をファイルに保存
with open(RESULT_FILE, 'w', encoding='utf-8') as f:
f.write('=== Real-ESRGAN動画品質改善処理結果 ===\n')
f.write(f'モデル: {MODEL_NAME}\n')
f.write(f'スケール: {SCALE_FACTOR}x\n')
f.write(f'デバイス: {device}\n\n')
for result in processing_results:
f.write(result + '\n')
print(f'\n{RESULT_FILE}に保存しました')
print('処理完了')