与之前的博客内容不同,本节处理的是视频流而非图像集。视频流来源可以是摄像头的实时图像反馈、已拍摄存储的视频。OpenCV可以读取这两种类型的视频流并采用RSNP和SSIM检查图像的相似度,使用函数为 cv::VideoCapture 。
本节的测试视频下载地址:
https://github.com/opencv/opencv/blob/master/samples/data/Megamind.avi
https://github.com/opencv/opencv/blob/master/samples/data/Megamind_bugy.avi
参考链接:https://docs.opencv.org/master/d5/dc4/tutorial_video_input_psnr_ssim.html
视频的转换操作是难以察觉的,因此通过帧与帧间的差异来测量相似度。
常用的方法是峰值信噪比PSNR(Peak signal-to-noise ratio),记I1、I2是输入的两幅图像,i,j是二维图像的大小,c是通道数量。
代码示例
#include
#include
#include // 控制浮动类型的打印精度
#include // 字符串和数值的转换
#include // CV::Mat,Scalar
#include // 高斯平滑
#include // 视频
#include
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 = "../data/Megamind.avi";
const string sourceCompareWith = "../data/Megamind_bugy.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 << "\tMSSIM:" << " 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;
}
运行结果