Files
mosaic/locate_finders.py

52 lines
1.7 KiB
Python

from PIL import Image
import numpy as np
from numpy.lib.stride_tricks import sliding_window_view
def load_bw(path):
# Black=1, White=0
im = Image.open(path).convert("L")
bw = (np.array(im) < 128).astype(np.uint8)
return bw
def finder_template(box_size=2):
# 7x7 finder: outer black, inner white (5x5), center black (3x3)
F = 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)
# upscale to pixels
return np.kron(F, np.ones((box_size, box_size), dtype=np.uint8))
def count_finders(img_path, box_size=2, max_mismatch=0):
bw = load_bw(img_path)
T = finder_template(box_size=box_size)
h, w = T.shape
# slide 14x14 window over the image
win = sliding_window_view(bw, (h, w))
# Hamming distance to template per window
mismatches = (win ^ T).sum(axis=(-2, -1))
hits = mismatches <= max_mismatch
# Optional: suppress overlapping duplicates by non-maximum suppression on exact matches
ys, xs = np.where(hits)
# Convert to center coordinates
centers = [(int(y + h/2), int(x + w/2)) for y, x in zip(ys, xs)]
# Greedy dedup within ~half a finder width
deduped = []
r = max(2, h//2)
for cy, cx in centers:
if all((abs(cy - y) > r) or (abs(cx - x) > r) for y, x in deduped):
deduped.append((cy, cx))
return len(deduped), deduped
if __name__ == "__main__":
n, centers = count_finders("./scrambled_test_qr_code.png", box_size=2, max_mismatch=0)
print("Finder count:", n, "centers:", centers, "Exactly three?", n == 3)