Advanced GUI Barcode Reader for Windows, Linux, macOS and Rasberry Pi OS

Qt Designer is a Qt tool for building cross-platform graphical user interfaces (GUIs) with Qt widgets. In this article, I will use the Qt designer to create an advanced desktop barcode reader app with Dynamsoft Python Barcode SDK for Windows, Linux, macOS, and Raspberry Pi OS. The app contains all the powerful functionalities of Dynamsoft Barcode Reader.

Constructing UI with Qt Designer

The required Qt widgets include:

  • Menu Bar
    • Open file
    • Open folder
    • Save template
    • Enter license key
    • About
  • Status Bar
    • Show status information
  • List Widget
    • List loaded file names
  • Label
    • Display image or webcam frame
  • Group Box
    • Groups for camera, template and barcode type settings
  • Push Button
    • Start camera
    • Stop camera
    • Load barcode template file
    • Export barcode template file
  • Combo Box
    • Camera resolution
  • Check Box
    • Auto-stop camera view when barcode recognition is done
    • Synchronously or asynchronously display barcode results
  • Text Edit
    • Enter parameter template
    • Show barcode decoding results

Qt designer

We can save the UI layout to design.ui file and then convert it to design.py file:

pyside2-uic design.ui -o design.py

Building Cross-platform Barcode Reader in Python

Let’s build the barcode reader app step by step.

Requirements

Install dependent Python packages. For Raspberry Pi OS, we can install PySide2 via apt-get command:

python3 -m pip install opencv-python
python3 -m pip install dbr

# Windows, Linux and macOS
python3 -m pip install PySide2

# Raspberry Pi OS
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

Loading UI File

Because the UI code is ready, we can load it as follows:

'''
Usage:
   app.py <license.txt>
'''

import sys
from PySide2.QtGui import QPixmap, QImage
from PySide2.QtWidgets import QApplication, QMainWindow, QInputDialog
from PySide2.QtCore import QFile, QTimer
from PySide2.QtWidgets import *
from design import Ui_MainWindow

from barcode_manager import *
import os
import cv2
from dbr import EnumBarcodeFormat, EnumBarcodeFormat_2

class MainWindow(QMainWindow):
    def __init__(self, license):
        super(MainWindow, self).__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        
def main():
    try:
        with open(sys.argv[1]) as f:
            license = f.read()
    except:
        license = ""

    app = QApplication(sys.argv)

    window = MainWindow(license)
    window.show()

    sys.exit(app.exec_())


if __name__ == '__main__':
    print(__doc__)
    main()

We can either launch the app with a valid barcode license key or activate the SDK later via the menu bar.

The next step is to create some global variables and bind widget signals to slots:

# Initialization
self._all_data = {}
self._results = None

# Dynamsoft Barcode Reader
self._barcodeManager = BarcodeManager(license)

# Create a timer.
self.timer = None

# Open camera
self._cap = None
# self.openCamera()

# Resolution list
self.ui.comboBox.currentTextChanged.connect(self.onComboBoxChanged)

# The current path.
self._path = os.path.dirname(os.path.realpath(__file__))

# Camera button
self.ui.pushButton_open.clicked.connect(self.openCamera)
self.ui.pushButton_stop.clicked.connect(self.stopCamera)

# Load file
self.ui.actionOpen_File.triggered.connect(self.openFile)

# Load directory
self.ui.actionOpen_Folder.triggered.connect(self.openFolder)

# Export template
self.ui.actionExport_template.triggered.connect(self.exportTemplate)

# About
self.ui.actionAbout.triggered.connect(self.about)

# Set license
self.ui.actionEnter_License_Key.triggered.connect(self.setLicense)

## List widget
self.ui.listWidget.currentItemChanged.connect(self.currentItemChanged)

## Template load button
self.ui.pushButton_template.clicked.connect(self.loadTemplate)

## Template export button
self.ui.pushButton_export_template.clicked.connect(self.exportTemplate)

File Dialog for Opening and Saving Files

The essential file operations include loading an image file, loading a batch of image files from a folder, loading and saving template files.

  • Load a file:

      def openFile(self):
          filename = QFileDialog.getOpenFileName(self, 'Open File',
                                                  self._path, "Barcode images (*)")
          if filename is None or filename[0] == '':
              return
    
          filename = filename[0]
    
  • Load files from a folder:

      def openFolder(self):
          dir = QFileDialog.getExistingDirectory(self, 'Open Folder',
                                                  self._path, QFileDialog.ShowDirsOnly)
          if dir is '':
              return
    
          files = [os.path.join(dir, f) for f in os.listdir(dir) if os.path.isfile(os.path.join(dir, f))]
          if len(files) == 0:
              return
    
          for filename in files:
              self.appendFile(filename)
    
    
  • Save a file:

      def exportTemplate(self):
          filename = QFileDialog.getSaveFileName(self, 'Save File',
                                                  self._path, "Barcode Template (*.json)")
          if filename is None or filename[0] == '':
              return
    
          filename = filename[0]
    

Decoding Barcodes from JPEG, PNG, TIFF, PDF and GIF Files

If you prefer OpenCV API, you can decode barcodes like this:

frame = cv2.imread(filename)
results = self._reader.decode_buffer(frame)

However, OpenCV only supports JPEG, PNG, and TIFF. The image codec provided by Dynamsoft Python Barcode SDK also supports PDF and GIF files. To cover more image formats, we can call decode_file():

results = self._reader.decode_file(filename)

Don’t forget we need to display the image in the label widget. Therefore, one more step is to get image data from the intermediate result and then convert it to a NumPy array:

intermediate_results = self._reader.get_all_intermediate_results()

imageData =  intermediate_results[0].results[0]
buffer = imageData.bytes
width = imageData.width
height = imageData.height
stride = imageData.stride
format = imageData.image_pixel_format
channel = 3
if format == EnumImagePixelFormat.IPF_RGB_888:
    channel = 3
elif format == EnumImagePixelFormat.IPF_BINARY or format == EnumImagePixelFormat.IPF_GRAYSCALED or format == EnumImagePixelFormat.IPF_BINARYINVERTED:
    channel = 1

if format == EnumImagePixelFormat.IPF_BINARY or format == EnumImagePixelFormat.IPF_BINARYINVERTED:
    whiteValue = 1
    if format == EnumImagePixelFormat.IPF_BINARYINVERTED:
        whiteValue = 0
    
    binData = bytearray(len(buffer) << 3)
    count = 0
    for pos in range(len(buffer)):
        for bit in range(7, -1, -1):
            if (buffer[pos] >> bit) & 0x01 == whiteValue:
                binData[count] = 255
            else:
                binData[count] = 0
            
            count += 1

    frame = np.ndarray((height, width, channel), np.uint8, binData, 0, (stride << 3, channel, 1))
else:

    frame = np.ndarray((height, width, channel), np.uint8, buffer, 0, (stride, channel, 1))

Showing Webcam Stream

We trigger a timer to show the webcam stream:

def openCamera(self):

    width = 640; height = 480
    self._cap = cv2.VideoCapture(0)
    self._cap.set(cv2.CAP_PROP_FRAME_WIDTH, width)
    self._cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)

    if not self._cap.isOpened(): 
        self.showMessageBox('Error', "Failed to open camera.")
        return

    self.timer = QTimer()
    self.timer.timeout.connect(self.nextFrameUpdate)
    self.timer.start(1000./24)

def nextFrameUpdate(self):
    ret, frame = self._cap.read()
    frame, self._results = self._barcodeManager.decode_frame(frame)

    self.showResults(frame, self._results)

Creating Python Process for Real-time Barcode Scanning

Decoding barcodes is a CPU-intensive task. To avoid blocking the UI in real-time, we create a Python process for running the code:

def create_barcode_process(self):
    size = 1
    self.frameQueue = Queue(size)
    self.resultQueue = Queue(size)
    self.barcodeScanning = Process(target=process_barcode_frame, args=(self._license, self.frameQueue, self.resultQueue, self._template, self._types, self._types2))
    self.barcodeScanning.start()

Running GUI Barcode Reader

python3 app_advanced.py license.txt

Windows

GUI barcode reader on Windows

Raspberry Pi OS

GUI barcode reader on Raspberry Pi OS

Source Code

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

Search Blog Posts