132 lines
3.6 KiB
Python
132 lines
3.6 KiB
Python
from PIL import Image
|
|
from pyzbar.pyzbar import decode
|
|
import itertools
|
|
from math import perm
|
|
from tqdm import tqdm
|
|
import numpy as np
|
|
import sys
|
|
|
|
NUM_PIECES_WIDTH = 4
|
|
NUM_PIECES_HEIGHT = 3
|
|
BOX_SIZE = 2
|
|
TILE = 78
|
|
# 7x7 finder, upscaled to 14x14 (black=1, white=0)
|
|
TEMPLATE = np.kron(np.array([
|
|
[1,1,1,1,1,1,1],
|
|
[1,0,0,0,0,0,1],
|
|
[1,0,1,1,1,0,1],
|
|
[1,0,1,1,1,0,1],
|
|
[1,0,1,1,1,0,1],
|
|
[1,0,0,0,0,0,1],
|
|
[1,1,1,1,1,1,1],
|
|
], dtype=np.uint8), np.ones((BOX_SIZE, BOX_SIZE), np.uint8))
|
|
H, W = TEMPLATE.shape # 14x14
|
|
|
|
|
|
def decodeQRCode(image : Image.Image) -> str | None:
|
|
# Decode the QR code
|
|
decoded_objects = decode(image)
|
|
|
|
# Check if any QR code was found
|
|
if decoded_objects:
|
|
# Iterate through all the detected QR codes and print their data
|
|
for obj in decoded_objects:
|
|
return obj.data.decode("utf-8")
|
|
else:
|
|
return None
|
|
|
|
|
|
def cut_pieces(image):
|
|
# Get the size of the image
|
|
width, height = image.size
|
|
|
|
# Calculate the size of each grid cell
|
|
cell_width = width // NUM_PIECES_WIDTH
|
|
cell_height = height // NUM_PIECES_HEIGHT
|
|
|
|
pieces = []
|
|
|
|
# Split the original image into 12 pieces
|
|
for i in range(NUM_PIECES_WIDTH * NUM_PIECES_HEIGHT):
|
|
# Calculate the coordinates of the current grid cell
|
|
x1 = (i % NUM_PIECES_WIDTH) * cell_width
|
|
y1 = (i // NUM_PIECES_WIDTH) * cell_height
|
|
x2 = x1 + cell_width
|
|
y2 = y1 + cell_height
|
|
|
|
# Crop the corresponding part of the original image
|
|
cropped_piece = image.crop((x1, y1, x2, y2))
|
|
pieces.append(cropped_piece)
|
|
|
|
return pieces
|
|
|
|
|
|
def to_bw01(img):
|
|
# Force 1-bit, then map black->1, white->0
|
|
a = np.array(img.convert("1")) # {0,255}
|
|
return (a == 0).astype(np.uint8)
|
|
|
|
|
|
def finder_corner_exact(tile_bw):
|
|
crops = {
|
|
'TL': tile_bw[0:H, 0:W ],
|
|
'TR': tile_bw[0:H, TILE-W:TILE ],
|
|
'BL': tile_bw[TILE-H:TILE, 0:W ],
|
|
'BR': tile_bw[TILE-H:TILE, TILE-W:TILE ],
|
|
}
|
|
for corner, crop in crops.items():
|
|
if np.array_equal(crop, TEMPLATE):
|
|
return corner
|
|
return None # no exact finder in this tile
|
|
|
|
|
|
def three_tiles_with_finders(tiles):
|
|
hits = []
|
|
for tile in tiles:
|
|
corner = finder_corner_exact(to_bw01(tile))
|
|
if corner is not None:
|
|
hits.append((tile, corner))
|
|
if len(hits) == 3:
|
|
break
|
|
return hits
|
|
|
|
|
|
if __name__ == "__main__":
|
|
img = Image.open("./scrambled_test_qr_code.png")
|
|
new_image = Image.new("RGB", (img.height, img.height))
|
|
|
|
pieces = cut_pieces(img)
|
|
corners = three_tiles_with_finders(pieces)
|
|
|
|
for tile, corner in corners:
|
|
if corner == 'TL':
|
|
new_image.paste(tile, (0,0))
|
|
elif corner == 'TR':
|
|
new_image.paste(tile, (156,0))
|
|
elif corner == 'BL':
|
|
new_image.paste(tile, (0,156))
|
|
else:
|
|
sys.exit()
|
|
|
|
|
|
pieces = [piece for piece in pieces if piece not in [tile for tile, _ in corners]]
|
|
assert len(pieces) == 9
|
|
new_image.show()
|
|
possible_combos = itertools.permutations(pieces, 6)
|
|
num_possible_combos = perm(len(pieces), 6)
|
|
print(f"Testing {num_possible_combos} combinations")
|
|
|
|
for a,b,c,d,e,f in tqdm(possible_combos, total=num_possible_combos):
|
|
new_image.paste(a, (78, 0))
|
|
new_image.paste(b, (0, 78))
|
|
new_image.paste(c, (78, 78))
|
|
new_image.paste(d, (156, 78))
|
|
new_image.paste(e, (78, 156))
|
|
new_image.paste(f, (156, 156))
|
|
output = decodeQRCode(new_image)
|
|
if output:
|
|
print(output)
|
|
|
|
print(decodeQRCode(img))
|
|
|