Pythonで画像品質を数値化:PSNRをOpenCV, scikit-image, NumPyで算出する方法


 


画像処理や画像圧縮の分野において、処理後の画像の品質を客観的に評価する指標は不可欠です。その中でも広く用いられるのが**PSNR(Peak Signal-to-Noise Ratio、ピーク信号対雑音比)**です。PSNRは、元の画像と処理後の画像との間の劣化の度合いを数値で示す指標であり、値が高いほど画像の品質が良いことを意味します。

Pythonでは、OpenCVscikit-image、そしてNumPyといった主要な画像処理・数値計算ライブラリを使ってPSNRを簡単に算出できます。この記事では、それぞれのライブラリを用いたPSNR算出方法を、短いサンプルコードとともに分かりやすく解説します。画像処理の評価指標を理解し、実践に役立てたい方は必見です。


 

PSNRとは?なぜ必要なのか?

 

PSNRは、元の画像とノイズや圧縮などによって劣化(歪み)した画像を比較し、その差がどの程度小さいかを示す指標です。主に画像の圧縮やノイズ除去アルゴリズムの性能評価に利用されます。

 

PSNRの計算式

 

PSNRは、まず画像の**MSE(Mean Squared Error、平均二乗誤差)**を計算し、そのMSEを基に以下の式で算出されます。

ここで、は画像の高さと幅、$I(i, j)$は元の画像のピクセル値、$K(i, j)$は比較対象の画像のピクセル値です。

そして、PSNRは以下の式で表されます。

ここで、は画像の最大取りうるピクセル値です。例えば、8ビット画像(0〜255)の場合、となります。

 

なぜPSNRを算出する必要があるのか?

 

  • 客観的な評価: アルゴリズムの性能を、人間の主観に頼らず数値で比較できます。

  • アルゴリズムの改善: PSNRを指標とすることで、画像処理アルゴリズムのパラメータ調整や改良の方向性を判断できます。

  • ベンチマーク: 異なる手法やシステム間で画像品質の比較を行う際の標準的な指標として利用されます。


 

PythonでPSNRを算出する

 

OpenCV, scikit-image, NumPyはそれぞれPSNR算出のための機能や、その元となる計算のためのツールを提供しています。

 

準備:画像とノイズの生成

 

まず、PSNRを計算するために、比較対象となる2つの画像を用意します。ここでは、NumPyを使って簡単な画像を生成し、それにノイズを加えることで「元の画像」と「劣化画像」をシミュレートします。

Python
 
import numpy as np
import cv2
from skimage.metrics import peak_signal_noise_ratio as ski_psnr
import math

# 画像のサイズ
height, width = 256, 256

# 1. 元の画像を生成 (例: グレースケールのグラデーション)
original_image = np.zeros((height, width), dtype=np.float32)
for i in range(height):
    original_image[i, :] = i / (height - 1) * 255 # 0-255の範囲
original_image_8bit = original_image.astype(np.uint8) # 8ビット整数に変換

# 2. ノイズを加えた劣化画像を生成
# ガウシアンノイズを追加
noise = np.random.normal(0, 20, (height, width)).astype(np.float32) # 標準偏差20のノイズ
noisy_image = original_image + noise
noisy_image = np.clip(noisy_image, 0, 255).astype(np.uint8) # 0-255にクリップして8ビット整数に変換

print("画像を生成しました。")

 

1. OpenCVでPSNRを算出する

 

OpenCVはcv2.PSNR()関数を提供しており、最も直接的にPSNRを算出できます。

Python
 
import cv2
import numpy as np # OpenCVはNumPy配列を扱うため必要

# 生成した画像を使用
# original_image_8bit
# noisy_image

# OpenCVでPSNRを算出
# ピクセル値は0-255のuint8型を想定
psnr_opencv = cv2.PSNR(original_image_8bit, noisy_image)

print(f"OpenCVによるPSNR: {psnr_opencv:.2f} dB")
# 出力例: OpenCVによるPSNR: 22.18 dB (ノイズの量によって変動)

cv2.PSNR()は、入力がNumpy配列であり、同じデータ型、同じサイズである必要があります。通常、8ビット画像(0-255)に対して計算されます。


 

2. scikit-imageでPSNRを算出する

 

scikit-image(skimage.metricsモジュール)もPSNRを計算する関数を提供しており、画像処理の研究や評価によく使われます。

Python
 
from skimage.metrics import peak_signal_noise_ratio as ski_psnr
import numpy as np

# 生成した画像を使用
# original_image_8bit
# noisy_image

# scikit-imageでPSNRを算出
# data_rangeはピクセル値の最大範囲を指定 (8bit画像なら255)
psnr_skimage = ski_psnr(original_image_8bit, noisy_image, data_range=255)

print(f"scikit-imageによるPSNR: {psnr_skimage:.2f} dB")
# 出力例: scikit-imageによるPSNR: 22.18 dB (OpenCVと同じ値になるはず)

scikit-imagepeak_signal_noise_ratio関数は、data_range引数でピクセル値の最大値を明示的に指定できる点が特徴です。これにより、画像のビット深度(例: 8ビット、10ビット、16ビット)に応じた正確な計算が可能です。


 

3. NumPyでPSNRを算出する(手動計算)

 

NumPyを使って、PSNRの計算式に基づいて手動でMSEを算出し、その後PSNRを計算することも可能です。これにより、PSNRの概念をより深く理解できます。

Python
 
import numpy as np
import math

# 生成した画像を使用 (ここではfloat型を直接使用)
# original_image (float32)
# noisy_image (uint8 なので float32 に変換)
noisy_image_float = noisy_image.astype(np.float32)
original_image_float = original_image.astype(np.float32)

# 1. MSE (Mean Squared Error) の計算
mse = np.mean((original_image_float - noisy_image_float) ** 2)

if mse == 0:
    psnr_numpy = float('inf') # 完全に一致する場合、PSNRは無限大
else:
    # 2. MAX_I (最大ピクセル値) の設定
    # 8ビット画像 (0-255) の場合
    max_pixel_value = 255.0 
    
    # 3. PSNRの計算
    psnr_numpy = 10 * math.log10(max_pixel_value**2 / mse)

print(f"NumPy(手動計算)によるPSNR: {psnr_numpy:.2f} dB")
# 出力例: NumPy(手動計算)によるPSNR: 22.18 dB (上記ライブラリと同じ値になるはず)

この手動計算では、まず二乗誤差の平均(MSE)を計算し、その後にPSNRの式を適用しています。mse == 0の場合(2つの画像が完全に同じ場合)、PSNRは無限大になる点に注意が必要です。


 

まとめと使い分け

 

PythonでPSNRを算出する方法は複数ありますが、それぞれのライブラリには得意分野があります。

  • OpenCV (cv2.PSNR):

    • 特徴: 最もシンプルで直接的。画像処理パイプラインの一部としてOpenCVを既に使っている場合に便利。

    • 使い分け: NumPy配列のuint8型画像を対象に、高速かつ手軽にPSNRを算出したい場合。

  • scikit-image (skimage.metrics.peak_signal_noise_ratio):

    • 特徴: data_range引数で画像のビット深度に柔軟に対応できる。画像品質評価関数が豊富。

    • 使い分け: 画像のビット深度が様々であったり、他の画像品質評価指標(SSIMなど)も同時に計算したい場合。研究用途やより厳密な評価が必要な場合。

  • NumPy(手動計算):

    • 特徴: PSNRの計算ロジックを理解するのに最適。特定のカスタムな計算(例: 特定の領域だけのPSNR)が必要な場合に柔軟に対応できる。

    • 使い分け: 計算プロセスをカスタマイズしたい、またはライブラリに依存しない純粋なNumPyベースの計算が必要な場合。

これらの方法を使いこなすことで、Pythonでの画像処理アルゴリズムの評価と改善が格段に容易になります。あなたのプロジェクトの要件に合わせて、最適なPSNR算出方法を選択しましょう。

■プロンプトだけでオリジナルアプリを開発・公開してみた!!

■AI時代の第一歩!「AI駆動開発コース」はじめました!

テックジム東京本校で先行開始。

■テックジム東京本校

「武田塾」のプログラミング版といえば「テックジム」。
講義動画なし、教科書なし。「進捗管理とコーチング」で効率学習。
より早く、より安く、しかも対面型のプログラミングスクールです。

<短期講習>5日で5万円の「Pythonミニキャンプ」開催中。

<月1開催>放送作家による映像ディレクター養成講座

<オンライン無料>ゼロから始めるPython爆速講座