您现在的位置是:首页 > 编程语言学习 > 前端编程语言 > 文章正文 前端编程语言

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

2022-08-08 10:43:44 前端编程语言

简介这篇文章主要为大家详细介绍了Python+OpenCV进行读写视频操作的示例代码,文中的示例代码讲解详细,感兴趣的小伙伴可以动手尝试一下!读视频...

这篇文章主要为大家详细介绍了Python+OpenCV进行读写视频操作的示例代码,文中的示例代码讲解详细,感兴趣的小伙伴可以动手尝试一下!

读视频,提取帧

接口函数:cv2.VideoCapture()

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

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

  1. import cv2 
  2.  
  3. video_capture = cv2.VideoCapture(video_path) 
  4. while True: 
  5. flag, frame = video_capture.read() 
  6. if not flag: 
  7. break 
  8. # do something with frame 
  9. video_capture.release() 

获取视频信息

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

常用的信息及示例如下:

  1. import cv2 
  2.  
  3. video_path = r'D:\peppa\Muddy_Puddles.mp4' 
  4. video_capture = cv2.VideoCapture(video_path) 
  5.  
  6. frame_num = video_capture.get(cv2.CAP_PROP_FRAME_COUNT) # ==> 总帧数 
  7. fps = video_capture.get(cv2.CAP_PROP_FPS)   # ==> 帧率 
  8. width = video_capture.get(cv2.CAP_PROP_FRAME_WIDTH) # ==> 视频宽度 
  9. height = video_capture.get(cv2.CAP_PROP_FRAME_HEIGHT)   # ==> 视频高度 
  10. pos = video_capture.get(cv2.CAP_PROP_POS_FRAMES)# ==> 句柄位置 
  11.  
  12. video_capture.set(cv2.CAP_PROP_POS_FRAMES, 1000)# ==> 设置句柄位置 
  13. pos = video_capture.get(cv2.CAP_PROP_POS_FRAMES)# ==> 此时 pos = 1000.0 
  14.  
  15. video_capture.release() 

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

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

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

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

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

  1. import cv2 
  2. import numpy as np 
  3.  
  4. video_path = r'D:\peppa\Muddy_Puddles.mp4' 
  5. video_capture = cv2.VideoCapture(video_path) 
  6. cnt = -1 
  7. frames1 = [] 
  8. while True: 
  9. cnt += 1 
  10. flag, frame = video_capture.read() 
  11. assert flag 
  12. if 100 <= cnt < 200: 
  13. frames1.append(frame) 
  14. if cnt >= 200: 
  15. break 
  16. video_capture.release() 
  17.  
  18. video_capture = cv2.VideoCapture(video_path) 
  19. frames2 = [] 
  20. for i in range(100, 200): 
  21. video_capture.set(cv2.CAP_PROP_POS_FRAMES, i) 
  22. flag, frame = video_capture.read() 
  23. assert flag 
  24. frames2.append(frame) 
  25. video_capture.release() 
  26.  
  27. frames1 = np.array(frames1) 
  28. frames2 = np.array(frames2) 
  29. print(np.all(frames1 == frames2))  # ==> check whether frames1 is same as frames2, result is True 

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

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

  1. import cv2 
  2. import numpy as np 
  3. import time 
  4.  
  5. video_path = r'D:\peppa\Muddy_Puddles.mp4' 
  6. video_capture = cv2.VideoCapture(video_path) 
  7. t0 = time.time() 
  8. while True: 
  9. flag, frame = video_capture.read() 
  10. if not flag: 
  11. break 
  12. t1 = time.time() 
  13. video_capture.release() 
  14.  
  15. video_capture = cv2.VideoCapture(video_path) 
  16. t2 = time.time() 
  17. frame_num = int(video_capture.get(cv2.CAP_PROP_FRAME_COUNT)) 
  18. for i in range(frame_num): 
  19. video_capture.set(cv2.CAP_PROP_POS_FRAMES, i) 
  20. flag, frame = video_capture.read() 
  21. assert flag 
  22. t3 = time.time() 
  23. video_capture.release() 
  24.  
  25. print(t1 - t0)  # ==> 76.3 s 
  26. print(t3 - t2)  # ==> 345.1 s 

读取函数(重点)

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

  1. # -*- coding: utf-8 -*- 
  2. import os 
  3. import cv2 
  4.  
  5.  
  6. def _extract_frame_mode_1(video_capture, frame_list, root_folder, ext='png'): 
  7. ""
  8. extract video frames and save them to disk. this method will go through all 
  9. the frames using video_capture.read() 
  10.  
  11. Parameters: 
  12. ----------- 
  13. video_capture: obtained by cv2.VideoCapture() 
  14. frame_list: list 
  15. list of frame numbers 
  16. root_folder: str 
  17. root folder to save frames 
  18. ext: str 
  19. extension of filename 
  20. ""
  21. frame_list = sorted(frame_list) 
  22. video_capture.set(cv2.CAP_PROP_POS_FRAMES, 0) 
  23. cnt = -1 
  24. index = 0 
  25. while True: 
  26. cnt += 1 
  27. flag, frame = video_capture.read() 
  28. if not flag: 
  29. break 
  30. if cnt == frame_list[index]: 
  31. filename = os.path.join(root_folder, str(cnt) + '.' + ext) 
  32. cv2.imwrite(filename, frame) 
  33. index += 1 
  34.  
  35.  
  36. def _extract_frame_mode_2(video_capture, frame_list, root_folder, ext='png'): 
  37. ""
  38. extract video frames and save them to disk. this method will use 
  39. video_capture.set() to locate the frame position and then use 
  40. video_capture.read() to read 
  41.  
  42. Parameters: 
  43. ----------- 
  44. video_capture: obtained by cv2.VideoCapture() 
  45. frame_list: list 
  46. list of frame numbers 
  47. root_folder: str 
  48. root folder to save frames 
  49. ext: str 
  50. extension of image filename 
  51. ""
  52. for i in frame_list: 
  53. video_capture.set(cv2.CAP_PROP_POS_FRAMES, i) 
  54. flag, frame = video_capture.read() 
  55. assert flag 
  56. filename = os.path.join(root_folder, str(i) + '.' + ext) 
  57. cv2.imwrite(filename, frame) 
  58.  
  59.  
  60. def extract_frame(video_path, increment=None, frame_list=None, 
  61.   mode=1, ext='png'): 
  62. ""
  63. extract video frames and save them to disk. the root folder to save frames 
  64. is same as video_path (without extension) 
  65.  
  66. Parameters: 
  67. ----------- 
  68. video_path: str 
  69. video path 
  70. increment: int of 'fps' 
  71. increment of frame indexes 
  72. frame_list: list 
  73. list of frame numbers 
  74. mode: int, 1 or 2 
  75. 1: go through all the frames using video_capture.read() 
  76. 2: use video_capture.set() to locate the frame position and then use 
  77. video_capture.read() to read 
  78. ext: str 
  79. extension of image filename 
  80. ""
  81. video_capture = cv2.VideoCapture(video_path) 
  82. frame_num = int(video_capture.get(cv2.CAP_PROP_FRAME_COUNT)) 
  83.  
  84. if increment is None: 
  85. increment = 1 
  86. elif increment == 'fps'
  87. fps = video_capture.get(cv2.CAP_PROP_FPS) 
  88. increment = round(fps) 
  89.  
  90. if frame_list is None: 
  91. frame_list = [i for i in range(0, frame_num, increment)] 
  92.  
  93. if frame_num // len(frame_list) > 5 and mode == 1: 
  94. print("the frames to be extracted is too sparse, " 
  95.   "please consider setting mode = 2 to accelerate"
  96.  
  97. root_folder = os.path.splitext(video_path)[0] 
  98. os.makedirs(root_folder, exist_ok=True) 
  99. if mode == 1: 
  100. _extract_frame_mode_1(video_capture, frame_list, root_folder, ext) 
  101. elif mode == 2: 
  102. _extract_frame_mode_2(video_capture, frame_list, root_folder, ext) 
  103. video_capture.release() 
  104.  
  105.  
  106. if __name__ == '__main__'
  107. video_path = r'D:\peppa\Muddy_Puddles.mp4' 
  108. 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. video_path = r'D:\peppa\Muddy_Puddles.avi' 
  2. root_folder = r'D:\peppa\Muddy_Puddles' 
  3.  
  4. fourcc = cv2.VideoWriter_fourcc('X''V''I''D'
  5. fps = 25 
  6. size = (1920, 1080) 
  7.  
  8. video_writer = cv2.VideoWriter(video_path, fourcc, fps, size) 
  9. for i in range(0, 7788, 30): 
  10. filename = os.path.join(root_folder, str(i) + '.png'
  11. image = cv2.imread(filename) 
  12. video_writer.write(image) 
  13. 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

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

相关文章

站点信息