![OpenCV 4机器学习算法原理与编程实战](https://wfqqreader-1252317822.image.myqcloud.com/cover/189/37669189/b_37669189.jpg)
2.2 基本视频操作
如果说单张图像是二维数据,则视频是三维数据,它增加了时间维度z,单张图像与视频序列如图2-18所示。在机器视觉和机器学习结合的许多应用中都需要处理视频,本节将介绍OpenCV的基本视频操作。
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-72-2.jpg?sign=1739287046-q5lD6LB8dGtUX0BMsyutwEOQSbpTo9dw-0-6311c8344aa0a7c51942194719198b77)
图2-18 单张图像与视频序列
2.2.1 读取和播放视频文件
1. 实现方法
使用OpenCV读取和播放视频文件几乎与显示图像一样简单,只需设置一个循环结构,每次循环读取视频文件中的一帧图像用于显示即可,当然还需要设置退出循环的条件。读取和播放视频文件如示例代码2-6所示。
示例代码2-6 读取和播放视频文件
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-73-1.jpg?sign=1739287046-2uWhdqLkPBRar59li7ROB3duFIEACXhC-0-41c52ef2fa58cb71a281695251c08d71)
2. 代码分析
首先,实例化一个视频控制类函数cv::VideoCapture的对象cap,用于读取和操作视频文件:
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-73-2.jpg?sign=1739287046-HAIDUcRAmGNV9IHCg4Kezklp6fnIBCP0-0-0365be6d948e89a6435590278a0ccf10)
其次,使用cap.open方法从源文件所在目录读取视频文件bike.avi:
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-73-3.jpg?sign=1739287046-J77EyeJulLGw7NT3dGzwvCviIKRehPb8-0-78929a72ad7d10e4f3fd831e7fc126f4)
然后,创建一个用于从视频文件中读取单帧图像的cv::Mat变量frame:
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-73-4.jpg?sign=1739287046-I4OtdyNNyPkfjyU77FBAcnt3nB2UDIUJ-0-2d5ab77486bf34c2ae2fc9fce943a0af)
接下来是关键部分,创建for循环从视频文件中依次读取单帧图像并存储在frame中:
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-73-5.jpg?sign=1739287046-CPHkIWpJ9IjrsTqteRpS3A7Ydx8o3poU-0-3ce1f93ce87b41d07523c8fea13a276b)
最后,当读取的frame是空图像时,即已读取到视频文件的最后一帧,退出循环:
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-74-1.jpg?sign=1739287046-lyLrWjvCnsJ5laPBRNjbA7im9e6ezRLx-0-33c1a597563fd802bfaa5594257c5725)
如果读取的frame有数据,则显示frame:
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-74-2.jpg?sign=1739287046-y6bVUZhBL8Hze6vaekGzXnEzQ7fdc2zX-0-ba0ede3a90dece82f010a1702c766a67)
另外,增加一个退出机制,即在播放过程中当有按键被按下时,cv::waitKey函数将返回按键的ASCII码(>0),并退出循环,停止播放,否则等待33ms后继续读取下一帧图像:
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-74-3.jpg?sign=1739287046-C7YNarQ3DtdAyNHUCCi407CCrA7bFCVU-0-aef1247be60f0172da1d7286b6760bbe)
当然,也可以使用while循环控制视频文件的播放。
3. 运行结果
示例代码2-6的运行结果如图2-19所示,视频中的小女孩骑车从左至右经过画面。
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-74-4.jpg?sign=1739287046-29FCchEGKWAW44TJrtsd08jxQ94r7kgf-0-2ea986ad8fcc4b8c420060901b0b7cf3)
图2-19 示例代码2-6的运行结果
2.2.2 处理视频文件
1. 实现方法
下面通过例子展示如何使用OpenCV调用摄像头并处理视频文件。如果计算机自带摄像头或已将USB摄像头连接至计算机,则只需将示例代码2-7中的cap.open("bike.avi")改为cap.open(0)即可打开摄像头。
示例代码2-7 调用摄像头并处理视频
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-75-1.jpg?sign=1739287046-0SBN0Eu3JTh3mRlhVvGyFzh0K4mofpTu-0-b9c905d31a662e861323ec4b9b99045f)
2. 代码分析
首先,使用cap.open(0)方法打开默认摄像头,新建两个cv::Mat变量——frame和edge_img,分别用于存放摄像头捕获的图像与边缘检测后的图像:
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-75-2.jpg?sign=1739287046-3bTRTMdU5KZQZbMYL7mk2TtYtU0sIJNX-0-685157255ec2e0be937cd5c2bb2af7c1)
然后,与示例代码2-6一样,在循环中获取一帧图像:
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-75-3.jpg?sign=1739287046-7sK0nh7fiCHgCoVPL2T78cr5AtvLeaTb-0-bc7fab25dfdaabe9eee157b6ea3de640)
接下来,使用cv::Canny函数检测图像的边缘图像,并将其存储于edge_img变量中:
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-75-4.jpg?sign=1739287046-u2NXfo7hTtMs9fB3HC1CqmwZxWCsHxKA-0-85820ee4550e9a845673dc26b6d6d8ad)
在两个窗口中分别显示源图与边缘图像:
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-75-5.jpg?sign=1739287046-JI4FhTrhmYwWCYvKRuuSKmyhXesgsNBr-0-ab700739e0840a58d707e7817808bfea)
设置循环退出条件,当按下“ESC”、“q”或者“Q”键时退出循环:
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-76-1.jpg?sign=1739287046-i7DVPG0rRf16rwxrC9gV6pRpu4mbuYv7-0-05e7861b9ddde498582569fe2a34f105)
前面从整体上分析了示例代码2-7,下面简单介绍Canny边缘检测算法。Canny边缘检测算法结合了高低阈值计算出的两个边缘分布图,可生成最优边缘分布图。具体做法是在低阈值的边缘分布图中只保留有连续路径的边缘点,同时把这些边缘点连接到属于高阈值边缘分布图的边缘上。这样一来,高阈值分布图上的所有边缘点就都保留了下来,而低阈值分布图上的边缘点的孤立链被全部移除。这是一种很好的折中方案,只要指定适当的阈值,就能获得高质量的轮廓。这种基于两个阈值获得二值分布图的策略,称为滞后化阈值,适用于任何需要用阈值获得二值分布图的场景。
cv::Canny函数是OpenCV提供的边缘检测函数。
cv::Canny函数:
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-76-2.jpg?sign=1739287046-9l9wSaP6bMZecAISKdRJiiRJALGbtLuS-0-481710da23ac09256428db70958c8c74)
函数参数:
◎ image:8bit输入图像。
◎ edges:输出边缘图像,单通道8bit图像,尺寸与输入图像一致。
◎ threshold1:滞后阈值化的第1阈值。
◎ threshold2:滞后阈值化的第2阈值。
◎ apertureSize:Sobel操作员的孔径大小。
◎ L2gradient:标志,指示是使用更精确的L2范数计算图像梯度的大小(L2gradient =true),还是使用默认的L1范数(L2gradient = false)计算图像梯度的大小。
3. 运行结果
读取摄像头并处理示例代码2-7的运行显示结果如图2-20所示。
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-77-1.jpg?sign=1739287046-VJhN8XzBMLbMzNIpBiOmMtmEKjGGpAx7-0-733988339fff3621fac4d8ab2166a3dc)
图2-20 读取摄像头并处理示例代码2-7的运行显示结果
2.2.3 存储视频文件
1. 实现方法
使用cv::VideoWrite类可以方便地在硬盘中写入不同编码方式的视频文件。在示例代码2-7的基础上,修改代码如下。
示例代码2-8 写视频文件
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-77-2.jpg?sign=1739287046-E1Vyd0i97lBcE6dKCQ20XOTiTUW1SlJI-0-d40f2b9d5d7641b65cf8039f151079d2)
2. 代码分析
与示例代码2-7相比,在增加的代码中首先使用了cv::VideoCapture类的get方法来获取摄像头图像的宽和高:
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-78-1.jpg?sign=1739287046-rhiPQbjOXVRgeQSemb55Cqx0RelGkFQu-0-ecf8098fd5d44bf986c2c954ef0db5b3)
然后根据这些参数,实例化一个cv::VideoWriter类的对象out,并调用open方法完成对out的初始化:
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-78-2.jpg?sign=1739287046-Jtii6C5XI5fikyKxnW7jb52gsjmEyYwA-0-2bd1ae597c75370df73639d12606ab0d)
最后,在while循环中计算图像边缘后,使用cv::VideoWriter类的write方法将边缘图像edge_img一帧接一帧地写入视频文件my_video.avi中:
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-78-3.jpg?sign=1739287046-H1Kc5102xL9XZkm43c7tWUHgJ0N9Kq8p-0-2525c94c2d85c8a5cb5a86b0cc28504b)
另一种写入视频的操作如下:
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-78-4.jpg?sign=1739287046-dpqxnW10qClRPKgrLu6cTs3WQ9cXmGtB-0-cfb9c568165fa01bfe6eb18bee20be24)
cv::VideoWriter::VideoWriter函数:
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-78-5.jpg?sign=1739287046-2x3e9hUg0HbcLLENGXrxpZXbhtXXsxtF-0-d45bed5a896512f6a965f3eb2c1401ab)
函数参数:
◎ filename:输出视频文件名。
◎ fourcc:由4个字母组成,表示帧压缩的编码方式。例如:
VideoWriter::fourcc('P', 'I', 'M', '1') 表示MPEG-1编码。
VideoWriter::fourcc('D', 'I', 'V', 'X') 表示MPEG-4编码。
VideoWriter::fourcc('M', 'J', 'P', 'G') 表示主运动jpeg编码。
◎ fps:每秒传输视频帧数,简称帧率。
◎ frameSize:视频帧的宽和高。
◎ isColor:如果不为零,则编码器如预期一样编码彩色帧,否则编码灰度帧(该标记当前仅在Windows上受支持)。
3. 运行结果
在源文件的目录下生成一个my_video.avi的视频文件,它存储了边缘检测结果视频,存储的视频文件缩略图如图2-21所示。
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-79-1.jpg?sign=1739287046-siByAbiIctqLsCMW8b9EuM02lMJjX5QO-0-881a3f14d9a745bedd4ac9ad7c652542)
图2-21 存储的视频文件缩略图