![]() |
OpenCV 4.13.0
开源计算机视觉库 (Open Source Computer Vision)
|
上一个教程: 使用 GDAL 读取地理空间栅格文件
下一个教程: 使用 OpenCV 创建视频
| 原作者 | Bernát Gábor |
| 兼容性 | OpenCV >= 3.0 |
如今,拥有数字视频录制系统是很常见的。因此,您最终会遇到不再处理批量图像,而是处理视频流的情况。这些视频流可能是两种类型:实时图像馈送(在网络摄像头的情况下)或预录制并存储在硬盘驱动器上的文件。幸运的是,OpenCV 以相同的方式使用相同的 C++ 类处理这两种情况。因此,在本教程中您将学到以下内容:
为了展示 OpenCV 的这些用法,我创建了一个小程序,该程序读取两个视频文件并对它们进行相似性检查。您可以使用此程序来检查新的视频压缩算法的效果如何。假设有一个参考(原始)视频,例如这个小小的 Megamind 片段,以及它的一个压缩版本。您还可以在 OpenCV 源库的 samples/data 文件夹中找到源代码和这些视频文件。
本质上,所有视频操作所需的功能都集成在 cv::VideoCapture C++ 类中。它本身基于 FFmpeg 开源库。这是 OpenCV 的一个基本依赖项,所以您不需要担心这个问题。视频由一系列图像组成,我们在文献中称之为帧。在视频文件的情况下,有一个帧率指定了两帧之间的时间长度。而对于摄像机,通常它们每秒可以数字化的帧数是有限制的,这个属性不那么重要,因为摄像机随时都能看到世界的当前快照。
您需要做的第一件事是为 cv::VideoCapture 类分配其源。您可以通过 cv::VideoCapture::VideoCapture 或其 cv::VideoCapture::open 函数来完成此操作。如果此参数是整数,则您将该类绑定到摄像机(一个设备)。此处传递的数字是操作系统分配的设备 ID。如果您的系统连接了一个摄像头,其 ID 可能为零,后续的摄像头 ID 会递增。如果传递给这些参数的参数是字符串,则它将指向一个视频文件,并且该字符串指向文件的位置和名称。例如,对于上面的源代码,一个有效的命令行是
我们进行相似性检查。这需要一个参考视频文件和一个测试用例视频文件。前两个参数指的是这些。这里我们使用相对地址。这意味着应用程序将在其当前工作目录中查找并打开视频文件夹,并尝试在其中找到 Megamind.avi 和 Megamind_bug.avi。
要检查类与视频源的绑定是否成功,请使用 cv::VideoCapture::isOpened 函数
当对象的析构函数被调用时,视频会自动关闭。但是,如果您想在此之前关闭它,您需要调用其 cv::VideoCapture::release 函数。视频帧只是简单的图像。因此,我们只需将它们从 cv::VideoCapture 对象中提取出来,并放入 Mat 对象中。视频流是连续的。您可以通过 cv::VideoCapture::read 或重载的 >> 运算符逐帧获取。
如果无法获取帧(无论是视频流已关闭还是已到达视频文件末尾),上述读取操作将使 Mat 对象为空。我们可以通过一个简单的 if 语句来检查这一点:
读取方法由帧抓取和对其应用的解码组成。您可以通过使用 cv::VideoCapture::grab 和 cv::VideoCapture::retrieve 函数来显式调用这两个操作。
除了帧的内容之外,视频还附带许多信息。这些通常是数字,但在某些情况下可能是短字符序列(4 字节或更少)。因此,为了获取这些信息,有一个名为 cv::VideoCapture::get 的通用函数,它返回包含这些属性的双精度值。使用位操作从双精度类型解码字符,并在有效值仅为整数时进行转换。它的单个参数是查询属性的 ID。例如,在这里我们获取参考视频和测试用例视频文件中帧的大小;以及参考视频中的帧数。
当您处理视频时,您可能经常想自己控制这些值。为此,有一个 cv::VideoCapture::set 函数。它的第一个参数仍然是您要更改的属性名称,第二个参数是双精度类型,包含要设置的值。如果成功,它将返回 true,否则返回 false。一个很好的例子是在视频文件中跳转到给定时间或帧:
有关您可以读取和更改的属性,请参阅 cv::VideoCapture::get 和 cv::VideoCapture::set 函数的文档。
我们想检查视频转换操作的不可察觉程度,因此我们需要一个逐帧检查相似性或差异的系统。最常用的算法是 PSNR(即峰值信噪比)。它的最简单定义始于均方误差。假设有两幅图像:I1 和 I2;二维尺寸为 i 和 j,由 c 个通道组成。
\[MSE = \frac{1}{c*i*j} \sum{(I_1-I_2)^2}\]
然后 PSNR 表示为
\[PSNR = 10 \cdot \log_{10} \left( \frac{MAX_I^2}{MSE} \right)\]
这里 \(MAX_I\) 是像素的最大有效值。在每个通道每像素简单单字节图像的情况下,它是 255。当两幅图像相同时,MSE 将为零,导致 PSNR 公式中的除零操作无效。在这种情况下,PSNR 未定义,我们需要单独处理这种情况。转换为对数刻度是因为像素值具有非常宽的动态范围。所有这些转换为 OpenCV 和一个函数如下所示:
通常,视频压缩的结果值在 30 到 50 之间,越高越好。如果图像显着不同,您将获得更低的值,例如 15 等。这种相似性检查易于计算且速度快,但实际上可能与人眼感知不一致。结构相似性算法旨在纠正这一点。
描述这些方法远远超出了本教程的目的。为此,我邀请您阅读介绍它的文章。尽管如此,您可以通过查看下面的 OpenCV 实现来很好地了解它。
这将返回图像每个通道的相似度指数。该值介于零和一之间,其中一表示完美匹配。不幸的是,多次高斯模糊的成本很高,因此虽然 PSNR 可能在实时环境中(每秒 24 帧)工作,但要获得类似的性能结果,这将花费更多的时间。
因此,教程开头提供的源代码将对每一帧执行 PSNR 测量,而 SSIM 仅对 PSNR 低于输入值的帧执行。为了可视化目的,我们在 OpenCV 窗口中显示两幅图像,并将 PSNR 和 MSSIM 值打印到控制台。预计会看到类似以下内容:
您可以在此处观看其运行时实例。