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:

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:

inverted color QR code

When the image is converted to a binary format, it becomes clear that the black and white regions of the QR code are inverted:

recognize inverted binary image

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:

inverted QR code

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.

perspective distorted QR code image

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

recognition for perspective distorted QR

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:

  1. Navigate to Advanced Settings > Binarization mode > Threshold.
  2. 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.

Air-Force-Reserve-San-Diego

Performance Summary

Image Time Cost Image Thumbnail
Color Image 0.014 s QR Code Color Image
Grayscale Image 0.010 s QR Code grayscale Image
Binary Image 0.009 s QR Code Black and White Image

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.

QR code parameter settings

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