Ir al contenido

← GUI de cámara PySide6 para visión en Raspberry Pi

Inspección visual (visión artificial)Raspberry Pi + cámara USBUSB/V4L2HMI / Dashboard

GUI de cámara PySide6 para visión en Raspberry Pi — ejemplo completo

Crea una GUI de inspección con PySide6 en Raspberry Pi: un hilo QThread de cámara que solo emite frames con la vista en vivo visible y libera la CPU.

Programa completo y ejecutable para el Raspberry Pi + cámara USB (gui-pyside6-camera-thread.py): incluye cabecera de conexionado, requisitos y notas de integración.

Descarga el pack completo del proyecto — gratisEste ejemplo + los relacionados + lista de materiales

Vista de solo lectura.

# -*- coding: utf-8 -*-
"""
COMPLETE EXAMPLE — Multi-tab PySide6 GUI with asynchronous camera thread

Hardware:  Raspberry Pi + USB camera
Based on:  visual inspection project in production (gui.py, vision.py)

Requirements:
    pip install opencv-python PySide6 numpy

What it does — the same architecture as the shop-floor application:
    - Camera QThread: captures frames in its own thread and emits them
      as a Qt signal. The GUI never blocks waiting for the camera.
    - Key CPU saving on the Raspberry Pi: the thread ONLY emits frames
      when the tab with the live view is active (set_live_enabled).
    - 3 tabs: Operation (video + Inspect button), References
      (capture pattern) and Configuration (persisted sliders).
    Without a connected camera it generates synthetic frames: it works on any PC.

Integration with the catalog:
    - "Inspect" would call the pipeline + matching from the catalog:
      ejemplos/inspeccion-visual/opencv-silkscreen-pipeline.py
      ejemplos/inspeccion-visual/rotational-template-matching.py
    - The sliders persist in config.json and the references as PNG:
      ejemplos/inspeccion-visual/json-config-references.py
"""

import sys
import time
import cv2
import numpy as np
from PySide6.QtCore import QThread, Signal, Qt
from PySide6.QtGui import QImage, QPixmap
from PySide6.QtWidgets import (QApplication, QMainWindow, QTabWidget,
                               QWidget, QVBoxLayout, QLabel, QPushButton,
                               QSlider)


class CameraThread(QThread):
    """Capture thread: emits BGR frames only if the live view is active."""
    frame_ready = Signal(np.ndarray)

    def __init__(self, cam_index=0):
        super().__init__()
        self._running = True
        self._live = True          # the GUI toggles it when switching tabs
        self._cam_index = cam_index

    def set_live_enabled(self, enabled):
        # Active tab without video -> no frames emitted -> CPU freed
        self._live = enabled

    def stop(self):
        self._running = False
        self.wait()

    def run(self):
        cap = cv2.VideoCapture(self._cam_index)
        use_camera = cap.isOpened()
        while self._running:
            if not self._live:           # live view off:
                time.sleep(0.1)          # sleep, do not capture or emit
                continue
            if use_camera:
                ok, frame = cap.read()
                if not ok: continue
            else:                        # hardware-less demo: synthetic frame
                frame = np.full((480, 640, 3), 30, np.uint8)
                cv2.putText(frame, time.strftime("%H:%M:%S"), (200, 250),
                            cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 255, 0), 2)
                time.sleep(0.033)        # ~30 simulated fps
            self.frame_ready.emit(frame)
        cap.release()  # no-op if the camera never opened


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Visual inspection - GUI demo")

        # --- Tabs ---------------------------------------------------
        self.tabs = QTabWidget()
        self.setCentralWidget(self.tabs)
        self.tabs.addTab(self._operation_tab(), "Operation")
        self.tabs.addTab(self._references_tab(), "References")
        self.tabs.addTab(self._config_tab(), "Configuration")
        self.tabs.currentChanged.connect(self._on_tab_changed)  # live on/off

        # --- Camera thread ----------------------------------------------
        self.cam = CameraThread()
        self.cam.frame_ready.connect(self._show_frame)
        self.cam.start()

    def _operation_tab(self):
        w, layout = QWidget(), QVBoxLayout()
        self.video_label = QLabel("Waiting for camera...")
        self.video_label.setAlignment(Qt.AlignCenter)
        self.result = QLabel("--")
        self.result.setAlignment(Qt.AlignCenter)
        btn = QPushButton("Inspect part")
        btn.clicked.connect(self._inspect)
        for widget in (self.video_label, btn, self.result):
            layout.addWidget(widget)
        w.setLayout(layout)
        return w

    def _references_tab(self):
        w, layout = QWidget(), QVBoxLayout()
        btn = QPushButton("Capture reference (saves processed PNG)")
        btn.clicked.connect(lambda: print("-> save_reference(), see "
                                          "json-config-references.py"))
        layout.addWidget(btn)
        w.setLayout(layout)
        return w

    def _config_tab(self):
        w, layout = QWidget(), QVBoxLayout()
        layout.addWidget(QLabel("bg_threshold (persists in config.json)"))
        s = QSlider(Qt.Horizontal)
        s.setRange(0, 255), s.setValue(60)
        s.valueChanged.connect(lambda v: print(f"bg_threshold = {v}"))
        layout.addWidget(s)
        w.setLayout(layout)
        return w

    def _on_tab_changed(self, index):
        # Only tab 0 (Operation) shows live video
        self.cam.set_live_enabled(index == 0)

    def _show_frame(self, frame):
        """Converts the OpenCV BGR frame to QPixmap and paints it."""
        rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        h, w, ch = rgb.shape
        qimg = QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888)
        self.video_label.setPixmap(QPixmap.fromImage(qimg))

    def _inspect(self):
        # Here you would call process() + rotational_match() from the catalog
        self.result.setText("PASS (score 0.93) - demo")

    def closeEvent(self, event):
        self.cam.stop()              # stop the thread before closing
        event.accept()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = MainWindow()
    win.resize(720, 600), win.show()
    sys.exit(app.exec())
Descarga el pack completo del proyecto — gratisEste ejemplo + los relacionados + lista de materiales