Real-Time Barcode and QR Code Scanning with Webcam, OpenCV, and Python
Dynamsoft is the only provider of an enterprise-class Python Barcode and QR Code SDK for Windows, Linux, macOS, and Raspberry Pi OS. The SDK allows developers to quickly build robust command-line, web, and desktop applications that can scan barcodes and QR codes from a wide range of sources. In this article, we will demonstrate how to use Dynamsoft Barcode Reader, OpenCV, and a webcam to create a cross-platform desktop barcode and QR code scanner in Python.
This article is Part 7 in a 11-Part Series.
- Part 1 - Detecting and Decoding QR Codes in Python with YOLO and Dynamsoft Barcode Reader
- Part 2 - How to a GUI Barcode Reader with Qt PySide6 on Raspberry Pi
- Part 3 - Advanced GUI Python Barcode and QR Code Reader for Windows, Linux, macOS and Rasberry Pi OS
- Part 4 - Advanced QR Code Recognition: Handling Inverted Colors, Perspective Distortion, and Grayscale Images
- Part 5 - Scanning QR Code from Desktop Screen with Qt and Python Barcode SDK
- Part 6 - Building an Online Barcode and QR Code Scanning App with Python Django
- Part 7 - Real-Time Barcode and QR Code Scanning with Webcam, OpenCV, and Python
- Part 8 - How to Build Flet Chat App with Barcode and Gemini APIs
- Part 9 - Comparing Barcode Scanning in Python: ZXing vs. ZBar vs. Dynamsoft Barcode Reader
- Part 10 - Python Ctypes: Invoking C/C++ Shared Library and Native Threading
- Part 11 - A Guide to Running ARM32 and ARM64 Python Barcode Readers in Docker Containers
Download the Required SDKs
-
OpenCV
Used for accessing the webcam and stitching images.
pip install opencv-python
-
Dynamsoft Barcode Reader
Used for decoding barcodes and QR codes from images.
pip install dbr
License Activation
Obtain a desktop license key from Dynamsoft to activate the Dynamsoft Barcode Reader:
from dbr import *
BarcodeReader.init_license("LICENSE-KEY")
Building a Barcode and QR Code Scanner in Python
Python’s Global Interpreter Lock (GIL) can be a performance bottleneck for multi-threaded applications. For CPU-intensive tasks like barcode and QR code detection, it’s recommended to use Python’s multiprocessing library. You can refer to OpenCV’s video_threaded.py sample for guidance.
Steps to Build the Scanner:
-
Import the necessary packages:
import numpy as np import cv2 as cv from multiprocessing.pool import ThreadPool from collections import deque import dbr from dbr import *
-
Activate the license and initialize the barcode reader:
BarcodeReader.init_license("LICENSE-KEY") reader = BarcodeReader()
-
Create a thread pool for processing frames:
threadn = 1 # cv.getNumberOfCPUs() pool = ThreadPool(processes = threadn) barcodeTasks = deque()
-
Define a function to process video frames:
def process_frame(frame): results = None try: results = reader.decode_buffer(frame) except BarcodeReaderError as bre: print(bre) return results while True: ret, frame = cap.read() while len(barcodeTasks) > 0 and barcodeTasks[0].ready(): results = barcodeTasks.popleft().get() if results != None: for result in results: points = result.localization_result.localization_points cv.line(frame, points[0], points[1], (0,255,0), 2) cv.line(frame, points[1], points[2], (0,255,0), 2) cv.line(frame, points[2], points[3], (0,255,0), 2) cv.line(frame, points[3], points[0], (0,255,0), 2) cv.putText(frame, result.barcode_text, points[0], cv.FONT_HERSHEY_SIMPLEX, 0.5, (0,0,255)) if len(barcodeTasks) < threadn: task = pool.apply_async(process_frame, (frame.copy(), )) barcodeTasks.append(task) cv.imshow('Barcode & QR Code Scanner', frame) ch = cv.waitKey(1) if ch == 27: break
-
Run the barcode and QR code scanner:
Dynamsoft Barcode Reader can detect multiple barcodes and QR codes from a single image. However, image quality affects detection accuracy. As shown in the above image, to capture all barcodes and QR codes, you may need to increase the camera’s depth of field, which can make the codes too small to read. To solve this, move the camera closer to get high-quality images for scanning, then use OpenCV’s stitching API to merge multiple images into a panorama.
Experiment: Image Stitching and Scanning Multiple QR Codes in a Batch
Stitching Multiple Barcode and QR Code Images into a Panorama
OpenCV includes a stitching.py sample that demonstrates how to use the stitching API.
Steps to Implement Panorama Stitching:
-
Initialize the stitcher object:
modes = (cv.Stitcher_PANORAMA, cv.Stitcher_SCANS) stitcher = cv.Stitcher.create(modes[1]) stitcher.setPanoConfidenceThresh(0.5)
-
Define a function to stitch images:
panoramaPool = ThreadPool(processes = threadn) panoramaTask = deque() def stitch_frame(self, frame): try: results = self.reader.decode_buffer(frame) if results != None: for result in results: points = result.localization_result.localization_points cv.line(frame, points[0], points[1], (0,255,0), 2) cv.line(frame, points[1], points[2], (0,255,0), 2) cv.line(frame, points[2], points[3], (0,255,0), 2) cv.line(frame, points[3], points[0], (0,255,0), 2) cv.putText(frame, result.barcode_text, points[0], cv.FONT_HERSHEY_SIMPLEX, 0.5, (0,0,255)) self.panorama.append((frame, len(results))) print('Stitching .............') try: all_images = [frame for frame, count in self.panorama] status, image = self.stitcher.stitch(all_images) if status != cv.Stitcher_OK: print("Can't stitch images, error code = %d" % status) return self.panorama[0][0] else: # Stop stitching if the output image is out of control if image.shape[0] >= frame.shape[0] * 1.5: self.isPanoramaDone = True self.save_frame(all_images[0]) print('Stitching is done.............') return None # Drop the stitched image if its quality is not good enough total = 0 for frame, count in self.panorama: total += count count_stitch = self.count_barcodes(image) if count_stitch > total or count_stitch < self.panorama[0][1]: return self.panorama[0][0] # Wait for the next stitching and return the current stitched image self.panorama = [(image, count_stitch)] return image except Exception as e: print(e) return None except BarcodeReaderError as e: print(e) return None return None while len(panoramaTask) > 0 and panoramaTask[0].ready(): image = panoramaTask.popleft().get() if image is not None: cv.imshow('panorama', image) if len(panoramaTask) < threadn: task = panoramaPool.apply_async(self.stitch_frame, (frame_cp, )) panoramaTask.append(task)
-
Run the code to see the panorama stitching result.
Barcode and QR Code Based Image Concatenation for Automated Inventory Management
Barcodes and QR codes are widely used in modern warehouse inventory management. A robot equipped with a camera can scan barcodes and QR codes to check product status and inventory. In the event of a missing product, a panorama image with drawn bounding boxes can indicate the missing item.
For warehouse robotics, precise image stitching may not be necessary if the camera moves at a constant speed, producing stable images. Instead, focus on barcode and QR code detection, concatenating images based on overlapping detection results.
Code Logic for Concatenating Images:
def stitch_frame(self, frame):
try:
results = self.reader.decode_buffer(frame)
if results != None:
# Draw results on the copy of the frame. Keep original frame clean.
frame_cp = frame.copy()
for result in results:
points = result.localization_result.localization_points
cv.line(frame_cp, points[0], points[1], (0,255,0), 2)
cv.line(frame_cp, points[1], points[2], (0,255,0), 2)
cv.line(frame_cp, points[2], points[3], (0,255,0), 2)
cv.line(frame_cp, points[3], points[0], (0,255,0), 2)
cv.putText(frame_cp, result.barcode_text, points[0], cv.FONT_HERSHEY_SIMPLEX, 0.5, (0,0,255))
# Save frame and barcode info if panorama is empty
if len(self.panorama) == 0:
self.panorama.append((frame, results, frame_cp))
else:
# Compare results. If there is an intersection, transform and stitch. Otherwise, discard.
preFrame = self.panorama[0][0]
preResults = self.panorama[0][1]
preFrameCp = self.panorama[0][2]
while len(results) > 0:
result = results.pop()
for preResult in preResults:
if preResult.barcode_text == result.barcode_text and preResult.barcode_format == result.barcode_format:
prePoints = preResult.localization_result.localization_points
points = result.localization_result.localization_points
# # Crop image based on min area rect
preFrame = preFrame[0: preFrame.shape[0], 0: max(prePoints[0][0], prePoints[1][0], prePoints[2][0], prePoints[3][0]) + 10]
frame = frame[0: frame.shape[0], max(points[0][0], points[1][0], points[2][0], points[3][0]): frame.shape[1] + 10]
preFrameCp = preFrameCp[0: preFrameCp.shape[0], 0: max(prePoints[0][0], prePoints[1][0], prePoints[2][0], prePoints[3][0]) + 10]
frame_cp = frame_cp[0: frame_cp.shape[0], max(points[0][0], points[1][0], points[2][0], points[3][0]): frame_cp.shape[1] + 10]
# # Stitch images
frame = concat_images([preFrame, frame])
frame_cp = concat_images([preFrameCp, frame_cp])
# Re-detect barcodes from the new image
results = self.reader.decode_buffer(frame)
# Save results
self.panorama = [(frame, results, frame_cp)]
return frame, frame_cp
return self.panorama[0][0], self.panorama[0][2]
except BarcodeReaderError as e:
print(e)
return None, None
return None, None
Result:
Source Code
https://github.com/yushulx/python-barcode-qrcode-sdk/tree/main/examples/official/9.x/webcam