I am creating a widget that shows a live camera feed from a machine vision camera on a custom widget derived from QLabel.
However, when I stop the live view of the feed, my program crashes silently.
When I use the debugger, the last executing line is self.ui.btn_prev_st.setText('Start')
on the button with which I stopped the feed. If I step over it, I step into the update_image()
of the CameraControl, but the application closes while the debugger is still halted.
After that, app.exec_()
seems to return and the application closes.
For image acquisition I use pymba. I call the self.cam.disarm()
function right before the setText()
, but it executes normally if I step through. (It calls gc.collect()
at the end, but that shouldn't do anything?)
So either I borked something in Qt or the pymba disarm function is the cause.
Does anybody have an idea? I provided a minimal working example.
Thanks in advance!
main.py code:
import sysimport osimport atexitfrom PySide2.QtWidgets import QApplication, QMainWindowfrom PySide2.QtCore import QFile, Signalfrom PySide2.QtUiTools import QUiLoaderfrom ui_main import Ui_mainfrom camera_control import CameraControlclass main(QMainWindow): start_acquisition_signal = Signal() stop_acquisition_signal = Signal() def __init__(self): super(main, self).__init__() self.ui = Ui_main() self.ui.setupUi(self) atexit.register(self.cleanup) self.ui.btn_prev_st.clicked.connect(self.prev_start_pushed) self.ui.btn_set_roi.clicked.connect(self.ui.camera_prev.apply_roi) self.ui.btn_reset_roi.clicked.connect(self.ui.camera_prev.reset_roi) self.start_acquisition_signal.connect(self.ui.camera_prev.start_preview) self.stop_acquisition_signal.connect(self.ui.camera_prev.stop_preview) def __del__(self): del self.ui.camera_prev def cleanup(self): del self def load_ui(self): loader = QUiLoader() path = os.path.join(os.path.dirname(__file__), "form.ui") ui_file = QFile(path) ui_file.open(QFile.ReadOnly) loader.load(ui_file, self) ui_file.close() def prev_start_pushed(self, event): if self.ui.btn_prev_st.text() != 'Stop': self.start_acquisition_signal.emit() self.ui.btn_prev_st.setText('Stop') else: self.stop_acquisition_signal.emit() self.ui.btn_prev_st.setText('Start')if __name__ == "__main__": app = QApplication([]) widget = main() widget.show() sys.exit(app.exec_())
camera_control.py:
from typing import Optionalimport cv2import numpy as npfrom pymba import Vimba, Framefrom PySide2 import QtGuifrom PySide2.QtWidgets import QLabelfrom PySide2.QtCore import Signal, Slot, Qt, QPoint, QRect, QSizefrom PySide2.QtGui import QPixmapclass CameraControl(QLabel): change_pixmap_signal = Signal(np.ndarray) def __init__(self, parent=None): super(CameraControl, self).__init__(parent) self._first_show = True # whether form is shown for the first time self.is_running = False self.change_pixmap_signal.connect(self.update_image) self.vimba = Vimba() self.vimba.startup() self.cam = self.vimba.camera(0) try: self.cam.close() except: pass self.cam.open() self.setup_camera() self.update() def __del__(self): try: self.cam.disarm() self.cam.close() except: pass del self.vimba @Slot() def stop_preview(self): self.is_running = False #self.cam.stop_frame_acquisition() self.cam.disarm() @Slot() def start_preview(self): self.cam.arm('Continuous', self.frame_handler) self.cam.start_frame_acquisition() self.is_running = True def frame_handler(self, frame: Frame, delay: Optional[int] = 1) -> None: img = frame.buffer_data_numpy() self.change_pixmap_signal.emit(img) @Slot(np.ndarray) def update_image(self, cv_img):""" Updates the image_label with a new opencv image""" qt_img = self.convert_cv_qt(cv_img) self.setPixmap(qt_img)
ui_main.py:
from PySide2.QtCore import *from PySide2.QtGui import *from PySide2.QtWidgets import *from camera_control import CameraControlclass Ui_main(object): def setupUi(self, main): if not main.objectName(): main.setObjectName(u"main") main.resize(1192, 752) self.centralwidget = QWidget(main) self.centralwidget.setObjectName(u"centralwidget") self.box_preview = QGroupBox(self.centralwidget) self.box_preview.setObjectName(u"box_preview") self.box_preview.setGeometry(QRect(10, 10, 601, 391)) self.btn_prev_st = QPushButton(self.box_preview) self.btn_prev_st.setObjectName(u"btn_prev_st") self.btn_prev_st.setGeometry(QRect(20, 20, 75, 23)) self.camera_prev = CameraControl(self.box_preview) self.camera_prev.setObjectName(u"camera_prev") self.camera_prev.setGeometry(QRect(110, 20, 480, 360)) self.camera_prev.setMinimumSize(QSize(0, 0)) self.camera_prev.setBaseSize(QSize(640, 480)) self.camera_prev.setMouseTracking(True) self.camera_prev.setFrameShape(QFrame.Panel) self.camera_prev.setScaledContents(False) self.camera_prev.setAlignment(Qt.AlignCenter) main.setCentralWidget(self.centralwidget) self.menubar = QMenuBar(main) self.menubar.setObjectName(u"menubar") self.menubar.setGeometry(QRect(0, 0, 1192, 21)) main.setMenuBar(self.menubar) self.statusbar = QStatusBar(main) self.statusbar.setObjectName(u"statusbar") main.setStatusBar(self.statusbar) self.retranslateUi(main) QMetaObject.connectSlotsByName(main) # setupUi def retranslateUi(self, main): main.setWindowTitle(QCoreApplication.translate("main", u"main", None)) self.box_preview.setTitle(QCoreApplication.translate("main", u"Preview", None)) self.btn_prev_st.setText(QCoreApplication.translate("main", u"Start", None)) # retranslateUi