Skip to content

Instantly share code, notes, and snippets.

@zhenwenc
Last active December 14, 2021 23:17
Show Gist options
  • Save zhenwenc/efed321fbc31e42e2ee29990dff87ce8 to your computer and use it in GitHub Desktop.
Save zhenwenc/efed321fbc31e42e2ee29990dff87ce8 to your computer and use it in GitHub Desktop.
Improve QR Code Recognition from Image
from flask import Flask, jsonify, request
from pyzbar.pyzbar import decode
from pyzbar.pyzbar import ZBarSymbol
import cv2
import numpy as np
import base64
import urllib
import pdf2image
app = Flask(__name__)
@app.route("/images/qrcode", methods=["POST"])
def detect_qrcode():
body = request.get_json()
app.logger.info(f"Received QR code detection request")
# parse and decode input from data URI
with urllib.request.urlopen(body["payload"]) as response:
if response.headers['Content-Type'] == "application/pdf":
# NOTE Increase `dpi` can improve detection, but slower
pages = pdf2image.convert_from_bytes(response.read(), dpi=500, single_file=True)
image = cv2.cvtColor(np.array(pages[0]), cv2.COLOR_RGB2BGR)
else:
image = np.asarray(bytearray(response.read()), dtype=np.int8)
image = cv2.imdecode(image, cv2.IMREAD_COLOR)
# attempt with zbar first, preprocess may not be required
result = decode_qrcode(image)
qrcode = None
# crop the qr code region and decode with zbar
if not result or not len(result):
qrcode = find_qrcode(image)
result = decode_qrcode(qrcode)
app.logger.info(f"Decoded QR code: {result}")
return jsonify({
# decoded qr code payload, supports extracting multiple qr codes
"data": [d.data.decode("utf-8") for d in result],
# original image with highlighted qr code region
"image": base64_image_uri(image),
# preprocessed qr code cropped from the original image
"qrcode": base64_image_uri(qrcode),
})
def find_qrcode(image: np.ndarray):
source = image.copy()
# Load imgae, grayscale, Gaussian blur, Otsu's threshold
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (9,9), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
# Morph close
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
close = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=2)
# Find contours and filter for QR code
cnts = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.04 * peri, True)
x,y,w,h = cv2.boundingRect(approx)
area = cv2.contourArea(c)
ar = w / float(h)
m = 2 # NOTE add margin to the cropped image
if len(approx) == 4 and area > 1000 and (ar > .85 and ar < 1.3):
cv2.rectangle(image, (x, y), (x + w, y + h), (36,255,12), 3)
qrcode = source[y-m:y+h+m, x-m:x+w+m]
break
# Enlarge the cropped qr code to improve detection
return cv2.resize(qrcode, [500, 500], interpolation = cv2.INTER_CUBIC)
def decode_qrcode(image: np.ndarray):
return decode(image, symbols=[ZBarSymbol.QRCODE])
def base64_image_uri(image: np.ndarray):
if image is not None and len(image) > 0:
encoded = base64.b64encode(cv2.imencode('.jpg', image)[1]).decode("utf-8")
return f"data:image/jpg;base64,{encoded}"
@zhenwenc
Copy link
Author

Quick Start

Install the required dependencies:

pip3 install flask kraken pyzbar Pillow opencv-python pdf2image

Launch the demo application:

export FLASK_APP=main.py
export FLASK_ENV=development

flask run --host=0.0.0.0

Extract QR code from an image file:

curl -X POST 'http://localhost:5000/images/qrcode' \
     --header 'Content-Type: application/json' \
     --data-binary '{ "payload": "file:///tmp/qrcode.png" }' |
    jq -r '.data[0]'

Extract QR code from a base64 encoded image:

base64 /tmp/qrcode.png | xargs printf '{ "payload": "data:image/png;base64,%s" }' |
    curl -X POST 'http://localhost:5000/images/qrcode' \
         --header 'Content-Type: application/json' \
         --data-binary @- |
    jq -r '.data[0]'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment