原文:基于OpenCV的视频流处理方法 - 2020.09.05

出处:人工智能技术干货

这里是关于基于OpenCV的视频文件和摄像头画面的读写操作方法.

OpenCV提供了 VideoCapture 类和 VideoWriter 类来支持各种格式的视频流,支持的格式类型会因系统的不同而有所变化,但基本上都是支持 avi格式的,且对于视频文件和摄像头画面的读写所用到的接口基本上都相同.

1. 获取VideoCapture类实例

不管是读取视频文件还是捕获摄像头画面,都使用到了VideoCapture类,但不同的是传入的形参不一样,如果传给VideoCapture类的是一个视频文件路径那么将是读取来自视频文件的画面,而如果传给VideoCapture类的是摄像头编号那么将是读取来自摄像头的画面,其使用示例如下:

# 获取VideoCapture类实例,读取视频文件
fcap = cv2.VideoCapture('demo.mp4')
# 读取摄像头画面
ccap = cv2.VideoCapture(0)

注,查看对于摄像头编号:

ls -al /dev/ | grep video

对于输出信息以video开头的其数字后缀即为可能的摄像头编号,如果一台电脑有多个摄像头设备,那么将会出现从0开始的多个摄像头编号。

2. 判断获取VideoCapture实例是否成功

如果传入无效的视频文件或摄像头编号,那么VideoCapture类将会在后续的read()接口返回(False,None),为了避免此类事件发生,可以通过VideoCapture类的 isOpened() 接口进行判断,该接口返回一个boolean值,正常获取实例返回True,否则返回False,其使用示例如下:

# 判断是否正确获取VideoCapture类实例
while fcap.isOpened():
    # next step operation

3. 获取视频流信息

一般视频流主要的帧信息包含画面宽高还有帧率,对于视频文件,则会多出整个视频流多少帧,因此,一般主要关注这四个视频流信息即可,用到的则是VideoCapture类的get接口,其使用示例如下:

# 获取视频帧的宽
w = fcap.get(cv2.CAP_PROP_FRAME_WIDTH)
# 获取视频帧的高
h = fcap.get(cv2.CAP_PROP_FRAME_HEIGHT)
# 获取视频帧的帧率
fps = fcap.get(cv2.CAP_PROP_FPS)

获取到的帧率对于摄像头设备来说,如果所使用的终端不支持查询,那么将会返回0值,且该值也不是非常精确的。

# 获取视频流的总帧数
fcount = fcap.get(cv2.CAP_PROP_FRAME_COUNT)

注,这个是对视频文件才有意义,对于摄像头是没意义的,且以上获取到的返回信息均是浮点型的,注意转换为整型。

4. 获取帧画面

这个就相对简单,直接使用VideoCapture类的read接口即可,该接口会返回两个参数,第一个参数是读取成功与否标志位,成功为True否则为False,第二个参数则为具体的帧数据,其是一个numpy.ndarray的数组,其使用示例如下:

# 获取帧画面
success, frame = fcap.read()

但在读取过程中,有可能会存在失败的情况出现,一般是在第二次读取时放入一个while循环来保障整个读取顺利进行,如下:

# 判断读取视频流是否成功
while success:
    success, frame = fcap.read()
    # do something in here

5. 针对一组或多头摄像头特殊处理

当需要同步一组摄像头或一个多头(multihead)摄像头(例如立体摄像头或Kinect)时,read()方法就不太适用了,这时,一般使用 grab()和retrieve()方法代替它。

对于一组摄像头,其使用示例如下:

# 一组摄像头的特殊处理
success0 = fcap0.grab()
success1 = fcap1.grab()
if success0 and success1:
    frame0 = fcap0.retrieve()
    frame1 = fcap1.retrieve()

6. 跳到视频流某一帧

对于视频文件,有时候需要直接跳到某一个感兴趣的帧并从该帧开始读取数据,那么可以使用VideoCapture类的set接口,其使用示例如下:

# 跳到某一感兴趣帧并从此帧开始读取,如从第360帧开始读取
fcap.set(cv2.CAP_PROP_POS_FRAMES, 360)
success, frame = fcap.read()

7. 设置摄像头分辨率

opencv读取到的摄像头画面大小一般为默认的640x480,但这并不一定满足日常使用要求,比如摄像头支持超高清画面,那么就希望能捕获到超高清1920x1080的画面,好在VideoCapture类的set接口提供了相应的功能,其使用示例如下:

# 设置摄像头分辨率的高
fcap.set(cv2.CAP_PROP_FRAME_HEIGHT, 1080)
# 设置摄像头分辨率的宽
fcap.set(cv2.CAP_PROP_FRAME_WIDTH, 1920)

8. 获取VideoWriter类实例

不管是对视频文件的再存储还是对摄像头画面的保存,都是用到了VideoWriter类,且传入的形参意义是一致的,都需要传入保存的文件名包含视频格式、指定视频编解码器、保存视频的帧率以及保存视频的分辨率. 一般来说,保存视频的帧率最好与读入的画面的帧率一致,但需要进行估计或使用计时器才会比较准确,而分辨率则可以更改,只是要求写入的帧其大小要与分辨率保持一致,其使用示例如下:

writer = cv2.VideoWriter(
    'output.avi',
    cv2.VideoWriter_fourcc('X', 'V', 'I', 'D'), 
    30, 
    (1080, 1920))

注,必须要为VideoWriter类的构造函数传入所需的参数,且若指定的文件名已存在则会被直接覆盖.

9. VideoWriter类支持的视频编解码器

在构造VideoWriter类实例时,必须要指定视频编解码器,那么VideoWriter类都支持哪些视频编解码器呢?

通过cv2.VideoWriter_fourcc来指定具体使用的编解码器:

'I','4','2','0':该选项是一个未压缩的YUV颜色编码,兼容性好,但产生文件较大,文件扩展名为.avi
'P','T','M','I':该选项是MPEG-1编码类型,文件扩展名为.avi
'X','V','T','D':该选项是MPEG-4编码类型,得到的视频大小处于平均值,文件扩展名为.avi
'T','H','E','O':该选项是Ogg Vorbis,文件扩展名为.ogv
'F','L','V','1':该选项是一个flash视频,文件扩展名为.flv

一般常用的是 cv2.VideoWriter_fourcc('X','V','T','D'),mp4编码文件相对小一些,或cv2.VideoWriter_fourcc('I','4','2','0'),文件相对大一些,但为了缩小文件空间,可能还需要用到ffmpeg工具进一步压缩文件.

10. 保存帧数据

直接使用VideoWriter类的write接口即可,该接口一次可以保存一帧数据到指定文件中,其使用示例如下:

# 保存帧数据
writer.write(frame)

11. 释放资源

不管是VideoCapture类还是VideoWriter类,使用完了它们之后,都应该将它们释放掉,避免资源一直被占用,而这两个类都有提供了release()接口,只需直接调用即可释放资源,使用示例如下:

# 释放VideoCapture资源
fcap.release()
# 释放VideoWriter资源
writer.release()

12. 一个完整的示例代码

#!/usr/bin/python3
#!--*--coding: utf-8--*--

'''
use opencv3 to capture video frame, show and save its stream.
'''

import cv2

def stream_processing():
    # 获取VideoCapture类实例,读取视频文件
    fcap = cv2.VideoCapture('demo.mp4')

    # 设置摄像头分辨率的高
    fcap.set(cv2.CAP_PROP_FRAME_HEIGHT, 1080)
    # 设置摄像头分辨率的宽
    fcap.set(cv2.CAP_PROP_FRAME_WIDTH, 1920)
    # 跳到某一感兴趣帧并从此帧开始读取,如从第360帧开始读取
    fcap.set(cv2.CAP_PROP_POS_FRAMES, 360)

    # 获取视频帧的宽
    w = fcap.get(cv2.CAP_PROP_FRAME_WIDTH)
    # 获取视频帧的高
    h = fcap.get(cv2.CAP_PROP_FRAME_HEIGHT)
    # 获取视频帧的帧率
    fps = fcap.get(cv2.CAP_PROP_FPS)
    # 获取视频流的总帧数
    fcount = fcap.get(cv2.CAP_PROP_FRAME_COUNT)
    
    # 获取VideoWriter类实例
    writer = cv2.VideoWriter('output.avi', cv2.VideoWriter_fourcc('X', 'V', 'I', 'D'), int(fps), (int(w), int(h)))

    # 判断是否正确获取VideoCapture类实例
    while fcap.isOpened():
        # 获取帧画面
        success, frame = fcap.read()
        while success:
            cv2.imshow("demo", frame)  ## 显示画面
            # 获取帧画面
            success,frame = fcap.read()

            # 保存帧数据
            writer.write(frame)

            if (cv2.waitKey(20) & 0xff) == ord('q'):  
                ## 等待20ms并判断是按“q”退出,相当于帧率是50hz,注意waitKey只能传入整数,
                break
        # 释放VideoCapture资源
        fcap.release()
    # 释放VideoWriter资源
    writer.release()
    cv2.destroyAllWindows()  ## 销毁所有opencv显示窗口


if __name__ == "__main__":
    stream_processing()

以上就是opencv对视频文件和摄像头画面的读写操作了,比较系统全面. 这里记录学习、备忘.

Last modification:October 20th, 2020 at 03:56 pm