OpenCV 库来捕获和处理视频输入和相似度测量(73)

 返回:OpenCV系列文章目录(持续更新中......)
上一篇:OpenCV的周期性噪声去除滤波器(70)
下一篇 :OpenCV系列文章目录(持续更新中......)

目标

如今,拥有数字视频录制系统供您使用是很常见的。因此,您最终会遇到不再处理一批图像,而是处理视频流的情况。这些可能有两种类型:实时图像馈送(在网络摄像头的情况下)或预先录制的硬盘驱动器存储文件。幸运的是,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::VideoCapture::open 函数执行此操作。如果此参数为整数,则将类绑定到相机、设备。此处传递的数字是设备的 ID,由操作系统分配。如果您的系统连接了单个摄像头,则其 ID 可能为零,并且从那里开始进一步增加。如果传递给这些参数的参数是字符串,它将引用视频文件,并且字符串指向文件的位置和名称。例如,对于上面的源代码,有效的命令行是:

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

我们进行相似性检查。这需要参考和测试用例视频文件。前两个参数指的是这一点。这里我们使用一个相对地址。这意味着应用程序将查看其当前工作目录并打开视频文件夹,并尝试在其中查找Megamind.aviMegamind_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 上观察此操作时实例。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/6936.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

FreeBSD RISCV 在QEME中实践-网络配置

在前一篇文章中&#xff0c;我们一起进行了FreeBSD RISCV 在QEME中实践 现在&#xff0c;让我们配置好网络吧&#xff01; 先上结论&#xff1a;用默认配置启动即可&#xff0c;网络就加载好了&#xff0c;只是不能ping罢了。因为不能ping&#xff0c;以为网络没通&#xff0…

opencv图片的平移-------c++

图片平移 cv::Mat opencvTool::translateImage(const cv::Mat& img, int dx, int dy) {// 获取图像尺寸int rows img.rows;int cols img.cols;// 定义仿射变换矩阵cv::Mat M (cv::Mat_<float>(2, 3) << 1, 0, dx, 0, 1, dy);// 进行仿射变换cv::Mat dst;cv…

[附源码+视频教程]暗黑纪元H5手游_架设搭建_畅玩三网全通西方3D世界_带GM

本教程仅限学习使用&#xff0c;禁止商用&#xff0c;一切后果与本人无关&#xff0c;此声明具有法律效应&#xff01;&#xff01;&#xff01;&#xff01; 教程是本人亲自搭建成功的&#xff0c;绝对是完整可运行的&#xff0c;踩过的坑都给你们填上了 一. 演示视频 暗黑纪…

目标检测——水下垃圾数据集DeepTrash

引言 亲爱的读者们&#xff0c;您是否在寻找某个特定的数据集&#xff0c;用于研究或项目实践&#xff1f;欢迎您在评论区留言&#xff0c;或者通过公众号私信告诉我&#xff0c;您想要的数据集的类型主题。小编会竭尽全力为您寻找&#xff0c;并在找到后第一时间与您分享。 …

[图解]不变式的构造和化简

1 00:00:02,420 --> 00:00:03,380 下面这个&#xff0c;我们来看 2 00:00:03,390 --> 00:00:09,940 X→select&#xff08;Y&#xff09;&#xff0c;用Y这个条件来筛选 3 00:00:09,950 --> 00:00:11,340 之后得到的集合 4 00:00:12,400 --> 00:00:14,390 forAl…

SD-WAN介绍,为何成为主推。

1、SD-WAN&#xff08;Software Defined Wide Area Network&#xff0c; 软件定义的广域网&#xff09; 将企业的分支、总部和多云之间互联起来&#xff0c;应用在不同混合链路&#xff08;MPLS&#xff0c;Internet&#xff0c;5G&#xff0c;LTE等&#xff09;之间选择最优的…

EmotionBench—— 基于 LLM 情绪的量化框架

介绍 大型语言模型&#xff08;LLM&#xff09;在近年来取得了显著的进展&#xff0c;这在计算机科学领域被视为一个重要的里程碑。像ChatGPT和Claude这样的综合性软件已经不再仅仅是用于句子校正、文本翻译和编程的工具&#xff0c;它们已经演进成为类似于人类的助手。因此&a…

Linux 操作系统IPC

目录 1、IPC简介 1.1、共享内存 1.1.1 创建/访问共享内存 1.1.2 映射 1.1.3 解除映射 1.1.4 删除/修改共享内存 1.2 信号量集 1.2.1 创建信号量集合 1.2.2 信号量的初始化 1.2.3 信号量的还原和消耗 1.3 消息队列 1.3.1 概念 1.3.3 添加消息队列 1.3.4 读取消息…

社区养老服务|基于Springboot+vue的社区养老服务平台设计与实现(源码+数据库+文档)

社区养老服务平台 目录 基于Java的社区养老服务平台设计与实现 一、前言 二、系统设计 三、系统功能设计 1用户信息管理 2 服务信息管理 3服务申请管理 4公告信息管理 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#…

【Linux系统化学习】网络套接字(编写简单的UDP服务端和客户端)

目录 理解源IP地址和目的IP地址 认识端口号 端口号和进程ID的区别 源端口号和目的端口号 认识TCP和UDP协议 TCP协议 UDP协议 网络字节序 socket编程接口 socket常见API sockaddr结构 简单的UDP网络程序 UDP服务端 创建套接字 填充本地网络信息 绑定 收取消息 …

Amine-PEG-Amine,956496-54-1在生物成像、生物传感器等领域具有广泛的应用

【试剂详情】 英文名称 Amine-PEG-Amine&#xff0c;NH2-PEG-NH2 中文名称 氨基-聚乙二醇-氨基&#xff0c;氨基PEG氨基&#xff0c; 双端氨基聚乙二醇 CAS号 956496-54-1 外观性状 由分子量决定&#xff0c;液体或者固体 分子量 0.4k&#xff0c;0.6k&#xff0c;1k&…

千古一帝秦始皇的一生

在中国历史上&#xff0c;秦始皇绝对是最有资格被称之为‘千古一帝’的皇帝。 这不光是因为&#xff0c;他是中国的第一个皇帝&#xff0c;更是因为他奠定了中国两千多年的政治格局&#xff0c;让中国从此有了大一统的意识。 1、赵国生涯 公元前259年&#xff0c;秦始皇嬴政…

利用PS中Lab颜色模式进行简单调色?

【原图】 详细步骤如下&#xff1a; Step 1 : 打开PS&#xff0c;打开素材&#xff0c;点菜单栏&#xff0c;【图像】-【模式】-【Lab颜色】&#xff0c;效果如下图 Step2&#xff1a;ctrl(或command)m打开曲线工具&#xff0c;选择a通道&#xff0c;效果如下图。 Step3: 把标…

SpringBoot---------Swagger

第一步&#xff1a;引入依赖 <!-- swagger--><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-spring-boot-starter</artifactId></dependency> 第二步&#xff1a;配置文件 import com.sky.intercept…

【刷题】代码随想录算法训练营第二十九天|491、递增子序列,46、全排列,47、全排列II

目录 491、递增子序列46、全排列47、全排列II 491、递增子序列 讲解&#xff1a;https://programmercarl.com/0491.%E9%80%92%E5%A2%9E%E5%AD%90%E5%BA%8F%E5%88%97.html class Solution { private:vector<vector<int>> result;vector<int> path;void backt…

pandas读取文件导致jupyter内核崩溃如何解决

读取execl文件出现以下问题: str_name "D:\\cao_use\\2017_2021(new).xlsx" train_df pd.read_excel(str_name, usecols[0])崩溃的指示图如下所示: bug原因:读入的文件太大&#xff0c;所需时间过长&#xff0c;在读取的过程中&#xff0c;使用中断按钮暂停会直…

OpenAI神秘模型,再次被Sam Altman提及

5月6日&#xff0c;OpenAI首席执行官Sam Altman在社交平台分享了一条推文“我是一个优秀的GPT-2聊天机器人”。 而在4月30日&#xff0c;Altman就提起过该模型非常喜欢GPT-2。按道理说一个只有15亿参数在2019年发布的开源模型&#xff0c;被反复提及两次就很不寻常。 更意外的…

volatile原理

文章目录 如何保证可见性如何保证有序性double-checked locking 问题double-checked locking 解决 volatile 的底层实现原理是内存屏障&#xff0c;Memory Barrier&#xff08;Memory Fence&#xff09; 对 volatile 变量的写指令后会加入写屏障对 volatile 变量的读指令前会加…

正则表达式_字符匹配/可选字符集

正则表达式&#xff08;Regular Expression&#xff09;也叫匹配模式(Pattern)&#xff0c;用来检验字符串是否满足特 定规则&#xff0c;或从字符串中捕获满足特定规则的子串。 字符匹配 最简单的正则表达式由“普通字符”和“通配符”组成。比如“Room\d\d\d”就这样 的正则…