DEV Community

Xiao Ling
Xiao Ling

Posted on • Originally published at dynamsoft.com

How to Build a Most Accurate Barcode Reader for Low-Resolution, Low-Light, and Glare Images

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?"

Low-resolution, low-light, and glare barcode test set

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, and dynamsoft-capture-vision-bundle.
  • Get a 30-day free trial license for Dynamsoft Barcode Reader.

Install the dependencies:

pip install -r requirements.txt
Enter fullscreen mode Exit fullscreen mode

Set your production license key before running the benchmark:

set DYNAMSOFT_LICENSE_KEY=YOUR-LICENSE-KEY
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"))
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

low resolution glare barcode benchmark

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.

Source Code

https://github.com/yushulx/python-barcode-qrcode-sdk/tree/main/examples/official/low-resolution-glare-barcode-reader

Top comments (0)