How to Build GUI Barcode Reader with PySide2 on Raspberry Pi

If you want to build a cross-platform GUI app with Python and Qt, you can either use PyQt or PySide. Both of them are Qt bindings for Python. The main difference is the license: PyQt5 is released under GPL or commercial, whereas PySide2 is released under LGPL. Since PySide2 is recommended officially, I will use PySid2 and Dynamsoft Python Barcode SDK to create a GUI barcode reader app on Raspberry Pi.

Requirements

  • OpenCV Python

    python3 -m pip install opencv-python
    
  • Dynamsoft Python Barcode SDK

    python3 -m pip install dbr
    
  • PySide2

    sudo apt-get install python3-pyside2.qt3dcore python3-pyside2.qt3dinput python3-pyside2.qt3dlogic python3-pyside2.qt3drender python3-pyside2.qtcharts python3-pyside2.qtconcurrent python3-pyside2.qtcore python3-pyside2.qtgui python3-pyside2.qthelp python3-pyside2.qtlocation python3-pyside2.qtmultimedia python3-pyside2.qtmultimediawidgets python3-pyside2.qtnetwork python3-pyside2.qtopengl python3-pyside2.qtpositioning python3-pyside2.qtprintsupport python3-pyside2.qtqml python3-pyside2.qtquick python3-pyside2.qtquickwidgets python3-pyside2.qtscript python3-pyside2.qtscripttools python3-pyside2.qtsensors python3-pyside2.qtsql python3-pyside2.qtsvg python3-pyside2.qttest python3-pyside2.qttexttospeech python3-pyside2.qtuitools python3-pyside2.qtwebchannel python3-pyside2.qtwebsockets python3-pyside2.qtwidgets python3-pyside2.qtx11extras python3-pyside2.qtxml python3-pyside2.qtxmlpatterns python3-pyside2uic
    

GUI Barcode Reader for Raspberry Pi OS

Let’s get started with the UI widgets:

  • A button for loading image files:

    self.btn = QPushButton("Load an image")
    self.btn.clicked.connect(self.pickFile)
    
    def pickFile(self):
        filename = QFileDialog.getOpenFileName(self, 'Open file',
                                                self._path, "Barcode images (*)")
        if filename is None or filename[0] == '':
            self.showMessageBox("No file selected")
            return
    
    
  • A button for opening the live camera stream:

    btnCamera = QPushButton("Open camera")
    btnCamera.clicked.connect(self.openCamera)
    
    self.timer = QTimer()
    self.timer.timeout.connect(self.nextFrameUpdate)
    
    def openCamera(self):
    
        if not self._cap.isOpened(): 
            self.showMessageBox("Failed to open camera.")
            return
    
        self.timer.start(1000./24)
    
  • A button for stopping the camera stream:

    btnCamera = QPushButton("Stop camera")
    btnCamera.clicked.connect(self.stopCamera)
    
    def stopCamera(self):
        self.timer.stop()
    
  • A label for displaying the camera frames and a text area for showing the barcode decoding results:

    self.label = QLabel()
    self.label.setFixedSize(self.WINDOW_WIDTH - 30, self.WINDOW_HEIGHT - 160)
    
    self.results = QTextEdit()
    
    def showResults(self, frame, results):
        frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        image = QImage(frame, frame.shape[1], frame.shape[0], frame.strides[0], QImage.Format_RGB888)
        pixmap = QPixmap.fromImage(image)
        pixmap = self.resizeImage(pixmap)
        self.label.setPixmap(pixmap)
        self.results.setText(results)
    

We use OpenCV to read image files and capture webcam frames:

frame = cv2.imread(filename)

self._cap = cv2.VideoCapture(0)
self._cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
self._cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
ret, frame = self._cap.read()

The next step is significant. We need to consider how to integrate the barcode decoding API.

The simplest way we instantly come up with is to call the decoding method once you get a frame:

def nextFrameSlot(self):
      ret, frame = self._cap.read()

      if not ret:
          self.showMessageBox('Failed to get camera frame!')
          return

      frame, results = self._manager.decode_frame(frame)
      self.showResults(frame, results)

However, we cannot do this on Raspberry Pi! Barcode scanning is a CPU-intensive task, which will block the UI thread if it takes too much time. So we have to execute the UI code and barcode recognition code respectively in different threads. Due to the performance restriction of Python’s GIL, the Python thread is not viable either. What about QThread? QThread is implemented in the same way as Python thread. To verify the QThread performance, we can make a test with the following code:

class WorkerSignals(QObject):
    result = Signal(object)

class BarcodeManager(QThread):
    def __init__(self, license):
        super(BarcodeManager, self).__init__()
        self.signals = WorkerSignals()

        self._reader = BarcodeReader()
        self._reader.init_license(license)
        settings = self._reader.get_runtime_settings()
        settings.max_algorithm_thread_count = 1
        self._reader.update_runtime_settings(settings)
        self.frameQueue = Queue(1)

    def register_callback(self, fn):
        self.signals.result.connect(fn)

    def run(self):
        while True:
            try:
                frame = self.frameQueue.get(False, 10)

                if type(frame) is str:
                    break
            except:
                time.sleep(0.01)
                continue

            try:
                results = self._reader.decode_buffer(frame)
                self.signals.result.emit(results)

            except BarcodeReaderError as error:
                print(error)

It turns out there is no performance improvement. So the most feasible way is to do barcode decoding tasks in another Python Process:

from multiprocessing import Process, Queue
import time
import numpy as np


def process_barcode_frame(license, frameQueue, resultQueue):
    # Create Dynamsoft Barcode Reader
    reader = BarcodeReader()
    # Apply for a trial license: https://www.dynamsoft.com/customer/license/trialLicense
    reader.init_license(license)
    settings = reader.get_runtime_settings()
    settings.max_algorithm_thread_count = 1
    reader.update_runtime_settings(settings)

    while True:
        results = None

        try:
            frame = frameQueue.get(False, 10)
            if type(frame) is str:
                break
        except:
            time.sleep(0.01)
            continue

        try:
            frameHeight, frameWidth, channel = frame.shape[:3]
            results = reader.decode_buffer_manually(np.array(frame).tobytes(), frameWidth, frameHeight, frame.strides[0], EnumImagePixelFormat.IPF_RGB_888)
        except BarcodeReaderError as error:
            print(error)

        try:
            resultQueue.put(results, False, 10)
        except:
            pass

def create_decoding_process(license):
        size = 1
        frameQueue = Queue(size)
        resultQueue = Queue(size)
        barcodeScanning = Process(target=process_barcode_frame, args=(license, frameQueue, resultQueue))
        barcodeScanning.start()
        return frameQueue, resultQueue, barcodeScanning

We create two queues as the data tunnel between the two processes.

So far, the app is done. One more thing is to apply for a free trial license and save it to the local disk.

Connect a USB webcam to Raspberry Pi, and then run the app:

python3 app.py license.txt

Raspberry Pi GUI Barcode Reader

The GUI barcode reader app is cross-platform. It can also work on Windows, Linux and macOS.

References

Source Code

https://github.com/Dynamsoft/python-gui-barcode-reader

Search Blog Posts