はじめに
過去の記事で非局所平均法アルゴリズムを実装した関数を紹介しましたが、本記事では同アルゴリズムを用いた動画や連続画像(バースト画像)に適したノイズ除去関数であるcv2.fastNlMeansDenoisingMulti関数、cv2.fastNlMeansDenoisingColoredMulti関数を紹介します。
効率的に高い効果のノイズ除去を行えるアルゴリズムとなっています。
動画・連続画像のノイズ除去
cv2.fastNlMeansDenoisingMulti関数、cv2.fastNlMeansDenoisingColoredMulti関数は、cv2.fastNlMeansDenoising関数、cv2.fastNlMeansDenoisingColored関数の拡張版で、複数のフレーム(画像)を使用してノイズ除去を行います。
主に動画やバースト撮影された連続画像のノイズ除去に適しています。
主な違いを下に示します。
| 項目 | cv2.fastNlMeansDenoisingMulti関数、cv2.fastNlMeansDenoisingColoredMulti関数 | cv2.fastNlMeansDenoising関数、cv2.fastNlMeansDenoisingColored関数 |
|---|---|---|
| 入力画像の数 | 複数の画像(通常は連続したフレーム)を処理 | 単一の画像を処理 |
| ノイズ除去の方法 | 複数フレーム間で類似パッチを探索し、時間的な情報も活用してノイズ除去 | 単一画像内の類似パッチを探索してノイズ除去 |
| 性能と品質 | 複数フレームの情報を使用するため、一般的により高品質なノイズ除去が可能 | ー |
| 適用範囲 | 動画や連続撮影された画像シーケンスのノイズ除去に適している | 単一の静止画像のノイズ除去に適している |
| 動きへの対応 | フレーム間の動きが小さい場合に特に効果的。 大きな動きがある場合、適切なフレーム選択や動き補償が必要になる場合がある。 | ー |
| メモリ使用量 | 複数フレームを同時に処理するため、より多くのメモリを必要とする | 単一の静止画像なのでそれほどメモリを必要としない |
cv2.fastNlMeansDenoisingMulti関数
この関数は、cv2.fastNlMeansDenoising関数の動画・連続画像対応版です。cv2.fastNlMeansDenoising関数と同様に、グレースケールの動画・連続画像に適しています。
引数など、使い方も類似しています。
引数
| 名称 | 説明 |
|---|---|
| srcImgs(必須) | ・入力画像のリスト。各画像は8ビット、または16 ビット (NORM_L1 のみ) の 1 チャネル、2 チャネル、3 チャネル、または 4 チャネル画像である必要があります。 ・すべての画像は同じタイプとサイズである必要があります。 ・ List[numpy.ndarray]オブジェクト。 |
| imgToDenoiseIndex(必須) | ・ノイズ除去を行う中心フレームのインデックス。 ・0 ≤ imgToDenoiseIndex < len(srcImgs) を満たす必要があります。 ・int型 |
| temporalWindowSize(必須) | ・時間軸方向の検索窓サイズ。 ・奇数である必要があります。 ・推奨値は 7 ・int型 |
| dst(オプション) | 結果がこの変数に格納されます。指定しない場合は、入力画像と同じサイズ・型で新しい配列が作成されて結果が格納され、戻り値として取得できます。 |
| h(オプション) | ・フィルタリングの強さを決定するパラメータ。(デフォルト:3) ・大きい値ほど強くノイズ除去されます。 ・float型 |
| templateWindowSize(オプション) | ・テンプレートパッチのサイズ(デフォルト:7) ・奇数である必要があります。 ・int型 |
| searchWindowSize(オプション) | ・各ピクセル周辺の検索窓サイズ(デフォルト:21) ・奇数である必要があります。 ・int型 |
| normType(オプション) | ・重み計算に使用されるノルムのタイプ。 ・NormTypesで定義されるNORM_L2 、または NORM_L1 のいずれか。(デフォルト:NORM_L2) ・int型 |
戻り値
imgToDenoiseIndexで指定されたフレームに対応する、ノイズ除去が適用された画像データをnumpy.ndarrayオブジェクトで返します。
使い方
以下に使い方を示します。
ノイズ除去を行うのは下の動画サイトの動画をモノクロに変換した動画を使用しました。
サイズ 960 × 540、フレーム数 122、再生時間8.1秒の動画となっています。
カラー動画をモノクロに変換する例は、この記事で紹介しています。
サンプルコードもありますので、参考にしてください。
下の画像は、処理後の動画の同じフレームを切り取った画像です。cv2.fastNlMeansDenoisingMulti関数と同様にノイズ除去の効果は十分です。
引数hの値が小さくてもcv2.fastNlMeansDenoising関数より強いノイズ除去効果が得られている様に思えます。
大きなノイズより、細かなノイズ除去に大きな効果を感じます。
一方で、処理時間は必要となります。
実測の結果、筆者の環境(M1 MacBook Pro)では1フレームあたり約0.66秒の処理時間でした。
CUDAなどで高速化した場合はどうなのか、気になるところです。


import cv2
def denoise_video(input_path, output_path, h=6, templateWindowSize=7, searchWindowSize=21, temporalWindowSize=3):
# 入力動画を開く
cap = cv2.VideoCapture(input_path)
if not cap.isOpened():
print("Could not open the video file.")
exit()
# 動画の情報を取得
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
fps = int(cap.get(cv2.CAP_PROP_FPS))
# 動画の長さを計算する(sec)
video_len_sec = total_frames / fps
print(f"Total time {video_len_sec}sec")
# 出力動画のwriter設定
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
# フレームバッファ初期化
frame_buffer = []
# 時間計測開始時間 取得
start_time = cv2.getTickCount()
for i in range(total_frames):
ret, frame = cap.read()
if not ret:
break
# フレームバッファにフレームを追加
frame_buffer.append(frame)
# バッファが必要なサイズになったらノイズ除去を適用
if len(frame_buffer) == temporalWindowSize:
# 中心フレームのインデックス
center_index = temporalWindowSize // 2
# ノイズ除去を適用
denoised_frame = cv2.fastNlMeansDenoisingMulti(
srcImgs=frame_buffer,
imgToDenoiseIndex=center_index,
temporalWindowSize=temporalWindowSize,
dst=None,
h=h,
templateWindowSize=templateWindowSize,
searchWindowSize=searchWindowSize
)
# ノイズ除去されたフレームを書き出し
out.write(denoised_frame)
# 最も古いフレームをバッファから削除
frame_buffer.pop(0)
print(f"Processing frame {i+1}/{total_frames}")
# 時間計測終了
end_time = cv2.getTickCount()
elapsed_time = (end_time - start_time) / cv2.getTickFrequency()
print("Elapsed time:", elapsed_time, "sec")
# リソースの解放
cap.release()
out.release()
cv2.destroyAllWindows()
if __name__ == "__main__":
# 使用例
input_video = "input_movie.mp4"
output_video = "denoised_movie.mp4"
denoise_video(input_video, output_video)
cv2.fastNlMeansDenoisingColoredMulti関数
cv2.fastNlMeansDenoisingColoredMulti関数はcv2.fastNlMeansDenoisingMulti関数のカラー画像に対応した関数です。
カラーの動画・連続画像に適しています。
引数など、使い方も類似しています。
引数
| 名称 | 説明 |
|---|---|
| srcImgs(必須) | ・入力画像のリスト。各画像は8ビット、または16 ビット (NORM_L1 のみ) の 1 チャネル、2 チャネル、3 チャネル、または 4 チャネル画像である必要があります。 ・すべての画像は同じタイプとサイズである必要があります。 ・ List[numpy.ndarray]オブジェクト。 |
| imgToDenoiseIndex(必須) | ・ノイズ除去を行う中心フレームのインデックス。 ・0 ≤ imgToDenoiseIndex < len(srcImgs) を満たす必要があります。 ・int型 |
| temporalWindowSize(必須) | ・時間軸方向の検索窓サイズ。 ・奇数である必要があります。 ・推奨値は 7 ・int型 |
| dst(オプション) | ・結果がこの変数に格納されます。指定しない場合は、入力画像と同じサイズ・型で新しい配列が作成されて結果が格納され、戻り値として取得できます。 ・ numpy.ndarrayオブジェクト・デフォルト値: None(新しい配列が作成されます) |
| h(オプション) | ・フィルタリングの強さを決定するパラメータ。(デフォルト:3) ・大きい値ほど強くノイズ除去されます。 ・float型 |
| hColor(オプション) | ・色チャンネルに対するフィルタ強度(デフォルト:3) ・float型 |
| templateWindowSize(オプション) | ・テンプレートパッチのサイズ(デフォルト:7) ・奇数である必要があります。 ・int型 |
| searchWindowSize(オプション) | ・各ピクセル周辺の検索窓サイズ(デフォルト:21) ・奇数である必要があります。 ・int型 |
| normType(オプション) | ・重み計算に使用されるノルムのタイプ。 ・NormTypesで定義されるNORM_L2 、または NORM_L1 のいずれか。(デフォルト:NORM_L2) ・int型 |
この関数は、特に低照度環境で撮影されたカラー動画や、高ISO感度で撮影された連続カラー画像のノイズ除去に有効です。
色情報を保持しながら効果的にノイズを除去できるため、画質の向上が期待できます。
戻り値
imgToDenoiseIndexで指定されたフレームに対応する、ノイズ除去が適用された画像データをnumpy.ndarrayオブジェクトで返します。
使い方
以下にサンプルコードを示します。
入力動画はcv2.fastNlMeansDenoisingMulti関数のサンプルコードで使用した動画のオリジナル(グレースケール化する前)のものを使用しました。
下の画像は、処理後の動画の同じフレームを切り取った画像です。
カラー画像でもcv2.fastNlMeansDenoisingMulti関数と同様に十分なノイズ除去がされている様です。
実測の結果、筆者の環境(M1 MacBook Pro)では1フレームあたり約0.86秒の処理時間でした。cv2.fastNlMeansDenoisingMulti関数による処理時間の増分は筆者の環境では約0.2秒なので、カラー動画の3チャンネル分のノイズ除去の処理ということを考慮するとかなり効率の高い処理がされていることがわかります。


import cv2
def denoise_video(input_path, output_path, h=6, hColor=4, templateWindowSize=7, searchWindowSize=21, temporalWindowSize=3):
# 入力動画を開く
cap = cv2.VideoCapture(input_path)
if not cap.isOpened():
print("動画ファイルを開けませんでした。")
exit()
# 動画の情報を取得
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
fps = int(cap.get(cv2.CAP_PROP_FPS))
# 動画の長さを計算する(sec)
video_len_sec = total_frames / fps
print(f"Total time {video_len_sec}sec")
# 出力動画のwriter設定
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
# フレームバッファ初期化
frame_buffer = []
# 時間計測開始時間 取得
start_time = cv2.getTickCount()
for i in range(total_frames):
ret, frame = cap.read()
if not ret:
break
# フレームバッファにフレームを追加
frame_buffer.append(frame)
# バッファが必要なサイズになったらノイズ除去を適用
if len(frame_buffer) == temporalWindowSize:
# 中心フレームのインデックス
center_index = temporalWindowSize // 2
# ノイズ除去を適用
denoised_frame = cv2.fastNlMeansDenoisingColoredMulti(
srcImgs=frame_buffer,
imgToDenoiseIndex=center_index,
temporalWindowSize=temporalWindowSize,
dst=None,
h=h,
hColor=hColor,
templateWindowSize=templateWindowSize,
searchWindowSize=searchWindowSize
)
# ノイズ除去されたフレームを書き出し
out.write(denoised_frame)
# 最も古いフレームをバッファから削除
frame_buffer.pop(0)
print(f"Processing frame {i+1}/{total_frames}")
# 時間計測終了
end_time = cv2.getTickCount()
elapsed_time = (end_time - start_time) / cv2.getTickFrequency()
print("Elapsed time:", elapsed_time, "sec")
# リソースの解放
cap.release()
out.release()
cv2.destroyAllWindows()
if __name__ == "__main__":
# 使用例
input_video = "input_movie.mp4"
output_video = "denoised_movie.mp4"
denoise_video(input_video, output_video)
おわりに
動画や連続画像(バースト画像)に適したノイズ除去関数であるcv2.fastNlMeansDenoisingMulti関数、cv2.fastNlMeansDenoisingColoredMulti関数を紹介しました。
ノイズ除去効果としては、特に低照度環境で撮影された動画や、高ISO感度で撮影された連続画像のノイズ除去に有効のようです。
また、カラー画像の処理効率はとても高いことも特徴の一つと言えるでしょう。
一方で、ノイズ除去効果が強いので、画像の細部の表現が削られてしまうこともあるかもしれませんが、高品質なノイズ除去結果を得るためには適切なパラメータ調整が必要です。
ご質問や取り上げて欲しい内容などがありましたら、コメントをお願いします。
最後までご覧いただきありがとうございました。
参考リンク
■(広告)OpenCVの参考書としてどうぞ!■


