说在前面
- opencv版本:4.0.1
- 操作系统:win10
- vs版本:2017
- 官方文档:Video Input with OpenCV and similarity measurement
- 其他说明:自学,记录,demo
SourceCode
-
读取视频流
- 所有的视频操作需要类VideoCapture(基于开源库FFmpeg)
- 视频由一系列图像组成,每一张图称为一帧。在视频文件中一个重要属性为帧率,用于度量帧与帧之间的时间间隔
- 我们使用 cv::VideoCapture::VideoCapture 或者 cv::VideoCapture::open 来初始化。参数有两种:
- 一个整数,代表摄像头设备,值取决于操作系统(一般是0/1/2,递增)
- 文件名(路径)
const string sourceReference = "test1.avi",sourceCompareWith = "test2.avi"; VideoCapture captRefrnc(sourceReference); // or VideoCapture captUndTst; captUndTst.open(sourceCompareWith);
- 我们可以使用cv::VideoCapture::isOpened来判断是否成功打开视频流
if ( !captRefrnc.isOpened()) { cout << "Could not open reference " << sourceReference << endl; return -1; }
- 视频流会在析构函数调用时自动关闭,但也可使用cv::VideoCapture::release 手动关闭
captRefrnc.release();
- 由于每一帧都是简单的图像,我们可以使用Mat来操作
- 使用重载的操作符>>
- 使用cv::VideoCapture::read
Mat frameReference, frameUnderTest; captRefrnc >> frameReference;//当前视频文件还未读取完的时候 captUndTst.read(frameUnderTest);
- 判断是否到视频文件末(上面读取的时候若已经无帧可读,则Mat为空)
if( frameReference.empty() || frameUnderTest.empty()) { // exit the program }
- 获取VideoCapture的属性,支持的属性下面的表格(未显示摄像头相关,全部见 ALL)
Size refS = Size((int) captRefrnc.get(CAP_PROP_FRAME_WIDTH), (int) captRefrnc.get(CAP_PROP_FRAME_HEIGHT)), cout << "Reference frame resolution: Width=" << refS.width << " Height=" << refS.height << " of nr#: " << captRefrnc.get(CAP_PROP_FRAME_COUNT) << endl;
- 设置VideoCapture的属性
captRefrnc.set(CAP_PROP_POS_MSEC, 1.2); // go to the 1.2 second in the video captRefrnc.set(CAP_PROP_POS_FRAMES, 10); // go to the 10th frame of the video // now a read operation would read the frame at the set position
名称 | 意义 |
---|---|
CAP_PROP_POS_MSEC | 当前帧所在视频流的位置(单位:毫秒) |
CAP_PROP_POS_FRAMES | 下一帧是第几帧(从0开始) |
CAP_PROP_POS_AVI_RATIO | 视频文件相对位置(0:靠近视频头;1:靠近视频尾) |
CAP_PROP_FRAME_WIDTH | 帧宽度(像素) |
CAP_PROP_FRAME_HEIGHT | 帧高度(像素) |
CAP_PROP_FPS | 帧率(frame per second) |
CAP_PROP_FOURCC | 四个字符的编码类型 |
CAP_PROP_FRAME_COUNT | 视频文件帧总数 |
CAP_PROP_FORMAT | VideoCapture::retrieve()返回的Mat格式 |
-
图像相似度
-
在进行视频处理的时候有时某些操作的结果是肉眼不可见的,所以我们需要逐帧进行对比(操作前后的同一帧图像)
-
最简单的计算方式为均方差(mean squad error)
M S E = 1 c ∗ i ∗ j ∑ ( I 1 − I 2 ) 2 MSE = \frac{1}{c*i*j} \sum{(I_1-I_2)^2} MSE=c∗i∗j1∑(I1−I2)2
其中 I 1 、 I 2 I_1、I_2 I1、I2为 i i i 行 j j j 列 c c c 通道的图像,后半部分为两个图像所有对应像素的对应通道的差值平方和的求和
P S N R = 10 ⋅ log 10 ( M A X I 2 M S E ) PSNR = 10 \cdot \log_{10} \left( \frac{MAX_I^2}{MSE} \right) PSNR=10⋅log10(MSEMAXI2)
上面的为PSNR公式,其中 M A X I MAX_I MAXI为通道的最大值(例如CV_8U就是255);PSNR值越小,表明差异越大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; } }
但是这种计算方式在实际应用中可能与人眼的感觉结果不一致(啥意思?)
-
另一种计算方式,structural similarity
用了高斯模糊,咱先不管这部分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 /***********************PRELIMINARY COMPUTING ******************************/ 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 = average of ssim map return mssim; }
-
-
Code
#include <iostream> // for standard I/O
#include <string> // for strings
#include <iomanip> // for controlling float print precision
#include <sstream> // string to number conversion
#include <opencv2/core.hpp> // Basic OpenCV structures (cv::Mat, Scalar)
#include <opencv2/imgproc.hpp> // Gaussian Blur
#include <opencv2/videoio.hpp>
#include <opencv2/highgui.hpp> // OpenCV window I/O
using namespace std;
using namespace cv;
double getPSNR(const Mat& I1, const Mat& I2);
Scalar getMSSIM(const Mat& I1, const Mat& I2);
int main()
{
stringstream conv;
const string sourceReference = "Megamind.avi", sourceCompareWith = "Megamind_bugy.avi";
int psnrTriggerValue=35, delay=10;
int frameNum = -1; // Frame counter
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";
// Windows
namedWindow(WIN_RF, WINDOW_AUTOSIZE);
namedWindow(WIN_UT, WINDOW_AUTOSIZE);
moveWindow(WIN_RF, 400, 0); //750, 2 (bernat =0)
moveWindow(WIN_UT, refS.width, 0); //1500, 2
cout << "Reference frame resolution: Width=" << refS.width << " Height=" << refS.height
<< " of nr#: " << captRefrnc.get(CAP_PROP_FRAME_COUNT) << endl;
cout << "PSNR trigger value " << setiosflags(ios::fixed) << setprecision(3)
<< psnrTriggerValue << endl;
Mat frameReference, frameUnderTest;
double psnrV;
Scalar mssimV;
for (;;) //Show the image captured in the window and repeat
{
captRefrnc >> frameReference;
captUndTst >> frameUnderTest;
if (frameReference.empty() || frameUnderTest.empty())
{
cout << " < < < Game over! > > > ";
break;
}
++frameNum;
cout << "Frame: " << frameNum << "# ";
psnrV = getPSNR(frameReference, frameUnderTest);
cout << setiosflags(ios::fixed) << setprecision(3) << psnrV << "dB";
if (psnrV < psnrTriggerValue && psnrV)
{
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(delay);
if (c == 27) break;
}
return 0;
}
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;
}
}
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;
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;
}
Result
- 测试视频为官方的那个,一个原视频、一个压缩后的视频
END-2019.7.5┗( T﹏T )┛