播放来自文件或相机的视频
在本教程中,我将向您展示如何使用OpenCV从文件或相机/网络摄像头捕获和播放视频。尽管OpenCV没有针对视频处理进行优化,但它提供了一个简单的API来播放视频。在我们的OpenCV程序中,我们所要做的就是从视频文件或相机中提取帧(图像)并在连续循环中显示。
OpenCV与视频的一个限制是它无法捕获视频中的任何声音。因此,您必须使用另一个库(如 FFmpeg)从视频文件或麦克风捕获声音。
播放视频文件
在本节中,我将向您展示如何播放视频文件。首先,应通过将有效位置传递给视频文件来构造 VideoCapture 对象。然后,应逐帧读取视频捕获对象。最后,这些帧应按顺序显示在窗口中。以下是执行上述任务的示例 OpenCV 代码。
#include <iostream> #include <opencv2\opencv.hpp> using namespace cv; using namespace std; int main() { // 从一个媒体文件读取并创建VideoCapture对象 VideoCapture cap("D:\\project\\vs-opencv\\OpenCVDemo2\\x64\\Debug\\点云应用.mp4"); // 判断是成功打开 if (!cap.isOpened()) { cout << "读取视频失败"; cin.get(); return -1; } // 获取总fps double fps = cap.get(CAP_PROP_FPS); cout << "每秒帧率为:" << fps << endl; // 创建窗体用于播放视频 String videoWinName = "播放视频"; namedWindow(videoWinName, WINDOW_NORMAL); // 开始循环读取每一帧进行窗体 while (true) { Mat frame; bool isSuccess = cap.read(frame); // 判断是否读取成功 if (!isSuccess) { cout << "读取视频贞失败"; break; } // 显示视频贞 imshow(videoWinName, frame); // 按ESC键结束播放 if (waitKey(10) == 27) { cout << "按下了ESC键,停止播放视频"; break; } } return 0; }
将上述简单代码片段复制并粘贴到 IDE 中并运行它。请注意,您必须将代码中的“D:/My OpenCV website/一群鹿奔跑.mp4”替换为计算机中视频的有效位置。然后,您应该会看到如下图所示的窗口。
解释
让我们逐行浏览上面的代码。
//#include "stdafx.h" #include <opencv2/opencv.hpp> #include <iostream> using namespace cv; using namespace std;
这些是我们程序中使用的包含文件和命名空间。
VideoCapture cap("C:/Users/Desktop/点云应用.mp4");
这是 VideoCapture 类中为数不多的构造函数之一。此构造函数将打开视频文件并初始化 VideoCapture 对象,以便从指定文件中读取视频流。
此类的析构函数将取消分配与打开的视频文件关联的任何内存。因此,无需在程序中显式取消分配内存。
如果上一次对 VideoCapture 构造函数的调用成功,cap.isOpened() 函数将返回 true。否则它将返回 false。如果函数返回 false,程序将向控制台打印一条消息,等待任何按键并以退出代码 -1 退出程序。
在播放视频之前,必须检查 VideoCapture 对象是否已成功初始化,以避免可能的崩溃。
if (cap.isOpened() == false) { cout << "Cannot open the video file" << endl; cin.get(); //wait for any key press return -1; }
//cap.set(CAP_PROP_POS_MSEC, 300);
如果您想在中间开始视频,您可以取消注释此行。您也可以在 while 循环中调用此函数。
bool VideoCapture::set**(int propId, double value)**
可以使用 VideoCapture::set 函数更改 VideoCapture 对象的某些属性。对于支持的属性,此函数将返回 true。否则它将返回 false。
propID - 要更改的 VideoCapture 对象的属性。可以更改的最常见属性是
- CAP_PROP_POS_MSEC - 视频文件的当前位置(以毫秒为单位)
- CAP_PROP_POS_FRAMES - 接下来要捕获的帧的相对位置(从 0 开始,例如 - 0、1、2、3 等)
- value - 指定属性的新值
ouble fps = cap.get(CAP_PROP_FPS); cout << "Frames per seconds : " << fps << endl;
这2行将获取视频的帧率并在控制台中打印。您也可以在 while 循环中调用此函数。
double VideoCapture::**get(int propId) const**
此函数返回 VideoCapture 对象的指定属性的值。对于不受支持的属性,此函数将返回 0。
propId - 此参数指定要获取的 VideoCapture 对象的属性。最常见的属性是
- CAP_PROP_POS_MSEC - 视频文件的当前位置(以毫秒为单位)
- CAP_PROP_POS_FRAMES - 接下来要捕获的帧的相对位置(从 0 开始,例如 - 0、1、2、3 等)
- CAP_PROP_FRAME_WIDTH - 视频中帧的宽度
- CAP_PROP_FRAME_HEIGHT - 视频中帧的高度
- CAP_PROP_FPS - 视频的帧速率
- CAP_PROP_FOURCC - 编解码器的4个字符的代码
- CAP_PROP_FRAME_COUNT - 视频文件中的总帧数
tring window_name = "My First Video"; namedWindow(window_name, WINDOW_NORMAL); //create a window
第一行指定窗口的名称。此名称稍后将用于标识代码中的窗口。
第二行将创建一个传递窗口名称和标志WINDOW_NORMAL的窗口。使用此标志,创建的窗口将变得可重新调整大小。
第二行将在while循环中逐个读取视频文件中的帧并将其存储在*帧*中。
Mat frame; bool bSuccess = cap.read(frame); // read a new frame from video
if (bSuccess == false) { cout << "Video is completed" << endl; break; }
当没有更多的帧要从视频文件中捕获时,*cap.read(frame)* 函数将返回 false。放置此 if 块以在 bSuccess 变为 false 时中断视频文件末尾的 while 循环。
此行将在上一步中创建的窗口中显示视频文件的每一帧。
此函数后应跟 waitKey(int) 函数,以便提供足够的时间在指定的持续时间(以毫秒为单位)内在窗口中绘制和显示帧。如果不调用waitKey(int)函数,则框架将不会显示在窗口中。
waitKey 函数等待 10 毫秒。如果在指定时间之前按下了某个键,它将返回按下的键的 ASCII 值。如果该值为 27(Esc 键的 ASCII 值为 27),程序将在 if 块内执行并中断 while 循环。如果在 10 毫秒内没有按下任何键,该函数将返回 -1,程序将继续 while 循环。
imshow(window_name, frame); if (waitKey(10) == 27) { cout << "Esc key is pressed by user. Stoppig the video" << endl; break; }
总结
在上面的部分中,您已经了解到,
- 如何从文件加载视频
- 如何逐个读取视频的每一帧,并在一段时间循环中将其显示在窗口中
- 如何打破视频末尾的while循环
- 如果在视频结束前按下特定键,如何中断 while 循环
从相机/网络摄像头捕获和播放视频
在本节中,将学习如何从连接到个人计算机的相机/网络摄像头捕获和播放视频。
该程序与上述程序的主要区别在于使用VideoCapture构造函数。在此程序中,只需要将相机/网络摄像头的索引提供给 VideoCapture 对象的构造函数,而不是视频文件名。这是示例 OpenCV 代码。
#include <iostream> #include <opencv2\opencv.hpp> using namespace cv; using namespace std; int main() { // 从一个媒体文件读取并创建VideoCapture对象 VideoCapture cap(0); // 判断是成功打开 if (!cap.isOpened()) { cout << "读取视频失败"; cin.get(); return -1; } // 获取总fps double fps = cap.get(CAP_PROP_FPS); cout << "每秒帧率为:" << fps << endl; double dWidth = cap.get(CAP_PROP_FRAME_WIDTH); //获取贞的宽度 double dHeight = cap.get(CAP_PROP_FRAME_HEIGHT); //获取贞高度 cout << "贞的分辨率 : " << dWidth << " x " << dHeight << endl; // 创建窗体用于播放视频 String videoWinName = "实时采集图像"; namedWindow(videoWinName, WINDOW_NORMAL); // 开始循环读取每一帧进行窗体 while (true) { Mat frame; bool isSuccess = cap.read(frame); // 判断是否读取成功 if (!isSuccess) { cout << "读取视频贞失败"; break; } // 显示视频贞 imshow(videoWinName, frame); // 按ESC键结束播放 if (waitKey(10) == 27) { cout << "按下了ESC键,停止图像采集"; break; } } return 0; }
解释
VideoCapture cap(0);
此行将使用默认相机和默认后端初始化 VideoCapture 对象。
VideoCapture**::VideoCapture(int index)**
这是 VideoCapture 类中为数不多的构造函数之一。此构造函数初始化并打开参数索引指定的相机。
可以为参数索引传递 0 以使用连接到计算机的默认相机。如果您的计算机连接到 1 台以上的相机,则可以使用正整数作为索引。
此类的析构函数将取消分配与此对象关联的任何内存。因此,无需在程序中显式取消分配内存。
ouble dWidth = cap.get(CV_CAP_PROP_FRAME_WIDTH); //get the width of frames of the video double dHeight = cap.get(CV_CAP_PROP_FRAME_HEIGHT); //get the height of frames of the video cout << "Resolution of the video : " << dWidth << " x " << dHeight << endl;
第一行将获得相机输出帧的宽度(以像素为单位)。下一行将获得相机输出帧的高度。然后它打印相机输出的分辨率,即(宽 x 高)。
所有其他代码行与播放视频文件的第一个程序相同。