-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathqr_decode.py
More file actions
136 lines (112 loc) · 4.26 KB
/
Copy pathqr_decode.py
File metadata and controls
136 lines (112 loc) · 4.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#!/usr/bin/env python3
"""
Load an image from a path or HTTP(S) URL and decode QR codes in it.
Requires numpy and opencv-python. For curved labels install pyzbar (ZBar backend).
"""
from __future__ import annotations
import argparse
import sys
import urllib.request
import numpy as np
try:
import cv2
except ImportError as e:
print("Missing OpenCV: pip install opencv-python numpy", file=sys.stderr)
raise SystemExit(1) from e
def load_image(path_or_url: str) -> np.ndarray | None:
if path_or_url.startswith(("http://", "https://")):
req = urllib.request.Request(
path_or_url,
headers={"User-Agent": "Mozilla/5.0 QRDecoder/1.0"},
)
with urllib.request.urlopen(req, timeout=30) as resp:
buf = resp.read()
arr = np.frombuffer(buf, dtype=np.uint8)
return cv2.imdecode(arr, cv2.IMREAD_COLOR)
return cv2.imread(path_or_url, cv2.IMREAD_COLOR)
def decode_qrs_opencv(img: np.ndarray) -> list[tuple[str, np.ndarray | None]]:
"""OpenCV finder + decoder (fast, but often fails on curved labels / glare)."""
det = cv2.QRCodeDetector()
texts: list[tuple[str, np.ndarray | None]] = []
def collect_from(ok: bool, infos, pts_list) -> None:
if not ok or infos is None:
return
pl = pts_list if pts_list is not None else []
for i, text in enumerate(infos):
p = pl[i] if i < len(pl) else None
if isinstance(text, str) and text:
texts.append((text, p))
# Multi + single + curved pipelines (different heuristics)
ok, infos, pts, _ = det.detectAndDecodeMulti(img)
collect_from(ok, infos, pts)
curved = det.detectAndDecodeCurved(img)
if isinstance(curved, tuple) and len(curved) >= 2:
data, pts = curved[0], curved[1]
collect_from(True, [data], [pts])
dec = det.detectAndDecode(img)
if isinstance(dec, tuple):
data = dec[0]
pts = dec[1] if len(dec) > 1 else None
collect_from(True, [data], [pts] if pts is not None and pts.size else [])
# Dedupe by text
seen: set[str] = set()
out: list[tuple[str, np.ndarray | None]] = []
for t, p in texts:
if t not in seen:
seen.add(t)
out.append((t, p))
return out
def decode_qrs_pyzbar(img: np.ndarray) -> list[str]:
"""ZBar-based decode; more robust for rotation, perspective, and mild cylinder warp."""
try:
from pyzbar import pyzbar
except ImportError:
return []
rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
out: list[str] = []
for sym in pyzbar.decode(rgb):
try:
out.append(sym.data.decode("utf-8"))
except UnicodeDecodeError:
out.append(sym.data.decode("latin-1"))
return out
def decode_qrs(img: np.ndarray) -> list[tuple[str, np.ndarray | None]]:
"""Try OpenCV, then ZBar (pyzbar) if available."""
oc = decode_qrs_opencv(img)
if oc:
return oc
return [(t, None) for t in decode_qrs_pyzbar(img)]
def main() -> None:
p = argparse.ArgumentParser(description="Decode QR codes from an image path or URL")
p.add_argument("source", help="Local file path or https:// URL to image")
args = p.parse_args()
img = load_image(args.source)
if img is None:
print("Could not load image.", file=sys.stderr)
sys.exit(2)
results = decode_qrs(img)
if not results:
print(
"No QR code decoded. Common causes: strong glare, heavy blur, or severe "
"curvature — try filling the frame with the code, diffuse light, straight-on angle."
)
try:
__import__("pyzbar")
except ImportError:
print(
"This Python environment has no pyzbar: run `pip install pyzbar` here "
"(needs system lib libzbar0 on Debian/Ubuntu). Then retry.",
file=sys.stderr,
)
else:
print(
"pyzbar is installed but decoded nothing — try cropping to the QR, "
"or install libzbar0 if decoding fails silently.",
file=sys.stderr,
)
sys.exit(0)
for i, (text, _) in enumerate(results, start=1):
print(f"--- QR #{i} ---")
print(text)
if __name__ == "__main__":
main()