本文目录:
- 峰值信噪比 (PSNR)
- 结构相似性 (SSIM)
1. 峰值信噪比 (PSNR)
1.1 计算公式
峰值信噪比 (Peak Signal-to-Noise Ratio, PSNR),用于衡量两张图像之间差异,例如压缩图像与原始图像,评估压缩图像质量;复原图像与 Ground Truth (GT),评估复原算法性能等。
公式:
\[\text{PSNR} = 10 \times \log_{10} \left( \frac{\text{MaxValue}^2}{\text{MSE}} \right) \tag{1}\]其中,$MSE$ 为两张图像逐像素的均方误差;$MaxValue$ 为图像像素可取到的最大值,例如 8 位图像为 $2^8-1=255$。在代码实现中,直接将次方提到外面: \(\text{PSNR}=20\times\log_{10}(\frac{\text{MaxValue}}{\sqrt{\text{MSE}}}) \tag{2}\)
1.2 代码实现
1.2.1 BGR 空间
使用 opencv 读取图像时,默认的通道顺序 HWC
,颜色顺序 BGR
,数据类型 np.ndarray
,数据范围为 np.uint8
型的 $[0, 255]$。如果是在 BGR
空间计算 PSNR,那么只需要分别在 BGR
三个通道上分别计算 $MSE$,取平均然后带入公式2即可。主要代码如下:
import cv2
import numpy as np
# 读取需要比较的图像
img1 = cv2.imread('./image1.png')
img2 = cv2.imread('./image2.png')
# 数据类型转换,结果更精确
img1 = img1.astype(np.float64)
img2 = img2.astype(np.float64)
# 计算3个通道的平均MSE,然后计算psnr
mse = np.mean((img1 - img2) ** 2)
if mse == 0:
psnr = float('inf')
else:
psnr = 20 * np.log10(255. / np.sqrt(mse))
print(f'PSNR value: {psnr:.6f}dB.')
1.2.2 YCbCr 空间
想要计算 YCbCr
空间的 PSNR,首先需要将 opencv 读取的 BGR
颜色空间的图像转换到 YCbCr
颜色空间。opencv 自带有颜色空间转换函数 cv2.COLOR_BGR2YCrCb()
,但是,opencv 的转换遵循的是 JPEG 转换,转换后的各通道具有完整的 8 bit 范围 $[0, 255]$。但是在超分的 PSNR 计算时,往往使用的是 ITU-R BT.601 转换,这种转换的结果是 Y 分量的范围从 $[0, 255]$ 变为 $[16, 235]$,而 $C_B$ 和 $C_R$ 分量从 $[0, 255]$ 变为 $[16, 240]$。ITU-R BT.601 转换公式:
其中,$B’G’R’$ 的数值范围是 $[0, 1]$ ` type(float)`。
仔细看 ITU-R BT.601 转换时可以发现,计算出来的并不是 Y,而是 Y’,这两者有什么区别呢?
伽马校正——人眼特性
由于人眼对于暗光的变化较为敏感,所以当亮度由 0 变到 0.01 时,人眼感觉到的亮度变化会比亮度由 0.99 变到 1.0 更明显。也就是说,人眼认为的中灰其实不在亮度为 0.5 的地方,而是在大约亮度为 0.18 的地方(18 度灰)。
针对于人眼的特性,在拍照时需要将更多的资源用于存储暗光部分,应该把人眼认为的中灰亮度放在像素值为 0.5 的地方,也就是说,0.18 亮度应该编码成 0.5 像素值。这样存储空间就可以充分利用起来了。所以,摄影设备如果使用了 8 位空间存储照片的话,会用大约为 2.2 或 2.4 的 Encoding Gamma 来对输入的亮度编码,得到一张图像。这一步使得图像数据更加紧凑,可以在相同的比特深度下保存更多的细节信息。2.2 这个值完全是由于人眼的特性测量得到的。
\[I_{\mathrm{encoded}}=I_{\mathrm{linear}}^{1/\gamma} \tag{4}\]由于这个原因,我们保存的图像一般都是经过了伽马编码的,在使用 opencv 读取时,得到的信息也都是经过了伽马编码的,所以在进行颜色空间转换时得到的是经过了伽马校正的 Y‘ 而不是原始的 Y。
那么我们使用 opencv 的
cv2.imshow()
函数来显示图像时,显示的是校正后的还是原始的呢?答案是校正后的。OpenCV 的cv2.imshow()
函数只是将图像数据直接显示到窗口中,不会应用任何额外的伽马校正。显示伽马校正(Display Gamma)通常是显示设备(如监视器、电视等)的责任,而不是 OpenCV 的功能。在显示图像之前,显示设备(如监视器、电视等)会应用 Display Gamma,以确保图像在显示设备上看起来是正确的。这里的 gamma 通常也是 2.2 或 2.4 ,与编码伽马相匹配,但也会根据显示设备的特性有所调整 \(I_{\mathrm{displayed}}=I_{\mathrm{encoded}}^{\gamma} \tag{5}\) 注:虽然计算出来的确实是 Y’,但是在超分论文中并没有明确指出来是 Y’,而是直接使用 Y 来表示
BGR
——> YCbCr
的主要步骤如下:[0, 255] type(int) —> [0, 255] type(float) —> [0, 1] type(float) (公式3中要求为此范围) —> [0, 255] type(float)
主要代码如下:
"""计算结果和官方代码稍有区别"""
import cv2
import numpy as np
# 读取需要比较的图像
img1 = cv2.imread('./images/bird.png')
img2 = cv2.imread('./images/bird_SwinIR.png')
img1 = img1.astype(np.float32) / 255.
img2 = img2.astype(np.float32) / 255.
img1_y = np.dot(img1, [24.996, 128.553, 65.481]) + 16.0
img2_y = np.dot(img2, [24.996, 128.553, 65.481]) + 16.0
img1_y = img1_y[..., None]
img2_y = img2_y[..., None]
# 计算Y通道的MSE,然后计算psnr
mse = np.mean((img1_y - img2_y) ** 2)
if mse == 0:
psnr = float('inf')
else:
psnr = 20 * np.log10(255. / np.sqrt(mse))
print(f'PSNR value: {psnr:.6f}dB.')
2. 结构相似性 (SSIM)
2.1 计算公式
结构相似性 (Structural Similarity, SSIM) 基于人眼会提取图像中结构化信息的假设,比传统方式更符合人眼视觉感知。
公式:
\[\mathrm{SSIM}(\mathbf{x},\mathbf{y})=[l(\mathbf{x},\mathbf{y})]^\alpha\cdot[c(\mathbf{x},\mathbf{y})]^\beta\cdot[s(\mathbf{x},\mathbf{y})]^\gamma \tag{6}\]SSIM 由三个部分组成,$α,β,γ>0$,用于调整三个部分的比重:
\[l(\mathbf{x},\mathbf{y})=\frac{2\mu_x\mu_y+C_1}{\mu_x^2+\mu_y^2+C_1} \\ c(\mathbf{x},\mathbf{y})=\frac{2\sigma_x \sigma_y+C_2}{\sigma_x^2+\sigma_y^2+C_2} \tag{7} \\ s(\mathbf{x},\mathbf{y})=\frac{\sigma_{xy}+C_3}{\sigma_x\sigma_y+C_3}\]其中,$C_1=(K_1L)^2$ , $C_2=(K_2L)^2$, 用于规避分母为0的情况,$L$ 等价于 PSNR 中的 $MaxValue$,$K_1, K_2 « 1$是很小的常数,默认 $K_1=0.01, K_2=0.03$。论文中设置 $α=β=γ=1$ 和 $C_3 = C_2/2$ 来简化公式:
\[\mathrm{SSIM}(\mathbf{x},\mathbf{y})=\frac{(2\mu_x\mu_y+C_1)(2\sigma_{xy}+C_2)}{\left(\mu_x^2+\mu_y^2+C_1\right)\left(\sigma_x^2+\sigma_y^2+C_2\right)} \tag{8}\]其中,均值: \(\mu_x=\frac1N\sum_{i=1}^Nx_i \tag{9}\) 标准差: \(\sigma_x = \left(\frac{1}{N-1}\sum_{i=1}^N\left(x_i - \mu_x\right)^2\right)^{1/2} \tag{10}\) 协方差: \(\sigma_{xy}=\frac{1}{N-1}\sum_{i=1}^{N}\left(x_i-\mu_x\right)\left(y_i-\mu_y\right) \tag{11}\)
2.2 代码实现
具体代码见 Github 仓库。