OpenCV  4.10.0
开源计算机视觉库
加载中...
搜索中...
无匹配项
使用 OpenCV 进行视频输入和相似度测量

上一教程: 使用 GDAL 读取地理空间栅格文件
下一教程: 使用 OpenCV 创建视频

原作者Bernát Gábor
兼容性OpenCV >= 3.0

目标

如今,人们经常使用数字视频录制系统。因此,您最终会遇到不再处理一批图像,而是处理视频流的情况。这些流可以是两种类型:实时图像馈送(对于网络摄像头而言)或预先录制并存储在硬盘上的文件。幸运的是,OpenCV 以相同的方式处理这两种类型,使用相同的 C++ 类。因此,在本教程中,您将学习:

  • 如何打开和读取视频流
  • 两种检查图像相似度的途径:PSNR 和 SSIM

源代码

为了演示使用 OpenCV 的方法,我创建了一个小程序,它读取两个视频文件并执行相似性检查。您可以使用它来检查新的视频压缩算法的有效性。假设有一个参考(原始)视频,例如 这个 Megamind 小片段,以及 它的压缩版本。您也可以在 OpenCV 源库的 samples/data 文件夹中找到源代码和这些视频文件。

如何读取视频流(在线摄像头或离线文件)?

本质上,所有用于视频操作的功能都集成在 cv::VideoCapture C++ 类中。它本身建立在 FFmpeg 开源库之上。这是 OpenCV 的基本依赖项,因此您无需担心它。视频由一系列图像组成,我们在文献中将它们称为帧。对于视频文件,有一个 *帧速率* 指定两帧之间的时间长度。而对于摄像机,通常会限制它们每秒能数字化多少帧,这一属性不那么重要,因为摄像机在任何时候都能够捕捉到当前世界的快照。

您需要做的第一件事是将 cv::VideoCapture 类分配到它的源。您可以通过 cv::VideoCapture::VideoCapture 或其 cv::VideoCapture::open 函数来实现。如果此参数是整数,那么您将把类绑定到摄像头,即一个设备。此处传递的数字是操作系统分配的设备 ID。如果您有一个摄像头连接到系统,它的 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);
// 或者
VideoCapture captUndTst;
captUndTst.open(sourceCompareWith);
virtual bool open(const String &filename, int apiPreference=CAP_ANY)
打开一个视频文件或捕获设备或 IP 视频流以进行视频捕获。

要检查类是否成功绑定到视频源,可以使用 cv::VideoCapture::isOpened 函数

if ( !captRefrnc.isOpened())
{
cout << "无法打开参考 " << sourceReference << endl;
return -1;
}

当对象的析构函数被调用时,视频会自动关闭。但是,如果您想在调用析构函数之前关闭它,则需要调用它的 cv::VideoCapture::release 函数。视频帧只是简单的图像。因此,我们只需要从 cv::VideoCapture 对象中提取它们,并将其放到一个 *Mat* 对象中。视频流是按顺序排列的。您可以使用 cv::VideoCapture::read 或重载的 >> 运算符,依次获取帧

Mat frameReference, frameUnderTest;
captRefrnc >> frameReference;
captUndTst.read(frameUnderTest);
virtual bool read(OutputArray image)
抓取、解码并返回下一视频帧。

上面的读取操作将清空 *Mat* 对象,如果无法获取帧(无论是由于视频流关闭,还是您已经到达视频文件的末尾)。我们可以通过一个简单的 if 语句来检查这一点

if( frameReference.empty() || frameUnderTest.empty())
{
// 退出程序
}
bool empty() const
如果数组没有元素,则返回 true。

读取方法包括帧抓取和应用于该帧的解码。您可以使用 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 << "参考帧分辨率:宽度=" << refS.width << " 高度=" << refS.height
<< " 编号:" << captRefrnc.get(CAP_PROP_FRAME_COUNT) << endl;

当您使用视频时,您可能经常想要自己控制这些值。为此,有一个 cv::VideoCapture::set 函数。它的第一个参数仍然是您想要更改的属性的名称,第二个参数是包含要设置的值的双精度类型。如果成功,它将返回 true,否则返回 false。这里的一个很好的例子是在视频文件中跳转到给定的时间或帧

captRefrnc.set(CAP_PROP_POS_MSEC, 1.2); // 跳到视频中的 1.2 秒处
captRefrnc.set(CAP_PROP_POS_FRAMES, 10); // 跳到视频中的第 10 帧
// 现在,读取操作将读取设置位置的帧

有关您可以读取和更改的属性,请查看 cv::VideoCapture::getcv::VideoCapture::set 函数的文档。

图像相似性 - PSNR 和 SSIM

我们想要检查我们的视频转换操作是多么不明显,因此我们需要一个系统来逐帧检查相似性或差异。最常用的算法是 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 实现来了解它。

注意
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." 这篇文章中进行了更深入的描述。

这将返回图像每个通道的相似性索引。此值在 0 到 1 之间,其中 1 表示完美匹配。不幸的是,许多高斯模糊的计算量很大,因此,虽然 PSNR 可以在实时环境中工作(每秒 24 帧),但要实现类似的性能结果,它需要花费更多时间。

因此,教程开头展示的源代码将对每一帧进行PSNR测量,而SSIM仅对PSNR低于输入值的帧进行测量。为了可视化,我们在OpenCV窗口中显示了这两张图像,并将PSNR和MSSIM值打印到控制台中。预计会看到类似以下内容

您可以在此处的YouTube上观察到此运行时实例。