目录

1 目标

2 原理

2.1 图像比较 - PSNR and SSIM¶

3 代码

3.1如何读取一个视频流(摄像头或者视频文件)?¶

3 运行效果


视频/图片转换:

  • 如何用OpenCV创建一个视频文件
  • 用OpenCV能创建什么样的视频文件
  • 如何释放视频文件当中的某个颜色通道

为了使例子简单,我就仅仅释放原始视频RGB通道中的一个,并把它放入新视频文件中。你可以使用命令行参数来控制程序的一些行为:

  • 第一个参数指向你需要操作的视频文件。
  • 第二个参数可以是如下的几个字母之一:R G B。用来指定你需要释放哪一个通道。
  • 最后一个参数是Y(Yes)或者N(No). 如果你选择N, 就直接使用视频输入格式来创建输出文件,否则就会弹出一个对话框来让你选择编码器。

1 目标

  • 如何打开和读取视频流

  • 两种检查相似度的方法:PSNR和SSIM

  • 数据链接:https://pan.baidu.com/s/1zqcFKmWViSF1O8QK2pVhMg 提取码:g5p9

2 原理

2.1 图像比较 - PSNR and SSIM¶

我们想检查压缩视频带来的细微差异的时候,就需要构建一个能够逐帧比较差视频差异的系统。最常用的比较算法是PSNR( Peak signal-to-noise ratio)。这是个使用“局部均值误差”来判断差异的最简单的方法,假设有这两幅图像:I1和I2,它们的行列数分别是i,j,有c个通道。

PSNR公式如下:

每个像素的每个通道的值占用一个字节,值域[0,255]。这里每个像素会有 个有效的最大值 注意当两幅图像的相同的话,MSE的值会变成0。这样会导致PSNR的公式会除以0而变得没有意义。所以我们需要单独的处理这样的特殊情况。此外由于像素的动态范围很广,在处理时会使用对数变换来缩小范围。这些变换的C++代码如下:

double getPSNR(const Mat& I1, const Mat& I2)
{Mat s1;absdiff(I1, I2, s1);       // |I1 - I2|s1.convertTo(s1, CV_32F);  // 不能在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) // 如果值太小就直接等于0return 0;else{double  mse =sse /(double)(I1.channels() * I1.total());double psnr = 10.0*log10((255*255)/mse);return psnr;}
}

在考察压缩后的视频时,这个值大约在30到50之间,数字越大则表明压缩质量越好。如果图像差异很明显,就可能会得到15甚至更低的值。**PSNR算法简单,检查的速度也很快。但是其呈现的差异值有时候和人的主观感受不成比例。所以有另外一种称作 结构相似性 的算法做出了这方面的改进。**

建议你阅读一些关于SSIM算法的文献来更好的理解算法,然而及时你直接看下面的源代码,应该也能建立一个不错的映像。请参考下面深度解析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.”

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);           // 不能在单字节像素上进行计算,范围不够。i2.convertTo(I2, d);
​Mat I2_2   = I2.mul(I2);        // I2^2Mat I1_2   = I1.mul(I1);        // I1^2Mat I1_I2  = I1.mul(I2);        // I1 * 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);              // 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 = ssim_map的平均值return mssim;
}

这个操作会针对图像的每个通道返回一个相似度,取值范围应该在0到1之间,取值为1时代表完全符合。然而尽管SSIM能产生更优秀的数据,但是由于高斯模糊很花时间,所以在一个实时系统(每秒24帧)中,人们还是更多地采用PSNR算法。

正是这个原因,最开始的源码里,我们用PSNR算法去计算每一帧图像,而仅当PSNR算法计算出的结果低于输入值的时候,用SSIM算法去验证。为了展示数据,我们在例程里用两个窗口显示了原图像和测试图像并且在控制台上输出了PSNR和SSIM数据。就像4运行结果。

3 代码

3.1如何读取一个视频流(摄像头或者视频文件)?¶

总的来说,视频捕获需要的所有函数都集成在 VideoCapture C++ 类里面。虽然它底层依赖另一个FFmpeg开源库,但是它已经被集成在OpenCV里所以你不需要额外地关注它的具体实现方法。你只需要知道一个视频由一系列图像构成,我们用一个专业点儿的词汇来称呼这些构成视频的图像:“帧”(frame)。此外在视频文件里还有个参数叫做“帧率”(frame rate)的,用来表示两帧之间的间隔时间,帧率的单位是(帧/秒)。这个参数只和视频的播放速度有关,对于单独的一帧图像来说没有任何用途。

你需要先定义一个 VideoCapture 类的对象来打开和读取视频流。具体可以通过 constructor 或者通过 open 函数来完成。如果使用整型数当参数的话,就可以将这个对象绑定到一个摄像机,将系统指派的ID号当作参数传入即可。例如你可以传入0来打开第一个摄像机,传入1打开第二个摄像机,以此类推。如果使用字符串当参数,就会打开一个由这个字符串(文件名)指定的视频文件。例如在上面的例子里传入如下参数:

video/Megamind.avi video/Megamind_bug.avi  //35 10

前两个参数传入了两个文件名,分别代表原始参考视频和测试视频。这里使用了相对地址,这也代表着系统会从软件的工作目录下的video子目录里寻找文件。然后程序将针对这些参数开始进行相似性检查

const string sourceReference = argv[1],sourceCompareWith = argv[2];
​
VideoCapture captRefrnc(sourceReference);
// 或者
VideoCapture captUndTst;
captUndTst.open(sourceCompareWith);

你可以用 isOpened 函数来检查视频是否成功打开与否:

if ( !captRefrnc.isOpened()){cout  << "Could not open reference " << sourceReference << endl;return -1;}

当析构函数调用时,会自动关闭视频。如果你希望提前关闭的话,你可以调用 release 函数. 视频的每一帧都是一幅普通的图像。因为我们仅仅需要从 VideoCapture 对象里释放出每一帧图像并保存成 Mat 格式。因为视频流是连续的,所以你需要在每次调用 read 函数后及时保存图像或者直接使用重载的>>操作符。

Mat frameReference, frameUnderTest;
captRefrnc >> frameReference;
captUndTst.open(frameUnderTest);

如果视频帧无法捕获(例如当视频关闭或者完结的时候),上面的操作就会返回一个空的 Mat 对象。我们可以用下面的代码检查是否返回了空的图像:

if( frameReference.empty()  || frameUnderTest.empty())
{// 退出程序
}

读取视频帧的时候也会自动进行解码操作。你可以通过调用 grab 和 retrieve 函数来显示地进行这两项操作。

视频通常拥有很多除了视频帧图像以外的信息,像是帧数之类,有些时候数据较短,有些时候用4个字节的字符串来表示。所以 get 函数返回一个double(8个字节)类型的数据来表示这些属性。然后你可以使用位操作符来操作这个返回值从而得到想要的整型数据等。这个函数有一个参数,代表着试图查询的属性ID。在下面的例子里我们会先获得食品的尺寸和帧数。

Size refS = Size((int) captRefrnc.get(CV_CAP_PROP_FRAME_WIDTH),(int) captRefrnc.get(CV_CAP_PROP_FRAME_HEIGHT)),
​
cout << "参考帧的分辨率: 宽度=" << refS.width << "  高度=" << refS.height<< " of nr#: " <<<< endl;

当你需要设置这些值的时候你可以调用 set 函数。函数的第一个参数是需要设置的属性ID,第二个参数是需要设定的值,如果返回true的话就表示成功设定,否则就是false。接下来的这个例子很好地展示了如何设置视频的时间位置或者帧数:

captRefrnc.set(CV_CAP_PROP_POS_MSEC, 1.2);  // 跳转到视频1.2秒的位置
captRefrnc.set(CV_CAP_PROP_POS_FRAMES, 10); // 跳转到视频的第10帧
// 然后重新调用read来得到你刚刚设置的那一帧

你可以参考 get 和 set 函数的文档来得到更多信息。

//src = imread("C:\\Users\\guoqi\\Desktop\\ch7\\4.jpg", 1);
#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/imgproc/imgproc.hpp>  // Gaussian Blur
#include <opencv2/core/core.hpp>        // Basic OpenCV structures (cv::Mat, Scalar)
#include <opencv2/highgui/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(int argc, char *argv[], char *window_name)
{//if (argc != 5)//{//  cout << "Not enough parameters" << endl;//  //return -1;//}stringstream conv;
​/*const string sourceReference = argv[1], sourceCompareWith = argv[2];*/const string sourceReference = "C:\\Users\\guoqi\\Desktop\\ch7\\Megamind.avi";const string sourceCompareWith = "C:\\Users\\guoqi\\Desktop\\ch7\\Megamind_bugy.avi";
​int psnrTriggerValue = 35;int delay = 10;//conv << argv[3] << endl << argv[4]; // put in the strings//conv >> psnrTriggerValue >> delay;    // take out the numbers
​char c;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(CV_CAP_PROP_FRAME_WIDTH),(int)captRefrnc.get(CV_CAP_PROP_FRAME_HEIGHT)),uTSi = Size((int)captUndTst.get(CV_CAP_PROP_FRAME_WIDTH),(int)captUndTst.get(CV_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";
​// WindowsnamedWindow(WIN_RF, CV_WINDOW_AUTOSIZE);namedWindow(WIN_UT, CV_WINDOW_AUTOSIZE);cvMoveWindow(WIN_RF, 400, 0);             //750,  2 (bernat =0)cvMoveWindow(WIN_UT, refS.width, 0);      //1500, 2
​cout << "Reference frame resolution: Width=" << refS.width << "  Height=" << refS.height<< " of nr#: " << captRefrnc.get(CV_CAP_PROP_FRAME_COUNT) << endl;
​cout << "PSNR trigger value " <<setiosflags(ios::fixed) << setprecision(3) << psnrTriggerValue << endl;
​Mat frameReference, frameUnderTest;double psnrV;Scalar mssimV;
​while (true) //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 << "# ";
​/ PSNR psnrV = getPSNR(frameReference, frameUnderTest);                 //get PSNRcout << setiosflags(ios::fixed) << setprecision(3) << psnrV << "dB";
​MSSIM /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;
​// Show Image /imshow(WIN_RF, frameReference);imshow(WIN_UT, frameUnderTest);
​c = cvWaitKey(delay);if (c == 27) break;}cv::waitKey();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 bitss1 = 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 zeroreturn 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 valuesi2.convertTo(I2, d);
​Mat I2_2 = I2.mul(I2);        // I2^2Mat I1_2 = I1.mul(I1);        // I1^2Mat I1_I2 = I1.mul(I2);        // I1 * I2
​/*************************** END INITS **********************************/
​Mat mu1, mu2;   // PRELIMINARY COMPUTINGGaussianBlur(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;
​/ FORMULA 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 mapreturn mssim;
}
​

4 运行效果

【OpenCV】图像/视频相似度测量PSNR( Peak signal-to-noise ratio) and SSIM,视频/图片转换相关推荐

  1. 图像处理随笔之峰值信噪比(peak signal to noise ratio)

    图像处理随笔之峰值信噪比(peak signal to noise ratio) the definition from WIKI:an engineering term for the ratio ...

  2. 图像的峰值信噪比(peak signal to noise ratio, PSNR)

    峰值信噪比(PSNR)是一个表示信号最大可能功率和影响它的表示精度的破坏性噪声功率的比值的工程术语.由于许多信号都有非常宽的动态范围,峰值信噪比常用对数分贝单位来表示. 在图像处理中,要对图像进行客观 ...

  3. android opencv 图像旋转90度,使用OpenCV转换图像( 旋转 90度)的简单方法?

    以下是我的EmguCV ( OpenCV的C# 端口) 解决方案:public static Image Rotate90(this Image img) where TColor : struct, ...

  4. 【图像相关】图像质量评价指标 PSNR 和 SSIM

    文章目录 PSNR SSIM 参考链接 PSNR PSNR 是 "Peak Signal to Noise Ratio" 的缩写,即峰值信噪比,是一种评价图像的客观标准,它具有局限 ...

  5. 视频相识度算法思路文献记录

    项目需要做一个判断视频是否相同功能,Google一番,收集了下相关的文章,这里做下记录,方便以后查阅 先对两个文件音频视频采样,视频各取一定数量的帧,音频截取一定长度的数据,分别提取特征,计算相关值, ...

  6. OpenCV之highgui 模块. 高层GUI和媒体I/O: 为程序界面添加滑动条 OpenCV的视频输入和相似度测量 用OpenCV创建视频

    为程序界面添加滑动条 在以前的教程中 (例如 linear blending 和 brightness and contrast adjustments)你有可能注意到需要 input 一些数值到我们 ...

  7. 图像相似度测量与模板匹配总结

    摘要 本文主要总结了进行目标跟踪.检测中经常使用到的图像相似度测量和模板匹配方法,并给出了具体的基于OpenCV的代码实现. 引言 模板匹配是一种在源图像中寻找与图像patch最相似的技术,常常用来进 ...

  8. 【OpenCV】视频输入与相似度测量

    视频输入   与之前的博客内容不同,本节处理的是视频流而非图像集.视频流来源可以是摄像头的实时图像反馈.已拍摄存储的视频.OpenCV可以读取这两种类型的视频流并采用RSNP和SSIM检查图像的相似度 ...

  9. 图像相似度测量和模板匹配方法

    摘要 本文主要总结了进行目标跟踪.检测中经常使用到的图像相似度测量和模板匹配方法,并给出了具体的基于OpenCV的代码实现. 引言 模板匹配是一种在源图像中寻找与图像patch最相似的技术,常常用来进 ...

最新文章

  1. TorchVision中使用FasterRCNN+ResNet50+FPN进行目标检测
  2. ActiveMQ — 单节点 — 安装与配置
  3. 工业界如何解决NER问题?12个trick,与你分享~
  4. Perl调试器的用法
  5. C++11新特性之新类型与初始化
  6. python3.6.0安装步骤
  7. mysql 1100_mysql数据库选择,有1100个用户,每个用户每月生成一张表,使用中该表内每秒上传一条数据,数据量很大...
  8. [html] 对于rtl网站的适配有哪些方案?
  9. 3青春痘长在哪里不害怕
  10. mysql的 怎么处理_本人的MySQL连接到底怎么处理才好……
  11. 信息学奥赛一本通(1406:单词替换)
  12. CV Code | 计算机视觉开源周报20191001期
  13. 防红直连php,【源码资源】20新PHP网址缩短防封防红短网址生成系统
  14. python当前日期时间_Python当前日期时间
  15. QAM调制原理_QAM调制:4/5G中各种调制方式基础,均由两条正弦波变化并勾勒出...
  16. stm32神舟I号开发板下的六子棋开发
  17. 谷歌浏览器一直无法上网,其他浏览器可以正常上网
  18. Java学历很重要_Java开发找工作,学历重要还是技术重要?
  19. iOS 微信支付和支付宝支付
  20. 弘泰俱乐部杨乐、车彦娇团队:凝聚慈善力量,关爱孤困儿童

热门文章

  1. java 1%10_Java 操作符与运算符详解
  2. PTA---指针错误汇总(就自己做个笔记)
  3. android 8.0可以实现后台包活么,Android 8.0 应用保活实践 · Jaqen Ng
  4. mysql 插入删除操作_MySQL——增删改操作
  5. flex和bison实例分析
  6. 关闭tomact被占用的进程
  7. 使用Python,OpenCV,K-Means聚类查找图像中最主要的颜色
  8. PCL:点云中的超体素数据
  9. PCL显示法线no override found vtkactor
  10. 和12岁小同志搞创客开发:Mind+编程软件简介、安装及使用