A high-accuracy, fault-tolerant batch QR code scanner that combines a custom-trained YOLOv8 detector with a multi-engine decoder cascade and an image-repair variant tier — designed to squeeze maximum decode rate out of real-world, damaged, blurry, and low-quality QR images.
| Metric | Baseline | With Variant Tier |
|---|---|---|
| Total images | 3,686 | 3,686 |
| Detected (YOLO + fallback) | 3,675 | 3,675 |
| Detector accuracy | 99.70% | 99.70% |
| Decoded | 2,049 | 2,285 |
| Decoder accuracy | ~55.6% | 62.18% |
| Overall decode rate | 55.60% | 61.99% |
| Fallback saves | 23 | 23 |
| Variant saves | — | 221 |
The image-repair variant tier rescued +236 images (+6.5 accuracy points) over the baseline decoder cascade.
- Dataset: The YOLOv8m detector was trained on the dataset available here: [DATASET LINK]
- Model weights (
.pt): The trained YOLOv8m weights are available on my Ultralytics platform here: [[MODEL LINK]]
┌─────────────────────┐
Input image ────────► │ Stage 1: YOLOv8 │ custom-trained QR detector
│ conf=0.60, sz=640 │
└─────────┬───────────┘
│ ROI crop + 25% padding (white)
▼
┌─────────────────────┐
│ Decoder Cascade │ zxing-cpp → WeChat QR
│ (Tier 1: original) │ → pyzbar → OpenCV
└─────────┬───────────┘
│ fail
▼
┌─────────────────────┐
│ Variant Tier │ sharpen / CLAHE /
│ (Tier 2: repaired) │ upscale — cascade retried
└─────────┬───────────┘ per variant
│ fail
▼
┌─────────────────────┐
│ Full-image │ decodes entire image,
│ fallback │ catches YOLO misses
└─────────┬───────────┘
▼
detected/ | decoded/ | not_decoded/ | not_detected/
No single decoder wins everywhere. zxing-cpp is the strongest general decoder, but WeChat's CNN-based detector excels on small/distorted codes, pyzbar handles some edge orientations, and OpenCV's detector occasionally succeeds where all others fail. Running all four in priority order — stopping at the first hit — maximizes coverage at minimal cost.
Applied only after all four decoders fail on the original ROI:
| Variant | Purpose |
|---|---|
Unsharp mask (σ=2.0, 1.8/-0.8) |
Mild/general blur — primary workhorse |
Strong unsharp (σ=4.0, 2.2/-1.2) |
Heavy blur |
| Classic sharpen kernel (9-point) | Crisper module edges |
| CLAHE (clip 3.0) | Low contrast / bad lighting |
| CLAHE boost (clip 5.0, +30 brightness) | Dark images |
| 3× upscale → sharpen (ROIs < 300 px) | Tiny QR codes — upscale before sharpening |
Each repaired image is re-run through the full 4-decoder cascade.
YOLO boxes frequently crop the QR's mandatory white border (quiet zone), which is a top cause of decode failure. Every ROI is expanded by 25% padding (white, value 255) before decoding.
Repository layout:
.
├── runs/detect/akshint-varma/train/ # YOLOv8m training run
├── wechatfinal/ # WeChat QR model files
├── LICENSE
├── QR scanner.py # detection + decoding pipeline
├── README.md
├── requirements.txt
└── results.txt # full statistics summary
Folders generated by the script on each run:
beast/
├── detected/ # every image where a QR was found (YOLO or decoder)
├── decoded/ # successfully decoded (subset of detected/)
├── not_decoded/ # QR found but unreadable — saved WITH YOLO boxes
│ # drawn in red for visual failure analysis
├── not_detected/ # YOLO missed it AND decoders found nothing
└── results.txt # full statistics summary
Console output logs each image's verdict and decoded content in real time.
All paths and thresholds are at the top of QR scanner.py:
| Setting | Value | Notes |
|---|---|---|
dataset |
...\QR final\dataset |
input images (.jpg/.png/.jpeg) |
model_path |
...\runs\detect\...\best.pt |
custom YOLO weights |
| WeChat model files | detect.prototxt/.caffemodel, sr.prototxt/.caffemodel |
from opencv-contrib wechat_qrcode |
| YOLO params | conf=0.60, imgsz=640, max_det=61, iou=0.6 |
tuned for this dataset |
| Padding | 0.25 * max(box_w, box_h) |
quiet-zone restoration |
Requirements: Python 3.9, Windows (pyzbar), Visual C++ Redistributable
pip install -r requirements.txtrequirements.txt:
opencv-contrib-python==4.10.0.84
numpy==1.26.4
pyzbar==0.1.9
zxing-cpp==2.2.2
ultralytics==8.3.40
torch==2.1.2
torchvision==0.16.2
⚠️ Useopencv-contrib-pythononly — it includes base OpenCV plus thewechat_qrcodemodule. Installing bothopencv-pythonandopencv-contrib-pythoncan causecv2conflicts.
⚠️ NumPy is pinned to 1.26.4 — NumPy 2.x breaks older OpenCV/torch wheels.
The WeChat QR model files (detect.prototxt, detect.caffemodel,
sr.prototxt, sr.caffemodel) are already included in this repository in the
wechatfinal/ folder, so no separate download is needed. They originate from the
opencv_3rdparty wechat_qrcode branch.
python "QR scanner.py"The script clears and recreates the output folders on every run, processes
all images in the dataset folder, prints live per-image results, and writes
final statistics to results.txt.
Note: The current QR scanner code runs detection and decoding on the dataset itself. To run it yourself, just:
- Change the directory paths of the folders (dataset, output folders, and
wechatfinal/) at the top ofQR scanner.py.- Change the model path to your downloaded weights (download the model from my Ultralytics platform link above).
- Run the script.
Expected runtime: slow on low-core CPUs (2 cores) — the variant tier multiplies decode attempts per failure. Budget accordingly for full runs.
- Detection is solved (99.70%); decoding is the bottleneck.
- Upscale before sharpen for small ROIs — not the reverse.
- Padding ~25% recovers most quiet-zone failures; going higher (35%) shrinks the QR's relative size and can hurt small codes — A/B test it.
- Fallback saves are rare but real (~0.6%) — the full-image pass catches YOLO localization failures cheaply; keep it.
- Trim variants that never fire to reclaim runtime on weak hardware.
- On 2-core machines, adaptive variant selection (score blur/contrast, apply only 2 relevant variants) beats multiprocessing (~1.7× cap).
- Tilted QRs (mild rotation + blur) are the largest remaining recoverable failure class — rotation variants are implemented but disabled pending stabilization.
- Genuinely damaged codes (torn, missing corners) are an unreachable ceiling — expect a hard floor on decode rate.
not_detected/images (≈0.3%) are YOLO edge cases; augmentation-driven retraining could close the gap.
- Stabilize + re-enable rotation variants (±15°/±30°, rotate+sharpen)
- A/B test padding 0.25 vs 0.35 on the not_decoded set
- Adaptive variant selection by image quality metrics
- Fine-tune YOLO on not_detected failures
- Parallelize decoding across available cores
Built with: Ultralytics YOLOv8, zxing-cpp, WeChat QR Code (via OpenCV contrib), pyzbar, OpenCV.