溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

怎么使用Python+OpenCV讀寫視頻

發(fā)布時間:2022-08-08 11:31:12 來源:億速云 閱讀:128 作者:iii 欄目:開發(fā)技術(shù)

本文小編為大家詳細(xì)介紹“怎么使用Python+OpenCV讀寫視頻”,內(nèi)容詳細(xì),步驟清晰,細(xì)節(jié)處理妥當(dāng),希望這篇“怎么使用Python+OpenCV讀寫視頻”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學(xué)習(xí)新知識吧。

讀視頻,提取幀

接口函數(shù):cv2.VideoCapture()

通過video_capture = cv2.VideoCapture(video_path)可以獲取讀取視頻的句柄。而后再通過flag, frame = video_capture.read()可以讀取當(dāng)前幀,flag表示讀取是否成功,讀取成功后,句柄會自動移動到下一幀的位置。讀取結(jié)束后使用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()

獲取視頻信息

為了能更好更靈活地了解并讀取視頻,我們有時候需要獲取視頻的一些信息,比如幀率,總幀數(shù)等等。獲取這些信息的方法是調(diào)用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) # ==> 總幀數(shù)
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)        # ==> 設(shè)置句柄位置
pos = video_capture.get(cv2.CAP_PROP_POS_FRAMES)        # ==> 此時 pos = 1000.0

video_capture.release()

句柄位置指的是下一次調(diào)用read()方法讀取到的幀號,幀號索引從0開始。

使用set(cv2.CAP_PROP_POS_FRAMES)讀取指定幀

從上面代碼中可以看到我們使用了set方法來設(shè)置句柄的位置,這個功能在讀取指定幀時很有用,這樣我們不必非要使用read()遍歷到指定位置。

但問題來了,這種方式讀取到的內(nèi)容和read()遍歷讀取到的內(nèi)容是否完全相同?

做個簡單的實驗,下面用兩種方法分別讀取同一個視頻的[100, 200)幀,然后檢查讀取的內(nèi)容是否完全相同,結(jié)果是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()設(shè)置位置,然后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

讀取函數(shù)(重點)

上面我們使用了兩種方法讀取視頻幀,第一種是使用read()進行暴力遍歷,第二種是使用set()設(shè)置幀號,再使用read()讀取。兩種方法讀取到的結(jié)果完全一樣,但是效率在不同的情況下各有優(yōu)勢,所以為了最大化發(fā)揮兩者的優(yōu)勢,在寫讀取幀函數(shù)時,就要把兩種方式都寫進去,由參數(shù)來決定使用哪種模式,這樣用戶可以針對電腦的硬件做一些簡單實驗后自行決定。

# -*- 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)

將圖像寫為視頻

寫視頻沒有那么多需要注意的地方,主要使用的接口函數(shù)是cv2.VideoWriter(video_path, fourcc, fps, size),該函數(shù)的主要注意點是入?yún)⒌脑O(shè)置,video_path是輸出視頻的文件名,fps是幀率,size是視頻的寬高,待寫入視頻的圖像的尺寸必需與size一致。其中不太容易理解的是與視頻編碼相關(guān)的fourcc,該參數(shù)的設(shè)置需要使用另外一個接口函數(shù):cv2.VideoWriter_fourcc(c1, c2, c3, c4),c1-c4分別是四個字符。

示例

因為獲取圖像的方式多種多樣,而寫視頻又比較簡單,所以不太適合將這部分寫成函數(shù),下面以一個例子呈現(xiàn)。

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有時候需要多嘗試一下,因為不同電腦里安裝的編解碼器可能不太一樣,不見得隨便設(shè)置一個參數(shù)就一定能成功,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

上表中的后綴名似乎并不需要嚴(yán)格遵守。

讀到這里,這篇“怎么使用Python+OpenCV讀寫視頻”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領(lǐng)會,如果想了解更多相關(guān)內(nèi)容的文章,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI