结构相似性指数测量是一种预测数字电视和电影画面以及其他类型的数字图像和视频的感知质量的方法。它也用于测量两幅图像之间的相似性,此指数是一个完全参考指标,换句话说,图像质量的测量或预测是基于初始未压缩或无失真图像作为参考的。

结构相似性指数测量是一种基于感知的模型,它将图像质量下降视为结构信息的感知变化,同时还结合了重要的感知现象,包括亮度掩蔽和对比度掩蔽项。与其他技术(如 MSE 或 PSNR)的不同之处在于,这些方法估算绝对误差。结构信息是指像素具有很强的相互依赖性,尤其是当它们在空间上接近时。这些依赖关系包含有关视觉场景中物体结构的重要信息。亮度掩蔽是一种现象,即图像失真(在这种情况下)在明亮区域中往往不太明显,而对比度掩蔽是一种现象,即当图像中存在显着活动或“纹理”时,失真会变得不太明显。

算法

此指数是在图像的各个窗口上计算的。共同大小为 $N \times N$ 的两个窗口 x 和 y 之间的度量为:

$$ \operatorname{结构相似性指数测量}(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)} $$

公式基于 x 和 y 样本之间的三个比较测量值:亮度 (l)、对比度 (c) 和结构 (s)。各个比较函数是:

$$ \begin{aligned} & 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} \end{aligned} $$

结构相似性指数测量的更高级形式称为多尺度结构相似性指数测量,它通过多阶段子采样过程在多个尺度上进行,让人想起早期视觉系统中的多尺度处理。事实证明,它在不同的主观图像和视频数据库上的表现与结构相似性指数测量相当或更好。

Python(TensorFlow)多尺度结构相似性指数测量

 import numpy as np
 from scipy import signal
 from scipy.ndimage.filters import convolve
 import tensorflow as tf
 ​
 ​
 tf.flags.DEFINE_string('original_image', None, 'Path to PNG image.')
 tf.flags.DEFINE_string('compared_image', None, 'Path to PNG image.')
 FLAGS = tf.flags.FLAGS
 ​
 ​
 def _FSpecialGauss(size, sigma):
 ​
   radius = size // 2
   offset = 0.0
   start, stop = -radius, radius + 1
   if size % 2 == 0:
     offset = 0.5
     stop -= 1
   x, y = np.mgrid[offset + start:stop, offset + start:stop]
   assert len(x) == size
   g = np.exp(-((x**2 + y**2)/(2.0 * sigma**2)))
   return g / g.sum()
 ​
 def _SSIMForMultiScale(img1, img2, max_val=255, filter_size=11,
                        filter_sigma=1.5, k1=0.01, k2=0.03):
 ​
   if img1.shape != img2.shape:
     raise RuntimeError('Input images must have the same shape (%s vs. %s).',
                        img1.shape, img2.shape)
   if img1.ndim != 4:
     raise RuntimeError('Input images must have four dimensions, not %d',
                        img1.ndim)
 ​
   img1 = img1.astype(np.float64)
   img2 = img2.astype(np.float64)
   _, height, width, _ = img1.shape
 ​
   size = min(filter_size, height, width)
 ​
   sigma = size * filter_sigma / filter_size if filter_size else 0
 ​
   if filter_size:
     window = np.reshape(_FSpecialGauss(size, sigma), (1, size, size, 1))
     mu1 = signal.fftconvolve(img1, window, mode='valid')
     mu2 = signal.fftconvolve(img2, window, mode='valid')
     sigma11 = signal.fftconvolve(img1 * img1, window, mode='valid')
     sigma22 = signal.fftconvolve(img2 * img2, window, mode='valid')
     sigma12 = signal.fftconvolve(img1 * img2, window, mode='valid')
   else:
 ​
     mu1, mu2 = img1, img2
     sigma11 = img1 * img1
     sigma22 = img2 * img2
     sigma12 = img1 * img2
 ​
   mu11 = mu1 * mu1
   mu22 = mu2 * mu2
   mu12 = mu1 * mu2
   sigma11 -= mu11
   sigma22 -= mu22
   sigma12 -= mu12
 ​
   c1 = (k1 * max_val) ** 2
   c2 = (k2 * max_val) ** 2
   v1 = 2.0 * sigma12 + c2
   v2 = sigma11 + sigma22 + c2
   ssim = np.mean((((2.0 * mu12 + c1) * v1) / ((mu11 + mu22 + c1) * v2)))
   cs = np.mean(v1 / v2)
   return ssim, cs
 ​
 ​
 def MultiScaleSSIM(img1, img2, max_val=255, filter_size=11, filter_sigma=1.5,
                    k1=0.01, k2=0.03, weights=None):
 ​
   if img1.shape != img2.shape:
     raise RuntimeError('Input images must have the same shape (%s vs. %s).',
                        img1.shape, img2.shape)
   if img1.ndim != 4:
     raise RuntimeError('Input images must have four dimensions, not %d',
                        img1.ndim)
 ​
   # Note: default weights don't sum to 1.0 but do match the paper / matlab code.
   weights = np.array(weights if weights else
                      [0.0448, 0.2856, 0.3001, 0.2363, 0.1333])
   levels = weights.size
   downsample_filter = np.ones((1, 2, 2, 1)) / 4.0
   im1, im2 = [x.astype(np.float64) for x in [img1, img2]]
   mssim = np.array([])
   mcs = np.array([])
   for _ in range(levels):
     ssim, cs = _SSIMForMultiScale(
         im1, im2, max_val=max_val, filter_size=filter_size,
         filter_sigma=filter_sigma, k1=k1, k2=k2)
     mssim = np.append(mssim, ssim)
     mcs = np.append(mcs, cs)
     filtered = [convolve(im, downsample_filter, mode='reflect')
                 for im in [im1, im2]]
     im1, im2 = [x[:, ::2, ::2, :] for x in filtered]
   return (np.prod(mcs[0:levels-1] ** weights[0:levels-1]) *
           (mssim[levels-1] ** weights[levels-1]))
 ​
 ​
 def main(_):
   if FLAGS.original_image is None or FLAGS.compared_image is None:
     print('\\nUsage: python msssim.py --original_image=original.png '
           '--compared_image=distorted.png\\n\\n')
     return
 ​
   if not tf.gfile.Exists(FLAGS.original_image):
     print('\\nCannot find --original_image.\\n')
     return
 ​
   if not tf.gfile.Exists(FLAGS.compared_image):
     print('\\nCannot find --compared_image.\\n')
     return
 ​
   with tf.gfile.FastGFile(FLAGS.original_image) as image_file:
     img1_str = image_file.read()
   with tf.gfile.FastGFile(FLAGS.compared_image) as image_file:
     img2_str = image_file.read()
 ​
   input_img = tf.placeholder(tf.string)
   decoded_image = tf.expand_dims(tf.image.decode_png(input_img, channels=3), 0)
 ​
   with tf.Session() as sess:
     img1 = sess.run(decoded_image, feed_dict={input_img: img1_str})
     img2 = sess.run(decoded_image, feed_dict={input_img: img2_str})
 ​
   print((MultiScaleSSIM(img1, img2, max_val=255)))
 ​
 ​
 if __name__ == '__main__':
   tf.app.run()

执行:

 python msssim.py --original_image=original.png --compared_image=distorted.png