单通道,三通道,多通道图像计算PSNR,SSIM 代码示例与函数注意事项讲解(peak_signal_noise_ratio,structural_similarity)

0、直接使用

单通道图片计算指标代码看2.2

三通道图片计算指标代码看2.3

1、PSNR,SSIM的知识点讲解、原理分析

1.1 PSNR

Peak Signal-to-Noise Ratio 峰值信噪比 单位为 d B dB dB

给定一个大小为 m × n m \times n m×n的干净图像 I I I和噪声图像 K K K,均方误差 M S E MSE MSE定义为:
M S E = 1 m n ∑ i = 0 m − 1 ∑ j = 0 n − 1 [ I ( i , j ) − K ( i , j ) ] 2 M S E=\frac{1}{m n} \sum_{i=0}^{m-1} \sum_{j=0}^{n-1}[I(i, j)-K(i, j)]^{2} MSE=mn1i=0m1j=0n1[I(i,j)K(i,j)]2
然后 P S N R PSNR PSNR就定义为:
P S N R = 10 ⋅ log ⁡ 10 ( M A X I 2 M S E ) 或 者 P S N R = 20 ⋅ log ⁡ 10 ( M A X I M S E ) P S N R=10 \cdot \log _{10}\left(\frac{M A X_{I}^{2}}{M S E}\right)\\ 或者\\ P S N R=20 \cdot \log _{10}\left(\frac{M A X_{I}}{\sqrt{M S E}}\right) PSNR=10log10(MSEMAXI2)PSNR=20log10(MSE MAXI)
其中 M A X I 2 M A X_{I}^{2} MAXI2为图片可能的最大像素值。如果每个像素都由 8 位二进制来表示,那么就为 255。通常,如果像素值由位 B B B二进制来表示,那么 M A X I = 2 B − 1 M A X_{I}=2^{B}-1 MAXI=2B1

一般地,针对 uint8 数据,最大像素值为 255;针对浮点型数据,最大像素值为 1。

上面是针对灰度图像的计算方法,如果是彩色图像,通常有三种方法来计算。其中,第二和第三种方法比较常见。

  • 分别计算 RGB 三个通道的 PSNR,然后取平均值。
  • 计算 RGB 三通道的 MSE ,然后再除以 3 。
  • 将图片转化为 YCbCr 格式,然后只计算 Y 分量也就是亮度分量的 PSNR。

针对超光谱图像,我们需要针对不同波段分别计算 P S N R PSNR PSNR,然后取平均值,这个指标称为 M P S N R MPSNR MPSNR

1.2 SSIM

Structural SIMilarity 结构相似性

S S I M SSIM SSIM公式基于样本 x x x和之 y y y间的三个比较衡量:亮度 (luminance)、对比度 (contrast) 和结构 (structure)。
l ( x , y ) = 2 μ x μ y + c 1 μ x 2 + μ y 2 + c 1 c ( x , y ) = 2 σ x σ y + c 2 σ x 2 + σ y 2 + c 2 s ( x , y ) = σ x y + c 3 σ x σ y + c 3 l(x, y)=\frac{2 \mu_{x} \mu_{y}+c_{1}}{\mu_{x}^{2}+\mu_{y}^{2}+c_{1}}\\c(x, y)=\frac{2 \sigma_{x} \sigma_{y}+c_{2}}{\sigma_{x}^{2}+\sigma_{y}^{2}+c_{2}}\\s(x, y)=\frac{\sigma_{x y}+c_{3}}{\sigma_{x} \sigma_{y}+c_{3}} l(x,y)=μx2+μy2+c12μxμy+c1c(x,y)=σx2+σy2+c22σxσy+c2s(x,y)=σxσy+c3σxy+c3
一般取 c 3 = c 2 / 2 c_{3}=c_{2} / 2 c3=c2/2

  • μ x \mu_x μx x x x的均值
  • μ y \mu_y μy y y y的均值
  • σ x 2 \sigma_x^2 σx2 x x x的方差
  • σ y 2 \sigma_y^2 σy2 y y y的方差
  • σ x y \sigma_{xy} σxy x x x y y y的协方差
  • c 1 = ( k 1 L ) 2 , c 2 = ( k 2 L ) 2 c_{1}=\left(k_{1} L\right)^{2}, c_{2}=\left(k_{2} L\right)^{2} c1=(k1L)2,c2=(k2L)2为两个常数,避免除零
  • L L L为像素值的范围, 2 B − 1 2^B-1 2B1
  • k 1 = 0.01 , k 2 = 0.03 k_{1}=0.01, k_{2}=0.03 k1=0.01,k2=0.03为默认值

那么
S S I M ( x , y ) = [ l ( x , y ) α ⋅ c ( x , y ) β ⋅ s ( x , y ) γ ] S S I M(x, y)=\left[l(x, y)^{\alpha} \cdot c(x, y)^{\beta} \cdot s(x, y)^{\gamma}\right] SSIM(x,y)=[l(x,y)αc(x,y)βs(x,y)γ]
α , β , γ \alpha, \beta, \gamma α,β,γ设为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 ) S S I M(x, y)=\frac{\left(2 \mu_{x} \mu_{y}+c_{1}\right)\left(2 \sigma_{x y}+c_{2}\right)}{\left(\mu_{x}^{2}+\mu_{y}^{2}+c_{1}\right)\left(\sigma_{x}^{2}+\sigma_{y}^{2}+c_{2}\right)} SSIM(x,y)=(μx2+μy2+c1)(σx2+σy2+c2)(2μxμy+c1)(2σxy+c2)
每次计算的时候都从图片上取一个 N × N N×N N×N的窗口,然后不断滑动窗口进行计算,最后取平均值作为全局的 SSIM。

对于多通道的SSIM

  • 分别计算 RGB 各个通道上的 PSNR\SSIM均值,然后取平均值(除以3)。
  • 将图像转换为YCbCr格式,然后只计算Y分量(亮度分量)的PSNR\SSIM。

针对超光谱图像,我们需要针对不同波段分别计算 SSIM,然后取平均值,这个指标称为 MSSIM。

2、计算代码

2.1 看一下skimage的源码

def peak_signal_noise_ratio(image_true, image_test, *, data_range=None):
    """
    Compute the peak signal to noise ratio (PSNR) for an image.
    Parameters
    ----------
    image_true : ndarray
        Ground-truth image, same shape as im_test.
    image_test : ndarray
        Test image.
    data_range : int, optional
        The data range of the input image (distance between minimum and
        maximum possible values).  By default, this is estimated from the image
        data-type.
    Returns
    -------
    psnr : float
        The PSNR metric.
    Notes
    -----
    .. versionchanged:: 0.16
        This function was renamed from ``skimage.measure.compare_psnr`` to
        ``skimage.metrics.peak_signal_noise_ratio``.
    References
    ----------
    .. [1] https://en.wikipedia.org/wiki/Peak_signal-to-noise_ratio
    """
    check_shape_equality(image_true, image_test)

    if data_range is None:
        if image_true.dtype != image_test.dtype:
            warn("Inputs have mismatched dtype.  Setting data_range based on "
                 "image_true.")
        dmin, dmax = dtype_range[image_true.dtype.type]#在dtype_range这个字典中会存储不同numpy类型所对应的可能去到的最大最小值  这个字典在下面一代码中
        true_min, true_max = np.min(image_true), np.max(image_true)
        if true_max > dmax or true_min < dmin:
            raise ValueError(
                "image_true has intensity values outside the range expected "
                "for its data type. Please manually specify the data_range.")
        # 真实图片最小值是不是会取到负的
        # 针对无符号整型 dmin, dmax 为0,255   data_range为255
        # 针对无符号整型 dmin, dmax 为-1,1    data_range为1
        if true_min >= 0:
            # most common case (255 for uint8, 1 for float)
            data_range = dmax
        else:
            data_range = dmax - dmin

    image_true, image_test = _as_floats(image_true, image_test)

    err = mean_squared_error(image_true, image_test)
    return 10 * np.log10((data_range ** 2) / err)

字典代码

dtype_range = {
    
    bool: (False, True),
               np.bool_: (False, True),
               np.bool8: (False, True),
               float: (-1, 1),
               np.float_: (-1, 1),
               np.float16: (-1, 1),
               np.float32: (-1, 1),
               np.float64: (-1, 1)}
dtype_range.update(_integer_ranges)#还补充了整型数据的取值范围  实际取值我算了一下是

测试一下这个dtype_range

image = image.astype(np.uint8)
print("数据类型:",type(image))
print("数据结构:",image.dtype)
print("最大最小值:",dtype_range[image.dtype.type])

image = image.astype(np.float64)
print("数据类型:",type(image))
print("数据结构:",image.dtype)
print("最大最小值:",dtype_range[image.dtype.type])

2.2 实际使用情况1:单通道情况

尽可能将输入转化为

数据类型为uint8,范围为0-255的图像image1,image2

数据类型为float64,范围为0-1.0的图像image1,image2

错误示范:输入不符合标准

float64 对应的范围应该归一化到0-1,判断的时候会出错报错说你的范围超过了数据类型所对应的范围

from skimage.metrics import peak_signal_noise_ratio
from skimage.metrics import structural_similarity
import skimage.io as io

image_path1 = "./1.png"
image_path2 = "./2.png"
# 因为是张彩色图片所以截取出一个通道
image1 = io.imread(image_path1)[...,0]
image2 = io.imread(image_path2)[...,0]
image1 = image1/1.0
image2 = image2/1.0
# 至此image1为float64 且0-255.0  就会报错

print(image1.dtype)
psnr_val = peak_signal_noise_ratio(image1, image2)
ssim_val = structural_similarity(image1,image2,win_size=11,gaussian_weights=True,multichannel=True,data_range=1.0,K1=0.01,K2=0.03,sigma=1.5)
print("psnr_val",psnr_val)
print("ssim_val",ssim_val)

正确示例:数据类型为uint8,范围为0-255的图像image1,image2

from skimage.metrics import peak_signal_noise_ratio
from skimage.metrics import structural_similarity
import skimage.io as io

image_path1 = "./1.png"
image_path2 = "./2.png"
# 因为是张彩色图片所以截取出一个通道
image1 = io.imread(image_path1)[...,0]
image2 = io.imread(image_path2)[...,0]
print(image1.dtype)# uint8 范围0-255

psnr_val = peak_signal_noise_ratio(image1, image2)
ssim_val = structural_similarity(image1,image2,win_size=11,gaussian_weights=True,multichannel=True,data_range=1.0,K1=0.01,K2=0.03,sigma=1.5)
print("psnr_val",psnr_val)
print("ssim_val",ssim_val)

正确示例:数据类型为float64,范围为0-1.0的图像image1,image2

from skimage.metrics import peak_signal_noise_ratio
from skimage.metrics import structural_similarity
import skimage.io as io

image_path1 = "./1.png"
image_path2 = "./2.png"
image1 = io.imread(image_path1)[...,0]
image2 = io.imread(image_path2)[...,0]
image1 = image1/1.0
image2 = image2/1.0
# 至此image1为float64 且0-255.0
# 归一化到0-1.0
image1 = image1/255.0
image2 = image2/255.0
print(image1.dtype)
psnr_val = peak_signal_noise_ratio(image1, image2)
ssim_val = structural_similarity(image1,image2,win_size=11,gaussian_weights=True,multichannel=True,data_range=1.0,K1=0.01,K2=0.03,sigma=1.5)
print("psnr_val",psnr_val)
print("ssim_val",ssim_val)

2.3 实际使用情况2:RGB三通道

需要先转换成YCbCr空间然后对亮度进行求解PSNR,转换方法可以参照我的另一篇博客

RGB图像转换成YCbCr图像,rgb2ycbcr的使用,转换参数_呆呆象呆呆的博客-CSDN博客

同时也要保证数值范围和数值类型要相符合,尽可能将输入转化为

数据类型为uint8,范围为0-255的图像image1,image2(不太推荐因为算出Y通道后,大概率都是浮点型的数据,强行转换成uint8这样精度会下降,所以比较推荐下面一种方式)

数据类型为float64,范围为0-1.0的图像image1,image2

正确示例1:使用rgb2ycbcr计算Y通道后求PSNR或者SSIM

from skimage.metrics import peak_signal_noise_ratio
from skimage.metrics import structural_similarity
from skimage.color import rgb2ycbcr
import skimage.io as io

image_path1 = "./1.png"
image_path2 = "./2.png"
image1 = io.imread(image_path1)
image2 = io.imread(image_path2)

# rgb2ycbcr的输入需要归一化到0-1.0的float
#这个在上一篇blog中讲过了rgb2ycbcr输出为浮点型且范围是0-255.0 所以需要再次归一化0-1
image1 = image1/255.0
image2 = image2/255.0
image1 = rgb2ycbcr(image1)[:, :, 0:1]
image2 = rgb2ycbcr(image2)[:, :, 0:1] 
image1 = image1/255.0
image2 = image2/255.0
print(image1.dtype)

psnr_val = peak_signal_noise_ratio(image1, image2)
ssim_val = structural_similarity(image1,image2,win_size=11,gaussian_weights=True,multichannel=True,data_range=1.0,K1=0.01,K2=0.03,sigma=1.5)
print("psnr_val",psnr_val)
print("ssim_val",ssim_val)

三通道的PSNR和单通道的PSNR肯定是不一样的

正确示例2:直接计算Y通道后求PSNR或者SSIM

from skimage.metrics import peak_signal_noise_ratio
from skimage.metrics import structural_similarity
import skimage.io as io

image_path1 = "./1.png"
image_path2 = "./2.png"
# 因为是张彩色图片 所以选一个通道
image1 = io.imread(image_path1)
image2 = io.imread(image_path2)

#我认为最简单的方法
image1 = image1/255.0
image2 = image2/255.0
image1  =  65.481 * image1[:,:,0] + 128.553 * image1[:,:,1] + 24.966 * image1[:,:,2]  # 不加16是因为之后会抵消
image2  =  65.481 * image2[:,:,0] + 128.553 * image2[:,:,1] + 24.966 * image2[:,:,2]  
image1 = image1/255.0
image2 = image2/255.0
# 只计算Y通道的值

print(image1.dtype)
psnr_val = peak_signal_noise_ratio(image1, image2)
ssim_val = structural_similarity(image1,image2,win_size=11,gaussian_weights=True,multichannel=True,data_range=1.0,K1=0.01,K2=0.03,sigma=1.5)
print("psnr_val",psnr_val)
print("ssim_val",ssim_val)

LAST、参考文献

scikit-image/simple_metrics.py at main · scikit-image/scikit-image · GitHub

图像质量评价指标之 PSNR 和 SSIM - 知乎

PSNR与SSIM对于彩色图像和灰度图像的计算区别_风雪夜归人o的博客-CSDN博客

图像质量的客观评估指标PSNR与SSIM_小村长技术blog-CSDN博客

猜你喜欢

转载自blog.csdn.net/qq_41554005/article/details/114418094