Scanning Barcode and QR Code Using Webcam, OpenCV and Python
Tags: qrcode, python, opencv, panorama
Published: 2022-04-14 06:33:04

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](https://www.dynamsoft.com/barcode-reader/sdk-desktop-server/), 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.
    
    ```bash
    pip install opencv-python
    ```
    
- **Dynamsoft Barcode Reader**
    
    Used for decoding barcodes and QR codes from images.

    ```bash
    pip install dbr
    ```

## License Activation
Get a desktop license key from [here](https://www.dynamsoft.com/customer/license/trialLicense?product=dbr) to activate Dynamsoft Barcode Reader:

```python
BarcodeReader.init_license("LICENSE-KEY")
```

## 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](https://github.com/opencv/opencv/blob/4.x/samples/python/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:
    
    ```python
	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:
    
    ```python
	BarcodeReader.init_license("LICENSE-KEY")
	reader = BarcodeReader()
	```

3. Create a thread pool with the amount of processes you want to use:
    
    ```python
	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:
    
    ```python
	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](https://www.dynamsoft.com/codepool/img/2022/04/multiple-barcode-qrcode-scan.png)

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](https://github.com/opencv/opencv/blob/4.x/samples/python/stitching.py) file showing how to use the OpenCV stitcher API.

To implement panorama stitching:
1. Initialize a stitcher object:
    
    ```python
    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:

    ```python
    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](https://www.dynamsoft.com/codepool/img/2022/04/panorama-barcode-qr-code.png)

## Source Code
[https://github.com/yushulx/webcam-barcode-qrcode-reader-python](https://github.com/yushulx/webcam-barcode-qrcode-reader-python)