如何使用 opencv 和多线程 (logitech c920) 在 python 中捕获视频

How to capture video in python with opencv and multithread (logitech c920)

我正在尝试使用 Manjaro XFCE 中的 python3.8 和 opencv(4.2.0) 每隔 Y 小时捕获一次持续时间为 X 秒的视频。我想在 720p 或 1080p 的分辨率下至少获得 20-30fps。我使用的是 Logitech c920,但具有良好的分辨率(例如 1920x1080),我以 5fps 的速度堆叠。我可以将 fps 设置为 30,但视频会缩短大约 6 倍(python 以约 5fps 的速度拍摄视频,但将其记录为 30fps)。因此,为了提高 fps,我应该使用多线程,尽管我无法在我的代码中实现我见过的任何示例。知道怎么做吗?

这是我的代码:

import numpy as np
import cv2
import time

#choose resolution and fps:

x=1920
y=1080
fps=30

# The duration in seconds of the video captured (s)
capture_duration = 5

# Lapse of time between videos (s), from beginning to beginning
vids_lapse=10

# Name of videos
data_string= time.strftime("%m-%d-%H-%M-%S")

for i in range (0,2): #Let's say I only want to do this process twice, to simplify
    cap = cv2.VideoCapture(2) #my camera is 2, but could be 0
    fourcc = cv2.VideoWriter_fourcc('M','J','P','G')

#set resolution and fps
    cap.set(3,int(x))
    cap.set(4,int(y))
    cap.set(cv2.CAP_PROP_FPS, int(fps))
#set name of video
    out = cv2.VideoWriter(data_string+"_vid"+str(i).zfill(2)+".avi",fourcc, fps, (x,y))
    start_time = time.time()
#start recording for a determined duration
    while( int(time.time() - start_time) < capture_duration+1 ):
        ret, frame = cap.read()
        if ret==True:
            #frame = cv2.flip(frame,0)
            out.write(frame)
            cv2.imshow('frame',frame)
        else:
            break

    cap.release()
    out.release()
    cv2.destroyAllWindows()
#Here I added time.sleep to wait for the next capture
    time.sleep(vids_lapse-capture_duration)

使用上面的代码,我的 30fps 视频比预期的要短,因为它不能超过 ~5fps。

我已经看到我可以使用多线程,并且我可以使用以下代码以 30fps 录制视频(至少 ffprobe 是这么说的,尽管我不确定):

from threading import Thread
import cv2, time

#again define resolution and fps
x=1920
y=1080
fps=30

class VideoStreamWidget(object):
    def __init__(self, src=2):
        self.capture = cv2.VideoCapture(src)
        self.capture.set(3,int(x))
        self.capture.set(4,int(y))
        self.capture.set(cv2.CAP_PROP_FPS, int(fps))
        # Start the thread to read frames from the video stream
        self.thread = Thread(target=self.update, args=())
        self.thread.daemon = True
        self.thread.start()

    def update(self):
        # Read the next frame from the stream in a different thread
        while True:
            if self.capture.isOpened():
                (self.status, self.frame) = self.capture.read()
            time.sleep(.03)

    def show_frame(self):
        out.write(self.frame)
        # Display frames in main program
        cv2.imshow('frame', self.frame)
        key = cv2.waitKey(1)
        if key == ord('q'):
            self.capture.release()
            out.release()
            cv2.destroyAllWindows()
            exit(1)

if __name__ == '__main__':
    fourcc = cv2.VideoWriter_fourcc('M','J','P','G')
    out = cv2.VideoWriter("output.avi",fourcc, fps, (x,y))
    video_stream_widget = VideoStreamWidget()
    while True:
        try:
            video_stream_widget.show_frame()
        except AttributeError:
            pass

第二个代码的问题是我真的不知道如何介绍我的参数(记录 5 秒,等待 10 秒,等等)或我正在使用多少线程(我可以使用 V4L2 工具从我的相机指定缓冲区的数量,不知道它有多相关)。

所以我的问题是:知道我该怎么做吗?是否可以合并这两个代码?或者有更好的方法吗?

这里有一些我已经检查过但无法在我的代码中使用的指南: https://www.pyimagesearch.com/2015/12/21/increasing-webcam-fps-with-python-and-opencv/ https://github.com/gilbertfrancois/video-capture-async

非常感谢!

干杯!

编辑

我终于有事可做了。尽管如此,即使我的视频是 30fps,它仍然看起来像 5fps 视频...似乎所有捕获的图片都几乎相同...知道如何解决这个问题吗?

这里是代码:

rom threading import Thread, Lock
import cv2, time

#choose resolution
x=1920
y=1080
fps=60

# The duration in seconds of the video captured (s)
capture_duration = 5

# Days
Days=1

# Lapse of time between videos (s), from beginning to beginning
vids_lapse=10

data_string= time.strftime("%m-%d-%H-%M-%S")

class CameraStream(object):
    def __init__(self, src=0):
        self.stream = cv2.VideoCapture(src)
        self.stream.set(3,int(x))
        self.stream.set(4,int(y))
        self.stream.set(cv2.CAP_PROP_FPS,int(fps))
        self.video_file_name = data_string+"_vid"+str(i).zfill(2)+".avi"
        (self.grabbed, self.frame) = self.stream.read()
        self.started = False
        self.read_lock = Lock()
        
        # Set up codec and output video settings
        self.codec = cv2.VideoWriter_fourcc('M','J','P','G')
        self.out = cv2.VideoWriter(self.video_file_name, self.codec, fps, (x, y))

    def start(self):
        if self.started:
            print("already started!!")
            return None
        self.started = True
        self.thread = Thread(target=self.update, args=())
        self.thread.start()
        return self

    def update(self):
        while self.started:
            (grabbed, frame) = self.stream.read()
            self.read_lock.acquire()
            self.grabbed, self.frame = grabbed, frame
            self.read_lock.release()
            # ~ time.sleep(1/fps)

    def read(self):
        self.read_lock.acquire()
        frame = self.frame.copy()
        self.read_lock.release()
        self.save_frame()
        return frame
    
    def save_frame(self):
        # Save obtained frame into video output file
        self.out.write(self.frame)

    def stop(self):
        self.started = False
        self.thread.join()
        self.stream.release()
        self.out.release()

    def __exit__(self, exc_type, exc_value, traceback):
        self.stream.release()


if __name__ == "__main__" :
    for i in range (0,2):
        start_time = time.time()
        cap = CameraStream(0).start()
        while (int(time.time() - start_time) < capture_duration):
            frame = cap.read()
            cv2.imshow('webcam', frame)
            # ~ if cv2.waitKey(1) == 27 :
        cap.stop()
        cv2.destroyAllWindows()
        time.sleep(vids_lapse-capture_duration)

问题出在 FOURCC,您应该设置 cv2.CAP_PROP_FOURCC,试试这个例子:

import cv2

HIGH_VALUE = 10000
WIDTH = HIGH_VALUE
HEIGHT = HIGH_VALUE

capture = cv2.VideoCapture(-1)

capture.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('M', 'J', 'P', 'G'))
capture.set(cv2.CAP_PROP_FRAME_WIDTH, WIDTH)
capture.set(cv2.CAP_PROP_FRAME_HEIGHT, HEIGHT)

width = int(capture.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(capture.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = capture.get(cv2.CAP_PROP_FPS)

print(width, height, fps)

while True:
    ret, frame = capture.read()
    if ret:
        cv2.imshow('frame', frame)

    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

capture.release()
cv2.destroyAllWindows()