![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=1738842894-dBia8FziCkX2wASQhUCN83H15UtYdSXf-0-60ecbfc86eec241ad511edd170ab84e7)
图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=1738842894-TVXMDY2JHhuAjujuuXyzJ9bh0sOgnf4U-0-a0263e31d8b2706216c98391a78b0bdd)
2. 代码分析
首先,实例化一个视频控制类函数cv::VideoCapture的对象cap,用于读取和操作视频文件:
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-73-2.jpg?sign=1738842894-GGjdzMGlQvXlzCYzs0tQkhw9ijBafgh5-0-e0c6550b543ecb7f0bc937ab0b658007)
其次,使用cap.open方法从源文件所在目录读取视频文件bike.avi:
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-73-3.jpg?sign=1738842894-QSsU3DgFWojTMLHjYMVCSkHMeCv4DFyp-0-7e73f66239eaa6cf47a57ac715065510)
然后,创建一个用于从视频文件中读取单帧图像的cv::Mat变量frame:
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-73-4.jpg?sign=1738842894-ZLNCDWalWTbprBEVbEXT5JWNZaG1cKw6-0-0ff62b66d2b108d2532aa077dea96cc7)
接下来是关键部分,创建for循环从视频文件中依次读取单帧图像并存储在frame中:
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-73-5.jpg?sign=1738842894-musussTVQG7tCB4lmmmbRcDKAmwMKtw4-0-8649fe3df2d349bcd45352275f5de9d1)
最后,当读取的frame是空图像时,即已读取到视频文件的最后一帧,退出循环:
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-74-1.jpg?sign=1738842894-21Y5AW7nVfMywDJ1eodV6XaIHcoOPthK-0-0f848028a9d14d672e88abf4215ed062)
如果读取的frame有数据,则显示frame:
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-74-2.jpg?sign=1738842894-6KVKCYjWSmkUUZbogjXs4gTYNgHxC6nU-0-30a5d26acc7280efcdb0f79ea0670226)
另外,增加一个退出机制,即在播放过程中当有按键被按下时,cv::waitKey函数将返回按键的ASCII码(>0),并退出循环,停止播放,否则等待33ms后继续读取下一帧图像:
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-74-3.jpg?sign=1738842894-vzIknqFktq8V6KCO76fR2WVokNSpOmrx-0-1ad6ef6adef89995d84dfca3380b37f2)
当然,也可以使用while循环控制视频文件的播放。
3. 运行结果
示例代码2-6的运行结果如图2-19所示,视频中的小女孩骑车从左至右经过画面。
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-74-4.jpg?sign=1738842894-Rzy8mHK2iDyKG9zJPFjxNCBaMHPLangD-0-f141de06e2aed571203bad18156d7387)
图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=1738842894-YYiJvykIBii3nol3ClCyw5EzPVRJARQc-0-cfacfaf556793217621563f2f48924ed)
2. 代码分析
首先,使用cap.open(0)方法打开默认摄像头,新建两个cv::Mat变量——frame和edge_img,分别用于存放摄像头捕获的图像与边缘检测后的图像:
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-75-2.jpg?sign=1738842894-CByLci9DpUbY7Wh14TXE0Qjjm3JTsg5P-0-286e33976f44ad44fd627ffc4539905d)
然后,与示例代码2-6一样,在循环中获取一帧图像:
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-75-3.jpg?sign=1738842894-5OugZAllTnIpHlYsXJlioJd2vzGUqaxj-0-2a50ac5e03fa038895f018547b85a879)
接下来,使用cv::Canny函数检测图像的边缘图像,并将其存储于edge_img变量中:
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-75-4.jpg?sign=1738842894-NxswOeEiiqcldKSvFaNAjbOJDmPj9kb5-0-85bf409e3465f6bdaed7672cc55bb336)
在两个窗口中分别显示源图与边缘图像:
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-75-5.jpg?sign=1738842894-RwDgOzPnl9TESFCywb8M9v9skTdi4S52-0-a09e2949802ff4461a75f1a5c3a713d3)
设置循环退出条件,当按下“ESC”、“q”或者“Q”键时退出循环:
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-76-1.jpg?sign=1738842894-WX3jz2fWL4Cl3jkfCYAUezO2s2a4guQa-0-28cb87547b6d4a7677b1f687fcacf244)
前面从整体上分析了示例代码2-7,下面简单介绍Canny边缘检测算法。Canny边缘检测算法结合了高低阈值计算出的两个边缘分布图,可生成最优边缘分布图。具体做法是在低阈值的边缘分布图中只保留有连续路径的边缘点,同时把这些边缘点连接到属于高阈值边缘分布图的边缘上。这样一来,高阈值分布图上的所有边缘点就都保留了下来,而低阈值分布图上的边缘点的孤立链被全部移除。这是一种很好的折中方案,只要指定适当的阈值,就能获得高质量的轮廓。这种基于两个阈值获得二值分布图的策略,称为滞后化阈值,适用于任何需要用阈值获得二值分布图的场景。
cv::Canny函数是OpenCV提供的边缘检测函数。
cv::Canny函数:
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-76-2.jpg?sign=1738842894-a1PmHMPYOiLCQmsF1gwIlwQ0RAGVu1dy-0-b5fe82310c245c3dbe117439709fb7fa)
函数参数:
◎ 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=1738842894-BzaCPiInTAYksctOUP4oKxXkFh3TC7sI-0-fe2bded692b40aace6835b26f7af9280)
图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=1738842894-82c6IxvHQgIFdloyLUtBRaeXSz44APIV-0-1355c33dbd7e22a9fe05e7c446dbd719)
2. 代码分析
与示例代码2-7相比,在增加的代码中首先使用了cv::VideoCapture类的get方法来获取摄像头图像的宽和高:
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-78-1.jpg?sign=1738842894-GtUAuHDqHmI1V9DnXVgHD1E39C32tmRS-0-e8e3afc6c0a43601b19f4500ae7ea656)
然后根据这些参数,实例化一个cv::VideoWriter类的对象out,并调用open方法完成对out的初始化:
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-78-2.jpg?sign=1738842894-tUKgXgMlN4ci8U8CYSkr83p48BEBrhmk-0-f7dd90b69146edcbcdee9219071c637b)
最后,在while循环中计算图像边缘后,使用cv::VideoWriter类的write方法将边缘图像edge_img一帧接一帧地写入视频文件my_video.avi中:
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-78-3.jpg?sign=1738842894-fKXAv8qK3hL4aYPre5IqdYuyh5SxoC8Y-0-7bf9c0eaeb6cf31855e568adf642dff6)
另一种写入视频的操作如下:
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-78-4.jpg?sign=1738842894-Ry0lLPhSdJo7jMb6aPF8FEyh55gmYUMf-0-78fe0d1bdd929562d3a1424b144262ab)
cv::VideoWriter::VideoWriter函数:
![](https://epubservercos.yuewen.com/EC2541/19938710301518806/epubprivate/OEBPS/Images/40830-00-78-5.jpg?sign=1738842894-H5gkCWUHduFa7cXNqvafk42pwhpYTZgU-0-65c646f6b6d46b8efb302be7953f1960)
函数参数:
◎ 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=1738842894-v0VDNtepPCM1n6KmK5RFC2U1tTsNcsii-0-cadbf7a3f01b9c23fe0b7768a4b98c96)
图2-21 存储的视频文件缩略图