This script is basically a workaround for the bug in zbarimg that it cannot read QRCodes with the Swiss Cross in them (as every QR Bill in Switzerland has). See https://qr-rechnung.net/#/
- Writeup/ticket for my problem: mchehab/zbar#216
Usage:
cat file.jpg | ./mask_swiss_cross.py > masked.jpg
cat file.pdf | ./mask_swiss_cross.py > masked.png
#--- decode with zbarimg with the bad encoding detection
cat file.jpg | ./mask_swiss_cross.py | zbarimg -
#--- decode with zbarimg raw
cat file.jpg | ./mask_swiss_cross.py | zbarimg -q --raw --nodbus -Sqr.binary -
#!/usr/bin/env python3
#web####pythoncode.py#####
import numpy as np
import sys, os
import io
import cv2
import logging
from pdf2image import convert_from_bytes as PDF_convert_from_bytes
#============================================================
# Description
#
# Note, the output is always PNG if the input is PDF. Otherwise it keeps the jpg/png formatting
#
# usage:
#
# cat file.jpg | ./mask_swiss_cross.py > masked.jpg
# cat file.pdf | ./mask_swiss_cross.py > masked.png
#--- decode with zbarimg with the bad encoding detection
# cat file.jpg | ./mask_swiss_cross.py | zbarimg -
#--- decode with zbarimg raw
# cat file.jpg | ./mask_swiss_cross.py | zbarimg -q --raw --nodbus -Sqr.binary -
#
#============================================================
#============================================================
# Functions
#============================================================
def identify_file_format(byte_stream):
logging.info(" - identify_file_format")
# Read the first few bytes of the stream
start = byte_stream[:8] # Read more bytes to ensure we cover different formats
# Convert to hexadecimal for easier matching
# start_hex = start.hex()
start_hex = ''.join(f'{byte:02x}' for byte in start)
logging.info(" - Checking the bytes")
# Check the signatures
if start_hex.startswith('25504446'): # %PDF in hex
return 'PDF'
elif start_hex.startswith('ffd8ff'):
return 'JPEG'
elif start_hex.startswith('89504e470d0a1a0a'):
return 'PNG'
else:
return 'other image'
def mask_swiss_cross(img):
#-----------------------------------------
# Since most QR decoders have issues with the swiss cros in the middle of the qrcode
#-----------------------------------------
logging.info(f" - Masking swiss cross")
qcd = cv2.QRCodeDetector()
retval, points = qcd.detect(img)
logging.debug(points)
poly_points = points[0]
# Calculate the centroid of the polygon
centroid = np.mean(poly_points, axis=0).astype(int)
#--- draw edges around the qrcode
#img = cv2.polylines(img, points.astype(int), True, (0, 255, 0), 3)
# Calculate the total width spanned by the poly-points
min_x = np.min(poly_points[:, 0])
max_x = np.max(poly_points[:, 0])
total_width = max_x - min_x
# Calculate the rectangle's width as 1/8 of the total width
#rect_width = total_width / 8
rect_width = total_width / 10
# Decide on the rectangle's height (arbitrary decision or based on specific criteria)
# For demonstration, let's say we keep the height equal to the rectangle's width for a square shape
rect_height = rect_width
# Calculate the top-left and bottom-right points of the rectangle
top_left = (int(centroid[0] - rect_width / 2), int(centroid[1] - rect_height / 2))
bottom_right = (int(centroid[0] + rect_width / 2), int(centroid[1] + rect_height / 2))
# Draw the filled rectangle in black
cv2.rectangle(img, top_left, bottom_right, (0, 0, 0), -1) # -1 thickness fills the rectangle
return img
def getFileBytesFromStream(file):
image_stream = io.BytesIO(file)
image_stream.seek(0)
file_bytes = np.asarray(bytearray(image_stream.read()), dtype=np.uint8)
return file_bytes
def identify_file_format(byte_stream):
logging.info(" - identify_file_format")
# Read the first few bytes of the stream
start = byte_stream[:8] # Read more bytes to ensure we cover different formats
# Convert to hexadecimal for easier matching
# start_hex = start.hex()
start_hex = ''.join(f'{byte:02x}' for byte in start)
logging.info(" - Checking the bytes")
# Check the signatures
if start_hex.startswith('25504446'): # %PDF in hex
return 'PDF'
elif start_hex.startswith('ffd8ff'):
return 'JPEG'
elif start_hex.startswith('89504e470d0a1a0a'):
return 'PNG'
else:
return 'other image'
#============================================================
# MAIN
#============================================================
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# Read image from stdin
file_bytes = sys.stdin.buffer.read()
format = identify_file_format(file_bytes)
logging.info("Format: " + format)
if format == "PDF":
images = PDF_convert_from_bytes(file_bytes, 300) # 300 is the dpi (dots per inch)
image_np = np.array(images[0])
img = cv2.cvtColor(image_np, cv2.COLOR_RGB2BGR)
outputFormat = ".png"
else:
img = cv2.imdecode(np.frombuffer(file_bytes, np.uint8), cv2.IMREAD_COLOR)
outputFormat = "." + format
# img = cv2.imdecode(file_bytes, cv2.IMREAD_COLOR)
img_masked = mask_swiss_cross(img)
#_, buffer = cv2.imencode('.' + format, img)
_, buffer = cv2.imencode(outputFormat, img)
sys.stdout.buffer.write(buffer)