You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python多进程应用中capture_read_frame_process进程无法退出求助

问题描述

按下显示窗口的'q'键后,窗口关闭,但程序卡在capture_read_frame_process.join()语句处,读取帧的进程无法终止。

相关代码

import cv2 as cv
import multiprocessing

class Camera(object):
    def __init__(self, source):
        self._source = source
        self.image_queue = multiprocessing.Queue()

        match self._source:
            case 'cam':
                self._source_url = 0
                self._apiPref = cv.CAP_DSHOW
            case 'boysr':
                self._source_url = 'rtsps://192.168.1.1:7441/xhw7D6R7BR8NP5vg'
                self._apiPref = cv.CAP_FFMPEG
            case 'frontd':   
                self._source_url = 'rtsps://192.168.1.1:7441/EOEohGh0eoXIWf28'
                self._apiPref = cv.CAP_FFMPEG
            case 'video':
                self._source_url = 'event.mp4'
                self._apiPref = cv.CAP_FFMPEG
    
    def read_frames(self, queue, stop_read):
        self.query_source = cv.VideoCapture(self._source_url, self._apiPref)
        self.query_source.set(cv.CAP_PROP_BUFFERSIZE, 1)
        self.query_source.set(cv.CAP_PROP_FPS, 20)

        if self.query_source.isOpened():
            print('Capture object successfully started')
        else:
            raise Exception('Unable to start capture object')

        _failFrame = 0
        while not stop_read.is_set():
            _hasFrame, _query_image = self.query_source.read()
            if not _hasFrame:
                _failFrame += 1
                if _failFrame == 15:
                    raise Exception(f"Failed to grab a frame after {_failFrame} consequtive tries")
                continue
            else:
                _failFrame = 0
                queue.put(_query_image)

        self.query_source.release()

    def show_frame(self, queue, stop_read):
        while True:
            _query_image = queue.get()
            cv.imshow('autoface', _query_image)
            if cv.waitKey(1) == ord('q'):
                cv.destroyAllWindows()
                break

if __name__ == '__main__':
    print('Starting capture object')
    capture = Camera('cam')

    # Create the event to use to stop the program
    stop_read = multiprocessing.Event()

    print('Starting read_frames process')
    capture_read_frame_process = multiprocessing.Process(target=capture.read_frames, args=(capture.image_queue, stop_read,))
    capture_read_frame_process.start()
    
    print('Starting show_frames process')
    capture_show_frame_process = multiprocessing.Process(target=capture.show_frame, args=(capture.image_queue, stop_read,))
    capture_show_frame_process.start()

    capture_show_frame_process.join()   # Should complete when 'q' is typed when the cv.imshow window is open and active.

    stop_read.set()     # This should then break the loop in the read_frames function of the Camera object
    
    capture_read_frame_process.join()
    
    print('\nProgram exited')
排查指引
  • 事件触发延迟:主进程在等待显示进程结束后才设置stop_read事件,此时读取进程可能卡在cv.VideoCapture.read()的阻塞调用中,无法及时响应停止信号。
  • 队列无边界风险:未限制队列容量,读取进程可能持续写入帧导致内存占用过高;若后续调整队列大小,queue.put()可能因队列满而阻塞读取进程。
  • 资源释放不严谨:读取进程中VideoCapture的释放逻辑未做异常处理,若进程抛出异常,可能导致设备资源无法正常释放。
解决方案

1. 提前触发停止事件

修改show_frame函数,按下'q'后立即设置停止事件,让读取进程及时收到终止信号:

def show_frame(self, queue, stop_read):
    while True:
        # 处理队列空的情况,避免阻塞
        if not queue.empty():
            _query_image = queue.get()
            cv.imshow('autoface', _query_image)
        # 非阻塞监听按键
        key = cv.waitKey(1)
        if key == ord('q'):
            cv.destroyAllWindows()
            stop_read.set()  # 立即触发停止事件
            break

2. 优化读取进程的事件响应

在读取循环中优先检查停止信号,同时将资源释放放入finally块,确保异常场景下也能释放设备:

def read_frames(self, queue, stop_read):
    self.query_source = None
    try:
        self.query_source = cv.VideoCapture(self._source_url, self._apiPref)
        self.query_source.set(cv.CAP_PROP_BUFFERSIZE, 1)
        self.query_source.set(cv.CAP_PROP_FPS, 20)

        if not self.query_source.isOpened():
            raise Exception('Unable to start capture object')
        print('Capture object successfully started')

        _failFrame = 0
        while not stop_read.is_set():
            # 先检查停止信号,避免卡在read调用中
            if stop_read.is_set():
                break
            _hasFrame, _query_image = self.query_source.read()
            if not _hasFrame:
                _failFrame += 1
                if _failFrame == 15:
                    raise Exception(f"Failed to grab a frame after {_failFrame} consecutive tries")
                continue
            _failFrame = 0
            # 设置put超时,避免队列满时阻塞
            queue.put(_query_image, block=True, timeout=1)
    finally:
        if self.query_source is not None:
            self.query_source.release()

3. 限制队列容量

初始化队列时设置最大容量,避免内存溢出:

def __init__(self, source):
    self._source = source
    self.image_queue = multiprocessing.Queue(maxsize=2)  # 限制队列最多存2帧
    # ... 其余代码不变

4. 给主进程join设置超时

避免读取进程因异常无法终止时,主进程无限等待:

if __name__ == '__main__':
    # ... 其余代码不变
    capture_show_frame_process.join()
    stop_read.set()
    # 设置2秒超时,超时则强制终止进程
    capture_read_frame_process.join(timeout=2)
    if capture_read_frame_process.is_alive():
        capture_read_frame_process.terminate()
        capture_read_frame_process.join()
    print('\nProgram exited')

内容的提问来源于stack exchange,提问作者Kelvin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 10:45:53