OpenCV4学习笔记(60)——图像相似度量(峰值信噪比PSNR、平均结构相似性MSSIM)

今天整理的笔记内容是:利用峰值信噪比PSNR和平均结构相似性MSSIM来对图像相似度进行度量。(学习参考于OpenCV官方文档)

首先我们需要大致了解一下什么是峰值信噪比PSNR和平均结构相似性MSSIM。

1.峰值信噪比PSNR
由维基百科上可以了解到:

峰值信噪比(英语:Peak signal-to-noise ratio,常缩写为PSNR)是一个表示信号最大可能功率和影响它的表示精度的破坏性噪声功率的比值的工程术语。由于许多信号都有非常宽的动态范围,峰值信噪比常用对数分贝单位来表示。
峰值信噪比经常用作图像压缩等领域中信号重建质量的测量方法,它常简单地通过均方误差(MSE)进行定义。图像压缩中典型的峰值信噪比值在 30 到 40dB 之间,愈高愈好。

均方误差的公式如下:
在这里插入图片描述
峰值信噪比由均方误差计算而来,公式如下:
在这里插入图片描述
其中MAX是表示图像的最大像素值,一般为255。

通过计算两幅图像的峰值信噪比PSNR,当PSNR的值越小时表示两幅图像越相似,当PSNR = 0 时则表示两幅图像完全相同。但是PSNR是基于每个像素误差计算而来,所以得出的结果可能和人眼视觉有较大差异。一般来说,当两幅图像的PSNR小于30时,那么这两幅图像可以说是比较相似的。

计算峰值信噪比PSNR的代码实现如下:

double getPSNR(const Mat& I1, const Mat& I2)
{
    Mat s1;
    absdiff(I1, I2, s1);       // 计算两幅图像差值的绝对值|I1 - I2|
    s1.convertTo(s1, CV_32F);  // 在进行平方操作之前先加深图像深度
    s1 = s1.mul(s1);           //对差值绝对值进行求平方 |I1 - I2|^2
    Scalar s = sum(s1);        // 对每个通道的像素值求和
    double sse = s.val[0] + s.val[1] + s.val[2]; // 三通道的总像素值
    if (sse <= 1e-10)           //如果两幅图像差值绝对值的平方三通道总和很小,则无差别
        return 0;
    else
    {
        double mse = sse / (double)(I1.channels() * I1.total());            //均方误差 = 差值平方和 / 总像素数
        double psnr = 10.0 * log10((255.0 * 255.0) / mse);          //峰值信噪比
        return psnr;
    }
}
  1. 平均结构相似性MSSIM
    结构相似性在维基百科上定义如下:

结构相似性指标(英文:structural similarity index,SSIM index)是一种用以衡量两张数位影像相似程度的指标。当两张影像其中一张为无失真影像,另一张为失真后的影像,二者的结构相似性可以看成是失真影像的影像品质衡量指标。相较于传统所使用的影像品质衡量指标,像是峰值信噪比(英语:PSNR),结构相似性在影像品质的衡量上更能符合人眼对影像品质的判断。

可见,峰值信噪比是针对于像素绝对误差,而结构相似性则是偏向于人眼视觉的感受。
结构相似性SSIM的取值范围是 [ 0 , 1 ] ,当两张图像越相似时,则SSIM越接近1。结构相似性SSIM的公式如下:
在这里插入图片描述
其中,
在这里插入图片描述
但是在实际使用中,一般会对图像进行分区计算结构相似性SSIM,然后再求取平均值,从而得到平均结构相似性MSSIM。
在计算两张图像的结构相似性时,使用尺寸为NxN的窗口进行遍历开窗操作,计算出每个窗口内的结构相似性,每次以像素为单位移动开窗,直到整张图像每个位置的局部结构相似性都计算完毕。最后将所有局部结构相似性进行平均即得到两张图像的平均结构相似性MSSIM。
平均结构相似性MSSIM的代码实现如下:

	Scalar getMSSIM(const Mat& i1, const Mat& i2)
{
    const double C1 = 6.5025, C2 = 58.5225;
    int d = CV_32F;
    Mat I1, I2;
    i1.convertTo(I1, d);            
    i2.convertTo(I2, d);
    Mat I2_2 = I2.mul(I2);        // I2^2
    Mat I1_2 = I1.mul(I1);        // I1^2
    Mat I1_I2 = I1.mul(I2);        // I1 * I2
    Mat mu1, mu2;                   
    GaussianBlur(I1, mu1, Size(11, 11), 1.5);
    GaussianBlur(I2, mu2, Size(11, 11), 1.5);
    Mat mu1_2 = mu1.mul(mu1);
    Mat mu2_2 = mu2.mul(mu2);
    Mat mu1_mu2 = mu1.mul(mu2);
    Mat sigma1_2, sigma2_2, sigma12;
    GaussianBlur(I1_2, sigma1_2, Size(11, 11), 1.5);
    sigma1_2 -= mu1_2;
    GaussianBlur(I2_2, sigma2_2, Size(11, 11), 1.5);
    sigma2_2 -= mu2_2;
    GaussianBlur(I1_I2, sigma12, Size(11, 11), 1.5);
    sigma12 -= mu1_mu2;
    Mat t1, t2, t3;
    t1 = 2 * mu1_mu2 + C1;
    t2 = 2 * sigma12 + C2;
    t3 = t1.mul(t2);                 // t3 = ((2*mu1_mu2 + C1).*(2*sigma12 + C2))
    t1 = mu1_2 + mu2_2 + C1;
    t2 = sigma1_2 + sigma2_2 + C2;
    t1 = t1.mul(t2);                 // t1 =((mu1_2 + mu2_2 + C1).*(sigma1_2 + sigma2_2 + C2))
    Mat ssim_map;
    divide(t3, t1, ssim_map);        // ssim_map =  t3./t1;
    Scalar mssim = mean(ssim_map);   // mssim是ssim的平均值
    return mssim;
}

到此我们就能计算出两幅图像的峰值信噪比PSNR和平均结构相似性MSSIM了,我们利用这两个指标来对两幅图像之间的相似度进行度量。并且由于PSNR的计算量相对来说更小,所以我们对每一张图像都进行PSNR的计算,而仅对PSNR小于一定阈值的图像进行MSSIM的计算。尤其是在对视频流进行相似度检测时,更应该通过这种方式来提高处理速度。

当两幅图像之间的峰值信噪比PSNR和平均结构相似性MSSIM都小于一定阈值时,就可以判定这两幅图像是足够相似的。

而且,对于不同图像来说,其亮度变化可能也会影响到两幅图像的相似度,所以我们需要尽可能的消除亮度、或光照的变化影响。

在常用的RGB色彩空间中,我们难以将亮度这一因素独立出来,所以我们需要将两幅图像都转换到HSV色彩空间。在HSV色彩空间中,H表示色调、S表示饱和度、V表示亮度,那么我们就可以只保留色调H和饱和度S通道,而将亮度V通道剔除掉,尽量地忽视亮度这一因素的影响。

实现代码如下:

	vector<string>path;
    glob("C:\\Users\\QXB\\Desktop\\Product_information_detection\\Printing detection\\test\\uncompleted_issue\\miss_patch_issue\\", path);
    Mat tem_image = imread("C:\\Users\\QXB\\Desktop\\Product_information_detection\\Printing detection\\template\\true_texts.png");
    //提取色调H和饱和度S通道,忽视亮度V通道的影响
    cvtColor(tem_image, tem_image, COLOR_BGR2HSV);
    vector<Mat>tem_hsv;
    split(tem_image, tem_hsv);
    vector<Mat>tem_hs;
    tem_hs.push_back(tem_hsv[0]);
    tem_hs.push_back(tem_hsv[1]);
    Mat tem_image_hs;
    merge(tem_hs, tem_image_hs);

    for (int i = 0; i < path.size(); i++)
    {
        Mat test_image = imread(path[i]);
        cvtColor(test_image, test_image, COLOR_BGR2HSV);
        vector<Mat>test_hsv;
        split(test_image, test_hsv);
        vector<Mat>test_hs;
        test_hs.push_back(test_hsv[0]);
        test_hs.push_back(test_hsv[1]);
        Mat test_image_hs;
        merge(test_hs, test_image_hs);

        double PSNR = getPSNR(tem_image_hs, test_image_hs);
        if (PSNR < 20)
        {
            Scalar MSSIM = getMSSIM(tem_image_hs, test_image_hs);
            if (MSSIM[0] > 0.995 && MSSIM[1] > 0.995)
            {
                cout << "测试图像" << i << ":PSNR=" << PSNR << "\tMSSIM=" << MSSIM << endl;
            }
        }
        else
        {
            cout << "测试图像" << i << ":PSNR=" << PSNR << endl;
        }

        //Scalar MSSIM = getMSSIM(tem_image_hs, test_image_hs);
        //cout << "测试图像" << i << ":PSNR=" << PSNR << "\tMSSIM=" << MSSIM << endl;

下面,我们使用一张模板图像和多张存在不同缺陷的测试图像进行相似度度量。模板图像如下图所示:
在这里插入图片描述
首先对存在颜色缺陷的测试图像集进行测试,如下图:
在这里插入图片描述
测试结果:
在这里插入图片描述
可以看到,在上面的颜色缺陷图像集中,只有两幅图像被认为是合格的,而且是完全相同的,其中最后一张也是模板图像,第三张图像和模板图像对比如下:
在这里插入图片描述
就我个人感觉来说,这两张其实看不出什么区别,而且PSNR的值为0,所以这两张图像实际上是同一张图像。

下面使用存在部分缺块的测试图像集进行测试,如下:
在这里插入图片描述
测试效果:
在这里插入图片描述
可见,对于图像不完整的缺陷,这种检测方式的效果还是不错的,所有的缺陷图像都判定为不合格,可以看到PSNR值基本上都在30左右徘徊。

通过对两幅图像的相似度度量,我们可以用来判断图像的失真程度、压缩程度、缺陷程度等等。例如本次笔记中使用一张合格的模板图像与其他存在各种缺陷的测试图像进行相似度度量,根据峰值信噪比PSNR和平均结构相似性MSSIM这两个指标来判断测试图像是否为合格的。

好的本次笔记到此结束,谢谢阅读。

PS:本人的注释比较杂,既有自己的心得体会也有网上查阅资料时摘抄下的知识内容,所以如有雷同,纯属我向前辈学习的致敬,如果有前辈觉得我的笔记内容侵犯了您的知识产权,请和我联系,我会将涉及到的博文内容删除,谢谢!

猜你喜欢

转载自blog.csdn.net/weixin_45224869/article/details/105923911