52 lines
1.7 KiB
Python
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)
|
|
|