GPU上的相似度检测(PNSR 和 SSIM)

本文探讨了在OpenCV中使用GPU加速PSNR和SSIM算法的方法,通过优化代码实现显著提升性能,特别关注了GPU与CPU的差异、如何在GPU上实现算法、数据传输优化以及异步调用的使用。实例代码展示了如何在GPU上高效计算图像相似度指标,最终对比了CPU与GPU版本的执行时间与结果一致性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

学习目标

在 OpenCV的视频输入和相似度测量 教程中我们已经学习了检测两幅图像相似度的两种方法:PSNR和SSIM。正如我们所看到的,执行这些算法需要相当长的计算时间,其中SSIM(结构相似度)的算法代价相当高昂。然而,OpenCV现在支持Nvidia的CUDA加速,如果你有一块支持CUDA的的Nvidia显卡。您可以将算法改为使用GPU计算从而大幅提高效率。

本教程将提供一个很好的示例来演示如何让OpenCV来使用GPU这些操作。当然在这之前,你应该了解一下如何操作core,highgui 和imgproc 模块。因此我们的目标是:

  • GPU与CPU有什么不同?
  • 为PSNR何SSIM编写GPU 代码
  • 优化代码以获得最佳性能

源代码

你也可以在OPENCV源库文件夹中 samples/cpp/tutorial_code/gpu/gpu-basics-similarity/gpu-basics-similarity 找到源代码以及一些视频文件或者 从这里下载 。 完整的源码很长(因为其中包含了通过命令行参数进行应用程序和性能测量等无关代码)。因此在这里只出现一部分关键代码。

如果两个输入图像的相似,PSNR 返回一个浮点数在30和50之间,(数值越高,相符程度越高).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
double getPSNR(const Mat& I1, const Mat& I2)
{
    Mat s1; 
    absdiff(I1, I2, s1);       // |I1 - I2|
    s1.convertTo(s1, CV_32F);  // cannot make a square on 8 bits
    s1 = s1.mul(s1);           // |I1 - I2|^2

    Scalar s = sum(s1);         // sum elements per channel

    double sse = s.val[0] + s.val[1] + s.val[2]; // sum channels

    if( sse <= 1e-10) // for small values return zero
        return 0;
    else
    {
        double  mse =sse /(double)(I1.channels() * I1.total());
        double psnr = 10.0*log10((255*255)/mse);
        return psnr;
    }
}



double getPSNR_GPU_optimized(const Mat& I1, const Mat& I2, BufferPSNR& b)
{    
    b.gI1.upload(I1);
    b.gI2.upload(I2);

    b.gI1.convertTo(b.t1, CV_32F);
    b.gI2.convertTo(b.t2, CV_32F);

    gpu::absdiff(b.t1.reshape(1), b.t2.reshape(1), b.gs);
    gpu::multiply(b.gs, b.gs, b.gs);

    double sse = gpu::sum(b.gs, b.buf)[0];

    if( sse <= 1e-10) // for small values return zero
        return 0;
    else
    {
        double mse = sse /(double)(I1.channels() * I1.total());
        double psnr = 10.0*log10((255*255)/mse);
        return psnr;
    }
}

struct BufferPSNR                                     // Optimized GPU versions
{   // Data allocations are very expensive on GPU. Use a buffer to solve: allocate once reuse later.
    gpu::GpuMat gI1, gI2, gs, t1,t2;

    gpu::GpuMat buf;
};

double getPSNR_GPU(const Mat& I1, const Mat& I2)
{
    gpu::GpuMat gI1, gI2, gs, t1,t2; 

    gI1.upload(I1);
    gI2.upload(I2);

    gI1.convertTo(t1, CV_32F);
    gI2.convertTo(t2, CV_32F);

    gpu::absdiff(t1.reshape(1), t2.reshape(1), gs); 
    gpu::multiply(gs, gs, gs);

    Scalar s = gpu::sum(gs);
    double sse = s.val[0] + s.val[1] + s.val[2];

    if( sse <= 1e-10) // for small values return zero
        return 0;
    else
    {
        double  mse =sse /(double)(gI1.channels() * I1.total());
        double psnr = 10.0*log10((255*255)/mse);
        return psnr;
    }
}

SSIM 返回图像的结构相似度指标。这是一个在0-1之间的浮点数(越接近1,相符程度越高), 这个算法对每个通道有一个值,所以最终会产生一个OpenCV的Scalar 数据结构:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
}

Scalar getMSSIM( const Mat& i1, const Mat& i2)
{ 
    const double C1 = 6.5025, C2 = 58.5225;
    /***************************** INITS **********************************/
    int d     = CV_32F;

    Mat I1, I2; 
    i1.convertTo(I1, d);           // cannot calculate on one byte large values
    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

    /*************************** END INITS **********************************/

    Mat mu1, mu2;   // PRELIMINARY COMPUTING
    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;

    ///////////////////////////////// FORMULA ////////////////////////////////
    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 = average of ssim map
    return mssim; 
}

Scalar getMSSIM_GPU( const Mat& i1, const Mat& i2)
{ 
    const float C1 = 6.5025f, C2 = 58.5225f;
    /***************************** INITS **********************************/
    gpu::GpuMat gI1, gI2, gs1, t1,t2; 

    gI1.upload(i1);
    gI2.upload(i2);

    gI1.convertTo(t1, CV_MAKE_TYPE(CV_32F, gI1.channels()));
    gI2.convertTo(t2, CV_MAKE_TYPE(CV_32F, gI2.channels()));

    vector<gpu::GpuMat> vI1, vI2; 
    gpu::split(t1, vI1);
    gpu::split(t2, vI2);
    Scalar mssim;

    for( int i = 0; i < gI1.channels(); ++i )
    {
        gpu::GpuMat I2_2, I1_2, I1_I2; 

        gpu::multiply(vI2[i], vI2[i], I2_2);        // I2^2
        gpu::multiply(vI1[i], vI1[i], I1_2);        // I1^2
        gpu::multiply(vI1[i], vI2[i], I1_I2);       // I1 * I2

        /*************************** END INITS **********************************/
        gpu::GpuMat mu1, mu2;   // PRELIMINARY COMPUTING
        gpu::GaussianBlur(vI1[i], mu1, Size(11, 11), 1.5);
        gpu::GaussianBlur(vI2[i], mu2, Size(11, 11), 1.5);

        gpu::GpuMat mu1_2, mu2_2, mu1_mu2; 
        gpu::multiply(mu1, mu1, mu1_2);   
        gpu::multiply(mu2, mu2, mu2_2);   
        gpu::multiply(mu1, mu2, mu1_mu2);   

        gpu::GpuMat sigma1_2, sigma2_2, sigma12; 

        gpu::GaussianBlur(I1_2, sigma1_2, Size(11, 11), 1.5);
        sigma1_2 -= mu1_2;

        gpu::GaussianBlur(I2_2, sigma2_2, Size(11, 11), 1.5);
        sigma2_2 -= mu2_2;

        gpu::GaussianBlur(I1_I2, sigma12, Size(11, 11), 1.5);
        sigma12 -= mu1_mu2;

        ///////////////////////////////// FORMULA ////////////////////////////////
        gpu::GpuMat t1, t2, t3; 

        t1 = 2 * mu1_mu2 + C1; 
        t2 = 2 * sigma12 + C2; 
        gpu::multiply(t1, t2, t3);     // t3 = ((2*mu1_mu2 + C1).*(2*sigma12 + C2))

        t1 = mu1_2 + mu2_2 + C1; 
        t2 = sigma1_2 + sigma2_2 + C2;     
        gpu::multiply(t1, t2, t1);     // t1 =((mu1_2 + mu2_2 + C1).*(sigma1_2 + sigma2_2 + C2))

        gpu::GpuMat ssim_map;
        gpu::divide(t3, t1, ssim_map);      // ssim_map =  t3./t1;

        Scalar s = gpu::sum(ssim_map);    
        mssim.val[i] = s.val[0] / (ssim_map.rows * ssim_map.cols);

    }
    return mssim; 
}
struct BufferMSSIM                                     // Optimized GPU versions
{   // Data allocations are very expensive on GPU. Use a buffer to solve: allocate once reuse later.
    gpu::GpuMat gI1, gI2, gs, t1,t2;

    gpu::GpuMat I1_2, I2_2, I1_I2;
    vector<gpu::GpuMat> vI1, vI2;

    gpu::GpuMat mu1, mu2; 
    gpu::GpuMat mu1_2, mu2_2, mu1_mu2; 

    gpu::GpuMat sigma1_2, sigma2_2, sigma12; 
    gpu::GpuMat t3; 

    gpu::GpuMat ssim_map;

    gpu::GpuMat buf;
};
Scalar getMSSIM_GPU_optimized( const Mat& i1, const Mat& i2, BufferMSSIM& b)
{ 
    int cn = i1.channels();

    const float C1 = 6.5025f, C2 = 58.5225f;
    /***************************** INITS **********************************/

    b.gI1.upload(i1);
    b.gI2.upload(i2);

    gpu::Stream stream;

    stream.enqueueConvert(b.gI1, b.t1, CV_32F);
    stream.enqueueConvert(b.gI2, b.t2, CV_32F);      

    gpu::split(b.t1, b.vI1, stream);
    gpu::split(b.t2, b.vI2, stream);
    Scalar mssim;

    for( int i = 0; i < b.gI1.channels(); ++i )
    {        
        gpu::multiply(b.vI2[i], b.vI2[i], b.I2_2, stream);        // I2^2
        gpu::multiply(b.vI1[i], b.vI1[i], b.I1_2, stream);        // I1^2
        gpu::multiply(b.vI1[i], b.vI2[i], b.I1_I2, stream);       // I1 * I2

        gpu::GaussianBlur(b.vI1[i], b.mu1, Size(11, 11), 1.5, 0, BORDER_DEFAULT, -1, stream);
        gpu::GaussianBlur(b.vI2[i], b.mu2, Size(11, 11), 1.5, 0, BORDER_DEFAULT, -1, stream);

        gpu::multiply(b.mu1, b.mu1, b.mu1_2, stream);   
        gpu::multiply(b.mu2, b.mu2, b.mu2_2, stream);   
        gpu::multiply(b.mu1, b.mu2, b.mu1_mu2, stream);   

        gpu::GaussianBlur(b.I1_2, b.sigma1_2, Size(11, 11), 1.5, 0, BORDER_DEFAULT, -1, stream);
        gpu::subtract(b.sigma1_2, b.mu1_2, b.sigma1_2, stream);
        //b.sigma1_2 -= b.mu1_2;  - This would result in an extra data transfer operation

        gpu::GaussianBlur(b.I2_2, b.sigma2_2, Size(11, 11), 1.5, 0, BORDER_DEFAULT, -1, stream);
        gpu::subtract(b.sigma2_2, b.mu2_2, b.sigma2_2, stream);
        //b.sigma2_2 -= b.mu2_2;

        gpu::GaussianBlur(b.I1_I2, b.sigma12, Size(11, 11), 1.5, 0, BORDER_DEFAULT, -1, stream);
        gpu::subtract(b.sigma12, b.mu1_mu2, b.sigma12, stream);
        //b.sigma12 -= b.mu1_mu2;

        //here too it would be an extra data transfer due to call of operator*(Scalar, Mat)
        gpu::multiply(b.mu1_mu2, 2, b.t1, stream); //b.t1 = 2 * b.mu1_mu2 + C1; 
        gpu::add(b.t1, C1, b.t1, stream);
        gpu::multiply(b.sigma12, 2, b.t2, stream); //b.t2 = 2 * b.sigma12 + C2; 
        gpu::add(b.t2, C2, b.t2, stream);     

        gpu::multiply(b.t1, b.t2, b.t3, stream);     // t3 = ((2*mu1_mu2 + C1).*(2*sigma12 + C2))

        gpu::add(b.mu1_2, b.mu2_2, b.t1, stream);
        gpu::add(b.t1, C1, b.t1, stream);

        gpu::add(b.sigma1_2, b.sigma2_2, b.t2, stream);
        gpu::add(b.t2, C2, b.t2, stream);


        gpu::multiply(b.t1, b.t2, b.t1, stream);     // t1 =((mu1_2 + mu2_2 + C1).*(sigma1_2 + sigma2_2 + C2))        
        gpu::divide(b.t3, b.t1, b.ssim_map, stream);      // ssim_map =  t3./t1;

        stream.waitForCompletion();

        Scalar s = gpu::sum(b.ssim_map, b.buf);    
        mssim.val[i] = s.val[0] / (b.ssim_map.rows * b.ssim_map.cols);

    }
    return mssim; 
}

如何在GPU上实现相同算法?

你可以总共有三种类型的函数来针对每个操作。其中包括一个CPU和2个GPU函数。之所以做两个GPU函数是为了说明一个常识:简单的移植你的CPU程序到GPU反而会让速度变慢。如果你想要一些性能上的改善,你将需牢记住一些规则,我在后面详细说明。

开发的GPU模块尽可能多的与CPU对应,这样才能方便移植。在你编写任何代码之间要做的第一件事是连GPU模块到你的项目中,包括模块的头文件。所有GPU的函数和数据结构都在以 cv 命名空间的子空间 gpu 内。你可以将其添加为默认 use namespace 关键字, 也可以显示的通过cv::来避免混乱。我在后面会做。

#include <opencv2/gpu/gpu.hpp>        // GPU结构和方法

GPU代表**图形处理单元**。最开始是为渲染各种图形场景而建立,这些场景是基于大量的矢量数据建立的。由于矢量图形的特殊性,数据不需要以串行的方式一步一步执行的,而是并行的方式一次性渲染大量的数据。从GPU的结构上来说,不像CPU基于数个寄存器和高速指令集,GPU一般有数百个较小的处理单元。这些处理单元每一个都比CPU的核心慢很多很多。然而,它的力量在于它的数目众多,能够同时进行大量运算。在过去的几年中已经有越来越多的人常识用GPU来进行图形渲染以外的工作。这就产生了GPGPU(general-purpose computation on graphics processing units)的概念。

图像处理器有它自己的内存,一般称呼为显存。当你从硬盘驱动器读数据并产生一个 Mat 对象的时候,数据是放在普通内存当中的(由CPU掌管)CPU可以直接操作内存, 然而GPU不能这样,对于电脑来说GPU只是个外设,它只能操作它自己的显存,当计算时,需要先让CPU将用于计算的信息转移到GPU掌管的显存上。 这是通过一个上传过程完成,需要比访问内存多很多的时间。而且最终的计算结果将要回传到你的系统内存处理器才能和其他代码交互,由于传输的代价高昂,所以注定移植太小的函数到GPU上并不会提高效率。

Mat对象仅仅存储在内存或者CPU缓存中。为了得到一个GPU能直接访问的opencv 矩阵你必须使用GPU对象 GpuMat 。它的工作方式类似于2维 Mat,唯一的限制是你不能直接引用GPU函数。(因为它们本质上是完全不同的代码,不能混合引用)。要传输*Mat*对象到*GPU*上并创建GpuMat时需要调用上传函数,在回传时,可以使用简单的重载赋值操作符或者调用下载函数。

Mat I1;         // 内存对象,可以用imread来创建
gpu::GpuMat gI; // GPU 矩阵 - 现在为空
gI1.upload(I1); //将内存数据上传到显存中

I1 = gI1;       //回传, gI1.download(I1) 也可以

记住:一旦你的数据被传到GPU显存中,你就只能调用GPU函数来操作,大部分gpu函数名字与原来CPU名字保持一致,不同的是他们只接收 GpuMat 输入。可以在:在线文档 中找到一些说明,或者查阅OpenCV参考手册。

另外要记住的是:并非所有的图像类型都可以用于GPU算法中。很多时候,GPU函数只能接收单通道或者4通道的uchar或者float类型(CV_8UC1,CV_8UC4,CV_32FC1,CV_32FC4),GPU函数不支持双精度。如果你试图向这些函数传入非指定类型的数据时,这些函数会抛出异常或者输出错误信息。这些函数的文档里大都说明了接收的数据类型。如果函数只接收4通道或者单通道的图像而你的数据类型刚好是3通道的话,你能做的就是两件事:1.增加一个新的通道(使用char类型)或 2.将图像的三个通道切分为三个独立的图像,为每个图像调用该函数。不推荐使用第一个方法,因为有些浪费内存。

对于不需要在意元素的坐标位置的某些函数,快速解决办法就是将它直接当作一个单通道图像处理。这种方法适用于PSNR中需要调用的absdiff方法。然而,对于GaussianBlur就不能这么用,对于SSIM,就需要使用分离通道的方法。知道这些知识就已经可以让你的GPU开始运行代码。但是你也可能发现GPU“加速”后的代码仍然可能会低于你的CPU执行速度。

优化

因为大量的时间都耗费在传输数据到显存这样的步骤了,大量的计算时间被消耗在内存分配和传输上,GPU的高计算能力根本发挥不出来,我们可以利用gpu::Stream 来进行异步传输,从而节约一些时间.

#. GPU上的显存分配代价是相当大的。因此如果可能的话,应该尽可能少的分配新的内存。如果你需要创建一个调用多次的函数,就应该在一开始分配全部的内存,而且仅仅分配一次。在第一次调用的时候可以创建一个结构体包含所有将使用局部变量。 对于PSNR例子:

struct BufferPSNR                                     // 优化版本
  {   // 基于GPU的数据分配代价是高昂的。所以使用一个缓冲区来改善这点:分配一次,以后重用。
  gpu::GpuMat gI1, gI2, gs, t1,t2;

  gpu::GpuMat buf;
};

在主程序中创建一个实例:
BufferPSNR bufferPSNR;

最后每次调用函数时都使用这个结构:
double getPSNR_GPU_optimized(const Mat& I1, const Mat& I2, BufferPSNR& b)

当你传入的参数为 b.gI1 , b.buf 等,除非类型发生变化,GpuMat都将直接使用原来的内容而不重新分配。

  1. 在GPU中,任何小的数据传输都是一次大的开销,所以应该尽量避免不必要的数据传输。因此应该尽可能的在现有对象上计算(换句话说,不创造新的显存对象,上面已经解释过)。例如,虽然使用算术表达式会让一个公式更浅显易懂但是在GPU代码里,这个步骤是很缓慢的。例如在SSIM例子中需要计算:

    b.t1 = 2 * b.mu1_mu2 + C1;
    

    在这个表达式的调用过程中必然会有一个隐性内存分配过程,调用乘法的结果必然要用一个临时对象储存,然后才能和*C1*相加。如果直接用表达式写GPU代码,就必然会创建一个临时变量来储存乘积的矩阵,然后加上 C1 值并储存在 t1 。为了避免这种额外的开销,我们应该使用GPU处理函数代替算术表达式,从而避免额外创建不必要的临时对象:

    gpu::multiply(b.mu1_mu2, 2, b.t1); //b.t1 = 2 * b.mu1_mu2 + C1;
    gpu::add(b.t1, C1, b.t1);
    
  2. 使用异步调用 (the gpu::Stream)。默认情况下,无论何时你调用一个GPU函数,系统都将等待调用完成并返回后继结果,这就意味着在GPU计算的时候CPU没干活。如果使异步调用,就会意味着它将调用后立即返回,并在后台异步执行,也就是说虽然CPU需要的数据还没从GPU返回,但是并不妨碍CPU进行其它的计算或者调用另一个函数。这是一个小的MSSIM可优化点。在我们的默认实现中,我们将图像分裂成多个通道,然后为每个通道调用GPU函数。这本来应该是一个平行操作,我们可以建立一个流,通过使用流我们可以异步进行数据分配、传输等各种操作,并且给GPU执行操作方法以便GPU在合适的时候自动调用。例如我们需要传输两幅图像。我们依次调用现成的函数处理它们。该函数还是要等到上传数据完毕才会开始工作,但是在执行第二个函数的时候,原来的输出缓存将直接作为第二个操作的输出参数而不需要传输过程。

    gpu::Stream stream;
    
    stream.enqueueConvert(b.gI1, b.t1, CV_32F);    // 上传
    
    gpu::split(b.t1, b.vI1, stream);              // 分离方法(在最后的参数里传递stream).
    gpu::multiply(b.vI1[i], b.vI1[i], b.I1_2, stream);        // I1^2
    

结果和结论

以下是一个Intel P8700的笔记本CPU,与一个低端的Nvidia GT220M的性能比较,结果如下:

Time of PSNR CPU (averaged for 10 runs): 41.4122 milliseconds. With result of: 19.2506
Time of PSNR GPU (averaged for 10 runs): 158.977 milliseconds. With result of: 19.2506
Initial call GPU optimized:              31.3418 milliseconds. With result of: 19.2506
Time of PSNR GPU OPTIMIZED ( / 10 runs): 24.8171 milliseconds. With result of: 19.2506

Time of MSSIM CPU (averaged for 10 runs): 484.343 milliseconds. With result of B0.890964 G0.903845 R0.936934
Time of MSSIM GPU (averaged for 10 runs): 745.105 milliseconds. With result of B0.89922 G0.909051 R0.968223
Time of MSSIM GPU Initial Call            357.746 milliseconds. With result of B0.890964 G0.903845 R0.936934
Time of MSSIM GPU OPTIMIZED ( / 10 runs): 203.091 milliseconds. With result of B0.890964 G0.903845 R0.936934

在这个案例中,使用GPU加速比单纯使用CPU提高了近100%的性能。这对构建高速实时系统甚有帮助。

<think>我们正在处理用户关于MATLAB中PSNRSSIM实现的问题。用户意图明确:寻找在MATLAB中计算PSNRSSIM以评估图像质量的方法。根据引用,我们得知:1.MATLAB内置了psnrssim函数,可直接调用(引用[1][2])。2.需要读取两张图像(原始图像处理后的图像),然后调用这两个函数。3.PSNRSSIM的数学定义(引用[3][4]):-PSNR:$$PSNR=10\cdot\log_{10}\left(\frac{MAX_I^2}{MSE}\right)$$,其中$MAX_I$为图像最大可能像素值(如8位图像为255),$MSE$为均方误差。-SSIM:$$SSIM(x,y)=\frac{(2\mu_x\mu_y+c_1)(2\sigma_{xy}+c_2)}{(\mu_x^2+\mu_y^2+c_1)(\sigma_x^2+\sigma_y^2+c_2)}$$因此,回答应包含:1.使用MATLAB内置函数的基本方法(简单易行)。2.可选:如果用户需要自定义实现,提供PSNRSSIM的公式及代码(但引用中显示MATLAB有内置函数,所以优先推荐内置函数)。注意:引用[1][2]提供了示例代码,我们可以参考。步骤:1.读取两张图像(假设为'original.jpg''processed.jpg')。2.使用psnr函数计算PSNR值。3.使用ssim函数计算SSIM值。另外,引用[3][4]提供了公式,如果用户需要了解原理,可以简要说明。根据指令,需要生成相关问题。回答结构:1.介绍PSNRSSIM的基本概念(简要)。2.提供MATLAB内置函数的使用方法(主要部分)。3.可选:自定义函数的实现(如果内置函数不满足需求,例如旧版本MATLAB可能没有内置ssim函数,但引用[2]显示有ssim函数,所以可能不需要。不过为了全面性,可以提一下自定义方法,但根据引用,用户似乎可以直接使用内置函数)。4.注意事项(如图像类型、范围等)。5.生成相关问题。注意:用户提供的引用中,引用[1][2]的代码示例略有不同,我们以最新MATLAB版本的内置函数为准(通常直接使用psnrssim函数,且参数顺序为psnr(A,ref)ssim(A,ref),其中A是待评估图像,ref是参考图像)。另外,引用[1]中代码是:psnr_value=psnr(B,A);%这里B是处理后的图像,A是原始图像ssim_value=ssim(B,A);而引用[2]中是:ssim(img1,img2)%未说明顺序,但通常第一个参数是待评估图像,第二个是参考图像。在MATLAB文档中,psnr函数的语法是:psnr(A,ref)因此,我们按照:待评估图像在前,参考图像在后。但是,注意引用[1]中变量B是处理后的图像(待评估),A是原始图像(参考)。所以顺序是:psnr(待评估,参考)因此,我们的代码示例将遵循这一顺序。步骤代码:```matlab%读取图像ref=imread('original_image.jpg');%参考图像(原始图像)A=imread('processed_image.jpg');%待评估图像(处理后的图像)%计算PSNRpsnrValue=psnr(A,ref);%注意:第一个参数是待评估图像,第二个是参考图像%计算SSIMssimValue=ssim(A,ref);%显示结果fprintf('PSNR:%.2fdB\n',psnrValue);fprintf('SSIM:%.4f\n',ssimValue);```注意事项:1.图像必须具有相同的大小类型(或可以自动转换)。2.对于彩色图像,psnrssim函数默认会分别计算每个通道然后取平均(具体可查看MATLAB文档)。3.如果图像不是灰度图像,MATLAB的ssim函数默认将彩色图像转换为灰度图像然后计算。但根据MATLAB文档,从R2014a开始,ssim函数对彩色图像的处理方式可以通过参数设置。用户若需要多通道评估,需查看最新文档。如果用户使用的是较老版本的MATLAB(如没有内置ssim函数),则需要自定义实现。但根据引用[2],用户直接输入ssim(img1,img2)即可,说明该函数存在。因此,我们主要提供内置函数方法。为了完整性,我们也可以简要说明如何自定义实现,但重点在内置函数。自定义实现PSNR(根据引用[3]):```matlabfunctionpsnrValue=myPSNR(A,ref)%假设Aref是uint8类型的图像maxPixel=255;mse=mean((A-ref).^2,'all');%计算整个图像的均方误差ifmse==0psnrValue=Inf;elsepsnrValue=10*log10(maxPixel^2/mse);endend```自定义实现SSIM较为复杂,公式见引用[4]。我们可以提供参考,但建议用户使用内置函数。根据用户问题,我们优先展示内置函数的使用。生成相关问题:1.PSNRSSIM指标各有什么优缺点?2.除了PSNRSSIM,还有哪些常用的图像质量评估指标?3.在MATLAB中如何处理彩色图像的PSNRSSIM计算?回答组织如下:</think>### MATLAB 实现 PSNR SSIM 图像质量评估 在 MATLAB 中计算 **PSNR(峰值信噪比)** **SSIM(结构相似性)** 可通过内置函数实现,以下是详细步骤: --- #### **1. 基本实现方法** **步骤 1:读取图像** ```matlab ref = imread('original_image.png'); % 参考图像(原始图像) A = imread('processed_image.png'); % 待评估图像(处理后图像) ``` **步骤 2:计算 PSNR** ```matlab psnr_value = psnr(A, ref); fprintf('PSNR: %.2f dB\n', psnr_value); ``` - **公式**:$$PSNR = 10 \cdot \log_{10}\left(\frac{MAX_I^2}{MSE}\right)$$ 其中 $MAX_I$ 为最大像素值(如 8 位图像 $MAX_I=255$),$MSE$ 为均方误差[^3]。 **步骤 3:计算 SSIM** ```matlab ssim_value = ssim(A, ref); fprintf('SSIM: %.4f\n', ssim_value); ``` - **公式**: $$SSIM(x, y) = \frac{(2\mu_x \mu_y + c_1)(2\sigma_{xy}+c_2)}{(\mu_x^2+ \mu_y^2 + c_1)(\sigma_x^2+\sigma_y^2+c_2)}$$ 其中 $\mu$ 为均值,$\sigma$ 为标准差/协方差,$c_1, c_2$ 为稳定常数[^4]。 --- #### **2. 关键注意事项** 1. **图像要求**: - 两幅图像需尺寸相同且数据类型一致(推荐 `uint8`)。 - 彩色图像会被自动转换为灰度图计算(除非指定 `ColorChannels` 参数)。 2. **动态范围调整**(若图像非 `uint8` 类型): ```matlab psnr_value = psnr(A, ref, 'DataFormat', 'SSC'); % 自适应动态范围 ``` 3. **多通道 SSIM**(针对彩色图像): ```matlab ssim_map = ssim(A, ref, 'ColorChannels', 'rgb'); ssim_value = mean(ssim_map, 'all'); % 取各通道平均值 ``` --- #### **3. 自定义实现(无内置函数时)** **PSNR 自定义函数**: ```matlab function psnr_val = myPSNR(A, ref) max_val = 255; % 8位图像 mse = mean((double(A) - double(ref)).^2, 'all'); psnr_val = 10 * log10(max_val^2 / mse); end ``` **SSIM 自定义函数**(简化版): ```matlab function ssim_val = mySSIM(A, ref) A = double(A); ref = double(ref); K = [0.01 0.03]; L = 255; % 动态范围 c1 = (K(1)*L)^2; c2 = (K(2)*L)^2; mu_A = mean(A, 'all'); mu_ref = mean(ref, 'all'); sigma_A = std(A, 0, 'all')^2; sigma_ref = std(ref, 0, 'all')^2; sigma_AB = cov(A(:), ref(:)); sigma_AB = sigma_AB(1,2); ssim_val = ((2*mu_A*mu_ref + c1) * (2*sigma_AB + c2)) / ... ((mu_A^2 + mu_ref^2 + c1) * (sigma_A + sigma_ref + c2)); end ``` --- #### **4. 结果解读** | 指标 | 理想值 | 评估标准 | |-------|--------|------------------------------| | PSNR | ∞ dB | > 30 dB 表示质量较好 | | SSIM | 1.0 | > 0.9 表示结构相似性高 | > **注意**:PSNR 侧重像素级误差,SSIM 更符合人眼感知[^1]。实际应用中建议结合两者。 --- ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值