东营远见网站建设公司,服装网站建设内容,建设银行网站背景图片,企业网络推广方案策划书返回:OpenCV系列文章目录#xff08;持续更新中......#xff09;
上一篇:OpenCV的周期性噪声去除滤波器(70)
下一篇 :使用 OpenCV 创建视频(74) 目标
如今#xff0c;拥有数字视频录制系统供您使用是很常见的。因此#xff0c;您最终会遇到不再处理一批图像#xf… 返回:OpenCV系列文章目录持续更新中......
上一篇:OpenCV的周期性噪声去除滤波器(70)
下一篇 :使用 OpenCV 创建视频(74) 目标
如今拥有数字视频录制系统供您使用是很常见的。因此您最终会遇到不再处理一批图像而是处理视频流的情况。这些可能有两种类型实时图像馈送在网络摄像头的情况下或预先录制的硬盘驱动器存储文件。幸运的是OpenCV以相同的方式处理这两者使用相同的C类。因此以下是您将在本教程中学到的内容
如何打开和阅读视频流检查图像相似度的两种方法PSNR 和 SSIM
C源代码 作为使用 OpenCV 展示这些内容的测试用例我创建了一个小程序可以读取两个视频文件并在它们之间执行相似性检查。您可以使用它来检查新的视频压缩算法的工作情况。让我们有一个参考原始视频比如这个小的 Megamind 剪辑和它的压缩版本。您还可以在 OpenCV 源库的文件夹samples/data中找到源代码和这些视频文件。 #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/Ousing 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]; // put in the stringsconv psnrTriggerValue delay; // take out the numbersint frameNum -1; // Frame counterVideoCapture 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;// WindowsnamedWindow(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, 2cout 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;
}// ![get-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 bitss1 s1.mul(s1); // |I1 - I2|^2Scalar s sum(s1); // sum elements per channeldouble sse s.val[0] s.val[1] s.val[2]; // sum channelsif( 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;}
}
// ![get-psnr]// ![get-mssim]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;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;
}
// ![get-mssim] 如何读取视频流在线摄像机或离线文件
从本质上讲视频操作所需的所有功能都集成在 cv::VideoCapture C 类中。这本身是建立在 FFmpeg 开源库之上的。这是 OpenCV 的基本依赖项因此您无需担心这一点。视频由一系列图像组成我们在文献中将这些图像称为帧。对于视频文件有一个帧速率指定两帧之间的长度。虽然对于摄像机来说它们通常每秒可以数字化多少帧是有限制的但这个属性并不那么重要因为摄像机在任何时候都能看到世界的当前快照。
您需要执行的第一个任务是将其源代码分配给 cv::VideoCapture 类。您可以通过 cv::VideoCapture::VideoCapture 或其 cv::VideoCaptureopen 函数执行此操作。如果此参数为整数则将类绑定到相机、设备。此处传递的数字是设备的 ID由操作系统分配。如果您的系统连接了单个摄像头则其 ID 可能为零并且从那里开始进一步增加。如果传递给这些参数的参数是字符串它将引用视频文件并且字符串指向文件的位置和名称。例如对于上面的源代码有效的命令行是
video/Megamind.avi video/Megamind_bug.avi 35 10
我们进行相似性检查。这需要参考和测试用例视频文件。前两个参数指的是这一点。这里我们使用一个相对地址。这意味着应用程序将查看其当前工作目录并打开视频文件夹并尝试在其中查找Megamind.avi和Megamind_bug.avi。
const string sourceReference argv[1],sourceCompareWith argv[2];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 函数。视频的帧只是简单的图像。因此我们只需要从 cv::VideoCapture 对象中提取它们并将它们放入 Mat 对象中。视频流是连续的。您可以通过 cv::VideoCapture::read 或重载的 运算符一个接一个地获取帧
Mat frameReference, frameUnderTest;
captRefrnc frameReference;
captUndTst.read(frameUnderTest);
如果无法获取任何帧导致视频流关闭或视频文件末尾则上面的读取操作将空 Mat 对象。如果出现以下情况我们可以通过一个简单的方法来检查这一点
if( frameReference.empty() || frameUnderTest.empty())
{// exit the program
}
读取方法由帧抓取和应用的解码组成。可以使用 cv::VideoCapture::grab 和 cv::VideoCapture::retrieve 函数显式调用这两个函数。
除了帧的内容之外视频还附加了许多信息。这些通常是数字但在某些情况下它可能是短字符序列4 个字节或更少。因此为了获取这些信息有一个名为 cv::VideoCapture::get 的通用函数返回包含这些属性的双精度值。使用按位运算对有效值仅为整数的双精度类型和转换中的字符进行解码。它的单个参数是查询属性的 ID。例如这里我们得到参考和测试用例视频文件中的帧大小;加上参照内部的帧数。
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;
在处理视频时您可能经常希望自己控制这些值。为此有一个 cv::VideoCapture::set 函数。它的第一个参数仍然是要更改的属性的名称第二个参数是包含要设置的值的 double 类型。如果成功它将返回 true否则返回 false。很好的例子是在视频文件中寻找给定的时间或帧
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
对于可以读取和更改的属性请查看 cv::VideoCapture::get 和 cv::VideoCapture::set 函数的文档。 图像相似度 - PSNR 和 SSIM
我们想检查我们的视频转换操作有多难以察觉因此我们需要一个系统来逐帧检查相似性或差异性。最常用的算法是PSNR又名峰值信噪比。最简单的定义是从均方误差开始的。假设有两个图像I1 和 I2;具有二维大小的 i 和 j由 c 个通道组成。 然后 PSNR 表示为 这里的 (MAX_I)是像素的最大有效值。对于简单的单字节图像每个通道每像素这是 255。当两个图像相同时MSE 将给出零从而导致 PSNR 公式中的除以零运算无效。在这种情况下PSNR 是未定义的因此我们需要单独处理这种情况。之所以过渡到对数刻度是因为像素值具有非常宽的动态范围。所有这些都转换为 OpenCV函数如下所示
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|^2Scalar s sum(s1); // sum elements per channeldouble sse s.val[0] s.val[1] s.val[2]; // sum channelsif( 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;}
}
通常视频压缩的结果值介于 30 到 50 之间越高越好。如果图像明显不同您将得到更低的图像例如 15 左右。这种相似性检查计算起来既简单又快捷但在实践中它可能与人眼感知有些不一致。结构相似性算法旨在纠正这一点。
描述这些方法远远超出了本教程的目的。为此我邀请您阅读介绍它的文章。尽管如此您可以通过查看下面的 OpenCV 实现来获得它的良好图像。
注意
SSIM在以下文章中进行了更深入的描述“Z. Wang A. C. Bovik H. R. Sheikh and E. P. Simoncelli“图像质量评估从错误可见性到结构相似性”IEEE Transactions on Image Processing第 13 卷第 4 期第 600-612 页2004 年 4 月。
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;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;
}
这将返回图像每个通道的相似性索引。此值介于 0 和 1 之间其中 1 对应于完全拟合。不幸的是许多高斯模糊的成本非常高因此虽然 PSNR 可以在实时环境中工作每秒 24 帧但这比实现类似的性能结果要多得多。
因此本教程开头提供的源代码将对每个帧执行 PSNR 测量并且仅对 PSNR 低于输入值的帧执行 SSIM。出于可视化目的我们在 OpenCV 窗口中显示两个图像并将 PSNR 和 MSSIM 值打印到控制台。期待看到类似的东西 您可以在 YouTube 上观察此操作时实例。