Python+OpenCV读写视频的方法详解

作者:拜阳 时间:2023-02-22 08:57:09 

读视频,提取帧

接口函数:cv2.VideoCapture()

通过video_capture = cv2.VideoCapture(video_path)可以获取读取视频的句柄。而后再通过flag, frame = video_capture.read()可以读取当前帧,flag表示读取是否成功,读取成功后,句柄会自动移动到下一帧的位置。读取结束后使用video_capture.release()释放句柄。

一个简单的逐帧读取的程序如下:

import cv2

video_capture = cv2.VideoCapture(video_path)
while True:
   flag, frame = video_capture.read()
   if not flag:
       break
   # do something with frame
video_capture.release()

获取视频信息

为了能更好更灵活地了解并读取视频,我们有时候需要获取视频的一些信息,比如帧率,总帧数等等。获取这些信息的方法是调用video_capture.get(PROP_ID)方法,其中PROP_ID是OpenCV定义的一些常量。

常用的信息及示例如下:

import cv2

video_path = r'D:\peppa\Muddy_Puddles.mp4'
video_capture = cv2.VideoCapture(video_path)

frame_num = video_capture.get(cv2.CAP_PROP_FRAME_COUNT) # ==> 总帧数
fps = video_capture.get(cv2.CAP_PROP_FPS)               # ==> 帧率
width = video_capture.get(cv2.CAP_PROP_FRAME_WIDTH)     # ==> 视频宽度
height = video_capture.get(cv2.CAP_PROP_FRAME_HEIGHT)   # ==> 视频高度
pos = video_capture.get(cv2.CAP_PROP_POS_FRAMES)        # ==> 句柄位置

video_capture.set(cv2.CAP_PROP_POS_FRAMES, 1000)        # ==> 设置句柄位置
pos = video_capture.get(cv2.CAP_PROP_POS_FRAMES)        # ==> 此时 pos = 1000.0

video_capture.release()

句柄位置指的是下一次调用read()方法读取到的帧号,帧号索引从0开始。

使用set(cv2.CAP_PROP_POS_FRAMES)读取指定帧

从上面代码中可以看到我们使用了set方法来设置句柄的位置,这个功能在读取指定帧时很有用,这样我们不必非要使用read()遍历到指定位置。

但问题来了,这种方式读取到的内容和read()遍历读取到的内容是否完全相同?

做个简单的实验,下面用两种方法分别读取同一个视频的[100, 200)帧,然后检查读取的内容是否完全相同,结果是True。

import cv2
import numpy as np

video_path = r'D:\peppa\Muddy_Puddles.mp4'
video_capture = cv2.VideoCapture(video_path)
cnt = -1
frames1 = []
while True:
   cnt += 1
   flag, frame = video_capture.read()
   assert flag
   if 100 <= cnt < 200:
       frames1.append(frame)
   if cnt >= 200:
       break
video_capture.release()

video_capture = cv2.VideoCapture(video_path)
frames2 = []
for i in range(100, 200):
   video_capture.set(cv2.CAP_PROP_POS_FRAMES, i)
   flag, frame = video_capture.read()
   assert flag
   frames2.append(frame)
video_capture.release()

frames1 = np.array(frames1)
frames2 = np.array(frames2)
print(np.all(frames1 == frames2))  # ==> check whether frames1 is same as frames2, result is True

接下来看看利用set读取的效率。还是利用小猪佩奇第一集做实验,这个视频共7788帧,下面分别用两种方法遍历读取视频中所有帧。第二种方法明显比第一种慢得多,所以这就很苦逼了。。。如果帧间隔比较小的话,单纯用read()进行遍历效率高;如果帧间隔比较大的话,用set()设置位置,然后read()读取效率高。

(如果给第二种方法加个判断,每隔n帧读取一次,那么效率确实会提高n倍,可以自行尝试)

import cv2
import numpy as np
import time

video_path = r'D:\peppa\Muddy_Puddles.mp4'
video_capture = cv2.VideoCapture(video_path)
t0 = time.time()
while True:
   flag, frame = video_capture.read()
   if not flag:
       break
t1 = time.time()
video_capture.release()

video_capture = cv2.VideoCapture(video_path)
t2 = time.time()
frame_num = int(video_capture.get(cv2.CAP_PROP_FRAME_COUNT))
for i in range(frame_num):
   video_capture.set(cv2.CAP_PROP_POS_FRAMES, i)
   flag, frame = video_capture.read()
   assert flag
t3 = time.time()
video_capture.release()

print(t1 - t0)  # ==> 76.3 s
print(t3 - t2)  # ==> 345.1 s

读取函数(重点)

上面我们使用了两种方法读取视频帧,第一种是使用read()进行暴力遍历,第二种是使用set()设置帧号,再使用read()读取。两种方法读取到的结果完全一样,但是效率在不同的情况下各有优势,所以为了最大化发挥两者的优势,在写读取帧函数时,就要把两种方式都写进去,由参数来决定使用哪种模式,这样用户可以针对电脑的硬件做一些简单实验后自行决定。

# -*- coding: utf-8 -*-
import os
import cv2

def _extract_frame_mode_1(video_capture, frame_list, root_folder, ext='png'):
   """
   extract video frames and save them to disk. this method will go through all
   the frames using video_capture.read()

Parameters:
   -----------
   video_capture: obtained by cv2.VideoCapture()
   frame_list: list
       list of frame numbers
   root_folder: str
       root folder to save frames
   ext: str
       extension of filename
   """
   frame_list = sorted(frame_list)
   video_capture.set(cv2.CAP_PROP_POS_FRAMES, 0)
   cnt = -1
   index = 0
   while True:
       cnt += 1
       flag, frame = video_capture.read()
       if not flag:
           break
       if cnt == frame_list[index]:
           filename = os.path.join(root_folder, str(cnt) + '.' + ext)
           cv2.imwrite(filename, frame)
           index += 1

def _extract_frame_mode_2(video_capture, frame_list, root_folder, ext='png'):
   """
       extract video frames and save them to disk. this method will use
       video_capture.set() to locate the frame position and then use
       video_capture.read() to read

Parameters:
       -----------
       video_capture: obtained by cv2.VideoCapture()
       frame_list: list
           list of frame numbers
       root_folder: str
           root folder to save frames
       ext: str
           extension of image filename
       """
   for i in frame_list:
       video_capture.set(cv2.CAP_PROP_POS_FRAMES, i)
       flag, frame = video_capture.read()
       assert flag
       filename = os.path.join(root_folder, str(i) + '.' + ext)
       cv2.imwrite(filename, frame)

def extract_frame(video_path, increment=None, frame_list=None,
                 mode=1, ext='png'):
   """
   extract video frames and save them to disk. the root folder to save frames
   is same as video_path (without extension)

Parameters:
   -----------
   video_path: str
       video path
   increment: int of 'fps'
       increment of frame indexes
   frame_list: list
       list of frame numbers
   mode: int, 1 or 2
       1: go through all the frames using video_capture.read()
       2: use video_capture.set() to locate the frame position and then use
       video_capture.read() to read
   ext: str
       extension of image filename
   """
   video_capture = cv2.VideoCapture(video_path)
   frame_num = int(video_capture.get(cv2.CAP_PROP_FRAME_COUNT))

if increment is None:
       increment = 1
   elif increment == 'fps':
       fps = video_capture.get(cv2.CAP_PROP_FPS)
       increment = round(fps)

if frame_list is None:
       frame_list = [i for i in range(0, frame_num, increment)]

if frame_num // len(frame_list) > 5 and mode == 1:
       print("the frames to be extracted is too sparse, "
             "please consider setting mode = 2 to accelerate")

root_folder = os.path.splitext(video_path)[0]
   os.makedirs(root_folder, exist_ok=True)
   if mode == 1:
       _extract_frame_mode_1(video_capture, frame_list, root_folder, ext)
   elif mode == 2:
       _extract_frame_mode_2(video_capture, frame_list, root_folder, ext)
   video_capture.release()

if __name__ == '__main__':
   video_path = r'D:\peppa\Muddy_Puddles.mp4'
   extract_frame(video_path, increment=30, mode=2)

将图像写为视频

写视频没有那么多需要注意的地方,主要使用的接口函数是cv2.VideoWriter(video_path, fourcc, fps, size),该函数的主要注意点是入参的设置,video_path是输出视频的文件名,fps是帧率,size是视频的宽高,待写入视频的图像的尺寸必需与size一致。其中不太容易理解的是与视频编码相关的fourcc,该参数的设置需要使用另外一个接口函数:cv2.VideoWriter_fourcc(c1, c2, c3, c4),c1-c4分别是四个字符。

示例

因为获取图像的方式多种多样,而写视频又比较简单,所以不太适合将这部分写成函数,下面以一个例子呈现。

video_path = r'D:\peppa\Muddy_Puddles.avi'
root_folder = r'D:\peppa\Muddy_Puddles'

fourcc = cv2.VideoWriter_fourcc('X', 'V', 'I', 'D')
fps = 25
size = (1920, 1080)

video_writer = cv2.VideoWriter(video_path, fourcc, fps, size)
for i in range(0, 7788, 30):
   filename = os.path.join(root_folder, str(i) + '.png')
   image = cv2.imread(filename)
   video_writer.write(image)
video_writer.release()

fourcc

fourcc有时候需要多尝试一下,因为不同电脑里安装的编解码器可能不太一样,不见得随便设置一个参数就一定能成功,fourcc有非常多,比如:

paramterscodecextension
(&lsquo;P&rsquo;,&lsquo;I&rsquo;,&lsquo;M&rsquo;,&lsquo;1&rsquo;)MPEG-1avi
(&lsquo;M&rsquo;,&lsquo;J&rsquo;,&lsquo;P&rsquo;,&lsquo;G&rsquo;)motion-jpegmp4
(&lsquo;M&rsquo;,&lsquo;P&rsquo;,&lsquo;4&rsquo;,&lsquo;V&rsquo;)MPEG-4mp4
(&lsquo;X&rsquo;,&lsquo;2&rsquo;,&lsquo;6&rsquo;,&lsquo;4&rsquo;)H.264mp4
(&lsquo;M&rsquo;, &lsquo;P&rsquo;, &lsquo;4&rsquo;, &lsquo;2&rsquo;)MPEG-4.2 
(&lsquo;D&rsquo;, &lsquo;I&rsquo;, &lsquo;V&rsquo;, &lsquo;3&rsquo;) MPEG-4.3 
(&lsquo;D&rsquo;, &lsquo;I&rsquo;, &lsquo;V&rsquo;, &lsquo;X&rsquo;)MPEG-4avi
(&lsquo;U&rsquo;, &lsquo;2&rsquo;, &lsquo;6&rsquo;, &lsquo;3&rsquo;)H263 
(&lsquo;I&rsquo;, &lsquo;2&rsquo;, &lsquo;6&rsquo;, &lsquo;3&rsquo;) H263Iflv
(&lsquo;F&rsquo;, &lsquo;L&rsquo;, &lsquo;V&rsquo;, &lsquo;1&rsquo;) FLV1 
(&lsquo;X&rsquo;,&lsquo;V&rsquo;,&lsquo;I&rsquo;,&lsquo;D&rsquo;) MPEG-4avi
(&lsquo;I&rsquo;,&lsquo;4&rsquo;,&lsquo;2&rsquo;,&lsquo;0&rsquo;) YUVavi

上表中的后缀名似乎并不需要严格遵守。

来源:https://blog.csdn.net/bby1987/article/details/108923361

标签:Python,OpenCV,读写,视频
0
投稿

猜你喜欢

  • java数据库操作类演示实例分享(java连接数据库)

    2024-01-28 03:30:20
  • 使用Dajngo 通过代码添加xadmin用户和权限(组)

    2021-08-07 17:43:59
  • VUE预渲染及遇到的坑

    2023-07-02 17:08:34
  • javascript结合ajax读取txt文件内容

    2024-04-30 10:14:58
  • MySQL之批量插入的4种方案总结

    2024-01-19 16:13:11
  • 利用Python内置库实现创建命令行应用程序

    2022-04-26 03:39:19
  • 使用SQL语句,查第10-20条记录

    2008-02-19 18:34:00
  • Python中asyncore异步模块的用法及实现httpclient的实例

    2021-02-19 01:05:18
  • Python类的定义和使用详情

    2021-03-10 18:10:59
  • Python图片处理之图片采样处理详解

    2021-01-14 12:58:36
  • vue3简单封装input组件和统一表单数据详解

    2024-04-26 17:41:18
  • 使用python接入微信聊天机器人

    2022-04-08 10:07:23
  • 解密ThinkPHP3.1.2版本之独立分组功能应用

    2023-11-20 09:06:02
  • Tensorflow中的dropout的使用方法

    2021-04-22 01:21:58
  • vue3.0语法糖内的defineProps及defineEmits解析

    2024-05-09 09:25:43
  • 浅谈pandas关于查看库或依赖库版本的API原理

    2023-10-14 04:54:13
  • 利用numba让python速度提升百倍

    2022-06-12 14:31:02
  • Python命名空间的本质和加载顺序

    2022-06-30 20:56:21
  • 解决TensorFlow训练模型及保存数量限制的问题

    2022-08-06 08:22:27
  • PHP容易被忽略而出错陷阱 数字与字符串比较

    2024-05-11 10:10:08
  • asp之家 网络编程 m.aspxhome.com