OpenCV基础(2)使用OpenCV读写视频

在OpenCV中读写视频与读写图像非常相似。视频只不过是一系列通常被称为帧的图像的集合。所以,你需要做的就是在一个视频序列中循环所有的帧,然后一次处理一帧。在这篇文章中,我们将演示如何从一个文件、一个图像序列和一个摄像头读取、显示和写入视频。我们还将研究过程中可能发生的一些错误,并帮助理解如何解决它们。

1.简单版本读写视频

让我们先看一下用于读取视频文件的代码示例。它本质上包含了从磁盘读取和显示视频的功能。随着您的深入,我们将详细讨论在这个实现中使用的函数。

请添加图片描述

(1)Python代码

import cv2 
# 创建一个vid_capture对象,在本例中,我们从文件中读取视频
vid_capture = cv2.VideoCapture('Resources/Cars.mp4')

if (vid_capture.isOpened() == False):
	print("Error opening the video file")
# 读取FPS和总帧数
else:
	# 获取帧率信息
	# 你也可以用CAP_PROP_FPS替换5
	fps = vid_capture.get(5)
	print('Frames per second : ', fps,'FPS')

	# 获取总帧数
	# 你也可以用CAP_PROP_FRAME_COUNT替换7
	frame_count = vid_capture.get(7)
	print('Frame count : ', frame_count)

while(vid_capture.isOpened()):
	# vid_capture.read()方法返回一个元组,其中第一个元素是布尔值,
	# 下一个元素是实际的视频帧。当第一个元素为True时,表示视频流包含要读取的帧。
	ret, frame = vid_capture.read()
	if ret == True:
		cv2.imshow('Frame',frame)
		# 20ms,试着增加这个值,比如50,然后观察结果
		key = cv2.waitKey(20)
		
		if key == ord('q'):
			break
	else:
		break

# 释放vid_capture对象
# 一旦视频流被完全处理或用户过早地退出循环,就释放视频捕获对象(vid_capture)并关闭窗口
vid_capture.release()
cv2.destroyAllWindows()

(2)C++代码

// Include库
#include<opencv2/opencv.hpp>
#include<iostream>

// 命名空间
using namespace std;
using namespace cv;

int main()
{
	// 初始化视频捕获对象
	VideoCapture vid_capture("Resources/Cars.mp4");
	// 如果流无效,则打印错误消息
	if (!vid_capture.isOpened())
	{
		cout << "Error opening video stream or file" << endl;
	}
	else
	{
		// 通过get()方法获取FPS和帧数
		// 你也可以用CAP_PROP_FPS替换5,它们是枚举
		int fps = vid_capture.get(5);
		cout << "Frames per second :" << fps;
		// 使用opencv内置的帧数读取方法获取帧数
		// 您也可以用CAP_PROP_FRAME_COUNT替换7,它们是枚举
		int frame_count = vid_capture.get(7);
		cout << "  Frame count :" << frame_count;
	}
	// 读取帧到最后一帧
	// isOpened()方法返回一个布尔值,用于指示视频流是否有效
	while (vid_capture.isOpened())
	{
		// 初始化frame
		Mat frame;
	    // 初始化一个布尔值来检查帧是否存在
		bool isSuccess = vid_capture.read(frame);
		// 如果有帧,就显示出来
		if(isSuccess == true)
		{
			//显示
			imshow("Frame", frame);
		}
		// 如果没帧,关闭它
		if (isSuccess == false)
		{
			cout << "Video camera is disconnected" << endl;
			break;
		}
		//连续帧之间等待20毫秒,如果按下q键就中断循环
		int key = waitKey(20);
		if (key == 'q')
		{
			cout << "q key is pressed by the user. Stopping the video" << endl;
			break;
		}
	}
	// 释放视频捕获对象
	vid_capture.release();
	destroyAllWindows();
	return 0;
}

以下是我们将在这篇博文中讨论的OpenCV视频I/O的主要功能:
(1)cv2.VideoCapture:创建视频捕捉对象,这将帮助流或显示视频。
(2)cv2.VideoWriter:将输出的视频保存到一个目录。
(3)此外,我们还讨论了其他需要的函数,如cv2.imshow()cv2.waitKey()get()方法,该方法用于读取视频元数据,如帧高,宽度,fps等。

2.读取图像序列

处理图像序列中的图像帧与处理视频流中的帧非常相似。只需指定要读取的图像文件。
在下面的例子中,

  • 继续使用视频捕捉对象
  • 但不指定视频文件,只需指定图像序列
    • 使用下面所示的符号(Cars%04d.jpg),其中%04d表示四位数序列命名约定(例如Cars0001.jpg、Cars0002.jpg、Cars0003.jpg等)。
    • 如果你已经指定了Race_Cars_%02d.jpg,那么你将寻找文件:(Race_Cars_01.jpg, Race_Cars_02.jpg, Race_Cars_03.jpg等)。

所有其他代码与第一个示例中描述的都是相同的。
(1)Python

vid_capture = cv2.VideoCapture('Resources/Image_sequence/Cars%04d.jpg')

(2)C++

VideoCapture vid_capture("Resources/Image_sequence/Cars%04d.jpg");

3.从网络摄像头读取视频

从网络摄像头读取视频流也与上面讨论的例子非常相似。这怎么可能?这都要归功于OpenCV中的视频捕捉类的灵活性,它有几个重载函数,方便地接受不同的输入参数。不需要为视频文件或图像序列指定源位置,只需给出视频捕获设备索引,如下所示。

  • 如果你的系统有一个内置的摄像头,那么摄像头的设备索引将是’ 0 '。
  • 如果您的系统连接了不止一个摄像头,那么与每个额外摄像头相关联的设备索引将递增(例如1,2等)。
    (1)Python
vid_capture = cv2.VideoCapture(0, cv2.CAP_DSHOW)

(2)C++

VideoCapture vid_capture(0);

你可能想知道CAP DSHOW。这是一个可选参数,因此不是必需的。CAP DSHOW只是另一个视频捕捉API首选项,它是通过directshow视频输入的缩写。

4.写视频

现在让我们看看如何写视频。就像读取视频一样,我们可以从任何来源(视频文件、图像序列或网络摄像头)写入视频。
为了写入视频:

  • 使用get()方法获取图像帧的高度和宽度。
  • 初始化视频捕获对象(如前几节所讨论的),使用前面描述的任何源将视频流读入内存。
  • 创建一个视频编码器对象。
  • 使用视频写入器对象将视频流保存到磁盘。

为了写视频文件,首先需要从VideoWriter()类创建一个视频编写器对象VideoWriter(filename, apiPreference, fourcc, fps, frameSize[, isColor])
VideoWriter()类接受以下参数:

  • filename:输出视频文件的路径名
  • apiPreference: API后端标识符
  • fourcc:编解码器的四字符编码,用于压缩帧(fourcc)。
  • fps:所创建视频流的帧率
  • frame_size:视频帧的大小
  • isColor:如果不为零,编码器将期望并编码彩色帧。否则它将与灰度帧一起工作(该标志目前仅在Windows上支持)。

下面的代码创建视频编写器对象,该对象由VideoWriter()类输出。一个特殊的方便函数用于检索四字符编解码器,它是视频编写器对象的第二个参数。

  • 在Python中, VideoWriter_fourcc('M', 'J', 'P', 'G')
  • 在C++中, VideoWriter::fourcc('M', 'J', 'P', 'G')

视频编解码器指定如何压缩视频流。它将未压缩的视频转换为压缩格式,反之亦然。要创建AVI或MP4格式,请使用以下fourcc规格:

  • AVI: cv2.VideoWriter_fourcc('M','J','P','G')
  • MP4: cv2.VideoWriter_fourcc(*'XVID')

接下来的两个输入参数指定帧率(FPS)和帧大小(宽度、高度)。

(1)Python

import cv2 
# 创建一个vid_capture对象,在本例中,我们从文件中读取视频
vid_capture = cv2.VideoCapture('Resources/Cars.mp4')

if (vid_capture.isOpened() == False):
	print("Error opening the video file")
# 读取FPS和总帧数
else:
	# 获取帧率信息
	# 你也可以用CAP_PROP_FPS替换5
	fps = vid_capture.get(5)
	print('Frames per second : ', fps,'FPS')

	# 获取总帧数
	# 你也可以用CAP_PROP_FRAME_COUNT替换7
	frame_count = vid_capture.get(7)
	print('Frame count : ', frame_count)
	
	# 使用get()方法获取帧大小信息
	# 在本例中,您通过指定3 (CAP_PROP_FRAME_WIDTH)和4 (CAP_PROP_FRAME_HEIGHT)
	# 来检索帧的宽度和高度。当将视频文件写入磁盘时,您将在下面进一步使用这些维度。
	
	frame_width = int(vid_capture.get(3))
	frame_height = int(vid_capture.get(4))
	frame_size = (frame_width,frame_height)
	fps = 20
	# 初始化视频写入器对象
	output = cv2.VideoWriter('Resources/output_video_from_file.avi', cv2.VideoWriter_fourcc('M','J','P','G'), 20, frame_size)

while(vid_capture.isOpened()):
	# vid_capture.read()方法返回一个元组,其中第一个元素是布尔值,
	# 下一个元素是实际的视频帧。当第一个元素为True时,表示视频流包含要读取的帧。
	ret, frame = vid_capture.read()
	if ret == True:
		# 将帧写入输出文件
		output.write(frame)
		cv2.imshow('Frame',frame)
		# 20ms,试着增加这个值,比如50,然后观察结果
		key = cv2.waitKey(20)
		
		if key == ord('q'):
			break
	else:
		break
# 释放vid_capture对象
# 一旦视频流被完全处理或用户过早地退出循环,就释放视频捕获对象(vid_capture)并关闭窗口
vid_capture.release()
cv2.destroyAllWindows()

(2)C++

// Include库
#include<opencv2/opencv.hpp>
#include<iostream>

// 命名空间
using namespace std;
using namespace cv;

int main()
{
	// 初始化视频捕获对象
	VideoCapture vid_capture("Resources/Cars.mp4");
	// 如果流无效,则打印错误消息
	if (!vid_capture.isOpened())
	{
		cout << "Error opening video stream or file" << endl;
	}
	else
	{
		// 通过get()方法获取FPS和帧数
		// 你也可以用CAP_PROP_FPS替换5,它们是枚举
		int fps = vid_capture.get(5);
		cout << "Frames per second :" << fps;
		// 使用opencv内置的帧数读取方法获取帧数
		// 您也可以用CAP_PROP_FRAME_COUNT替换7,它们是枚举
		int frame_count = vid_capture.get(7);
		cout << "  Frame count :" << frame_count;
	}
	// 使用get()方法获取帧大小信息
	// 在本例中,您通过指定3 (CAP_PROP_FRAME_WIDTH)和4 (CAP_PROP_FRAME_HEIGHT)
	// 来检索帧的宽度和高度。当将视频文件写入磁盘时,您将在下面进一步使用这些维度。
	int frame_width = static_cast<int>(vid_capture.get(3));
	int frame_height = static_cast<int>(vid_capture.get(4));
	Size frame_size(frame_width, frame_height);
	int fps = 20;
	
	// 为了写视频文件,首先需要从VideoWriter()类创建一个视频编写器对象
	VideoWriter output("Resources/output.avi", VideoWriter::fourcc('M', 'J', 'P', 'G'),frames_per_second, frame_size);
	// 读取帧到最后一帧
	// isOpened()方法返回一个布尔值,用于指示视频流是否有效
	while (vid_capture.isOpened())
	{
		// 初始化frame
		Mat frame;
	    // 初始化一个布尔值来检查帧是否存在
		bool isSuccess = vid_capture.read(frame);
		// 如果有帧,就显示出来
		if(isSuccess == true)
		{
			// 写入视频
			output.write(frame);
			//显示
			imshow("Frame", frame);
		}
		// 如果没帧,关闭它
		if (isSuccess == false)
		{
			cout << "Video camera is disconnected" << endl;
			break;
		}
		//连续帧之间等待20毫秒,如果按下q键就中断循环
		int key = waitKey(20);
		if (key == 'q')
		{
			cout << "q key is pressed by the user. Stopping the video" << endl;
			break;
		}
	}
	// 释放视频捕获对象
	vid_capture.release();
	destroyAllWindows();
	return 0;
}

4.读写视频时可能会遇到的错误

  • 视频读取
    在读取帧时,如果路径错误、文件损坏或帧丢失,它可能会抛出错误。这就是我们在while循环中使用if语句的原因。如果ret == True,可以从这一行中看出。这样,它将只在帧出现时处理它。下面是在本例中观察到的错误日志示例。它不是完整的日志,只包含关键参数。cap_gstreamer.cpp:890: error: (-2) GStreamer: unable to start pipeline in function
    • 路径错误
      当您提供了一个错误的视频路径时,videoCapture()类将不会显示任何错误或警告。当您试图对视频帧进行任何操作时,问题就会出现。为此,您可以使用一个简单的if块来检查是否已经读取了视频文件,就像我们在示例中所做的那样。这应该打印以下消息。Error opening the video file
  • 视频写入文件
    在这个步骤中可能会出现各种错误。最常见的是帧大小错误和api首选项错误。如果帧大小与视频不相似,那么即使我们在输出目录中得到一个视频文件,它也将是空白的。如果您使用NumPy的shape方法来检索帧大小,请记住反向输出,因为OpenCV将返回高x宽x通道。如果它抛出一个api首选项错误,我们可能需要在videoCapture()参数中传递CAP_ANY标志。可以在webcam示例中看到,我们使用CAP_DHOW来避免生成警告。
    错误日志示例如下:

CAP_DSHOW不被传递时:[WARN:0]...cap_msmf.cpp(438) …. terminating async callback
当帧大小不正确时:cv2.error: OpenCV(4.5.2) :-1: error: (-5:Bad argument) in function VideoWriter

Overload resolution failed:

  • Can’t parse ‘frameSize’. Sequence item with index 0 has a wrong type
  • VideoWriter() missing required argument ‘frameSize’ (pos 5)
  • VideoWriter() missing required argument ‘params’ (pos 5)
  • VideoWriter() missing required argument ‘frameSize’ (pos 5)`

总结

在本博客中,您学习了使用视频捕捉对象读和显示来自三个不同来源的视频流。甚至看到了如何使用视频捕捉对象从视频流中检索重要的元数据。我们还演示了如何使用视频编写器对象将视频流写入磁盘。您可能还会发现,调整视频帧的大小也很有帮助。为此,只需修改单个图像帧。

参考目录

https://learnopencv.com/reading-and-writing-videos-using-opencv/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值