Advanced QR Code Recognition: Handling Inverted Colors, Perspective Distortion, and Grayscale Images
When using a QR code reader or scanner SDK, the key priorities are detection speed and recognition accuracy. Several factors can influence the performance of a QR code detection SDK, which can be broadly categorized into two areas: image quality and algorithm efficiency. This article uses the Dynamsoft Python Barcode Reader SDK as an example to demonstrate how to enhance the performance of QR code detection by preprocessing input images and fine-tuning algorithm parameters on desktop platforms.
We will evaluate performance under the following scenarios:
- Inverted Color QR Codes
- QR Codes with Perspective Distortion
- QR Images: Color vs. Grayscale vs. Black/White
This article is Part 4 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
Prerequisites
-
OpenCV Python: Required for image processing tasks.
pip install opencv-python pip install opencv-contrib-python
-
Dynamsoft Barcode Reader SDK: The core SDK for QR code detection.
pip install dbr
-
Sample QR Code Images: Download QR code images for testing via Google Image Search
QR Codes with Inverted Color
Attempting to read the following inverted color QR code will result in a recognition failure:
When the image is converted to a binary format, it becomes clear that the black and white regions of the QR code are inverted:
Correcting the Inversion with OpenCV
To make the QR code recognizable, you can use OpenCV’s bitwise_not
function to invert the image colors:
ret, thresh = cv2.threshold(grayscale_image, 150,255,cv2.THRESH_BINARY )
cv2.bitwise_not(thresh, thresh)
detect('Inverted', thresh, dbr.EnumImagePixelFormat.IPF_GRAYSCALED)
This adjustment successfully decodes the QR code:
Time taken: 0.0541 seconds
Barcode Format :
QR_CODE
Barcode Text :
https://www.jalna.com.au/
Using Dynamsoft’s Built-in API to Invert the Color
Dynamsoft’s QR code SDK also includes a built-in option for handling inverted QR codes. You can configure this setting as shown below:
With this built-in feature, the SDK delivers even faster performance:
Time taken: 0.0160 seconds
Barcode Format :
QR_CODE
Barcode Text :
https://www.jalna.com.au/
QR Codes with Perspective Distortion
Let’s explore a more challenging scenario: decoding a QR code with perspective distortion.
Using the previous methods, this QR code cannot be recognized, whether it’s in color, grayscale, or binary format. However, this does not mean the barcode is unreadable.
Adjusting the Threshold with OpenCV
By manually adjusting the threshold value, I was able to decode the QR code successfully:
image = cv2.imread('perspective-distortion-qr-code.jpg')
grayscale_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(grayscale_image, 125, 255,cv2.THRESH_BINARY)
detect('Binary', thresh, dbr.EnumImagePixelFormat.IPF_GRAYSCALED)
With the threshold set to 125
, the QR code is recognized:
Time taken: 0.0080 seconds
Barcode Format :
QR_CODE
Barcode Text :
http://goo.by/wb0Ric
Optimizing Settings in Dynamsoft’s SDK
Dynamsoft’s QR Code SDK offers extensive customization through various scanning templates. Two commonly used templates are Best Speed and Best Coverage. There’s a tradeoff between decoding speed and accuracy—the more algorithms enabled, the longer the processing time.
You can test these templates in the server-side barcode scanner demo. When uploading the perspective-distorted QR image:
- Best speed: Recognizes nothing in 15 milliseconds.
- Best coverage: Successfully decodes the QR code but takes 828 milliseconds.
Neither result is optimal.
Fine-Tuning Threshold for Better Performance
For the best results:
- Navigate to
Advanced Settings > Binarization mode > Threshold
. - Set the threshold value to
125
.
This adjustment allows for extremely fast recognition.
Settings | Result |
---|---|
Best speed | Failed to recognize |
Best coverage | Success in 828 milliseconds |
Threshold at ‘125’ | Success in 8 milliseconds |
Finally, export the template as a JSON file and load it in your Python code:
error = reader.init_runtime_settings_with_file('perspective-qr.json')
image = cv2.imread('perspective-distortion-qr-code.jpg')
detect('Color', image_copy, dbr.EnumImagePixelFormat.IPF_RGB_888)
QR Images: Color vs. Grayscale vs. Black/White
To evaluate the performance of the Dynamsoft Barcode Reader SDK, we tested it using color, grayscale, and binary images as input.
Performance Summary
Image | Time Cost | Image Thumbnail |
---|---|---|
Color Image | 0.014 s | |
Grayscale Image | 0.010 s | |
Binary Image | 0.009 s |
Decoding a Color QR Code Image
Let’s start by decoding a color image of a QR code.
The following code loads the image using OpenCV and reads the barcode information using Dynamsoft Barcode Reader:
import cv2
import numpy as np
from dbr import *
import time
BarcodeReader.init_license(license)
reader = BarcodeReader()
image = cv2.imread('Air-Force-Reserve-San-Diego.jpg')
def detect(windowName, image, pixel_format):
try:
buffer = image.tobytes()
height = image.shape[0]
width = image.shape[1]
stride = image.strides[0]
start = time.time()
results = reader.decode_buffer_manually(buffer, width, height, stride, pixel_format, "")
end = time.time()
print("Time taken: {:.3f}".format(end - start) + " seconds")
cv2.putText(image, "Time taken: {:.3f}".format(end - start) + " seconds", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)
if results != None:
for result in results:
print("Barcode Format : ")
print(result.barcode_format_string)
print("Barcode Text : ")
print(result.barcode_text)
points = result.localization_result.localization_points
data = np.array([[points[0][0], points[0][1]], [points[1][0], points[1][1]], [points[2][0], points[2][1]], [points[3][0], points[3][1]]])
cv2.drawContours(image=image, contours=[data], contourIdx=-1, color=(0, 255, 0), thickness=2, lineType=cv2.LINE_AA)
x = min(points[0][0], points[1][0], points[2][0], points[3][0])
y = min(points[0][1], points[1][1], points[2][1], points[3][1])
cv2.putText(image, result.barcode_text, (x, y), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)
cv2.imshow(windowName, image)
except BarcodeReaderError as bre:
print(bre)
detect('Color', image, dbr.EnumImagePixelFormat.IPF_RGB_888)
cv2.waitKey(0)
Explanation
- The
decode_buffer_manually
method decodes the barcode from the image buffer. - The
localization_points
attribute returns the four corner points of the barcode. - The
drawContours
function draws the barcode boundary on the image. - The
putText
function displays the barcode text on the image. - The
imshow
function displays the image with the barcode boundary and text. - The
waitKey
function waits for a key event to close the window.
Decoding a Grayscale QR Code Image
Grayscale images contain only intensity information, which can speed up the recognition process as the conversion step is skipped. Here’s how to test it:
grayscale_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
detect('Grayscale', grayscale_image, dbr.EnumImagePixelFormat.IPF_GRAYSCALED)
Decoding a Binary QR Code Image
Converting a grayscale image to a binary image simplifies the data and can further improve recognition efficiency:
blur = cv2.GaussianBlur(grayscale_image,(5,5),0)
ret, thresh = cv2.threshold(blur, 0,255,cv2.THRESH_BINARY + cv2.THRESH_OTSU)
detect('Binary', thresh, dbr.EnumImagePixelFormat.IPF_GRAYSCALED)
Image Preprocessing and Parameter Tuning
While we’ve demonstrated basic image preprocessing techniques, Dynamsoft Barcode SDK provides even more advanced options. Fine-tuning the SDK’s settings can help optimize performance for various use cases.
To examine and adjust these settings, use the following code to print the current runtime parameters:
print(reader.get_runtime_settings().__dict__)
{'terminate_phase': 32, 'timeout': 10000, 'max_algorithm_thread_count': 4, 'expected_barcodes_count': 0, 'barcode_format_ids': -31457281, 'barcode_format_ids_2': 0, 'pdf_raster_dpi': 300, 'scale_down_threshold': 2300, 'binarization_modes': [2, 0, 0, 0, 0, 0, 0, 0], 'localization_modes': [2, 16, 4, 8, 0, 0, 0, 0], 'colour_clustering_modes': [0, 0, 0, 0, 0, 0, 0, 0],
'colour_conversion_modes': [1, 0, 0, 0, 0, 0, 0, 0], 'grayscale_transformation_modes': [2, 0, 0, 0, 0, 0, 0, 0], 'region_predetection_modes': [2, 0, 0, 0, 0, 0, 0, 0], 'image_preprocessing_modes': [2, 0, 0, 0, 0, 0, 0, 0], 'texture_detection_modes': [2, 0, 0, 0, 0, 0, 0, 0], 'text_filter_modes': [2, 0, 0, 0, 0, 0, 0, 0], 'dpm_code_reading_modes': [0, 0, 0, 0, 0, 0,
0, 0], 'deformation_resisting_modes': [0, 0, 0, 0, 0, 0, 0, 0], 'barcode_complement_modes': [0, 0, 0, 0, 0, 0, 0, 0], 'barcode_colour_modes': [1, 0, 0, 0, 0, 0, 0, 0], 'text_result_order_modes': [1, 2, 4, 0, 0, 0, 0, 0], 'text_assisted_correction_mode': 2, 'deblur_level': 9, 'intermediate_result_types': 0, 'intermediate_result_saving_mode': 1, 'result_coordinate_type': 1, 'return_barcode_zone_clarity': 0, 'region_top': 0, 'region_bottom': 0, 'region_left': 0, 'region_right': 0, 'region_measured_by_percentage': 0, 'min_barcode_text_length': 0, 'min_result_confidence': 0, 'scale_up_modes': [1, 0, 0, 0, 0, 0, 0, 0], 'accompanying_text_recognition_modes': [0, 0, 0, 0, 0, 0, 0, 0], 'pdf_reading_mode': 1, 'deblur_modes': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'barcode_zone_min_distance_to_image_borders': 0}
For more complex scenarios, consider using Dynamsoft’s online demo tool to fine-tune settings and export them as a JSON file for use in your Python code.
In summary, start with basic settings and incrementally adjust parameters to achieve the best performance for your specific needs.
Source Code
https://github.com/yushulx/python-barcode-qrcode-sdk/tree/main/examples/official/9.x/qrcode_template