如何改进我的 python openCV 视频流?

How can I improve my python openCV video-stream?

我一直在做一个项目,我使用 raspberry pi 将实时视频发送到我的服务器。这有点管用,但不是我想要的。 问题主要是速度。现在我可以以大约 3.5 FPS 的速度发送 640x480 视频流和大约 0.5 FPS 的 1920x1080 视频流,这太糟糕了。由于我不是专业人士,我认为应该有一种方法可以改进我的代码。

发件人(Raspberry pi):

def send_stream():
    connection = True
    while connection:
        ret,frame = cap.read()
        if ret:
            # You might want to enable this while testing.
            # cv2.imshow('camera', frame)
            b_frame = pickle.dumps(frame)
            b_size = len(b_frame)
            try:
                s.sendall(struct.pack("<L", b_size) + b_frame)
            except socket.error:
                print("Socket Error!")
                connection = False

        else:
            print("Received no frame from camera, exiting.")
            exit()

接收方(服务器):

    def recv_stream(self):
        payload_size = struct.calcsize("<L")
        data = b''
        while True:
            try:
                start_time = datetime.datetime.now()
                # keep receiving data until it gets the size of the msg.
                while len(data) < payload_size:
                    data += self.connection.recv(4096)
                # Get the frame size and remove it from the data.
                frame_size = struct.unpack("<L", data[:payload_size])[0]
                data = data[payload_size:]
                # Keep receiving data until the frame size is reached.
                while len(data) < frame_size:
                    data += self.connection.recv(32768)
                # Cut the frame to the beginning of the next frame.
                frame_data = data[:frame_size]
                data = data[frame_size:]

                frame = pickle.loads(frame_data)
                frame = cv2.cvtColor(frame,cv2.COLOR_BGR2RGB)

                end_time = datetime.datetime.now()
                fps = 1/(end_time-start_time).total_seconds()
                print("Fps: ",round(fps,2))

                self.detect_motion(frame,fps)

                self.current_frame = frame

            except (socket.error,socket.timeout) as e:
                # The timeout got reached or the client disconnected. Clean up the mess.
                print("Cleaning up: ",e)
                try:
                    self.connection.close()
                except socket.error:
                    pass
                self.is_connected = False
                break

一个潜在的原因可能是 I/O 读取帧时的延迟。由于 cv2.VideoCapture().read() 是一个阻塞操作,主程序会暂停,直到从相机设备读取一帧并返回。一种提高性能的方法是生成另一个线程来处理 并行 中的抓取帧,而不是依赖单个线程按 顺序 顺序抓取帧.我们可以通过创建一个新线程来提高性能,该线程仅轮询新帧,而主线程处理 processing/graphing 最近的帧。

您当前的方法(顺序):

线程 1:抓取框架 -> 处理框架 -> 绘图

建议的方法(并行):

线程 1:抓取帧

from threading import Thread
import time

def get_frames():
    while True:
        ret, frame = cap.read()
        time.sleep(.01)

thread_frames = Thread(target=self.get_frames, args=())
thread_frames.daemon = True
thread_frames.start()

线程 2:过程框架 -> 情节

def process_frames():
    while True:
        # Grab most recent frame
        # Process/plot frame
        ...

通过使用单独的线程,您的程序将是并行的,因为总会有一个帧准备好进行处理,而不必等待帧被读入才能完成处理。

注意: 此方法将在减少 I/O 延迟的基础上提高性能。这并不是 FPS 的真正增加,因为它 延迟的显着减少 (帧始终可用于处理;我们不需要轮询相机设备并等待I/O 完成)。

在网上搜索了很久之后,我找到了一个可以将 fps 翻倍的快速解决方案(这仍然太低了:1.1 fps @1080p)。我所做的是停止使用 pickle 并改用 base64。显然酸洗图像只需要一段时间。不管怎样,这是我的新代码:

发件人(Raspberry pi):

def send_stream():
global connected
connection = True
while connection:
    if last_frame is not None:

        # You might want to uncomment these lines while testing.
        # cv2.imshow('camera', frame)
        # cv2.waitKey(1)
        frame = last_frame

        # The old pickling method.
        #b_frame = pickle.dumps(frame)

        encoded, buffer = cv2.imencode('.jpg', frame)
        b_frame = base64.b64encode(buffer)

        b_size = len(b_frame)
        print("Frame size = ",b_size)
        try:
            s.sendall(struct.pack("<L", b_size) + b_frame)
        except socket.error:
            print("Socket Error!")
            connection = False
            connected = False
            s.close()
            return "Socket Error"
    else:
        return "Received no frame from camera"

接收方(服务器):

    def recv_stream(self):
    payload_size = struct.calcsize("<L")
    data = b''
    while True:
        try:
            start_time = datetime.datetime.now()
            # keep receiving data until it gets the size of the msg.
            while len(data) < payload_size:
                data += self.connection.recv(4096)
            # Get the frame size and remove it from the data.
            frame_size = struct.unpack("<L", data[:payload_size])[0]
            data = data[payload_size:]
            # Keep receiving data until the frame size is reached.
            while len(data) < frame_size:
                data += self.connection.recv(131072)
            # Cut the frame to the beginning of the next frame.
            frame_data = data[:frame_size]
            data = data[frame_size:]

            # using the old pickling method.
            # frame = pickle.loads(frame_data)

            # Converting the image to be sent.
            img = base64.b64decode(frame_data)
            npimg = np.fromstring(img, dtype=np.uint8)
            frame = cv2.imdecode(npimg, 1)

            frame = cv2.cvtColor(frame,cv2.COLOR_BGR2RGB)

            end_time = datetime.datetime.now()
            fps = 1/(end_time-start_time).total_seconds()
            print("Fps: ",round(fps,2))
            self.detect_motion(frame,fps)

            self.current_frame = frame

        except (socket.error,socket.timeout) as e:
            # The timeout got reached or the client disconnected. Clean up the mess.
            print("Cleaning up: ",e)
            try:
                self.connection.close()
            except socket.error:
                pass
            self.is_connected = False
            break

我还增加了数据包大小,这在测试时从我的本地机器发送到我的本地机器时增加了 fps,但是这在使用 raspberry pi 时没有任何改变。

你可以在我的github上看到完整的代码:https://github.com/Ruud14/SecurityCamera