ノイズ除去 (バイラテラルフィルタ)、彩度調整、ガンマ補正、コントラスト調整 (CLAHE)によるリアルタイム動画補正(ソースコードと説明と利用ガイド)
【概要】
このページのリアルタイム動画補正プログラムは、動画ファイルやウェブカメラ映像に対し、GUIスライダーを用いて、ノイズ除去 (バイラテラルフィルタ)、彩度調整、ガンマ補正、コントラスト調整 (CLAHE) を対話的に適用できるものである。調整したパラメータはプリセットとしてファイルに保存・読み込みが可能であり、画像処理の学習教材としての活用も意図している。
#Python #OpenCV #画像処理 #画像補正
【目次】
第1章 プログラム利用ガイド
1. このツールの利用シーン
このツールは、動画ファイルやウェブカメラの映像をリアルタイムで美しく補正するソフトウェアである。暗い映像を明るくしたり、色あせた映像を鮮やかにしたりといった調整を、結果を確認しながら直感的に行うことができる。ウェブ会議の映像改善や動画コンテンツの基本的な色補正などに活用される。
2. 主な機能
- リアルタイムプレビュー: 補正後の映像(左画面)と元の映像(右画面)を並べて表示し、効果を直接比較することができる。
- 多彩な画質調整: 以下の項目を専用ウィンドウのスライダーで調整することができる。
- ノイズ除去: 映像のざらつきを滑らかにする。
- 彩度: 色の鮮やかさを調整する。
- ガンマ: 映像全体の明るさを調整する。
- コントラスト: 明暗の差を調整し、よりはっきりとした映像にする。
- 設定の保存と読み込み: 調整した設定をファイル(preset.json)に保存し、いつでも呼び出すことができる。
- 動画ファイルへの保存: 調整後の映像を新しい動画ファイル(MP4形式)として保存することができる。
3. 基本的な使い方
- 起動と入力の選択
プログラムを起動すると、コマンドプロンプト(黒い画面)で入力方法を選択する画面が表示される。キーボードで0(動画ファイル)、1(ウェブカメラ)、2(サンプル動画)のいずれかを入力し、Enterキーを押す。
- 画質の調整
映像のプレビュー画面と「パラメータ調整」ウィンドウが表示される。「パラメータ調整」ウィンドウのスライダーを操作し、好みの画質になるよう調整する。変更はリアルタイムでプレビュー画面に反映される。
- 終了方法
映像が表示されているウィンドウを選択した状態でキーボードのqキーを押すか、「パラメータ調整」ウィンドウの「終了」ボタンをクリックする。
4. 便利な機能
- 設定の保存: 最適な設定が見つかった場合、「設定を保存」ボタンで記録することができる。
- 設定の読み込み: 次回使用時に「設定を読み込み」ボタンで以前の調整内容を再現することができる。
- 動画をファイルに保存: 調整結果を動画として保存したい場合は、「動画をファイルに保存...」ボタンを押す。(※カメラ映像ではこの機能は使用できない)
- 先頭に戻る: 動画ファイルの再生中にキーボードのrキーを押すと、映像の先頭に戻って再生をやり直すことができる。
第2章 Python開発環境,ライブラリ類
ここでは、最低限の事前準備について説明する。機械学習や深層学習を行う場合は、NVIDIA CUDA、Visual Studio、Cursorなどを追加でインストールすると便利である。これらについては別ページ https://www.kkaneko.jp/cc/dev/aiassist.htmlで詳しく解説しているので、必要に応じて参照してください。
第3章 Python 3.12 のインストール
Pythonのインストールを行い、Pythonのプログラムを実行する環境を整える。扱う環境は、Windows搭載パソコンである。金子研究室では、Python 3.12.10を推奨する。
[Windows での Python 3.12 のインストール手順を見るには、ここをクリック]
Windows での Python 3.12 のインストール
以下のいずれかの方法でPython 3.12をインストールする。Pythonがインストール済みの場合、この手順は不要である。
方法 1:winget によるインストール
【インストールコマンドの実行方法】
管理者権限でコマンドプロンプトを起動する(手順:Windowsキーまたはスタートメニュー → cmd と入力 → 右クリック → 「管理者として実行」)。そして、コマンド全体をコマンドプロンプトにコピー&ペーストする。
--scope machine を指定することで、システム全体(全ユーザー向け)にインストールされる。このオプションの実行には管理者権限が必要である。インストール完了後、コマンドプロンプトを再起動するとPATHが反映される。
REM Python 3.12 をシステム領域にインストール
winget install --id Python.Python.3.12 -e --scope machine --silent --accept-source-agreements --accept-package-agreements --override "/quiet InstallAllUsers=1 PrependPath=1 Include_test=0 Include_pip=1 Include_launcher=1 InstallLauncherAllUsers=1 TargetDir=\"C:\Program Files\Python312\""
REM Python と Scripts を PATH 先頭に追加
powershell -NoProfile -Command "$p='C:\Program Files\Python312'; $s=\"$p\Scripts\"; $c=[Environment]::GetEnvironmentVariable('Path','Machine'); if((Test-Path $p) -and (';'+$c+';' -notlike \"*;$p;*\") -and (';'+$c+';' -notlike \"*;$s;*\")){[Environment]::SetEnvironmentVariable('Path',\"$p;$s;$c\",'Machine')}"
方法 2:インストーラーによるインストール
- Python公式サイト(https://www.python.org/downloads/)にアクセスし、「Download Python 3.x.x」ボタンからWindows用インストーラーをダウンロードする。
- ダウンロードしたインストーラーを実行する。
- 初期画面の下部に表示される「Add python.exe to PATH」にチェックを入れてから「Customize installation」を選択する。このチェックを入れ忘れると、コマンドプロンプトから
pythonコマンドを実行できない。 - 「Install Python 3.xx for all users」にチェックを入れ、「Install」をクリックする。
インストールの確認
コマンドプロンプトで以下を実行する。
python --version
バージョン番号(例:Python 3.12.x)が表示されればインストール成功である。「'python' は、内部コマンドまたは外部コマンドとして認識されていません。」と表示される場合は、インストールが正常に完了していない。
第4章 Python の開発環境 Visual Studio Code のインストールと Python 用の設定
Python の開発環境Visual Studio Code(プログラムを編集するソフトウェア。以下、VS Code)を整える。
[Windows での Visual Studio Code のインストールと Python 用の設定手順を見るには、ここをクリック]
Windows での Visual Studio Code のインストールと Python 用の設定手順
1. VS Code と拡張機能のインストール
以下のコマンドにより,既存の VS Code を削除し,全ユーザー共有の設定で再インストールしたうえで,拡張機能(VS Code に機能を追加するソフトウェア)をまとめて導入する.
【インストールコマンドの実行方法】
管理者権限でコマンドプロンプトを起動する(手順:Windows キーまたはスタートメニュー → cmd と入力 → 右クリック → 「管理者として実行」)。そして,コマンド全体をコマンドプロンプトにコピー&ペーストする。
インストールコマンド
REM ============================================================
REM Microsoft Visual Studio Code
REM ============================================================
winget uninstall -e --id Microsoft.VisualStudioCode --silent --disable-interactivity --accept-source-agreements
rmdir /s /q C:\ProgramData\vscode-extensions 2>nul
rmdir /s /q "%APPDATA%\Code" 2>nul
rmdir /s /q "%USERPROFILE%\.vscode" 2>nul
rmdir /s /q "%LOCALAPPDATA%\Microsoft\vscode-update" 2>nul
REM VS Code をシステム領域に新規インストール
winget install --scope machine --id Microsoft.VisualStudioCode -e --silent --accept-source-agreements --accept-package-agreements
REM 全ユーザー共有の拡張機能フォルダ
mkdir C:\ProgramData\vscode-extensions 2>nul
icacls "C:\ProgramData\vscode-extensions" /grant "Everyone:(OI)(CI)M" /T
REM スタートメニューのショートカットを --extensions-dir 付きで再作成
rmdir /s /q "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code" 2>nul
del "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code.lnk" 2>nul
powershell -NoProfile -Command "$s=New-Object -ComObject WScript.Shell; $lnk=$s.CreateShortcut('C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code.lnk'); $lnk.TargetPath='C:\Program Files\Microsoft VS Code\Code.exe'; $lnk.Arguments='--extensions-dir \"C:\ProgramData\vscode-extensions\"'; $lnk.Save()"
REM ショートカットの検証
powershell -NoProfile -Command "$s=New-Object -ComObject WScript.Shell; $lnk=$s.CreateShortcut('C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code.lnk'); Write-Host 'TargetPath:' $lnk.TargetPath; Write-Host 'Arguments:' $lnk.Arguments"
REM ファイル / フォルダ右クリックの「Code で開く」を登録
reg add "HKLM\SOFTWARE\Classes\*\shell\VSCode\command" /ve /d "\"C:\Program Files\Microsoft VS Code\Code.exe\" --extensions-dir \"C:\ProgramData\vscode-extensions\" \"%1\"" /f
reg add "HKLM\SOFTWARE\Classes\Directory\shell\VSCode\command" /ve /d "\"C:\Program Files\Microsoft VS Code\Code.exe\" --extensions-dir \"C:\ProgramData\vscode-extensions\" \"%1\"" /f
reg add "HKLM\SOFTWARE\Classes\Directory\Background\shell\VSCode\command" /ve /d "\"C:\Program Files\Microsoft VS Code\Code.exe\" --extensions-dir \"C:\ProgramData\vscode-extensions\" \"%V\"" /f
REM --extensions-dir 付きで起動する code.cmd ラッパを作成
REM (%* を echo で書くと対話的 cmd で失われるため、PowerShell で [char]37+'*' を書き出す)
powershell -NoProfile -Command "$pct=[char]37; $q=[char]34; $c='@echo off'+[char]13+[char]10+$q+'C:\Program Files\Microsoft VS Code\bin\code.cmd'+$q+' --extensions-dir '+$q+'C:\ProgramData\vscode-extensions'+$q+' '+$pct+'*'+[char]13+[char]10; [IO.File]::WriteAllText('C:\ProgramData\vscode-extensions\vscode.cmd',$c,[Text.Encoding]::ASCII)"
REM 拡張機能のインストール
set "CODE=C:\Program Files\Microsoft VS Code\bin\code.cmd"
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --uninstall-extension GitHub.copilot
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --uninstall-extension GitHub.copilot-chat
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension ms-python.python
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension ms-python.vscode-pylance
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension ms-python.debugpy
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension MS-CEINTL.vscode-language-pack-ja
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension saoudrizwan.claude-dev
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension rust-lang.rust-analyzer
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension tamasfe.even-better-toml
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension anthropic.claude-code
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension almenon.arepl
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --list-extensions --show-versions
echo === セットアップ完了 ===
2. Python インタプリタの選択
同一マシンに複数の Python がインストールされている場合,VS Code で使用する Python 本体(インタプリタ:Python プログラムを解釈・実行するソフトウェア)を選択する必要がある.
- コマンドパレット(コマンド名で機能を呼び出す VS Code の入力欄)を開く(
Ctrl+Shift+P) Python: Select Interpreterと入力する
- 表示される一覧から,使用する Python(例:
C:\Program Files\Python312\python.exe)を選択する.
必要なライブラリをシステム領域にインストール
管理者権限でコマンドプロンプトを起動する
(手順:Windowsキーまたはスタートメニュー → cmd と入力 → 右クリック → 「管理者として実行」)。
pip install --no-user opencv-python numpy
第5章 ノイズ除去 (バイラテラルフィルタ)、彩度調整、ガンマ補正、コントラスト調整 (CLAHE)によるリアルタイム動画補正プログラム
概要
本プログラムは、動画ファイルまたはカメラからの映像に対し、複数の画像処理技術を適用してリアルタイムに画質を補正するツールである。利用者はGUIを通じて各種パラメータを対話的に調整し、その結果を即座にプレビューできる。また、調整したパラメータ設定の保存・読み込みや、補正後の映像を動画ファイルとして出力する機能も備える。
主要技術
本プログラムは、画質補正のために以下の主要な画像処理アルゴリズムを組み合わせている。
- バイラテラルフィルタ (Bilateral Filter): 映像のノイズを除去するためのフィルタである。ピクセル間の空間的な距離と輝度値の差の両方を考慮することで、エッジ(輪郭)の鮮明さを保持しながら平滑化を行う[1]。
- ガンマ補正 (Gamma Correction): 映像の輝度を非線形に調整する手法である。暗部または明部のディテールを改善する目的で使用される。
- CLAHE (Contrast Limited Adaptive Histogram Equalization): 映像のコントラストを改善する適応的ヒストグラム均等化の一種である。画像を複数の小領域に分割し、各領域でヒストグラム均等化を行う。その際、ノイズが過剰に増幅されるのを防ぐためにコントラストに上限(クリップリミット)を設ける[2]。
- 色空間変換 (Color Space Conversion): 本プログラムでは、彩度調整のためにBGR色空間からHSV色空間へ、また色彩情報を保持したままコントラスト調整を行うためにBGR色空間からCIELAB色空間へと変換を行っている。
技術的特徴
本プログラムは、「ノイズ除去(バイラテラルフィルタ)」、「彩度調整」、「輝度調整(ガンマ補正)」、「コントラスト調整(CLAHE)」の順に行われる。特にノイズ除去を初期段階で行うことで、以降の処理におけるノイズ増幅を抑制している。また、CLAHEを適用する際には、輝度チャンネル(Lチャンネル)にのみ処理を施すことで、元の色彩情報を損なうことなくコントラストを改善している。
実装の特色
本プログラムは、OpenCVによる画像処理とtkinterによるGUI操作を単一のループ内で同期させている。これにより、パラメータ変更をリアルタイムで映像に反映させることが可能となっている。また、調整したパラメータ群は、JSON形式の外部ファイルとして保存・読み込みが可能であり、特定の設定をプリセットとして再現できる。さらに、GUIで設定したパラメータを用いて動画全体を非対話的に処理し、新しい動画ファイルとして書き出すバッチ処理機能も実装されている。
参考文献
- [1] Tomasi, C., & Manduchi, R. (1998). Bilateral filtering for gray and color images. Proceedings of the Sixth International Conference on Computer Vision, 839–846. https://doi.org/10.1109/ICCV.1998.710815
- [2] Zuiderveld, K. (1994). Contrast limited adaptive histogram equalization. In P. S. Heckbert (Ed.), Graphics Gems IV (pp. 474–485). Academic Press.
ソースコード
# プログラム名: ノイズ除去 (バイラテラルフィルタ)、彩度調整、ガンマ補正、コントラスト調整 (CLAHE)によるリアルタイム動画補正プログラム
# プログラム名: リアルタイム動画補正プログラム
# 特徴技術名: CLAHE (Contrast Limited Adaptive Histogram Equalization)
# 出典: Bradski, G., & Kaehler, A. (2008). Learning OpenCV: Computer Vision with the OpenCV Library. O'Reilly Media, Inc.
# 特徴機能: 画像を小さな領域(タイル)に分割し、各領域でヒストグラム均等化を行います。その際、ノイズが増幅されるのを防ぐためにコントラストに上限(クリップリミット)を設定する機能です。これにより、画像全体の照明のむらを補正しつつ、局所的なディテールを鮮明にすることが可能です。
# 学習済みモデル: なし
# 特徴技術および学習済モデルの利用制限: 本プログラムが利用する技術(OpenCV)は、Apache License 2.0の下で提供されており、商用利用も可能ですが、一部特許で保護されたアルゴリズムが含まれる可能性があります。詳細は公式のライセンス情報を参照し、必ず利用者自身で利用制限を確認してください。
# 方式設計:
# - 関連利用技術:
# - OpenCV: 動画の入出力、画像処理(バイラテラルフィルタ、彩度調整、ガンマ補正、色空間変換、CLAHE)に使用します。
# - tkinter: 各種補正パラメータをリアルタイムで調整するためのGUI(スライダー)を提供します。
# - 入力と出力:
# 入力: 動画(ユーザは「0:動画ファイル,1:カメラ,2:サンプル動画」のメニューで選択.0:動画ファイルの場合はtkinterでファイル選択.1の場合はOpenCVでカメラが開く.2の場合はhttps://raw.githubusercontent.com/opencv/opencv/master/samples/data/vtest.aviを使用)
# 出力: 処理結果をOpenCVウィンドウでリアルタイムに表示します(左側:元画像、右側:処理後)。また、プログラム終了時に各フレームで適用されたパラメータ設定を`result.txt`に保存します。
# - 処理手順:
# 1. tkinterで作成したスライダーから各種パラメータを取得します。
# 2. 入力動画のフレームに対し、ノイズ除去のためバイラテラルフィルタを適用します。
# 3. 次に、HSV色空間に変換し、彩度を調整します。
# 4. その後、ガンマ補正を適用します。
# 5. 最後に、LAB色空間に変換し、輝度チャンネルにCLAHEを適用してコントラストを調整します。
# 6. 元のフレームと処理後のフレームを水平に連結し、一つのウィンドウに表示します。
# - 前処理、後処理: 本プログラムでは、特徴技術の効果を発揮させるための特別な前処理および後処理は実装しません。
# - 追加処理: ユーザがGUIを通じてリアルタイムにパラメータを変更できるようにするため、OpenCVのメインループ内でtkinterのイベントループを更新する処理を追加します。
# - 調整を必要とする設定値:
# - bilateral_d: バイラテラルフィルタの近傍領域の直径。0で無効化。
# - sigma_color: バイラテラルフィルタの色空間におけるシグマ値。
# - sigma_space: バイラテラルフィルタの座標空間におけるシグマ値。
# - saturation: 彩度の倍率。1.0で無補正。
# - gamma: ガンマ補正の強度。1.0で無補正。
# - clip_limit: CLAHEにおけるコントラストの上限値。
# - tile_grid_size: CLAHEが画像を分割する際のタイル(格子)のサイズ。
# 将来方策: 現在は手動で調整しているパラメータについて、画像の特性(ヒストグラムなど)を分析し、最適な値を自動で提案する機能を将来的に追加することが考えられます。
# 前準備: pip install --no-user opencv-python numpy
import cv2
import numpy as np
import tkinter as tk
from tkinter import filedialog, messagebox
import urllib.request
import os
import time
from datetime import datetime
import json
# --- グローバル変数 ---
frame_count = 0
results_log = []
current_input_path = None
exit_requested = False
# Tkinter関連の変数
root_tk = None
gamma_var = None
clip_limit_var = None
tile_size_var = None
bilateral_d_var = None
sigma_color_var = None
sigma_space_var = None
saturation_var = None
# --- ヘルパー関数 ---
def create_slider(parent, text, from_, to, resolution, variable):
tk.Label(parent, text=text).pack()
slider = tk.Scale(parent, from_=from_, to=to, resolution=resolution, orient=tk.HORIZONTAL, variable=variable)
slider.pack(fill=tk.X, padx=10)
return slider
def apply_image_processing(frame, params):
processed_frame = frame.copy()
if params['d'] > 0:
processed_frame = cv2.bilateralFilter(processed_frame, params['d'], params['sigma_color'], params['sigma_space'])
if params['saturation_scale'] != 1.0:
hsv = cv2.cvtColor(processed_frame, cv2.COLOR_BGR2HSV)
h, s, v = cv2.split(hsv)
s = np.clip(s.astype(np.float32) * params['saturation_scale'], 0.0, 255.0).astype(np.uint8)
processed_frame = cv2.cvtColor(cv2.merge((h, s, v)), cv2.COLOR_HSV2BGR)
if params['gamma'] != 1.0:
look_up_table = np.array([((i / 255.0) ** (1.0 / params['gamma'])) * 255 for i in np.arange(0, 256)]).astype("uint8")
processed_frame = cv2.LUT(processed_frame, look_up_table)
lab_frame = cv2.cvtColor(processed_frame, cv2.COLOR_BGR2LAB)
l_channel, a_channel, b_channel = cv2.split(lab_frame)
clahe = cv2.createCLAHE(clipLimit=params['clip_limit'], tileGridSize=(params['tile_size'], params['tile_size']))
clahe_l_channel = clahe.apply(l_channel)
processed_frame = cv2.cvtColor(cv2.merge([clahe_l_channel, a_channel, b_channel]), cv2.COLOR_LAB2BGR)
return processed_frame
def video_frame_processing(frame):
global frame_count
current_time = time.time()
frame_count += 1
params = {
'd': bilateral_d_var.get(), 'sigma_color': sigma_color_var.get(), 'sigma_space': sigma_space_var.get(),
'saturation_scale': saturation_var.get(), 'gamma': gamma_var.get(),
'clip_limit': clip_limit_var.get(), 'tile_size': tile_size_var.get()
}
processed_frame = apply_image_processing(frame, params)
# 表示順を「左:元画像, 右:処理後」に変更
combined_frame = np.hstack((frame, processed_frame))
result = f"Bilateral(d:{params['d']},cσ:{params['sigma_color']},sσ:{params['sigma_space']}), Sat:{params['saturation_scale']:.2f}, Gamma:{params['gamma']:.2f}, CLAHE(clip:{params['clip_limit']:.1f},tile:{params['tile_size']})"
return combined_frame, result, current_time
def request_exit():
global exit_requested
exit_requested = True
def save_preset():
preset_data = {
"bilateral_d": bilateral_d_var.get(),
"sigma_color": sigma_color_var.get(),
"sigma_space": sigma_space_var.get(),
"saturation": saturation_var.get(),
"gamma": gamma_var.get(),
"clip_limit": clip_limit_var.get(),
"tile_grid_size": tile_size_var.get()
}
try:
with open("preset.json", "w", encoding="utf-8") as f:
json.dump(preset_data, f, indent=4)
messagebox.showinfo("成功", "設定を preset.json に保存しました。")
except Exception as e:
messagebox.showerror("エラー", f"設定の保存中にエラーが発生しました:\n{e}")
def load_preset():
try:
if not os.path.exists("preset.json"):
messagebox.showwarning("警告", "preset.json が見つかりません。")
return
with open("preset.json", "r", encoding="utf-8") as f:
preset_data = json.load(f)
bilateral_d_var.set(preset_data.get("bilateral_d", 0))
sigma_color_var.set(preset_data.get("sigma_color", 75))
sigma_space_var.set(preset_data.get("sigma_space", 75))
saturation_var.set(preset_data.get("saturation", 1.0))
gamma_var.set(preset_data.get("gamma", 1.0))
clip_limit_var.set(preset_data.get("clip_limit", 2.0))
tile_size_var.set(preset_data.get("tile_grid_size", 8))
messagebox.showinfo("成功", "設定を読み込みました。")
except json.JSONDecodeError:
messagebox.showerror("エラー", "preset.json の形式が正しくありません。")
except Exception as e:
messagebox.showerror("エラー", f"設定の読み込み中にエラーが発生しました:\n{e}")
def reset_parameters():
"""スライダーをすべて初期値に戻す"""
bilateral_d_var.set(0)
sigma_color_var.set(75)
sigma_space_var.set(75)
saturation_var.set(1.0)
gamma_var.set(1.0)
clip_limit_var.set(2.0)
tile_size_var.set(8)
def save_video_file():
global current_input_path
if not current_input_path:
messagebox.showerror("エラー", "保存対象の動画ファイルがありません。")
return
output_path = filedialog.asksaveasfilename(defaultextension=".mp4", filetypes=[("MP4 files", "*.mp4"), ("All files", "*.*")])
if not output_path:
return
params = {
'd': bilateral_d_var.get(), 'sigma_color': sigma_color_var.get(), 'sigma_space': sigma_space_var.get(),
'saturation_scale': saturation_var.get(), 'gamma': gamma_var.get(),
'clip_limit': clip_limit_var.get(), 'tile_size': tile_size_var.get()
}
cap_save = cv2.VideoCapture(current_input_path)
if not cap_save.isOpened():
messagebox.showerror("エラー", "入力動画ファイルを開けませんでした。")
return
fps = cap_save.get(cv2.CAP_PROP_FPS)
width = int(cap_save.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap_save.get(cv2.CAP_PROP_FRAME_HEIGHT))
total_frames = int(cap_save.get(cv2.CAP_PROP_FRAME_COUNT))
writer = cv2.VideoWriter(output_path, cv2.VideoWriter_fourcc(*'XVID'), fps, (width, height))
print("\n=== 動画ファイル保存開始 ===")
print(f"入力: {os.path.basename(current_input_path)}")
print(f"出力: {os.path.basename(output_path)}")
for i in range(total_frames):
ret, frame = cap_save.read()
if not ret:
break
processed_frame = apply_image_processing(frame, params)
writer.write(processed_frame)
progress = (i + 1) / total_frames * 100
print(f"\r処理中: {i + 1}/{total_frames} ({progress:.2f}%)", end="")
print("\n=== 動画ファイル保存完了 ===")
cap_save.release()
writer.release()
messagebox.showinfo("完了", "動画の保存が完了しました。")
# --- メイン処理 ---
print("リアルタイム動画補正プログラム")
print("補正パラメータをGUIで調整できます。")
print("\n入力ソースを選択してください:")
print("0: 動画ファイル")
print("1: カメラ")
print("2: サンプル動画")
choice = input("選択: ")
cap = None
if choice == '0':
temp_root = tk.Tk()
temp_root.withdraw()
path = filedialog.askopenfilename()
temp_root.destroy()
if not path:
exit()
current_input_path = path
cap = cv2.VideoCapture(current_input_path)
elif choice == '1':
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
if not cap.isOpened():
cap = cv2.VideoCapture(0)
elif choice == '2':
SAMPLE_URL = 'https://raw.githubusercontent.com/opencv/opencv/master/samples/data/vtest.avi'
SAMPLE_FILE = 'vtest.avi'
try:
urllib.request.urlretrieve(SAMPLE_URL, SAMPLE_FILE)
current_input_path = os.path.abspath(SAMPLE_FILE)
cap = cv2.VideoCapture(current_input_path)
except Exception as e:
print(f"ダウンロードに失敗しました: {e}")
exit()
else:
print("無効な選択です。")
exit()
if not cap.isOpened():
print('動画ファイル・カメラを開けませんでした')
exit()
root_tk = tk.Tk()
root_tk.title("パラメータ調整")
root_tk.geometry("300x630")
bilateral_d_var = tk.IntVar(value=0)
sigma_color_var = tk.IntVar(value=75)
sigma_space_var = tk.IntVar(value=75)
saturation_var = tk.DoubleVar(value=1.0)
gamma_var = tk.DoubleVar(value=1.0)
clip_limit_var = tk.DoubleVar(value=2.0)
tile_size_var = tk.IntVar(value=8)
create_slider(root_tk, "バイラテラルフィルタ径 d (0-15)", 0, 15, 1, bilateral_d_var)
create_slider(root_tk, "色空間シグマ σColor (0-200)", 0, 200, 1, sigma_color_var)
create_slider(root_tk, "座標空間シグマ σSpace (0-200)", 0, 200, 1, sigma_space_var)
create_slider(root_tk, "彩度 (0.0-3.0)", 0.0, 3.0, 0.1, saturation_var)
create_slider(root_tk, "ガンマ (0.1-5.0)", 0.1, 5.0, 0.1, gamma_var)
create_slider(root_tk, "CLAHE クリップリミット (1.0-40.0)", 1.0, 40.0, 1.0, clip_limit_var)
create_slider(root_tk, "CLAHE タイルサイズ (1-32)", 1, 32, 1, tile_size_var)
preset_frame = tk.Frame(root_tk)
preset_frame.pack(pady=5)
load_button = tk.Button(preset_frame, text="設定を読み込み", command=load_preset)
load_button.pack(side=tk.LEFT, padx=5)
save_preset_button = tk.Button(preset_frame, text="設定を保存", command=save_preset)
save_preset_button.pack(side=tk.LEFT, padx=5)
reset_button = tk.Button(preset_frame, text="リセット", command=reset_parameters)
reset_button.pack(side=tk.LEFT, padx=5)
save_button_state = tk.NORMAL if choice != '1' else tk.DISABLED
save_button = tk.Button(root_tk, text="動画をファイルに保存...", command=save_video_file, state=save_button_state)
save_button.pack(pady=5)
exit_button = tk.Button(root_tk, text="終了", command=request_exit)
exit_button.pack(pady=5)
print('\n=== 動画処理開始 ===')
print('操作方法:')
print(' q キー: プログラム終了')
print(' r キー: 動画の先頭に戻る (動画ファイルのみ)')
print(' 終了ボタン: GUIからプログラムを終了')
try:
# ウィンドウタイトルを「左:元画像, 右:処理後」に変更
MAIN_FUNC_DESC = "画像補正 (左: 元画像, 右: 処理後)"
while True:
if exit_requested:
break
ret, frame = cap.read()
if not ret:
if choice != '1':
cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
frame_count = 0
continue
else:
break
processed_frame, result, current_time = video_frame_processing(frame)
cv2.imshow(MAIN_FUNC_DESC, processed_frame)
log_entry = f"{datetime.fromtimestamp(current_time).strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]} {result}" if choice == '1' else f"Frame: {frame_count}, {result}"
print(log_entry)
results_log.append(log_entry)
root_tk.update()
key = cv2.waitKey(1) & 0xFF
if key == ord('q'):
break
elif key == ord('r') and choice != '1':
cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
frame_count = 0
results_log.clear()
print("\n--- 動画の先頭に戻りました ---")
finally:
print('\n=== プログラム終了 ===')
if cap:
cap.release()
cv2.destroyAllWindows()
if root_tk:
root_tk.destroy()
if results_log:
with open('result.txt', 'w', encoding='utf-8') as f:
f.write('=== 結果 ===\n')
f.write(f'処理フレーム数: {frame_count}\n\n')
f.write('\n'.join(results_log))
print(f'\n処理結果をresult.txtに保存しました')