Python OpenCV从摄像头流式传输-多线程,时间戳

12

我在树莓派3上运行了一个简单的python脚本。该脚本负责打开视频设备并使用MJPEG将数据流(800x600)发送到HTTP端点。当我接收此流时,我的一个树莓派核心占用100%。是否可以使用多线程运行OpenCV?

这是我的代码

import cv2
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
import time
import argparse
import socket as Socket    
camera = None  

def setUpCameraCV():
    global camera
    camera = cv2.VideoCapture(0)

class mjpgServer(BaseHTTPRequestHandler):

    ip = None
    hostname = None

    def do_GET(self):

        print('connection from:', self.address_string())

        if self.ip is None or self.hostname is None:
            self.ip, _ = 0.0.0.0
            self.hostname = Socket.gethostname()

        if self.path == '/mjpg':

            self.send_response(200)
            self.send_header('Cache-Control', 'no-cache')
            self.send_header('Pragma', 'no-cache')
            self.send_header('Connection', 'close')
            self.send_header(
                'Content-type',
                'multipart/x-mixed-replace; boundary=mjpegstream'
            )
            self.end_headers()

            while True:
                if camera:
                    ret, img = camera.read()

                else:
                    raise Exception('Error, camera not setup')

                if not ret:
                    print('no image from camera')
                    time.sleep(1)
                    continue

                ret, jpg = cv2.imencode('.jpg', img)
                
                self.end_headers()
                self.wfile.write('--mjpegstream')
                self.end_headers()

                self.send_header('Content-type', 'image/jpeg')
                self.send_header('Content-length', str(jpg.size))
                self.end_headers()
                self.wfile.write(jpg.tostring())    

def main():
    try:
        setUpCameraCV()         
        mjpgServer.ip = 0.0.0.0
        mjpgServer.hostname = Socket.gethostname()
        server = HTTPServer((ipv4, args['port']), mjpgServer)
        print("server started on {}:{}".format(Socket.gethostname(), args['port']))
        server.serve_forever()

    except KeyboardInterrupt:
        print('KeyboardInterrupt')

    server.socket.close()


if __name__ == '__main__':
    main()

另一个问题,如何在客户端(接收器)获取每一帧的时间戳,是否可能?

在此输入图片描述


1
请点击这里 - GPPK
1个回答

26

使用线程来处理I/O密集型操作(例如从网络摄像头读取帧)是一种经典的编程模型。由于使用cv2.VideoCapture().read()访问网络摄像头/相机是一个阻塞操作,我们的主程序会被挂起,直到摄像头设备读取并返回帧为止。本质上的想法是产生另一个线程来处理以并行方式抓取帧,而不是依赖单个线程(我们的“主”线程)以顺序方式抓取帧。这将允许从I/O线程连续读取帧,而我们的根线程处理当前帧。一旦根线程完成其帧的处理,它只需要从I/O线程获取当前帧,而无需等待阻塞的I/O操作。

因此,我们可以通过创建一个仅轮询新帧的新线程来提高性能,同时我们的主线程处理当前帧。有关处理多个摄像头流的实现,请查看使用OpenCV捕获多个相机流

from threading import Thread
import cv2, time
 
class VideoStreamWidget(object):
    def __init__(self, src=0):
        self.capture = cv2.VideoCapture(src)
        # 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(.01)
    
    def show_frame(self):
        # Display frames in main program
        cv2.imshow('frame', self.frame)
        key = cv2.waitKey(1)
        if key == ord('q'):
            self.capture.release()
            cv2.destroyAllWindows()
            exit(1)

if __name__ == '__main__':
    video_stream_widget = VideoStreamWidget()
    while True:
        try:
            video_stream_widget.show_frame()
        except AttributeError:
            pass

相关摄像机/IP/RTSP、FPS、视频、线程和多进程帖子

  1. Python OpenCV从摄像头流式传输-多线程,时间戳

  2. 使用OpenCV cv2.VideoCapture在Python中从IP摄像机流式传输视频

  3. 如何使用OpenCV捕获多个摄像机流?

  4. OpenCV实时流式视频捕获速度慢。如何丢弃帧或与实时同步?

  5. 使用OpenCV VideoWriter将RTSP流存储为视频文件

  6. OpenCV视频保存

  7. Python OpenCV多进程cv2.VideoCapture mp4


好奇,i)为什么要使用time.sleep(.01)? ii)从真实IP相机读取时需要吗?因为我正在开发的解决方案需要实时性,但是从更大的角度来看,这100毫秒的添加会导致很多延迟。 - Aakash Basu
2
@AakashBasu time.sleep的作用是因为相机只能轮询得这么快,如果缓冲区中没有新帧,那么没有必要以荒谬的速度轮询。我强烈推荐使用它,您需要查看IP相机规格。大多数IP相机的帧率在30-60 FPS范围内,因此更快的轮询不会有任何好处。您可以减少延迟或完全取消它,我发现延迟对我的相机很有用。 - nathancy
那么一个好的参考点是1秒/帧率?例如,在30帧每秒的相机上,您应该在1/30 = 0.033秒或更短时间内轮询吗? - Josh
@Josh 听起来差不多,我的相机只能拍摄30帧每秒。一个好主意是轮询速度比相机的容量快一点,以确保您始终获得最新的画面。每种情况都不同,所以只需进行实验以确定哪个值可以产生平滑的画面。 - nathancy
1
这段代码从未检查self.status。此外,对于capture.isOpened()的检查是错误的。它应该在进入任何循环之前检查一次。这段代码只会导致无限循环。那是不好的。 - Christoph Rackwitz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接