Scanning Barcode and QR Code Using Webcam, OpenCV and Python

Dynamsoft is the only company that provides enterprise-class Python Barcode and QR Code SDK for Windows, Linux, macOS, and Raspberry Pi OS. The SDK allows developer 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 use Dynamsoft Barcode Reader, OpenCV, and webcam to create cross-platform desktop barcode and QR code scanner in Python.

Download SDK

  • 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

Get a desktop license key from here to activate Dynamsoft Barcode Reader:

BarcodeReader.init_license("DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==")

Steps to Build Barcode and QR Code Scanner in Python

It is known that Python’s GIL (Global Interpreter Lock) is a performance bottleneck for multi-threaded applications. Therefore, it is recommended to use Python’s multiprocessing library to run barcode and QR code detection algorithm which is CPU-intensive. The sample code video_threaded.py demonstrates how to use Python’s multiprocessing library.

Here are the steps to build our barcode and QR code scanner:

  1. 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 *
    
  2. Set the license key to activate and instantiate Dynamsoft Barcode Reader:

     BarcodeReader.init_license("DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==")
     reader = BarcodeReader()
    
  3. Create a thread pool with the amount of processes you want to use:

     threadn = 1 # cv.getNumberOfCPUs()
     pool = ThreadPool(processes = threadn)
     barcodeTasks = deque()
    

    Note: If you use all CPU cores, the CPU usage will be high.

  4. Create a task function to detect barcodes and QR codes from webcam 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
    
  5. Run the barcode and QR code scanner:

    Python barcode and QR code reader

Dynamsoft Barcode Reader can detect multiple barcodes and QR codes from a single image. However, the image quality affects the detection accuracy. As you can see in the above image, to capture all barcode and QR code, we need to increase the lens depth of field. In this way, the barcode and QR code may become too small to read. To solve this problem, we get camera closer to get high quality image for scanning, and then use OpenCV stitching API to stitch multiple barcode and QR code images into a panorama.

Stitching Multiple Barcode and QR Code Images into a Panorama

OpenCV repository contains a stitching.py file showing how to use the OpenCV stitcher API.

To implement panorama stitching:

  1. Initialize a stitcher object:

     modes = (cv.Stitcher_PANORAMA, cv.Stitcher_SCANS)
     stitcher = cv.Stitcher.create(modes[1])
     stitcher.setPanoConfidenceThresh(0.5)
    
  2. create a new task function for stitching images that contains barcode and QR code:

     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)
    
  3. Run the code to get the panorama stitching result.

    Python barcode and QR code reader with panorama stitching

Barcode and QR Code Based Image Concatenation for Automated Inventory Management

Barcode and QR code are widely used in modern warehouse inventory management. A robot with camera can scan barcode and QR code for checking the product status and inventory. In case of a product is missing, we can utilize a panorama image, on which we can draw bounding boxes to indicate the missing product.

Considering the scenario is specific to warehouse robotics, we do not need to put effort on the image stitching, because the camera is moving at a constant speed and the image is stable. Our focus is on barcode and QR code detection. The panorama image is generated by concatenating multiple barcode and QR code images.

The code logic is changed to compare barcode and QR code results between adjacent images. We crop the duplicate part of the image and concatenate the remaining part:

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

Here is the test result:

concatenate images based on barcode and QR code

Source Code

https://github.com/yushulx/webcam-barcode-qrcode-reader-python