读视频,提取帧
接口函数:cv2.VideoCapture()
通过video_capture = cv2.VideoCapture(video_path)可以获取读取视频的句柄。而后再通过flag, frame = video_capture.read()可以读取当前帧,flag表示读取是否成功,读取成功后,句柄会自动移动到下一帧的位置。读取结束后使用video_capture.release()释放句柄。
一个简单的逐帧读取的程序如下:
1
2
3
4
5
6
7
8
9
|
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定义的一些常量。
常用的信息及示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
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倍,可以自行尝试)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
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()读取。两种方法读取到的结果完全一样,但是效率在不同的情况下各有优势,所以为了最大化发挥两者的优势,在写读取帧函数时,就要把两种方式都写进去,由参数来决定使用哪种模式,这样用户可以针对电脑的硬件做一些简单实验后自行决定。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
|
# -*- 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分别是四个字符。
示例
因为获取图像的方式多种多样,而写视频又比较简单,所以不太适合将这部分写成函数,下面以一个例子呈现。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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有非常多,比如:
paramters | codec | extension |
---|---|---|
(‘P’,‘I’,‘M’,‘1’) | MPEG-1 | avi |
(‘M’,‘J’,‘P’,‘G’) | motion-jpeg | mp4 |
(‘M’,‘P’,‘4’,‘V’) | MPEG-4 | mp4 |
(‘X’,‘2’,‘6’,‘4’) | H.264 | mp4 |
(‘M’, ‘P’, ‘4’, ‘2’) | MPEG-4.2 | |
(‘D’, ‘I’, ‘V’, ‘3’) | MPEG-4.3 | |
(‘D’, ‘I’, ‘V’, ‘X’) | MPEG-4 | avi |
(‘U’, ‘2’, ‘6’, ‘3’) | H263 | |
(‘I’, ‘2’, ‘6’, ‘3’) | H263I | flv |
(‘F’, ‘L’, ‘V’, ‘1’) | FLV1 | |
(‘X’,‘V’,‘I’,‘D’) | MPEG-4 | avi |
(‘I’,‘4’,‘2’,‘0’) | YUV | avi |
上表中的后缀名似乎并不需要严格遵守。
以上就是Python+OpenCV读写视频的方法详解的详细内容,更多关于Python OpenCV读写视频的资料请关注服务器之家其它相关文章!
原文链接:https://blog.csdn.net/bby1987/article/details/108923361