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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 13:10:55