英文原文链接
文章目录
- 目的
- 源码
- 如何读取视频流(在线相机或离线文件)?
- 图像相似度- PSNR和SSIM
目的
如今,有一个数字视频记录系统任由你操作是常见的。因此,您最终会遇到这类情况:即不再处理一批图像,而是处理视频流的情况。这可能有两种:实时图像
(在网络摄像头的情况下输入 )或者预先录制和硬盘驱动器
存储的文件。幸运的是,OpenCV使用相同的c++类以相同的方式处理这两种情况。以下是你将在本教程中学到的:
- 如何打开和读取视频流
- 两种检测图像相似度的方法: PSNR和SSIM
源码
如何读取视频流(在线相机或离线文件)?
图像相似度- PSNR和SSIM
我们想要检查我们的视频转换操作有多难以察觉,因此我们需要一个系统来逐帧检查相似度或不同性。最常用的算法是PSNR(即峰值信噪比 Peak signal-to-noise ratio)。最简单的定义是从均方误差(mean squad error)开始的。假设有两个图像
I
1
I_1
I1和
I
2
I_2
I2;具有二维大小
i
i
i和
j
j
j,由
c
c
c个通道组成。
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
则 PSNR 表示为:
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)
这里的
M
A
X
I
MAX_I
MAXI是像素的最大有效值。对于简单的单字节图像,每个通道的像素:
M
A
X
I
MAX_I
MAXI是255。当两幅图像相同时,MSE 为零,导致 PSNR 公式的除零操作无效。在这种情况下,PSNR是未定义的,因为我们需要单独处理这种情况。转换到对数尺度是因为像素值有一个非常宽的动态范围。所有这些 被转换成OpenCV和一个c++函数,看起来像:
#include <iostream>
#include <string>
#include <iomanip>
#include <sstream>
#include <opencv2/core.hpp>
#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);
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;
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;
cout << "PSNR trigger value " << setiosflags(ios::fixed) << setprecision(3)
<< psnrTriggerValue << endl;
Mat frameReference, frameUnderTest;
double psnrV;
Scalar mssimV;
for(;;)
{
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);
s1.convertTo(s1, CV_32F);
s1 = s1.mul(s1);
Scalar s = sum(s1);
double sse = s.val[0] + s.val[1] + s.val[2];
if( sse <= 1e-10)
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;
int d = CV_32F;
Mat I1, I2;
i1.convertTo(I1, d);
i2.convertTo(I2, d);
Mat I2_2 = I2.mul(I2);
Mat I1_2 = I1.mul(I1);
Mat I1_I2 = I1.mul(I2);
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);
t1 = mu1_2 + mu2_2 + C1;
t2 = sigma1_2 + sigma2_2 + C2;
t1 = t1.mul(t2);
Mat ssim_map;
divide(t3, t1, ssim_map);
Scalar mssim = mean(ssim_map);
return mssim;
}
get-psnr (峰值信噪比)
对于压缩视频,结果值通常在30到50之间,越高越好。如果图像差异很大,你会得到更低的峰值信噪比值,比如15。这种相似性检验计算简单、快速,但在实际应用中可能与人眼的感知不一致。
结构相似度算法就是为了纠正这一点。
描述这些方法远远超出了本教程的目的。为此,我邀请您阅读介绍它的文章。不过,您可以通过查看下面的OpenCV实现来了解它。
SSIM将在文章 “图像质量评估:从错误可见性到结构相似性”《IEEE图像处理学报》
中得到更深入的描述
SSIM将得到更深入的描述;在这篇文章中: “Z. Wang, A. C. Bovik, H. R. Sheikh and E. P. Simoncelli, “Image quality assessment: From error visibility to structural similarity,” IEEE Transactions on Image Processing, vol. 13, no. 4, pp. 600-612, Apr. 2004.”
#include <iostream>
#include <string>
#include <iomanip>
#include <sstream>
#include <opencv2/core.hpp>
#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);
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;
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;
cout << "PSNR trigger value " << setiosflags(ios::fixed) << setprecision(3)
<< psnrTriggerValue << endl;
Mat frameReference, frameUnderTest;
double psnrV;
Scalar mssimV;
for(;;)
{
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);
s1.convertTo(s1, CV_32F);
s1 = s1.mul(s1);
Scalar s = sum(s1);
double sse = s.val[0] + s.val[1] + s.val[2];
if( sse <= 1e-10)
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;
int d = CV_32F;
Mat I1, I2;
i1.convertTo(I1, d);
i2.convertTo(I2, d);
Mat I2_2 = I2.mul(I2);
Mat I1_2 = I1.mul(I1);
Mat I1_I2 = I1.mul(I2);
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);
t1 = mu1_2 + mu2_2 + C1;
t2 = sigma1_2 + sigma2_2 + C2;
t1 = t1.mul(t2);
Mat ssim_map;
divide(t3, t1, ssim_map);
Scalar mssim = mean(ssim_map);
return mssim;
}
get-mssim 结构相似度(structural similarity)
这将为图像的每个通道返回一个相似度指标值。这个值在0和1之间,其中1对应于完美拟合。不幸的是,许多高斯模糊操作是相当耗时的。所以, PSNR可以工作在一个实时的环境(24帧每秒),这将比 实现相似度量性能 更具有重要意义。
因此,本教程开头提供的源代码将对每一帧执行PSNR测量,而SSIM只对 PSNR低于输入值的帧执行。为了可视化,我们在OpenCV窗口中显示这两个图像,并将PSNR和MSSIM值打印到控制台。
期待看到这样的事情:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)