Low-resolution barcode images, low-light warehouse photos, and glare from glossy labels are common causes of barcode scanning failures. The practical question is not only "which barcode reader is most accurate?", but also "when should I tune the DBR template, and when is the image too degraded to recover?"
This tutorial builds a reproducible Python benchmark using Dynamsoft Barcode Reader through the Dynamsoft Capture Vision bundle. It programmatically generates Code 128 and QR code images, degrades them with low resolution, low light, glare, blur, inverted polarity, and inverted low-light conditions, then compares the built-in PT_READ_BARCODES preset against a focused custom template.
What you'll build: A Python benchmark that creates difficult barcode and QR test images, runs Dynamsoft Barcode Reader with the default preset and a focused custom template, writes per-image JSON results, and produces an HTML report with bar charts for accuracy and speed.
Demo Video: Low-Resolution and Glare Benchmark
Prerequisites
- Python 3.8 or later.
-
pillow,qrcode,python-barcode,opencv-python, anddynamsoft-capture-vision-bundle. - Get a 30-day free trial license for Dynamsoft Barcode Reader.
Install the dependencies:
pip install -r requirements.txt
Set your production license key before running the benchmark:
set DYNAMSOFT_LICENSE_KEY=YOUR-LICENSE-KEY
The sample also includes a public trial-key fallback for quick local testing.
Step 1: Generate Low-Resolution, Low-Light, and Glare Test Images
The sample project lives in samples/low-resolution-glare-barcode-reader/. Run the generator first:
python generate_test_images.py
The script creates a clean Code 128 inventory label and a QR warehouse bin label, then applies low-resolution scaling, low-light noise, glare, blur, inverted polarity, and inverted low-light conditions. The core degradation functions are real code from generate_test_images.py:
def low_resolution(img: Image.Image, factor=0.24) -> Image.Image:
small = img.resize(
(max(1, int(img.width * factor)), max(1, int(img.height * factor))),
Image.Resampling.BILINEAR,
)
return small.resize(img.size, Image.Resampling.BILINEAR)
def low_light(img: Image.Image) -> Image.Image:
arr = np.array(img).astype(np.float32)
arr *= np.array([0.42, 0.43, 0.46], dtype=np.float32)
noise = np.random.default_rng(7).normal(0, 10, arr.shape)
arr = np.clip(arr + noise, 0, 255).astype(np.uint8)
out = Image.fromarray(arr, "RGB")
return ImageEnhance.Contrast(out).enhance(0.72)
def glare(img: Image.Image) -> Image.Image:
base = img.convert("RGBA")
overlay = Image.new("RGBA", img.size, (255, 255, 255, 0))
draw = ImageDraw.Draw(overlay)
w, h = img.size
for i in range(42):
alpha = max(0, 140 - i * 3)
bbox = (
int(w * 0.42) - i * 8,
int(h * 0.22) - i * 5,
int(w * 0.98) + i * 8,
int(h * 0.58) + i * 5,
)
draw.ellipse(bbox, fill=(255, 255, 255, alpha))
return Image.alpha_composite(base, overlay).convert("RGB")
def inverted(img: Image.Image) -> Image.Image:
return ImageOps.invert(img.convert("RGB"))
The generator writes:
-
assets/images/*.png: 14 generated test images. -
assets/ground_truth.json: expected barcode text for each image.
Step 2: Use the SDK Preset as the Default Baseline
The default baseline uses the SDK preset directly. It does not load a JSON file from the project:
run_profile("PT_READ_BARCODES preset", None, EnumPresetTemplate.PT_READ_BARCODES.value, image_paths, truth)
That matters for a fair comparison: "default" means the SDK's built-in PT_READ_BARCODES behavior, not a checked-in template file.
Step 3: Build a Fast Custom DBR Template
The fast custom template starts from the SDK preset settings, but only the custom template is saved as a project artifact. The sample uses a temporary file to read the SDK preset JSON, modifies two low-cost settings, and writes read-barcodes-fast-inverted.json.
def load_default_template() -> dict:
router = CaptureVisionRouter()
with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir) / "preset-settings.json"
err, msg = router.output_settings_to_file(EnumPresetTemplate.PT_READ_BARCODES.value, str(temp_path))
if err != 0:
raise RuntimeError(f"Failed to export default template: {msg}")
return json.loads(temp_path.read_text(encoding="utf-8"))
def build_fast_inverted_template() -> Path:
TEMPLATE_DIR.mkdir(parents=True, exist_ok=True)
template = load_default_template()
template["CaptureVisionTemplates"][0]["Name"] = "ReadBarcodes_FastInverted"
task = template["BarcodeReaderTaskSettingOptions"][0]
task["BarcodeFormatIds"] = ["BF_CODE_128", "BF_QR_CODE"]
set_stage_modes(
template,
"SST_TRANSFORM_GRAYSCALE",
"GrayscaleTransformationModes",
[{"Mode": "GTM_ORIGINAL"}, {"Mode": "GTM_INVERTED"}],
)
fast_path = TEMPLATE_DIR / "read-barcodes-fast-inverted.json"
fast_path.write_text(json.dumps(template, indent=2), encoding="utf-8")
return fast_path
The custom template changes only two things. First, it adds GTM_INVERTED so the reader can handle light modules on a dark background. Second, it restricts barcode formats to the two known symbologies in the test set: Code 128 and QR Code. The generated template is saved as templates/read-barcodes-fast-inverted.json; no default template JSON is kept in the project.
Step 4: Compare Default and Custom Templates
Run the benchmark:
python benchmark_dbr_templates.py
The script loads each PNG image, decodes it with the selected profile, and compares returned text against ground_truth.json:
def run_profile(name: str, template_path: Path | None, template_name: str, image_paths: list[Path], truth: dict) -> dict:
router = CaptureVisionRouter()
if template_path:
err, msg = router.init_settings_from_file(str(template_path))
if err != 0:
raise RuntimeError(f"Failed to load template {template_path}: {msg}")
per_image = []
correct = 0
total_ms = 0.0
for image_path in image_paths:
start = time.perf_counter()
items = decode_image(router, image_path, template_name)
elapsed_ms = (time.perf_counter() - start) * 1000
total_ms += elapsed_ms
expected = set(truth.get(image_path.name, []))
found = {item["text"] for item in items}
ok = bool(expected and expected.issubset(found))
if ok:
correct += 1
On the generated dataset, the output was:
PT_READ_BARCODES preset: 10/14 (71.43%), avg 46.19 ms/image
Fast custom template: 14/14 (100.0%), avg 21.73 ms/image
Recovered by Fast custom template: 4
- code128_inverted.png
- code128_inverted_low_light.png
- qr_inverted.png
- qr_inverted_low_light.png
The default preset decoded every standard difficult-image variant: clean, low-resolution, low-light, glare, and blur for both Code 128 and QR. The focused custom template added value on a specific failure class, inverted polarity and inverted low-light images, without turning on expensive search paths.


Top comments (0)