Standalone benchmark comparing old (zbar, qrdetect) and new (zedbar) QR code readers used in VxSuite ballot scanning.
- Rust toolchain (stable)
- Node.js 20+
libzbar-dev(for the zbar-rust crate)
# Rust benchmark
cargo build --release
# Node benchmark
cd node && npm install && cd ..# Rust (rqrr, zbar, zedbar) — uses rayon for parallelism
./target/release/qr-benchmark /path/to/ballot/images/
./target/release/qr-benchmark --limit 100 /path/to/ballot/images/
# Node (quirc, qrdetect, zedbar-wasm)
node node/benchmark.mjs /path/to/ballot/images/
node node/benchmark.mjs --limit 100 /path/to/ballot/images/Both accept --output <file> to write detailed per-image JSON results (defaults
to rust_results.json / node_results.json).
Benchmarked 5 QR code readers on 2,472 real ballot PNG images from a TRR CVR export. Each image was cropped to bottom-left and top-right corners (width/4 square) matching production detection logic, then tested with each reader.
| Reader | Success | Fail | Rate | Mean | Median | Min | Max | p95 |
|---|---|---|---|---|---|---|---|---|
| rqrr | 1,968 | 504 | 79.6% | 3.26ms | 2.87ms | 2.44ms | 21.42ms | 4.84ms |
| zbar (old) | 2,429 | 43 | 98.3% | 1.71ms | 1.43ms | 1.33ms | 16.77ms | 2.71ms |
| zedbar (new) | 2,429 | 43 | 98.3% | 3.08ms | 2.57ms | 2.01ms | 18.77ms | 6.37ms |
| Reader | Success | Fail | Rate | Mean | Median | Min | Max | p95 |
|---|---|---|---|---|---|---|---|---|
| quirc | 2,001 | 471 | 80.9% | 1.37ms | 1.32ms | 0.78ms | 8.43ms | 2.28ms |
| qrdetect (old) | 2,429 | 43 | 98.3% | 2.57ms | 2.34ms | 1.80ms | 8.33ms | 3.88ms |
| zedbar-wasm (new) | 2,429 | 43 | 98.3% | 4.43ms | 4.10ms | 3.30ms | 18.33ms | 6.64ms |
| Comparison | Old succeeded, new failed | New succeeded, old failed | Data mismatches |
|---|---|---|---|
| zbar vs zedbar (Rust) | 0 | 0 | 0 |
| qrdetect vs zedbar-wasm (Node) | 0 | 0 | 0 |
- Accuracy is identical. zedbar detects the exact same 2,429/2,472 images as zbar. zedbar-wasm detects the exact same 2,429/2,472 images as qrdetect. Zero discrepancies in either direction, zero data mismatches.
- Performance is ~1.8x slower. zedbar median is 2.57ms vs zbar's 1.43ms (Rust native). zedbar-wasm median is 4.10ms vs qrdetect's 2.34ms (Node). Both are well within acceptable bounds for ballot scanning.
- rqrr and quirc remain weaker. Both miss ~20% of ballots, confirming why the fallback chain (rqrr then zbar/zedbar) is necessary.
- Rust binary:
rqrr 0.7.1,zbar-rust 0.0.23,zedbar 0.2.1viaimagecrate, parallelized withrayon - Node script:
node-quirc 2.3.0,@votingworks/qrdetect 1.0.1,zedbar 0.2.1(WASM) viasharpfor image loading - Cropping logic replicated from
detect.rs:get_detection_areas—crop_size = width/4, bottom-left then top-right corners - Timing covers only the detection call, not image loading or cropping