You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何用QThread+OpenCV在PyQt5 GUI中实现摄像头录像功能?

解决PyQt5 QThread结合OpenCV录像崩溃的问题

我来帮你分析下代码里的问题,以及怎么修复:

问题根源

  1. 图像格式不匹配:你在setImage槽函数里调用self.th.writer.write(image),这里的image是PyQt的QImage对象,但cv2.VideoWriter.write()只接受OpenCV的Mat格式图像,直接传递QImage会导致底层错误,程序崩溃。
  2. 线程无安全退出机制:原线程的while循环只依赖self.cap.isOpened()判断是否继续,但程序关闭时,线程还在后台运行,没有机会释放cv2.VideoCapturecv2.VideoWriter资源,这也会引发崩溃。
  3. 即使把写入逻辑放在run里,也因为没有正确终止线程,导致资源无法正常释放

修复步骤 & 完整代码

下面是修正后的代码,我会标注关键修改点:

import cv2
import sys
from PyQt5.QtWidgets import QWidget, QLabel, QApplication, QVBoxLayout
from PyQt5.QtCore import QThread, Qt, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QImage, QPixmap

class CameraThread(QThread):
    changePixmap = pyqtSignal(QImage)
    
    def __init__(self):
        super().__init__()
        self._is_running = True  # 添加线程运行控制标志

    def run(self):
        self.cap = cv2.VideoCapture(0)
        self.width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
        self.height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
        self.codec = cv2.VideoWriter_fourcc('X', 'V', 'I', 'D')
        self.writer = cv2.VideoWriter('output.avi', self.codec, 30.0, (self.width, self.height))

        # 同时检查线程运行标志和摄像头状态
        while self._is_running and self.cap.isOpened():
            ret, frame = self.cap.read()
            if ret:
                # 处理OpenCV帧,先写入录像(用原始Mat格式)
                frame = cv2.flip(frame, 1)
                self.writer.write(frame)

                # 转换为QImage用于GUI显示
                rgb_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                h, w, ch = rgb_image.shape
                bytes_per_line = ch * w
                qt_image = QImage(rgb_image.data, w, h, bytes_per_line, QImage.Format_RGB888)
                scaled_image = qt_image.scaled(640, 480, Qt.KeepAspectRatio)
                self.changePixmap.emit(scaled_image)

        # 循环退出后必须释放资源
        self.writer.release()
        self.cap.release()

    def stop(self):
        # 设置标志位让线程安全退出循环,等待线程结束
        self._is_running = False
        self.wait()

class MyApp(QWidget):
    def __init__(self):
        super(MyApp, self).__init__()
        self.title = 'Camera'
        self.initUI()

    def initUI(self):
        self.label = QLabel(self)
        lay = QVBoxLayout()
        lay.addWidget(self.label)
        self.setLayout(lay)

        self.camera_thread = CameraThread()
        self.camera_thread.changePixmap.connect(self.setImage)
        self.camera_thread.start()

        self.setWindowTitle(self.title)
        self.show()

    @pyqtSlot(QImage)
    def setImage(self, image):
        self.label.setPixmap(QPixmap.fromImage(image))
        # 移除错误的写入逻辑,录像工作交给线程处理

    def closeEvent(self, event):
        # 窗口关闭时主动停止线程,确保资源释放
        self.camera_thread.stop()
        event.accept()

def main():
    app = QApplication(sys.argv)
    ex = MyApp()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

关键修改说明

  • 新增线程运行标志_is_running:用来安全控制线程循环的退出,避免强制终止线程引发的资源泄漏。
  • 把录像写入移到线程内部:直接使用OpenCV原生的frame(Mat格式)调用writer.write(),确保格式完全匹配。
  • 添加stop方法:通过标志位让线程主动退出循环,并等待线程完全结束,保证capwriter被正确释放。
  • 重写closeEvent:在窗口关闭时触发线程停止逻辑,避免程序退出时线程还在后台运行。
  • 移除setImage里的错误写入代码:彻底避免QImage传递给OpenCV的格式错误。

这样修改后,程序就能正常显示摄像头画面并同步录像,关闭时也能安全保存文件,不会再出现崩溃问题了。

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

火山引擎 最新活动