How to Detect and Decode QR Code with YOLO, OpenCV, and Dynamsoft Barcode Reader

In the past two weeks, I trained a custom YOLOv3 model for QR code detection and tested it with Darknet. In this article, I will use OpenCV’s DNN (Deep Neural Network) module to load the YOLO model for making detection from static images and real-time camera video stream. Besides, I will use Dynamsoft Barcode Reader to decode QR codes from the regions detected by YOLO.

QR Code detection with YOLO and OpenCV

YOLO QR Code Detection with OpenCV Python

Install OpenCV (CPU only) via pip:

pip install opencv-python

You can get qrcode.names, qrcode-yolov3-tiny.cfg and qrcode-yolov3-tiny.weights files from the package YOLOv3-tiny-QR.

To quickly get familiar with the OpenCV DNN APIs, we can refer to object_detection.py, which is a sample included in the OpenCV GitHub repository.

Now, let’s implement the QR detection code logic step by step.

First, we need to read an image to a Mat object using the imread() function. In case the image size is too large to display, I defined the maximum width and height values:

frame = cv.imread("416x416.jpg")

threshold = 0.6  
maxWidth = 1280; maxHeight = 720
imgHeight, imgWidth = frame.shape[:2]
hScale = 1; wScale = 1
thickness = 1

if imgHeight > maxHeight:
    hScale = imgHeight / maxHeight
    thickness = 6

if imgWidth > maxWidth:
    wScale = imgWidth / maxWidth
    thickness = 6

The next step is to initialize the network by loading the *.names, *.cfg and *.weights files:

classes = open('qrcode.names').read().strip().split('\n')
net = cv.dnn.readNetFromDarknet('qrcode-yolov3-tiny.cfg', 'qrcode-yolov3-tiny.weights')
net.setPreferableBackend(cv.dnn.DNN_BACKEND_OPENCV)

The network requires a blob object as the input, therefore we can convert the Mat object to a blob object as follows:

blob = cv.dnn.blobFromImage(frame, 1/255, (416, 416), swapRB=True, crop=False)

Determine the output layer, and then input the blob object to the network and perform a forward pass:

ln = net.getLayerNames()
ln = [ln[i[0] - 1] for i in net.getUnconnectedOutLayers()]
net.setInput(blob)
outs = net.forward(ln)

As we get the network outputs, we can extract class names, confidence scores, and bounding boxes. In the meantime, we can draw them with OpenCV APIs:

def postprocess(frame, outs):
    frameHeight, frameWidth = frame.shape[:2]

    classIds = []
    confidences = []
    boxes = []

    for out in outs:
        for detection in out:
            scores = detection[5:]
            classId = np.argmax(scores)
            confidence = scores[classId]
            if confidence > threshold:
                x, y, width, height = detection[:4] * np.array([frameWidth, frameHeight, frameWidth, frameHeight])
                left = int(x - width / 2)
                top = int(y - height / 2)
                classIds.append(classId)
                confidences.append(float(confidence))
                boxes.append([left, top, int(width), int(height)])

    indices = cv.dnn.NMSBoxes(boxes, confidences, threshold, threshold - 0.1)
    for i in indices:
        i = i[0]
        box = boxes[i]
        left = box[0]
        top = box[1]
        width = box[2]
        height = box[3]

        # Draw bounding box for objects
        cv.rectangle(frame, (left, top), (left + width, top + height), (0, 0, 255), thickness)

        # Draw class name and confidence
        label = '%s:%.2f' % (classes[classIds[i]], confidences[i])
        cv.putText(frame, label, (left, top), cv.FONT_HERSHEY_SIMPLEX, 0.5, (255,255,255))

Finally, we could adjust the image size to display appropriately on screen:

if hScale > wScale:
    frame = cv.resize(frame, (int(imgWidth / hScale), maxHeight))
elif hScale < wScale:
    frame = cv.resize(frame, (maxWidth, int(imgHeight / wScale)))

cv.imshow('QR Detection', frame)
cv.waitKey()
YOLO for QR code detection

Decoding QR Code with Dynamsoft Barcode Reader

Once the QR code detection is done, we can get the corresponding bounding boxes, with which we are able to take a further step to decode the QR code.

Install Dynamsoft Barcode Reader:

pip install dbr

According to the coordinates of bounding boxes, we can decode the QR code by setting region parameters. Region decoding is much faster than the full image decoding:

from dbr import *

# Initialize Dynamsoft Barcode Reader
reader = BarcodeReader()
# Apply for a trial license: https://www.dynamsoft.com/customer/license/trialLicense
license_key = "LICENSE KEY"
reader.init_license(license_key)

def decodeframe(frame, left, top, right, bottom):
    settings = reader.reset_runtime_settings() 
    settings = reader.get_runtime_settings()
    settings.region_bottom  = bottom
    settings.region_left    = left
    settings.region_right   = right
    settings.region_top     = top
    settings.barcode_format_ids = EnumBarcodeFormat.BF_QR_CODE
    settings.expected_barcodes_count = 1
    reader.update_runtime_settings(settings)

    try:
        text_results = reader.decode_buffer(frame)
        if text_results != None:
            return text_results[0]
    except BarcodeReaderError as bre:
        print(bre)

    return None

On my screenshot, you can see the decoding result is obfuscated because I didn’t use a valid license key. If you want to experience the full functionalities of Dynamsoft Barcode Reader, you’d better apply for a free trial license.

decode qr code

References

Source Code

https://github.com/yushulx/opencv-yolo-qr-detection