如何用QThread+OpenCV在PyQt5 GUI中实现摄像头录像功能?
解决PyQt5 QThread结合OpenCV录像崩溃的问题
我来帮你分析下代码里的问题,以及怎么修复:
问题根源
- 图像格式不匹配:你在
setImage槽函数里调用self.th.writer.write(image),这里的image是PyQt的QImage对象,但cv2.VideoWriter.write()只接受OpenCV的Mat格式图像,直接传递QImage会导致底层错误,程序崩溃。 - 线程无安全退出机制:原线程的
while循环只依赖self.cap.isOpened()判断是否继续,但程序关闭时,线程还在后台运行,没有机会释放cv2.VideoCapture和cv2.VideoWriter资源,这也会引发崩溃。 - 即使把写入逻辑放在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方法:通过标志位让线程主动退出循环,并等待线程完全结束,保证cap和writer被正确释放。 - 重写
closeEvent:在窗口关闭时触发线程停止逻辑,避免程序退出时线程还在后台运行。 - 移除
setImage里的错误写入代码:彻底避免QImage传递给OpenCV的格式错误。
这样修改后,程序就能正常显示摄像头画面并同步录像,关闭时也能安全保存文件,不会再出现崩溃问题了。
内容的提问来源于stack exchange,提问作者aadi_k




