#include <iostream> #include <string> #include <iomanip> // 控制浮动类型的打印精度 #include <sstream> // 字符串和数值的转换 #include <opencv2/core.hpp> // CV::Mat,Scalar #include <opencv2/imgproc.hpp> // 高斯平滑 #include <opencv2/videoio.hpp> // 视频 #include <opencv2/highgui.hpp> using namespace std; using namespace cv; double getPSNR(const Mat& I1, const Mat& I2); Scalar getMSSIM(const Mat& I1, const Mat& I2); int main(int argc, char *argv[]) { const string sourceReference = "E:\VS2015Opencv\vs2015\project\video\01.avi"; const string sourceCompareWith = "E:\VS2015Opencv\vs2015\project\video\011.avi"; int frameNum = -1; // 计算帧数 int psnrTriggerValue = 35; VideoCapture captRefrnc(sourceReference), captUndTst(sourceCompareWith); // 获取视频 if (!captRefrnc.isOpened() || !captUndTst.isOpened()) { return -1; } Size refS = Size((int)captRefrnc.get(CAP_PROP_FRAME_WIDTH), // 视频帧的大小 (int)captRefrnc.get(CAP_PROP_FRAME_HEIGHT)); Size uTSi = Size((int)captUndTst.get(CAP_PROP_FRAME_WIDTH), (int)captUndTst.get(CAP_PROP_FRAME_HEIGHT)); if (refS != uTSi) { return -1; } // 视频帧大小应相同 const char* WIN_UT = "Under Test"; // 显示窗口 const char* WIN_RF = "Reference"; namedWindow(WIN_RF, WINDOW_AUTOSIZE); namedWindow(WIN_UT, WINDOW_AUTOSIZE); moveWindow(WIN_RF, 0, 0); moveWindow(WIN_UT, refS.width, 0); cout << "Reference frame resolution: Width=" << refS.width << " Height=" << refS.height << " of nr#: " << captRefrnc.get(CAP_PROP_FRAME_COUNT) << endl; cout << "PSNR trigger value " << psnrTriggerValue << endl; Mat frameReference, frameUnderTest; double psnrV; // PSNR方法 Scalar mssimV; // SSIM方法 for (;;) { captRefrnc >> frameReference; captUndTst >> frameUnderTest; if (frameReference.empty() || frameUnderTest.empty()) { cout << "The End" << endl; break; } ++frameNum; cout << "Frame:" << frameNum << "#"; // 当前帧数,0开始 psnrV = getPSNR(frameReference, frameUnderTest); // 定义的PSNR函数 // setiosflags(ios::fixed)用定点方式显示实数,setprecision(n)可控制输出流显示浮点数的数字个数 cout << setiosflags(ios::fixed) << setprecision(3) << psnrV << "dB"; if (psnrV < psnrTriggerValue && psnrV) // PSNR结果不为零且小于输入值 { mssimV = getMSSIM(frameReference, frameUnderTest); cout << " MSSIM:" << " R " << setiosflags(ios::fixed) << setprecision(2) << mssimV.val[2] * 100 << "%" << " G " << setiosflags(ios::fixed) << setprecision(2) << mssimV.val[1] * 100 << "%" << " B " << setiosflags(ios::fixed) << setprecision(2) << mssimV.val[0] * 100 << "%"; } cout << endl; imshow(WIN_RF, frameReference); imshow(WIN_UT, frameUnderTest); char c = (char)waitKey(30); if (c == 27) break; } return 0; } double getPSNR(const Mat& I1, const Mat& I2) // PSNR方法 { Mat s1; absdiff(I1, I2, s1); // |I1 - I2| s1.convertTo(s1, CV_32F); // 转换为32位进行运算 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) // 当值太小时近似于0,由公式可知分母为0时需另外对待,使用SSIM方法 return 0; else { double mse = sse / (double)(I1.channels() * I1.total()); // 公式 double psnr = 10.0 * log10((255 * 255) / mse); return psnr; } } Scalar getMSSIM(const Mat& i1, const Mat& i2) // SSIM方法 { const double C1 = 6.5025, C2 = 58.5225; Mat I1, I2; i1.convertTo(I1, CV_32F); // 转换为32位进行运算 i2.convertTo(I2, CV_32F); Mat I1_2 = I1.mul(I1); // I1^2 Mat I2_2 = I2.mul(I2); // I2^2 Mat I1_I2 = I1.mul(I2); // I1 * I2 Mat sigma1_2, sigma2_2, sigma12; // 先平方再高斯滤波 GaussianBlur(I1_2, sigma1_2, Size(11, 11), 1.5); GaussianBlur(I2_2, sigma2_2, Size(11, 11), 1.5); GaussianBlur(I1_I2, sigma12, Size(11, 11), 1.5); 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); sigma1_2 -= mu1_2; // 两种方式的差值 sigma2_2 -= mu2_2; sigma12 -= mu1_mu2; Mat t1, t2, t3, t4; 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; t4 = t1.mul(t2); // t4 =((mu1_2 + mu2_2 + C1).*(sigma1_2 + sigma2_2 + C2)) Mat ssim_map; divide(t3, t4, ssim_map); // ssim_map = t3./t4; Scalar mssim = mean(ssim_map); // ssim map矩阵的平均值 return mssim; }
代码解析:
关于视频读取函数:opencv视频操作基础---VideoCapture类
VideoCapture capture;
capture.open("E:\Workspace\OpenCV\test\video\video.avi");
上面代码是读取本地视频,下面代码与上面代码类似,只不过换了名字;
VideoCapture captRefrnc(sourceReference), captUndTst(sourceCompareWith); // 获取视频 if (!captRefrnc.isOpened() || !captUndTst.isOpened()) { return -1; }
视频通常拥有很多除了视频帧图像以外的信息,像是帧数之类,有些时候数据较短,有些时候用4个字节的字符串来表示。所以 get 函数返回一个double(8个字节)类型的数据来表示这些属性。然后你可以使用位操作符来操作这个返回值从而得到想要的整型数据等。这个函数有一个参数,代表着试图查询的属性ID。在下面的例子里我们会先获得视频的尺寸和帧数。
Size refS = Size((int)captRefrnc.get(CAP_PROP_FRAME_WIDTH), // 视频帧的大小 (int)captRefrnc.get(CAP_PROP_FRAME_HEIGHT)); Size uTSi = Size((int)captUndTst.get(CAP_PROP_FRAME_WIDTH), (int)captUndTst.get(CAP_PROP_FRAME_HEIGHT)); if (refS != uTSi) { return -1; } // 视频帧大小应相同 const char* WIN_UT = "Under Test"; // 显示窗口 const char* WIN_RF = "Reference"; namedWindow(WIN_RF, WINDOW_AUTOSIZE); namedWindow(WIN_UT, WINDOW_AUTOSIZE); moveWindow(WIN_RF, 0, 0); moveWindow(WIN_UT, refS.width, 0); cout << "Reference frame resolution: Width=" << refS.width << " Height=" << refS.height << " of nr#: " << captRefrnc.get(CAP_PROP_FRAME_COUNT) << endl; cout << "PSNR trigger value " << psnrTriggerValue << endl;
图像比较 - PSNR and SSIM
当我们想检查压缩视频带来的细微差异的时候,就需要构建一个能够逐帧比较差视频差异的系统。最常用的比较算法是PSNR( Peak signal-to-noise ratio)。这是个使用“局部均值误差”来判断差异的最简单的方法,假设有这两幅图像:I1和I2,它们的行列数分别是i,j,有c个通道。
PSNR公式如下:
每个像素的每个通道的值占用一个字节,值域[0,255]。这里每个像素会有 个有效的最大值 注意当两幅图像的相同的话,MSE的值会变成0。这样会导致PSNR的公式会除以0而变得没有意义。所以我们需要单独的处理这样的特殊情况。此外由于像素的动态范围很广,在处理时会使用对数变换来缩小范围。这些变换的C++代码如下:
double getPSNR(const Mat& I1, const Mat& I2) // PSNR方法 { Mat s1; absdiff(I1, I2, s1); // |I1 - I2| s1.convertTo(s1, CV_32F); // 转换为32位进行运算 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) // 当值太小时近似于0,由公式可知分母为0时需另外对待,使用SSIM方法 return 0; else { double mse = sse / (double)(I1.channels() * I1.total()); // 公式 double psnr = 10.0 * log10((255 * 255) / mse); return psnr; } } Scalar getMSSIM(const Mat& i1, const Mat& i2) // SSIM方法 { const double C1 = 6.5025, C2 = 58.5225; Mat I1, I2; i1.convertTo(I1, CV_32F); // 转换为32位进行运算 i2.convertTo(I2, CV_32F); Mat I1_2 = I1.mul(I1); // I1^2 Mat I2_2 = I2.mul(I2); // I2^2 Mat I1_I2 = I1.mul(I2); // I1 * I2 Mat sigma1_2, sigma2_2, sigma12; // 先平方再高斯滤波 GaussianBlur(I1_2, sigma1_2, Size(11, 11), 1.5); GaussianBlur(I2_2, sigma2_2, Size(11, 11), 1.5); GaussianBlur(I1_I2, sigma12, Size(11, 11), 1.5); 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); sigma1_2 -= mu1_2; // 两种方式的差值 sigma2_2 -= mu2_2; sigma12 -= mu1_mu2; Mat t1, t2, t3, t4; 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; t4 = t1.mul(t2); // t4 =((mu1_2 + mu2_2 + C1).*(sigma1_2 + sigma2_2 + C2)) Mat ssim_map; divide(t3, t4, ssim_map); // ssim_map = t3./t4; Scalar mssim = mean(ssim_map); // ssim map矩阵的平均值 return mssim;
调用相似度测量方法
for (;;) { captRefrnc >> frameReference; captUndTst >> frameUnderTest; if (frameReference.empty() || frameUnderTest.empty()) { cout << "The End" << endl; break; } ++frameNum; cout << "Frame:" << frameNum << "#"; // 当前帧数,0开始 psnrV = getPSNR(frameReference, frameUnderTest); // 定义的PSNR函数 // setiosflags(ios::fixed)用定点方式显示实数,setprecision(n)可控制输出流显示浮点数的数字个数 cout << setiosflags(ios::fixed) << setprecision(3) << psnrV << "dB"; if (psnrV < psnrTriggerValue && psnrV) // PSNR结果不为零且小于输入值 { mssimV = getMSSIM(frameReference, frameUnderTest); cout << " MSSIM:" << " R " << setiosflags(ios::fixed) << setprecision(2) << mssimV.val[2] * 100 << "%" << " G " << setiosflags(ios::fixed) << setprecision(2) << mssimV.val[1] * 100 << "%" << " B " << setiosflags(ios::fixed) << setprecision(2) << mssimV.val[0] * 100 << "%"; } cout << endl; imshow(WIN_RF, frameReference); imshow(WIN_UT, frameUnderTest); char c = (char)waitKey(30); if (c == 27) break; }