opencv拉取视频流帧时,cv2.capture.read()导致程序卡住

这是我定义的函数,当读取电脑摄像头视频流时,会导致前端卡住阻塞。直接放视频没问题,一些rtsp的视频流有时好有时坏。请问如何解决。我了解到可能原因是电脑摄像头有打开时间,然后read读不到就卡死了,如何解决呢

    def button_camera_open(self):
        if not self.timer_video.isActive():
            # 默认使用第一个本地camera
            flag1 = self.cap.open(0)
            self.cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('M', 'J', 'P', 'G'))
            if flag1 == False:
                QtWidgets.QMessageBox.warning(
                    self, u"Warning", u"打开摄像头失败", buttons=QtWidgets.QMessageBox.Ok,
                    defaultButton=QtWidgets.QMessageBox.Ok)
            else:
                self.out = cv2.VideoWriter(r'D:\videos\prediction1.avi', cv2.VideoWriter_fourcc(
                    *'MJPG'), 30, (int(self.cap.get(3)), int(self.cap.get(4))))
                self.timer_video.start(100)
                #self.pushButton_3.setDisabled(True)
                #self.pushButton.setDisabled(True)
                self.pushButton.setText(u"关闭摄像头")
        else:
            self.timer_video.stop()
            self.cap.release()
            self.out.release()
            self.label.clear()
            self.init_logo()
            #self.pushButton_3.setDisabled(False)
            #self.pushButton.setDisabled(False)
            self.pushButton.setText(u"摄像头检测"def show_video_frame(self):
        name_list1 = []
        if self.cap.isOpened():

            flag1, img1 = self.cap.read()
            if flag1:
                showimg1 = img1
                with torch.no_grad():
                    img1 = letterbox(img1, new_shape=self.opt.img_size)[0]
                    img1[0:320,0:640]=[0,0,0]
                # Convert
                # BGR to RGB, to 3x416x416
                    img1 = img1[:, :, ::-1].transpose(2, 0, 1)
                    img1 = np.ascontiguousarray(img1)
                    img1 = torch.from_numpy(img1).to(self.device)
                    img1 = img1.half() if self.half else img1.float()  # uint8 to fp16/32
                    img1 /= 255.0  # 0 - 255 to 0.0 - 1.0
                    if img1.ndimension() == 3:
                        img1 = img1.unsqueeze(0)
                # Inference
                    pred1 = self.model(img1, augment=self.opt.augment)[0]

                # Apply NMS
                    pred1 = non_max_suppression(pred1, self.opt.conf_thres, self.opt.iou_thres, classes=self.opt.classes,
                                            agnostic=self.opt.agnostic_nms)
                # Process detections
                    for i, det1 in enumerate(pred1):  # detections per image
                        if det1 is not None and len(det1):

                        # Rescale boxes from img_size to im0 size
                            det1[:, :4] = scale_boxes(
                                img1.shape[2:], det1[:, :4], showimg1.shape).round()
                        # Write results
                            for *xyxy1, conf1, cls1 in reversed(det1):
                                label1 = '%s %.2f' % (self.names[int(cls1)], conf1)
                                name_list1.append(self.names[int(cls1)])
                                print(label1)
                                plot_one_box(
                                    xyxy1, showimg1, label=label1, color=self.colors[int(cls1)], line_thickness=2)

                self.out.write(showimg1)
                show1 = cv2.resize(showimg1, (640, 480))
                self.result1 = cv2.cvtColor(show1, cv2.COLOR_BGR2RGB)
                showImage1 = QtGui.QImage(self.result1.data, self.result1.shape[1], self.result1.shape[0],
                                            QtGui.QImage.Format_RGB888)
                self.label.setPixmap(QtGui.QPixmap.fromImage(showImage1))
            else:
                self.timer_video.stop()
                self.cap.release()
                self.out.release()
                self.label.clear()

单线程当然卡死呀,你可以用多线程来解决阻塞。创建两个线程,一个用于读取视频帧并将其放入队列中,另一个用于从队列中读取视频帧并进行显示。你参考一下

import threading
import queue

class VideoCaptureThread(threading.Thread):
    def __init__(self, cap, frame_queue):
        super().__init__()
        self.cap = cap
        self.frame_queue = frame_queue
        self._stop_event = threading.Event()

    def run(self):
        while not self._stop_event.is_set():
            ret, frame = self.cap.read()
            if ret:
                self.frame_queue.put(frame)
            else:
                self.stop()

    def stop(self):
        self._stop_event.set()

class VideoDisplayThread(threading.Thread):
    def __init__(self, frame_queue, label):
        super().__init__()
        self.frame_queue = frame_queue
        self.label = label
        self._stop_event = threading.Event()

    def run(self):
        while not self._stop_event.is_set():
            try:
                frame = self.frame_queue.get(timeout=0.1)
            except queue.Empty:
                continue

            # 进行图片处理
            # ...

            # 显示图片
            # ...

    def stop(self):
        self._stop_event.set()


在button_camera_open方法中,创建队列和两个线程:

from queue import Queue

self.frame_queue = Queue()
self.capture_thread = VideoCaptureThread(self.cap, self.frame_queue)
self.display_thread = VideoDisplayThread(self.frame_queue, self.label)
self.capture_thread.start()
self.display_thread.start()


在show_video_frame方法中,不再调用cap.read()方法,而是从队列中读取视频帧:

def show_video_frame(self):
    name_list1 = []
    try:
        img1 = self.frame_queue.get(timeout=0.1)
    except queue.Empty:
        return

    # 进行图片处理
    # ...

    # 显示图片
    # ...


在button_camera_close方法中,停止两个线程:

self.capture_thread.stop()
self.display_thread.stop()


最后注意一下队列的缓冲区有限,如果处理图片的速度比读取视频帧的速度慢,则队列可能会被填满,导致读取线程阻塞。你可以在队列中保留最近的几帧视频。