【opencv/videoio module】(一)Video Input with OpenCV and similarity measurement

说在前面

SourceCode

  • 读取视频流

    • 所有的视频操作需要类VideoCapture(基于开源库FFmpeg)
    • 视频由一系列图像组成,每一张图称为一帧。在视频文件中一个重要属性为帧率,用于度量帧与帧之间的时间间隔
    • 我们使用 cv::VideoCapture::VideoCapture 或者 cv::VideoCapture::open 来初始化。参数有两种:
      1. 一个整数,代表摄像头设备,值取决于操作系统(一般是0/1/2,递增)
      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来操作
      1. 使用重载的操作符>>
      2. 使用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_FORMATVideoCapture::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=cij1(I1I2)2
      其中 I 1 、 I 2 I_1、I_2 I1I2 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=10log10(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 )┛

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值