Last active
December 14, 2021 23:17
-
-
Save zhenwenc/efed321fbc31e42e2ee29990dff87ce8 to your computer and use it in GitHub Desktop.
Improve QR Code Recognition from Image
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Quick Start
Install the required dependencies:
Launch the demo application:
Extract QR code from an image file:
Extract QR code from a base64 encoded image: