A reader that clears 99 of 100 codes on the bench can fall to the low nineties once labels arrive scuffed, laminated or faintly printed. This guide is for the engineer closing that gap on a fixed installation: it orders the decode settings that matter, sets the pixel density the optics must deliver, applies the one filter that rescues a damaged symbol, and runs the before-and-after test that proves a change helps without breaking the codes that already read. The sequence follows the way you meet the problem: decoder flags first because they cost nothing, optics second because pixels per module cap everything, image repair third, print quality last — which is where most 'unreadable' codes are actually made. One symbol per frame, any of four right-angle orientations, speed explicitly secondary.
Which decoder settings raise the read rate first?
Turn on the try-harder and downscale flags, run the read twice with the two binarizers, and leave the thresholds alone: the shipped defaults are already chosen to maximize detection, so these three switches are the only knobs worth touching first.
Each switch trades decode time for detection rate. On a one-code-per-part station with no speed limit, that trade is free, so take all of it. Every option lives in the decoder's hint set (DecoderHints), and the order below is also the order of effort:
- Set
tryHarder. It extends the search per frame and is the difference between a demo-style quick pass and a reader-grade attempt. - Set
tryDownscale. Oversampled frames and very large modules can hide a symbol at native scale; a downscaled copy gives the decoder a second size to search. - Touch
downscaleThresholdanddownscaleFactoronly if your frames are unusually small or large. The defaults are 500 and 3; lowering them helps corner cases, mostly on matrix codes. - If DataMatrix is in your symbology mix, build the decoder against a C++20 toolchain — the project's issue #189 documents the detection gain that depends on it.
If the frame carries clutter around the symbol, crop an ROI to the code region as well; the ROI tuning guide shows the reliability and frame-rate arithmetic.
Which binarizer order wins on low-contrast codes?
Run the decode twice: first with the LocalAverage binarizer, then with GlobalHistogram, and count either result as a read; the two thresholds fail on different images, and the split shows on matrix codes such as QR and DataMatrix, not on linear barcodes.
A binarizer is the pass that turns grey pixels into black and white before pattern search. LocalAverage thresholds each region against its neighbourhood, so it survives shadow, curvature and a light gradient across the symbol. GlobalHistogram thresholds the whole frame with one histogram split; it is faster and can win where the local pass merges a faint code into its background. Because they fail on different images, the retry recovers codes the first pass loses.
Low contrast often has a physical cause worth fixing at the optics: hood out stray light with the checks in machine vision stray light: diagnose low image contrast, and change lighting geometry against glare as in reading QR codes on reflective and curved surfaces.
How many pixels per module does a damaged code need?
Size the optics for 3 to 5 pixels per module, with 2 px per module as the hard floor any decoder flag needs; below that the module structure is gone from the image and no setting recovers it.
One module is the smallest black or white square in a matrix code, or one bar-plus-space pair in a linear one. At 3-5 px per module the structure survives focus error, motion blur and uneven light with margin. The 2 px floor is a limit, not a target: any damage, blur or glare tips such a frame below the detection threshold. Above roughly 5 px per module nothing more is gained, and heavily oversampled frames are exactly what the downscale pass exists for — see downscaling before barcode decoding: when large modules fail.
Worked example — sizing the field of view to the smallest module in the mix:
- A version 1 QR code has 21 modules per side; printed 20 mm wide, one module measures 20 / 21 = 0.95 mm.
- Target 4 px per module: the sensor must deliver 4 / 0.95 = 4.2 px/mm.
- A 2560 px wide sensor then covers 2560 / 4.2 ≈ 610 mm. For a 20 mm QR, resolution is never the limiting factor; contrast and damage are.
- Direct part mark, 0.15 mm modules: 4 / 0.15 = 26.7 px/mm, so the same sensor must frame 2560 / 26.7 ≈ 96 mm or tighter.
Decision: a 20 mm label QR reads across a 600 mm field; a 0.15 mm DPM mark needs the field tightened to about 96 mm on this sensor. Frame to the smallest module you must read, not to the part.
Can preprocessing rescue a damaged code?
Yes, but only one targeted operation at a time: in the failure set behind this guide, a single erosion pass recovered one washed-out QR and widening a broken finder square by a pixel or two fixed another, while blanket contrast filters that fix one symbol break the rest.
Sort the failure first, because the two modes take opposite repairs:
- Not found at any rotation. The finder patterns — the corner squares that locate a QR — are broken or glare-covered. Repainting a finder's missing corner and widening the shortened 3x3 center square made one sample detectable as it stood.
- Found, then checksum error. Too many data modules have washed out. Filtering cannot recreate data that never reached the sensor; go to the marking (next section).
When a pass does help, morphology beats contrast sliders. Erosion shrinks white features one pixel per pass and re-opens modules that bleed or glare has closed; a single pass recovered a washed-out QR outright in the failure set. Dilation is its opposite, for dark modules that have thinned. Run one pass, re-decode, stop — a second pass eats thin modules and lands below where you started.
Keep the diagnosis structured when several codes fail at once: separate image faults from search faults as in multiple QR codes not reading: isolate image and search faults, and locate the finder patterns explicitly with the method in QR code finder pattern detection: locate before decoding.
Why does a fixed reader read what a phone cannot?
Because a fixed reader controls what a phone cannot: working distance, focus, matched illumination and the aggressive decode flags, so the same symbols read first pass on the reader and fail on the higher-resolution phone camera.
In the side-by-side test behind this guide, high-density QR codes that a phone app could not decode read on a simple USB reader within the first second, on a lower-resolution sensor. Three differences explain it. Working distance and focus are fixed, so there is no autofocus hunt and no handheld perspective; in one failing sample, a slight geometric distortion alone prevented detection. Illumination is matched to the symbol instead of ambient light. And the reader runs the aggressive flags — tryHarder and tryDownscale were off in the phone app — while its decoder also lacked a proper analysis of the alignment patterns inside the symbol, for which there is no trivial software fix.
The decision: judge candidate codes on the reader you will deploy, mounted at its production working distance and square to the symbol. A phone-app failure predicts nothing about line performance. The fixed code readers category and the camera finder are where the candidates live.
Which reader and light suit damaged, low-contrast codes?
Pick the sensor for the module size, the light for the surface: a 12 MP reader for dense or tiny modules, a 5 MP global-shutter reader for general labels, a 1.3 MP white-light reader for close standard codes, and a polarizing cover whenever the label is laminated or shiny.
Three readers cover the common cases:
- Interface
- GigE
- Resolution
- Up to 3 MP
- Interface
- Fast Ethernet
- Resolution
- 5 MP
- Interface
- GigE
- Resolution
- 12 MP
The MV-ID5120RM-08M-RBN brings 12 MP over GigE with an M12 mount. It is the pick for dense DataMatrix and direct part marks, holding 4 px per module on 0.15 mm marks inside a 96 mm field per the worked example.
The LS-CRD-0028 pairs a 2560 x 2048 global-shutter sensor with an 8 mm lens and reads up to 60 codes/s. It is the general-purpose choice for printed labels a few tens of millimetres across.
The MV-ID813M-05S-WBN-SR combines a 1.3 MP sensor, a 4.9 mm focal length and built-in white lighting on GigE, which removes one light to aim and wire at close range.
On laminated, glossy or film-wrapped labels, add polarization before touching any setting: it removes the specular glare that defeats both binarizers. The MV-SC3000-C-HP is a square semi-polarizing lens cover that sits in front of the optics with no bracket or light change.
Wavelength is the other no-cost contrast gain. Red and orange inks reflect red light but absorb blue, so blue illumination turns a faint red print into a dark-on-light symbol — often the difference between a checksum error and a clean read. The MV-IDA-L-8-47-41-B drives 8 blue LEDs as a compact light board for exactly this job.
Both accessories come from the code reader accessories and lighting accessories pages, alongside extension cables and diffusers.
When is the print the fault, not the reader?
If the decoder finds the symbol but rejects the data — a checksum error — the modules themselves are unreadable, and no decoder flag or filter will recover them; the fix is in the marking: ink, contrast, cure or the code's printed grade.
Sort every failure into one row before changing settings; each row has a different owner:
| What you see | Where the fault lives | What moves the decode rate |
|---|---|---|
| Not found at any rotation | Finder pattern damaged or glare-covered |
tryHarder + tryDownscale, the two-binarizer sequence, one erosion pass, polarized or diffuse light |
| Found, then checksum error | Data modules washed out | Marking fix: darker ink, larger quiet zone, verified grade — no decoder flag helps |
| Reads on the bench, fails on the line | Stray light, exposure drift, trigger timing | Shield the optics, strobe the light, verify the exposure window |
| Phone app fails, reader reads | Nothing | Ship it |
Checksum errors cost the most wasted days: one repaired finder pattern in the failure set still left a checksum error, because too many data modules had washed out. Escalate to the marking process instead. Keep the quiet zone clear — four modules on every side of a QR; the geometry is in crop barcode images safely while preserving quiet zones — and ask the supplier for a graded verification report as in barcode reader verification for label inspection. Where a read is marginal, apply the criteria in barcode read confidence: how to judge whether a decode is trustworthy before the change reaches production.
How do you prove the new decode rate before rollout?
Build a regression set before you change anything: keep every failed frame, add a few hundred known-good frames, and accept a tuning change only when the failure set reads more often and the good set still reads 100 percent.
Then work in this order:
- Log raw frames — untouched by any filter — for every failed read across one shift. Fifty to a hundred failing frames make a workable tuning set; three images do not.
- Add at least 200 known-good frames from the same station, so collateral damage is visible.
- Encode the current settings as the baseline and record pass or fail plus decode time for every frame.
- Change one variable at a time: flags, then the binarizer sequence, then optics, then a single preprocessing pass.
- Re-run the full set after each change. Keep a change only when the failed-set rate rises and the good set still reads 100 percent.
- Freeze the winning configuration and back it up before anything else drifts; the reader configuration backup procedure covers the save-and-restore drill.
Expect the flags and the binarizer sequence to be the free wins, the pixel-density decision to be the durable one, and print quality to hold the last few percent. The decisions by case: a symbol never found gets tryHarder + tryDownscale, the two-binarizer sequence and, if the finder is visibly broken, one erosion pass. A symbol found but checksum-rejected goes back to the print specification, not the decoder. An oversampled frame gets the downscale flags or a cropped ROI. A 0.15 mm direct part mark gets its field of view tightened to about 96 mm on a 2560 px sensor.
More decode-rate and preprocessing work sits on the code-reading topic page. If the readers, covers or light boards above fit the station, start from the build-a-system configurator; for a second pair of eyes on your FOV arithmetic, contact the team.
Questions
Does the try-harder flag slow down decoding?
It adds search passes, so a failing frame takes longer to reject, and the downscale pass adds a second full decode attempt. On a fixed reader handling one code per part, the extra time per bad frame rarely breaks the cycle budget; measure it on your largest frames, because decode time grows with image area.
Do I need a special setting for codes presented at 0, 90, 180 or 270 degrees?
For 2D symbols, no: matrix decoders search all four 90-degree orientations in every pass, so rotation costs a little time but no setting. For 1D codes, keep the mount aligned with the code or enable the decoder's rotation search; a linear barcode lying at 90 degrees to the scan direction will not decode without it.
Does a higher-resolution reader always raise the decode rate?
No. Above roughly 5 pixels per module, extra resolution adds decode time without adding readability, and very large modules can themselves fail, which is why downscaled decoding exists. Buy resolution for small modules or long working distances, not for codes that already cover dozens of pixels per module.
What print quality should I demand from the label supplier?
Ask for verification to ISO/IEC 15415 for 2D codes (ISO/IEC 15416 for linear) and set a contract grade: 2.5 (B) or better leaves decode margin, while 1.5 (C) symbols are the ones that start failing when lighting drifts or ink fades. Grade under lighting close to what the reader sees on the line.




