图片相似性比较之哈希算法 - AIUAI 中有博友推荐所 PSNR 和 SSIM 很好用,这里学习备忘下.

1. PSNR

1.1. 定义

PSNR,Peak Signal-to-Noise Ratio,峰值信噪比,是一个表示信号最大可能功率和影响它的表示精度的破坏性噪声功率的比值的工程术语。由于许多信号都有非常宽的动态范围,峰值信噪比常用对数分贝单位来表示

峰值信噪比 - 维基百科

PSNR 是用于衡量图像质量的指标,比如在图像压缩、超分辨率重建图像等领域,其是一种重要的指标.

PSNR 常简单的通过均方误差(MSE)来定义. 比如,对于两张单色图像 I 和 K,如果一个为另外一个的噪声近似,则二者之间的均方误差定义为:

$$ MSE = \frac{1}{mn}\sum_{i=0}^{m-1} \sum_{j=0}^{n-1} [I (i, j) - K(i, j)]^2 $$

而峰值信噪比PSNR 的定义为:

$$ PSNR = 10 \cdot log_{10} (\frac{MAX_I^2}{MSE}) = 20 \cdot log_{20}(\frac{MAX_I}{\sqrt{MSE}}) $$

其中,$MAX_I$ 表示图像点颜色的最大数值,如果每个采样点用 8 位表示,则最大数值为 255.

更通用的表示是,如果每个采样点用 B 位线性脉冲编码调制表示,则 $MAX_I = 2^B - 1$,即:

$$ PSNR = 20 \cdot log_{10}(\frac{2^B - 1}{\sqrt{MSE}}) $$

类似地,对于每点有 RGB 三个值的彩色图像来说,PSNR 的定义也是类似的,只是均方误差是所有方差之和除以图像尺寸再除以3.

MSE 越小,则 PSNR 越大;PSNR越大,代表着图像质量越好.

[1] - PSNR高于40dB说明图像质量极好(即非常接近原始图像)

[2] - 在30-40dB通常表示图像质量是好的(即失真可以察觉但可以接受)

[3] - 在20-30dB说明图像质量差

[4] - 低于20dB图像不可接受

1.2. Python 实现

出处:PSNR-峰值信噪比(原理及python代码实现)

作者:木盏

# PSNR.py
import numpy as np
import math
 
 
def psnr(target, ref, scale):
    # target: 目标图像  
    # ref: 参考图像  
    # scale: 尺寸大小
    # assume RGB image
    target_data = np.array(target)
    target_data = target_data[scale:-scale,scale:-scale]
 
    ref_data = np.array(ref)
    ref_data = ref_data[scale:-scale,scale:-scale]
 
    diff = ref_data - target_data
    diff = diff.flatten('C')
    rmse = math.sqrt(np.mean(diff ** 2.) )
    return 20*math.log10(1.0/rmse)

出处:图像质量评估指标 SSIM / PSNR / MSE

作者:江户川柯壮

def cal_psnr(im1, im2):
    mse = (np.abs(im1 - im2) ** 2).mean()
    psnr = 10 * np.log10(255 * 255 / mse)
    return psnr

skimage 库也提供了相关的计算实现.

skimage.measure.compare_psnr

psnr = skimage.measure.compare_psnr(im1, im2, 255)

1.3 TensorFlow 实现

TensorFlow 中已经包含 PSNR 计算的函数,可直接采用.

tf.image.psnr

import tensorflow as tf
 
def read_img(path):
    return tf.image.decode_image(tf.read_file(path))
 
def psnr(tf_img1, tf_img2):
    return tf.image.psnr(tf_img1, tf_img2, max_val=255)
 
def main():
    t1 = read_img('t1.jpg')
    t2 = read_img('t2.jpg')
    #两张图像t1和t2的尺寸要完全一致
    with tf.Session() as sess:
        sess.run(tf.global_variables_initializer())
        y = sess.run(psnr(t1, t2))
        print(y)
 
if __name__ == '__main__':
    main()

2. SSIM

2.1. 定义

SSIM,Structural Similarity,结构相似性. 也是衡量两幅图片相似性的指标.

结构相似性的基本原理是,认为自然图像时高度结构化的,即相邻像素间具有很强的关联性,而这种关联性表达了场景中物体的结构信息. 人类视觉系统对于图像已经具有很强的理解与信息抽取能力,所以在衡量图像质量时,结构性失真是很重要的考量.

结构相似性 - 维基百科

给定两张图片 $\mathbf{x}$ 和 $\mathbf{y}$,一张为未经压缩处理的图片(即 GT),另一张为算法恢复重建后得到的图片,计算其相似性:

$$ SSIM(\mathbf{x}, \mathbf{y}) = [l(\mathbf{x}, \mathbf{y})]^{\alpha} [c(\mathbf{x}, \mathbf{y})]^{\beta} [s(\mathbf{x}, \mathbf{y})]^{\gamma} $$

其中,$\alpha > 0; \beta > 0; \gamma > 0$,为调整 $l(\mathbf{x}, \mathbf{y})$ 、$c(\mathbf{x}, \mathbf{y})$ 和 $s(\mathbf{x}, \mathbf{y})$ 相对重要性的参数.

$$ 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} $$

$$ s(\mathbf{x}, \mathbf{y}) = \frac{\sigma_{xy} + C_3}{\sigma_x \sigma_y + C_3} $$

其中,$C_1$、$C_2$ 和 $C_3$ 均为常数,用于维持 $l(\mathbf{x}, \mathbf{y})$ 、$c(\mathbf{x}, \mathbf{y})$ 和 $s(\mathbf{x}, \mathbf{y})$ 的稳定,避免分母为 0.

[1] - $l(\mathbf{x}, \mathbf{y})$ 比较 $\mathbf{x}$ 和 $\mathbf{y}$ 的亮度;

[2] - $c(\mathbf{x}, \mathbf{y})$ 比较 $\mathbf{x}$ 和 $\mathbf{y}$ 的对比度;

[3] - $s(\mathbf{x}, \mathbf{y})$ 比较 $\mathbf{x}$ 和 $\mathbf{y}$ 的结构(Structure);

[4] - $\mu_x$ 和 $\mu_y$ 为 $\mathbf{x}$ 和 $\mathbf{y}$ 的平均值;

[5] - $\sigma_x$ 和 $\sigma_y$ 为 $\mathbf{x}$ 和 $\mathbf{y}$ 的标准差;

[6] - $\sigma_{xy}$ 为 $\mathbf{x}$ 和 $\mathbf{y}$ 的协方差.

结构相似值越大,则表示相似性越高. 比如,以两张完全一致的图片计算SSIM,即:$\mu_x = \mu_y$ 且 $\sigma_x = \sigma_y$,有:

$$ SSIM(\mathbf{x}, \mathbf{x}) = \frac{2\mu_x^2 + C_1}{\mu_x^2 + \mu_x^2 + C_1} \times \frac{2\sigma_x^2 + C_2}{\sigma_x^2 + \sigma_x^2 + C_2} \times \frac{\sigma_{xx} + C_3}{\sigma_x \sigma_x + C_3} = 1 $$

实际应用中,一般设定 $\alpha = \beta = \gamma = 1$,$C_3 = C_2/2$,则有:

$$ SSIM(\mathbf{x}, \mathbf{y}) = \frac{(2 \mu_x \mu_y + C_1)(\sigma_{xy} + C_2)}{(\mu_x^2 + \mu_y^2 + C_1) (\sigma_x^2 + \sigma_y^2 + C_2)} $$

在计算两张图片的结构相似性时,会采用局部滑窗处理,一般为 NxN 的小区块,计算该滑窗内的结构相似性,每次以像素为单位滑动滑窗,直到整张图片每个位置的局部结构相似性都计算完毕,将全部的局部结构相似性求平均即为两张图片的结构相似性.

SSIM 的性质:

[1] - 对称性. 即$SSIM(\mathbf{x}, \mathbf{y}) = SSIM(\mathbf{y}, \mathbf{x})$.

[2] - 有上下界. SSIM 的取值范围 为 [-1, 1],完全相同时则 SSIM 值为 1.

2.2. Python 实现

出处:图像质量评估指标 SSIM / PSNR / MSE

作者:江户川柯壮

def cal_ssim(im1,im2):
    assert len(im1.shape) == 2 and len(im2.shape) == 2
    assert im1.shape == im2.shape
    mu1 = im1.mean()
    mu2 = im2.mean()
    sigma1 = np.sqrt(((im1 - mu1) ** 2).mean())
    sigma2 = np.sqrt(((im2 - mu2) ** 2).mean())
    sigma12 = ((im1 - mu1) * (im2 - mu2)).mean()
    k1, k2, L = 0.01, 0.03, 255
    C1 = (k1*L) ** 2
    C2 = (k2*L) ** 2
    C3 = C2/2
    l12 = (2*mu1*mu2 + C1)/(mu1 ** 2 + mu2 ** 2 + C1)
    c12 = (2*sigma1*sigma2 + C2)/(sigma1 ** 2 + sigma2 ** 2 + C2)
    s12 = (sigma12 + C3)/(sigma1*sigma2 + C3)
    ssim = l12 * c12 * s12
    
    return ssim

skimage 库也提供了相关的计算实现.

skimage.measure.compare_ssim

# im1 和 im2 都为灰度图像,uint8 类型
ssim = skimage.measure.compare_ssim(im1, im2, data_range=255)

2.3. TensorFlow 实现

TensorFlow 中已经包含 SSIM 计算的函数,也可直接采用.

tf.image.ssim

tf.image.ssim_multiscale

# Read images from file.
im1 = tf.decode_png('path/to/im1.png')
im2 = tf.decode_png('path/to/im2.png')
# Compute SSIM over tf.uint8 Tensors.
ssim1 = tf.image.ssim(im1, 
                      im2, 
                      max_val=255,
                      filter_size=11,
                      filter_sigma=1.5, 
                      k1=0.01, 
                      k2=0.03)

# Compute SSIM over tf.float32 Tensors.
im1 = tf.image.convert_image_dtype(im1, tf.float32)
im2 = tf.image.convert_image_dtype(im2, tf.float32)
ssim2 = tf.image.ssim(im1, 
                      im2, 
                      max_val=1.0, 
                      filter_size=11,
                      filter_sigma=1.5, 
                      k1=0.01, 
                      k2=0.03)
# ssim1 and ssim2 both have type tf.float32 and are almost equal.

具体源码:

https://github.com/tensorflow/tensorflow/blob/r1.14/tensorflow/python/ops/image_ops_impl.py#L3064-L3136

@tf_export('image.ssim')
def ssim(img1,
         img2,
         max_val,
         filter_size=11,
         filter_sigma=1.5,
         k1=0.01,
         k2=0.03):
  """Computes SSIM index between img1 and img2.
  This function is based on the standard SSIM implementation from:
  Wang, Z., Bovik, A. C., Sheikh, H. R., & Simoncelli, E. P. (2004). Image
  quality assessment: from error visibility to structural similarity. IEEE
  transactions on image processing.
  Note: The true SSIM is only defined on grayscale.  This function does not
  perform any colorspace transform.  (If input is already YUV, then it will
  compute YUV SSIM average.)
  Details:
    - 11x11 Gaussian filter of width 1.5 is used.
    - k1 = 0.01, k2 = 0.03 as in the original paper.
  The image sizes must be at least 11x11 because of the filter size.

  Args:
    img1: First image batch.
    img2: Second image batch.
    max_val: The dynamic range of the images (i.e., the difference between the
      maximum the and minimum allowed values).
    filter_size: Default value 11 (size of gaussian filter).
    filter_sigma: Default value 1.5 (width of gaussian filter).
    k1: Default value 0.01
    k2: Default value 0.03 (SSIM is less sensitivity to K2 for lower values, so
      it would be better if we taken the values in range of 0< K2 <0.4).
  Returns:
    A tensor containing an SSIM value for each image in batch.  Returned SSIM
    values are in range (-1, 1], when pixel values are non-negative. Returns
    a tensor with shape: broadcast(img1.shape[:-3], img2.shape[:-3]).
  """
  _, _, checks = _verify_compatible_image_shapes(img1, img2)
  with ops.control_dependencies(checks):
    img1 = array_ops.identity(img1)

  # Need to convert the images to float32.  Scale max_val accordingly so that
  # SSIM is computed correctly.
  max_val = math_ops.cast(max_val, img1.dtype)
  max_val = convert_image_dtype(max_val, dtypes.float32)
  img1 = convert_image_dtype(img1, dtypes.float32)
  img2 = convert_image_dtype(img2, dtypes.float32)
  ssim_per_channel, _ = _ssim_per_channel(img1, img2, max_val, filter_size,
                                          filter_sigma, k1, k2)
  # Compute average over color channels.
  return math_ops.reduce_mean(ssim_per_channel, [-1])

3. 相关材料

[1] - 图像质量评价指标之 PSNR 和 SSIM

[2] - PSNR-峰值信噪比(原理及python代码实现)

[3] - SSIM(结构相似性)-数学公式及python实现

[4] - SSIM 的原理和代码实现

[5] - pytorch-ssim/compare_ssim_mse

Last modification:September 4th, 2019 at 10:03 pm