PyQt QThread视频流触发Segmentation fault,求助问题排查
问题描述
使用PyQt开发Dalsa相机图像采集与处理应用时,GUI显示相机流环节出现崩溃:参考普通摄像头适配教程修改代码后,程序启动后GUI刷新5-6帧,随即在执行self.change_pixmap_signal.emit(img)更新图像时随机触发Segmentation fault。若不向GUI发送信号,相机线程可正常处理图像,无法确定问题出在相机库还是PyQt工作流程。
相关代码:
from PyQt5.QtWidgets import QApplication, QDialog, QMainWindow, QMessageBox, QWidget, QLabel, QApplication, QVBoxLayout from PyQt5.QtCore import QThread, Qt, pyqtSignal, pyqtSlot, QObject from PyQt5.QtGui import QImage, QPixmap from PyQt5 import QtGui from PyQt5.uic import loadUi from frontend.gui import Ui_MainWindow import numpy as np import sys import pygigev import faulthandler class VideoThread(QThread): change_pixmap_signal = pyqtSignal(np.ndarray) def __init__(self): super().__init__() self._run_flag = True self.ctx = pygigev.PyGigEV() self.ctx.GevOpenCamera() self.params = self.ctx.GevGetImageParameters() self.widthFrame = self.params['width'] #1456 self.heightFrame = self.params['height'] #1088 x_offset = int(self.widthFrame / 2) y_offset = int(self.heightFrame / 2) self.ctx.GevSetImageParameters(self.widthFrame, self.heightFrame, x_offset, y_offset, self.params['pixelFormat'][0]) def run(self): self.ctx.GevInitializeImageTransfer(10) self.ctx.GevStartImageTransfer(-1) self.params = self.ctx.GevGetCameraInterfaceOptions() while self._run_flag: img = self.ctx.GevWaitForNextImage(1) if type(img) is int: if img == -6: continue else: break img = img.reshape(self.heightFrame, self.widthFrame) self.change_pixmap_signal.emit(img) # < --- Here the crash, if i detach the stream works, but not visible on the pyqt gui # shut down capture system self.ctx.GevStopImageTransfer() self.ctx.GevAbortImageTransfer() self.ctx.GevCloseCamera() def stop(self): """Sets run flag to False and waits for thread to finish""" self._run_flag = False self.wait() class Window(QMainWindow, Ui_MainWindow,QWidget): def __init__(self, parent=None): super().__init__(parent) self.setupUi(self) vbox = QVBoxLayout() vbox.addWidget(self.imageStream) # create the video capture thread self.streamThread = VideoThread() # connect its signal to the update_image slot self.streamThread.change_pixmap_signal.connect(self.update_image) # start the thread self.streamThread.start() def closeEvent(self, event): self.streamThread.stop() event.accept() @pyqtSlot(np.ndarray) def update_image(self, img): h, w = img.shape bytes_per_line = 2 * w convert_to_Qt_format = QtGui.QImage(img, w, h, bytes_per_line, QtGui.QImage.Format_Grayscale8) p = convert_to_Qt_format.scaled(self.imageStream.width(), self.imageStream.height(), Qt.KeepAspectRatio) #width 728, height 544 p = QPixmap.fromImage(p) self.imageStream.setPixmap(p) if __name__ == "__main__": app = QApplication(sys.argv) win = Window() win.show() sys.exit(app.exec())
错误信息:
Fatal Python error: Segmentation fault Thread 0x00007efceee35640 (most recent call first): File "/home/---/main.py", line 50 in run # img = self.ctx.GevWaitForNextImage(1) Thread 0x00007efd769f3440 (most recent call first): File "/home/---/main.py", line 103 in update_image # p = QPixmap.fromImage(p) File "/home/---/main.py", line 113 in <module> # sys.exit(app.exec()) Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)
排查与解决方案
修复QImage内存访问失效问题
相机线程返回的numpy数组内存可能被相机库复用或回收,GUI线程访问时内存已失效。在发送信号前复制数组,确保内存独立:# 在emit前添加copy操作 img = img.reshape(self.heightFrame, self.widthFrame).copy() self.change_pixmap_signal.emit(img)或者在创建QImage时执行深拷贝,避免直接引用原始内存:
convert_to_Qt_format = QtGui.QImage(img.data, w, h, w, QtGui.QImage.Format_Grayscale8).copy()修正像素格式与行字节数不匹配错误
Format_Grayscale8对应每个像素1字节,代码中bytes_per_line = 2 * w的设置错误,会导致内存越界。修改为:bytes_per_line = w # Grayscale8每个像素占1字节,行字节数等于图像宽度 convert_to_Qt_format = QtGui.QImage(img, w, h, bytes_per_line, QtGui.QImage.Format_Grayscale8)优化线程停止逻辑,避免资源阻塞冲突
当前stop方法仅设置_run_flag=False,但GevWaitForNextImage是阻塞调用,无法及时响应停止信号。修改stop方法,先中断图像传输再等待线程结束:def stop(self): """Sets run flag to False and waits for thread to finish""" self._run_flag = False # 中断图像传输,唤醒阻塞的GevWaitForNextImage self.ctx.GevAbortImageTransfer() self.wait()验证相机库线程安全性
确认pygigev库的API在多线程环境下是否安全,特别是GevWaitForNextImage返回的图像数据是否允许跨线程访问。若库本身线程不安全,需在相机线程内完成所有图像数据的拷贝和预处理,再传递给GUI线程。
内容的提问来源于stack exchange,提问作者DoubleShot
相关产品推荐
相关产品推荐

