本教程开始介绍的源代码将对每一帧执行PSNR测量,并且只对PSNR低于输入值的帧进行SSIM测量。为了可视化的目的,我们在OpenCV窗口中展示两幅图像,并将PSNR和MSSIM值打印到控制台。期望看到如下内容:
video-input-psnr-ssim.cpp 将两个视频的每一帧逐一读取并计算其峰值信号噪声比(PSNR) 和 结构相似性指标(MSSIM)
#include <iostream> // 标准输入输出流
#include <string> // 字符串操作库
#include <iomanip> // 输入输出流格式控制
#include <sstream> // 字符串与数字转换
#include <opencv2/core.hpp> // OpenCV基础结构 (cv::Mat, Scalar)
#include <opencv2/imgproc.hpp> // 高斯模糊处理
#include <opencv2/videoio.hpp>
#include <opencv2/highgui.hpp> // OpenCV窗口输入输出
using namespace std;
using namespace cv;
double getPSNR(const Mat& I1, const Mat& I2); // 声明计算PSNR值的函数
Scalar getMSSIM(const Mat& I1, const Mat& I2); // 声明计算MSSIM值的函数
static void help() // 帮助文本输出函数
{
cout
<< "------------------------------------------------------------------------------" << endl
<< "This program shows how to read a video file with OpenCV. In addition, it "
<< "tests the similarity of two input videos first with PSNR, and for the frames "
<< "below a PSNR trigger value, also with MSSIM." << endl
<< "Usage:" << endl
<< "./video-input-psnr-ssim <referenceVideo> <useCaseTestVideo> <PSNR_Trigger_Value> <Wait_Between_Frames> " << endl
<< "--------------------------------------------------------------------------" << endl
<< endl;
}
int main(int argc, char *argv[]) // 主函数
{
help(); // 显示帮助文本
if (argc != 5) // 检查输入参数数量
{
cout << "Not enough parameters" << endl;
return -1;
}
stringstream conv; // 创建字符串流
const string sourceReference = argv[1], sourceCompareWith = argv[2]; // 引用视频和待比较视频路径
int psnrTriggerValue, delay; // PSNR阈值和帧间延迟
conv << argv[3] << endl << argv[4]; // 将参数放入字符串流
conv >> psnrTriggerValue >> delay; // 从字符串流提取参数值
int frameNum = -1; // 帧计数器
VideoCapture captRefrnc(sourceReference), captUndTst(sourceCompareWith); // 创建视频捕捉对象
if (!captRefrnc.isOpened()) // 检查引用视频文件是否成功打开
{
cout << "Could not open reference " << sourceReference << endl;
return -1;
}
if (!captUndTst.isOpened()) // 检查待比较视频文件是否成功打开
{
cout << "Could not open case test " << sourceCompareWith << endl;
return -1;
}
Size refS = Size((int) captRefrnc.get(CAP_PROP_FRAME_WIDTH),
(int) captRefrnc.get(CAP_PROP_FRAME_HEIGHT)),
uTSi = Size((int) captUndTst.get(CAP_PROP_FRAME_WIDTH),
(int) captUndTst.get(CAP_PROP_FRAME_HEIGHT)); // 获取视频的尺寸
if (refS != uTSi) // 检查两个视频的尺寸是否一致
{
cout << "Inputs have different size!!! Closing." << endl;
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, 400, 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;
// 输出PSNR阈值信息
cout << "PSNR trigger value " << setiosflags(ios::fixed) << setprecision(3)
<< psnrTriggerValue << endl;
Mat frameReference, frameUnderTest; // 创建存储参考帧和待测帧的Mat对象
double psnrV; // PSNR值
Scalar mssimV; // MSSIM值
for(;;) // 无限循环,用于显示视频帧并处理
{
captRefrnc >> frameReference; // 读取参考帧
captUndTst >> frameUnderTest; // 读取待测帧
if (frameReference.empty() || frameUnderTest.empty()) // 如果读取为空,则表明视频结束
{
cout << " < < < Game over! > > > ";
break;
}
++frameNum; // 增加帧数
cout << "Frame: " << frameNum << "# ";
// 计算PSNR值
psnrV = getPSNR(frameReference,frameUnderTest);
cout << setiosflags(ios::fixed) << setprecision(3) << psnrV << "dB";
// 如果PSNR值低于阈值,并且非零,则计算MSSIM值
if (psnrV < psnrTriggerValue && psnrV)
{
mssimV = getMSSIM(frameReference, frameUnderTest);
// 输出MSSIM的RGB通道值
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);
// 等待按键,如果按下ESC键,则退出循环
char c = (char)waitKey(delay);
if (c == 27) break;
}
return 0; // 返回0,表明程序正常退出
}
// 计算PSNR值的函数
double getPSNR(const Mat& I1, const Mat& I2)
{
Mat s1;
absdiff(I1, I2, s1); // 计算I1和I2的绝对差值 |I1 - I2|
s1.convertTo(s1, CV_32F); // 将结果转换为32位浮点数,因为不能在8位上进行平方运算
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
return 0;
else
{
double mse = sse / (double)(I1.channels() * I1.total()); // 计算均方误差MSE
double psnr = 10.0 * log10((255 * 255) / mse); // 根据MSE计算PSNR值
return psnr;
}
}
// 计算MSSIM值的函数
Scalar getMSSIM(const Mat& i1, const Mat& i2)
{
const double C1 = 6.5025, C2 = 58.5225; // 定义常数C1和C2
/***************************** 初始化 **********************************/
int d = CV_32F;
Mat I1, I2;
i1.convertTo(I1, d); // 将图像转换为32位浮点数进行计算
i2.convertTo(I2, d);
Mat I2_2 = I2.mul(I2); // 计算I2的平方
Mat I1_2 = I1.mul(I1); // 计算I1的平方
Mat I1_I2 = I1.mul(I2); // 计算I1和I2的乘积
/**************************** 结束初始化 *******************************/
Mat mu1, mu2; // 预先计算
GaussianBlur(I1, mu1, Size(11, 11), 1.5); // 计算均值mu1
GaussianBlur(I2, mu2, Size(11, 11), 1.5); // 计算均值mu2
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
sigma1_2 -= mu1_2;
GaussianBlur(I2_2, sigma2_2, Size(11, 11), 1.5); // 计算标准差sigma2_2
sigma2_2 -= mu2_2;
GaussianBlur(I1_I2, sigma12, Size(11, 11), 1.5); // 计算协方差sigma12
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); // 计算ssim_map的平均值
return mssim;
}
此代码是一个用于比较两个视频文件的相似性的C++程序,它使用OpenCV库来读取和处理视频帧。首先,程序通过计算峰值信噪比(PSNR)来比较每对视频帧。如果PSNR值低于某个阈值,程序额外使用结构相似性指数(MSSIM)进行比较。结果随着视频播放实时显示,并通过命令行参数控制一些基本设置,如PSNR阈值和帧间等待时间。程序还能够在窗口中实时显示参考视频和待测视频的帧。
cout << setiosflags(ios::fixed) << setprecision(3) << psnrV << "dB";