How to Build GUI Barcode Reader with Qt PySide2 on Raspberry Pi
If you’re looking to build a cross-platform GUI app using Python and Qt, you have two primary options: PyQt and PySide. Both serve as Qt bindings for Python. Their primary distinction lies in their licensing: PyQt5 is available under the GPL or a commercial license, while PySide2 uses the LGPL. Given that PySide2 is officially recommended, I’ll be using it in conjunction with Dynamsoft’s Python Barcode SDK to craft a GUI barcode reader app for Raspberry Pi.
Prerequisites
-
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 crucial. We must determine how to integrate the barcode decoding API. The most straightforward approach that immediately comes to mind is to invoke the decoding method as soon as we capture 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, this approach isn’t feasible on Raspberry Pi! Barcode scanning is a CPU-intensive task. If it consumes too much time, it will block the UI thread. Therefore, we must run the UI code and barcode recognition code in separate threads. Due to the limitations imposed by Python’s GIL, using a Python thread isn’t a viable solution either. What about QThread
? QThread operates similarly to a Python thread. To assess the performance of QThread, let’s conduct a test using 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 appears that there’s no performance improvement. Thus, the most practical approach is to handle barcode decoding tasks in a separate 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.
At this point, the app is complete. The only remaining step is to request a trial license and save it to your local disk.
Afterwards, you can connect a USB webcam to Raspberry Pi, and then run the app:
python3 app.py license.txt
The GUI barcode reader app is cross-platform. It can also work on Windows
, Linux
and macOS
.
References
- https://forum.qt.io/topic/112813/installing-pyside2-on-raspberry-pi
- https://doc.qt.io/qtforpython/quickstart.html
- https://doc.qt.io/qtforpython/tutorials/basictutorial/uifiles.html
Source Code
https://github.com/yushulx/python-barcode-qrcode-reader-qt-gui