- OpenCV 4计算机视觉:Python语言实现(原书第3版)
- (加)约瑟夫·豪斯 (爱尔兰)乔·米尼奇诺
- 1635字
- 2025-02-26 19:14:21
2.4.1 基于managers.CaptureManager提取视频流
正如我们所看到的,OpenCV可以获取、显示和记录来自视频文件或来自摄像头的图像流,但是在每种情况下都会有一些特殊考虑的事项。CaptureManager类提取了一些差异并提供了一个更高级的接口,将图像从获取流分发到一个或多个输出——静态图像文件、视频文件,或者窗口。
CaptureManager对象是由VideoCapture对象初始化的,并拥有enterFrame和exitFrame方法,通常应该在应用程序主循环的每次迭代中调用这两个方法。在调用enterFrame和exitFrame之间,应用程序可以(任意次)设置一个channel属性并获得一个frame属性。channel属性初始为0,只有多摄像头相机使用其他值。frame属性是在调用enterFrame时,对应于当前通道状态的一幅图像。
CaptureManager类还拥有可以在任何时候调用的writeImage、startWriting Video和stopWritingVideo方法。实际的文件写入被推迟到exitFrame。同样,在执行exitFrame方法期间,可以在窗口中显示frame,这取决于应用程序代码将WindowManager类作为CaptureManager构造函数的参数提供,还是通过设置previewWindowManager属性提供。
如果应用程序代码操作frame,那么将在记录文件和窗口中体现这些操作。CaptureManager类有一个构造函数参数和一个名为shouldMirrorPreview的属性,如果想要在窗口中镜像(水平翻转)frame,但不记录在文件中,那么此属性应该为True。通常,在面对摄像头时,用户更喜欢镜像实时摄像头回传信号。
回想一下,VideoWriter对象需要一个帧率,但是OpenCV没有提供任何可靠的方法来为摄像头获取准确的帧率。CaptureManager类通过使用帧计数器和Python的标准time.time函数来解决此限制,如有必要还会估计帧率。这种方法并非万无一失。取决于帧率的波动和依赖于系统的time.time实现,估计的准确率在某些情况下可能仍然很糟糕。但是,如果部署到未知的硬件,这也比只假设用户摄像头有某个特定的帧率要好。
我们创建一个名为managers.py的文件,该文件将包含CaptureManager实现。这个实现非常很长,所以我们将分成几个部分介绍:
(1)首先,添加导入和构造函数,如下所示:

(2)接下来,为CaptureManager的属性添加下面的getter和setter方法:


请注意,大多数member变量是非公共的,由变量名称中下划线前缀所示,如self._enteredFrame。这些非公共变量与当前帧的状态和任何文件的写入操作相关。如前所述,应用程序代码只需要配置一些内容,这些内容是作为构造函数参数和可设置的公共属性(摄像头通道、窗口管理器以及镜像摄像头预览的选项)实现的。
本书假设读者对Python有一定的了解,但是如果你对这些@注释(例如@property)感到困惑,请参考有关decorator的Python文档,decorator是Python语言的内置特性,允许函数被另一个函数封装,通常用来在应用程序的几个地方应用用户定义的行为。具体来说,可以在https://docs.python.org/3/reference/compound_stmts.html#grammar-token-decorator查看相关文档。
Python没有强制使用非公共的成员变量的概念,但是在开发人员想要将变量视为非公共的情况下,通常会看到单下划线前缀(_)或者双下划线前缀(__)。单下划线前缀只是一种约定,表示应该将变量视为受保护的(仅在类及其子类中访问)。双下划线前缀实际上会导致Python解释器重命名变量,这样MyClass.__myVariable就变成了MyClass._MyClass__myVariable。这被称为名称重整(非常恰当)。按照惯例,应该将这样的变量视为私有的(只能在类内访问,不能在子类中访问)。相同的前缀,具有相同的意义,可以应用于方法和变量。
(3)将enterFrame方法添加到managers.py:

请注意,enterFrame的实现只(同步地)抓取一帧,而来自通道的实际检索被推迟到frame变量的后续读取。
(4)接下来,把exitFrame方法添加到managers.py:

exitFrame的实现从当前通道获取图像,估计帧率,通过窗口管理器(如果有的话)显示图像,并完成将图像写入文件的所有挂起请求。
(5)其他几种方法也适用于文件的写入。将下列名为writeImage、startWriting Video和stopWritingVideo的公共方法的实现添加到managers.py:


上述方法只更新了文件写入操作的参数,实际的写入操作被推迟到exitFrame的下一次调用。
(6)在本节的前面,我们看到exitFrame调用了一个名为_writeVideoFrame的辅助方法。把下面的_writeVideoFrame实现添加到managers.py:

上述方法创建或添加视频文件的方式应该与之前的脚本相似(请参考2.2.4节)。但是,在帧率未知的情况下,我们在捕获会话开始时,跳过一些帧,这样就有时间构建帧率的估计。
我们对CaptureManager的实现就结束了。尽管CaptureManager的实现依赖于VideoCapture,我们可以完成不使用OpenCV作为输入的其他实现。例如,我们可以创建用套接字连接实例化的子类,将其字节流解析为图像流。另外,我们还可以使用第三方摄像头库创建子类,并提供与OpenCV不同的硬件支持。但是,对于Cameo,当前的实现就足够了。