做图像处理的实验,常用到的定量比较的三个指标:均方误差(MSE), 峰值信噪比(PSNR), 结构相似性指数(SSIM).
本文将着重介绍这三个指标的定义和Python版本的代码实现。
均方误差 Mean Squared Error
功能:
衡量两张图像相似度的一个指标,均方误差越小,表示两张图像越相似。
特点:
对大误差敏感:由于是平方差,MSE对大误差特别敏感,因此如果图像中有较大的差异(比如大面积的噪声),MSE值会显著增大。
对视觉质量的感知差异不敏感:MSE主要从数学角度衡量图像差异,但它并不一定与人眼对图像质量的感知一致。例如,两个图像的MSE可能非常相近,但其中一个图像在视觉上可能明显更好。
公式:
图像处理中的均方误差公式可以如下计算:
M
S
E
=
1
m
×
n
∑
i
=
1
m
∑
j
=
1
n
(
I
(
i
,
j
)
−
I
^
(
i
,
j
)
)
2
其
中
:
I
是
图
像
,
I
^
是
其
处
理
版
本
m
×
n
是
图
像
的
尺
寸
,
即
图
像
中
的
像
素
数
。
I
(
i
,
j
)
是
原
始
图
像
在
位
置
(
i
,
j
)
处
的
像
素
值
。
I
^
(
i
,
j
)
是
处
理
后
图
像
在
位
置
(
i
,
j
)
处
的
像
素
值
。
MSE = \frac{1}{m \times n} \sum_{i=1}^{m} \sum_{j=1}^{n} \left( I(i,j) - \hat{I}(i,j) \right)^2 \\\\ 其中: I是图像,\hat{I}是其处理版本\\ m \times n 是图像的尺寸,即图像中的像素数。 \\ I(i,j) 是原始图像在位置 (i,j)处的像素值。\\ \hat{I}(i,j)是处理后图像在位置 (i,j)处的像素值。
MSE=m×n1i=1∑mj=1∑n(I(i,j)−I^(i,j))2其中:I是图像,I^是其处理版本m×n是图像的尺寸,即图像中的像素数。I(i,j)是原始图像在位置(i,j)处的像素值。I^(i,j)是处理后图像在位置(i,j)处的像素值。
代码实现(Python):
本文提供的参考代码可以实现多通道的MSE计算,计算的数值已经经过归一化处理。
import cv2
import numpy as np
def resize_to_smaller(image1, image2):
"""
将两幅图像调整为相同的较小尺寸。
"""
h1, w1 = image1.shape[:2]
h2, w2 = image2.shape[:2]
target_height = min(h1, h2)
target_width = min(w1, w2)
resized_image1 = cv2.resize(image1, (target_width, target_height), interpolation=cv2.INTER_AREA)
resized_image2 = cv2.resize(image2, (target_width, target_height), interpolation=cv2.INTER_AREA)
return resized_image1, resized_image2
def calculate_mse_per_channel(image1, image2):
"""
计算彩色图像每个通道的均方误差 (MSE)。
"""
assert image1.shape == image2.shape, "两幅图像的尺寸必须相同"
mse_values = []
for channel in range(3): # 对B、G、R通道分别计算
channel1 = image1[:, :, channel] / 255.0
channel2 = image2[:, :, channel] / 255.0
mse = np.mean((channel1 - channel2) ** 2)
mse_values.append(mse)
return mse_values
def main(image_path1, image_path2):
"""
主函数,读取图像、调整尺寸并计算每个通道的 MSE。
"""
img1 = cv2.imread(image_path1)
img2 = cv2.imread(image_path2)
if img1 is None or img2 is None:
print("无法加载图像,请检查文件路径!")
return
img1_resized, img2_resized = resize_to_smaller(img1, img2)
# 计算每个通道的 MSE
mse_values = calculate_mse_per_channel(img1_resized, img2_resized)
# print(f"MSE (B通道): {mse_values[0]:.6f}")
# print(f"MSE (G通道): {mse_values[1]:.6f}")
# print(f"MSE (R通道): {mse_values[2]:.6f}")
# 平均 MSE
avg_mse = np.mean(mse_values)
print(f"平均 MSE: {avg_mse:.6f}")
# 程序入口
image1 = ''
image2 = ''
main(image1, image2)
峰值信噪比 Peak Signal-to-Noise Ratio
功能
PSNR 表示图像的信号与噪声之间的比率。值越高,图像质量越好。
特点
越大越好:PSNR 值越高,表示图像质量越好。通常来说,PSNR 大于 30 dB 时,人眼几乎无法分辨原始图像和压缩后的图像之间的差异。
对视觉质量有一定反映:与 MSE 相比,PSNR 直接通过对数转换来反映图像质量,且以分贝为单位,更符合人类对信号强度的感知。
MSE的关系:PSNR 和 MSE 是反相关的,MSE 越小,PSNR 越大,意味着图像质量越好。实际上,PSNR 是通过均方误差来推导的,目的是提供一个更容易理解的度量,尤其是在图像压缩中,PSNR 值越高,通常代表图像的失真较小。
单位:PSNR 的单位是分贝(dB)。PSNR 值通常在 20 dB 到 40 dB 之间,超过 40 dB 时,图像质量通常非常接近原始图像。
公式
P S N R = 10 ⋅ log 10 ( I max 2 M S E ) 其 中 : I m a x 是 图 像 中 可 能 的 最 大 像 素 值 , 对 于 8 位 图 像 , 通 常 I m a x = 255. M S E 即 上 文 介 绍 的 均 方 误 差 PSNR = 10 \cdot \log_{10} ( \frac{I_{\text{max}}^2}{MSE}) \\\\ 其中: I_{max}是图像中可能的最大像素值,对于8位图像,通常I_{max}=255. \\ MSE即上文介绍的均方误差 PSNR=10⋅log10(MSEImax2)其中:Imax是图像中可能的最大像素值,对于8位图像,通常Imax=255.MSE即上文介绍的均方误差
代码实现(Python)
三通道,归一化处理的PSNR值
import cv2
import numpy as np
def calculate_psnr_from_mse(avg_mse, max_pixel=1.0):
"""
根据平均 MSE 计算 PSNR。
max_pixel: float
图像的最大像素值(归一化图像为 1.0,原始图像为 255)。
"""
if avg_mse == 0:
return float('inf') # 如果平均 MSE 为 0,PSNR 为无穷大
return 10 * np.log10((max_pixel ** 2) / avg_mse)
def process_rgb_psnr(image1, image2):
"""
计算 RGB 图像的平均 MSE,并根据平均 MSE 计算 PSNR。
"""
assert image1.shape == image2.shape, "两幅图像的尺寸必须相同"
# 归一化到 [0, 1]
image1_normalized = image1 / 255.0
image2_normalized = image2 / 255.0
# 计算 RGB 各通道的 MSE
mse_b = np.mean((image1_normalized[:, :, 0] - image2_normalized[:, :, 0]) ** 2)
mse_g = np.mean((image1_normalized[:, :, 1] - image2_normalized[:, :, 1]) ** 2)
mse_r = np.mean((image1_normalized[:, :, 2] - image2_normalized[:, :, 2]) ** 2)
# 计算平均 MSE
avg_mse = (mse_b + mse_g + mse_r) / 3
# 计算 PSNR
psnr = calculate_psnr_from_mse(avg_mse)
return {"Average MSE": avg_mse, "PSNR": psnr}
def main(image_path1, image_path2):
"""
主函数,读取图像并计算平均 MSE 和 PSNR。
"""
# 读取图像
img1 = cv2.imread(image_path1)
img2 = cv2.imread(image_path2)
if img1 is None or img2 is None:
print("无法加载图像,请检查文件路径!")
return
# 调整图像大小为相同
h1, w1 = img1.shape[:2]
h2, w2 = img2.shape[:2]
target_height = min(h1, h2)
target_width = min(w1, w2)
img1_resized = cv2.resize(img1, (target_width, target_height), interpolation=cv2.INTER_AREA)
img2_resized = cv2.resize(img2, (target_width, target_height), interpolation=cv2.INTER_AREA)
# 计算平均 MSE 和 PSNR
results = process_rgb_psnr(img1_resized, img2_resized)
print(f"PSNR: {results['PSNR']:.3f} dB")
# 程序入口
image1 = ''
image2 = ''
main(image1, image2)
结构相似性指数 Structural Similarity Index
功能
SSIM 不仅关注像素值之间的差异,还考虑了图像的结构信息(例如纹理、亮度和对比度),因此更加符合人类视觉感知。值越大,表明图像越相同。
特点
更符合人眼视觉感知:SSIM 考虑了图像的亮度、对比度和结构信息,模拟了人眼对图像的感知,因此它的评估结果更符合人类的主观感知。
不受单纯像素差异影响:不同于 MSE 和 PSNR,SSIM 更注重结构信息,因此对于相同的均方误差,SSIM 能提供更精确的质量评估。
局部结构信息:SSIM 是局部计算的,通常通过在图像中滑动一个小窗口(例如 8x8 或 11x11 的窗口),逐块计算图像的结构相似性。
综合评价:通过整幅图像的局部SSIM值来得到整体的相似性评估。
单位:SSIM值的范围通常在0到1之间,
公式
S S I M ( x , y ) = ( 2 μ x μ y + C 1 ) ( 2 σ x y + C 2 ) ( μ x 2 + μ y 2 + C 1 ) ( σ x 2 + σ y 2 + C 2 ) 其 中 : x 和 y 是 两 张 图 像 ( 原 始 图 像 和 重 建 图 像 ) 的 局 部 图 像 块 ( 一 般 是 滑 动 窗 口 的 区 域 ) 。 μ x 和 μ y 是 图 像 x 和 y 块 的 平 均 值 ( 亮 度 信 息 ) 。 σ x 2 和 σ y 2 是 图 像 块 x 和 y 的 方 差 ( 对 比 度 信 息 ) 。 σ x y 是 图 像 x 和 y 块 之 间 的 协 方 差 ( 结 构 信 息 ) 。 C 1 和 C 2 是 常 数 , 用 于 避 免 分 母 为 零 的 情 况 , 通 常 选 择 C 1 = ( K 1 L ) 2 和 C 2 = ( K 2 L ) 2 , 其 中 L 是 像 素 值 的 动 态 范 围 , K 1 和 K 2 是 常 数 。 SSIM(x, y) = \frac{(2 \mu_x \mu_y + C_1)(2 \sigma_{xy} + C_2)}{(\mu_x^2 + \mu_y^2 + C_1)(\sigma_x^2 + \sigma_y^2 + C_2)} \\ 其中: x 和 y 是两张图像(原始图像和重建图像)的局部图像块(一般是滑动窗口的区域)。\\ \mu_x 和 \mu_y 是图像 x和 y 块的平均值(亮度信息)。\\ \sigma_x^2 和\sigma_y^2 是图像块 x 和 y 的方差(对比度信息)。\\ \sigma_{xy} 是图像 x 和 y 块之间的协方差(结构信息)。\\ C_1 和 C_2 是常数,用于避免分母为零的情况,通常选择 C_1 = (K_1 L)^2 和 C_2 = (K_2 L)^2 ,\\ 其中 L 是像素值的动态范围, K_1 和 K_2 是常数。 SSIM(x,y)=(μx2+μy2+C1)(σx2+σy2+C2)(2μxμy+C1)(2σxy+C2)其中:x和y是两张图像(原始图像和重建图像)的局部图像块(一般是滑动窗口的区域)。μx和μy是图像x和y块的平均值(亮度信息)。σx2和σy2是图像块x和y的方差(对比度信息)。σxy是图像x和y块之间的协方差(结构信息)。C1和C2是常数,用于避免分母为零的情况,通常选择C1=(K1L)2和C2=(K2L)2,其中L是像素值的动态范围,K1和K2是常数。
代码实现(Python)
借助scikit-image库实现,逐通道计算,求平均
from skimage.metrics import structural_similarity as ssim
import cv2
def calculate_ssim(image1, image2):
"""
计算两幅图像的 SSIM(结构相似性指数),逐通道计算 RGB 的 SSIM。
综合 SSIM 值(平均 SSIM)。
"""
# 确保输入的图像尺寸相同
assert image1.shape == image2.shape, "两幅图像的尺寸必须相同"
# 分离 RGB 通道
r1, g1, b1 = cv2.split(image1)
r2, g2, b2 = cv2.split(image2)
# 计算每个通道的 SSIM
ssim_r = ssim(r1, r2)
ssim_g = ssim(g1, g2)
ssim_b = ssim(b1, b2)
# 计算总体 SSIM(三个通道的平均值)
overall_ssim = (ssim_r + ssim_g + ssim_b) / 3
return overall_ssim
def main(image_path1, image_path2):
"""
主函数,读取图像并计算 SSIM。
"""
# 读取图像(RGB 图像)
img1 = cv2.imread(image_path1, cv2.IMREAD_COLOR)
img2 = cv2.imread(image_path2, cv2.IMREAD_COLOR)
if img1 is None or img2 is None:
print("无法加载图像,请检查文件路径!")
return
# 调整图像大小为相同
h1, w1 = img1.shape[:2]
h2, w2 = img2.shape[:2]
target_height = min(h1, h2)
target_width = min(w1, w2)
img1_resized = cv2.resize(img1, (target_width, target_height), interpolation=cv2.INTER_AREA)
img2_resized = cv2.resize(img2, (target_width, target_height), interpolation=cv2.INTER_AREA)
# 计算 SSIM
ssim_value = calculate_ssim(img1_resized, img2_resized)
print(f"SSIM: {ssim_value:.6f}")
# 程序入口
image1 = ''
image2 = ''
main(image1, image2)