Code readingHow-to

Read multiple QR codes in one image and pick the right one

Decode multiple QR codes in one image with a multi-result call, then pick the right one by payload or position, with a worked Swiss payment-slip example.

LumaSpec Vision10 min read Share on LinkedIn

On this page 10 sections
  1. 01Why your decode returns the wrong code first
  2. 02What you need and the build order
  3. 03Step 1: decode every symbol in one pass
  4. 04Step 2: filter the result list by payload
  5. 05Step 3: confirm the pick by position
  6. 06Worked example: the payment QR on a two-code Swiss invoice page
  7. 07The same selection on a fixed code reader
  8. 08When rotate-and-reread is still acceptable
  9. 09Verify the selection before it runs unattended
  10. 10Questions

Two QR codes sit on one page and the decode hands back the wrong one. A single-result call returns whichever symbol its detector met first, not the one your process needs. The fix is not to rotate the image and read again. Decode once with a multi-result call, keep the symbols that qualify by payload, and confirm the pick by position. The same selection logic runs on a fixed code reader set to return every code it finds. You will leave with the calls to use, a worked example on a two-code Swiss invoice page, and the checks that keep the pick correct when the layout changes.

Why your decode returns the wrong code first

A barcode detector sweeps the image in its own search pattern, and the order in which it meets symbols is an internal detail. It is not page order, and it can change between library versions, between firmware builds, or when a code moves a few millimetres. On a page holding a payment slip and a stray marketing code, the single-result call can return either one, and a different render DPI can flip the answer. Code that takes the first result passes the demo and fails in production.

When several codes are present and none decodes at all, that is a different fault: the guide on multiple QR codes that refuse to read walks through isolating image and search faults first.

What you need and the build order

Three inputs before any code:

  1. Sample pages for every variant. Both codes present, stray code absent, layout shifted, and a page with two qualifying codes if that can ever happen. The rule you write must survive all of them.
  2. A decode path that returns every result. The multi-result call in your barcode library, or a fixed code reader configured to return all codes per frame.
  3. The qualifying rule, written down first. A payload signature, a region, or both — decided from the documents and the layout, not from the result order.

Step 1: decode every symbol in one pass

Barcode libraries expose a single-result call and a multi-result call beside it. In one widely used open-source library the pair is DecodeImageFile and DecodeImageFileMultiple; the multi-result variant returns a list, and each entry carries the decoded text plus the symbol's corner points. Restrict the accepted formats before decoding: in the same library, options.PossibleFormats takes entries such as BarcodeFormat_QR_CODE, and a QR-only filter cuts false hits from 1D codes and graphics.

options.PossibleFormats = ["QR_CODE"]             ' format filter: QR only
results = DecodeImageFileMultiple("page.png")     ' list of results, one pass
chosen  = none                                    ' type: decode result
for r in results:                                 ' r.text, r.corner points
    if left(r.text, 3) == "SPD":                  ' payload filter (Step 2)
        chosen = r
print(len(results))                               ' test: prints 2 on the sample page

Option names beyond the two documented calls are illustrative. Run the call on the two-code sample and check the count first: two results means the detector sees both symbols and selection can begin. One result means the second code is not being detected — check its size and damage before blaming the filter.

1D codes need a different technique: the guide on reading multiple barcodes in one image shows iterative decoding, where the same frame is decoded again with the found codes masked out. QR rarely needs that, because one multi-result pass already returns every symbol.

Step 2: filter the result list by payload

Content is the strongest filter, because a rule on the decoded text survives any layout change. Swiss QR payment slips give the case a clean signature: a QR-bill payload begins with the three letters SPD, so a prefix test on each result's text keeps the payment symbol and drops an ordinary URL code printed elsewhere on the page. Where the full payload is known and stable, match it exactly, or check its version and checksum fields.

If two symbols can pass — two genuine payment slips on one document, say — let position break the tie: take the lower-left one, or the one carrying the expected account number. When a single result survives the filter, judge it before acting: the guide on barcode read confidence explains how contrast, module size and angle weigh into whether a decode deserves trust.

Step 3: confirm the pick by position

When payloads look alike — two labels printed from one template, for example — position replaces content. Every multi-result entry reports corner points, so compute each symbol's centre and test it against the region where the qualifying code must sit:

cx = (x_min + x_max) / 2 cy = (y_min + y_max) / 2 accept when cx < W/2 and cy > 0.7 × H

W is the image width in pixels and H its height; the 0.7 bound comes from your layout, as the worked example below shows. Locating finder patterns before a full decode, as the guide on finder-pattern detection describes, also rejects clutter cheaply.

Worked example: the payment QR on a two-code Swiss invoice page

The case: one A4 page carrying a Swiss QR payment slip and a stray marketing code, both decoded in one pass.

Inputs. The page renders at 300 dpi: 210 mm ÷ 25.4 mm/in = 8.27 in, × 300 px/in = 2480 px wide; 297 mm ÷ 25.4 = 11.69 in, × 300 = 3508 px tall. The QR-bill layout standard fixes the payment part at the bottom 105 mm of the page, with the 55 mm symbol inset 5 mm from its left and top edges.

Rule in words. Accept a symbol only when its payload starts with SPD and its centre lies in the left half of the page and below 70% of the page height.

Arithmetic. Payment-part top edge: 297 − 105 = 192 mm. Symbol centre: 5 + 27.5 = 32.5 mm from the left, 192 + 5 + 27.5 = 224.5 mm from the top. In pixels: 32.5 ÷ 25.4 × 300 = 384 px; 224.5 ÷ 25.4 × 300 = 2652 px. Thresholds: 2480 ÷ 2 = 1240 px; 0.7 × 3508 = 2455 px.

Result. The payment code's centre (384 px, 2652 px) passes both tests, with 1240 − 384 = 856 px of margin on x and 2652 − 2455 = 197 px on y. The 55 mm symbol measures 55 ÷ 25.4 × 300 = 650 px across — several pixels per module, so decoding stays comfortable at this DPI.

Decision. Above y = 2455 px nothing is accepted, so a marketing code added anywhere in the upper 70% of the page can never steal the read. Below it, the SPD prefix does the work, and a second genuine payment slip resolves through the lower-left rule. On the two-code page the two filters leave exactly one symbol. If your template differs from the standard layout, measure once from a rendered sample and re-derive the thresholds the same way.

The same selection on a fixed code reader

On the line, the two filters run inside the reader. Fixed code readers offer a multi-code mode that returns every symbol found per frame, each with content and position, and most accept one or more read regions so the detector only looks where the qualifying code can sit. Mask the region, apply a payload test on the result strings in the PLC or PC, and no ambiguous answer reaches your process.

Throughput decides when several codes ride one part. The LS-CRD-0028 lists 60 codes/s at 30 fps from its 2560 x 2048 global-shutter sensor with an 8 mm lens — a multi-symbol rate printed on the data sheet.

Fixed code readers2560 x 2048 fixed code reader, 30 fps, 60 codes/s, 8 mm lens (LS-CRD-0028)LS-CRD-0028
Interface
Fast Ethernet
Resolution
5 MP
Sensor format
1/1.7"
Shutter
Global
$920.00View part

For a compact reader at a fixed, close layout, the MV-ID3013PM-12M-WBN pairs a 12 mm lens with a 1.3 MP global-shutter sensor over Fast Ethernet.

Fixed code readersHikrobot MV-ID3013PM-12M-WBN 12mm focal length 1.3 MP Compact Industrial Code ReaderMV-ID3013PM-12M-WBN
Interface
Fast Ethernet
Resolution
Up to 3 MP
Shutter
Global
Lens mount
M12
$1,979.00View part

When codes sit wide apart in the frame, the MV-ID813M-05S-WBN-SR brings a 4.9 mm focal length, built-in white light and a GigE interface at 1.3 MP.

Fixed code readersHikrobot MV-ID813M-05S-WBN-SR 1.3 MP resolution, 4.9 mm focal length, White light, SR, GigE, Static cableMV-ID813M-05S-WBN-SR
Interface
GigE
Resolution
Up to 3 MP
Lens mount
M10
$719.00View part

Browse the fixed code readers or the camera finder to filter by resolution, interface and mount. When the same station also inspects the part, a smart camera runs the code read and the inspection on one device. The guide on OCR for multiple text strings in one image covers the same one-image-many-objects selection for text, and the USB first-connection checks get a reader talking before you tune the read.

When rotate-and-reread is still acceptable

Rotating the image 90° and decoding again changes which symbol the detector meets first, which is why the trick spreads: on pages where at most one code is legible, it lifts the read rate for a few lines of code. It does not scale. Each orientation is a full decode pass, so four rotations cost up to four passes per page, and on a genuine two-code page a rotated pass can still return the stray code first.

Keep rotation only as a fallback for a library that offers no multi-result call, and gate every pass through the payload test so a qualifying read stops the loop immediately — never rotate past a hit. The permanent fix is the multi-result call plus the selection rule above.

Verify the selection before it runs unattended

  • Assert the count on every sample variant: two codes present, stray absent, layout shifted. A count change is the first sign the page changed.
  • Assert exactly one qualifying symbol after both filters, and log the rejected payloads, not just the winner, so a new stray code shows up in the record.
  • Test the tie: a second payload-matching symbol must resolve through the position rule, never through the list order.
  • Re-run the set whenever the template or label printer changes; a rule that survives a moved code is the one you wrote down.
  • Time it: full-page decode against the cropped region, and crop when the timing margin says so.

To pick hardware for the line-side version, configure a vision set-up step by step, or start from the fixed code readers category above and filter by your interface. The chosen result then travels onward: routing a barcode reader result to robot job selection shows the hand-off to the controller. More decode guides sit on the code-reading topic page, and if a page defeats both filters, contact us with the image and read set-up and we will work through it with you.

Questions

How many QR codes can one image or one reader return?

Check the multi-code figure on the data sheet instead of assuming one. The LS-CRD-0028 lists 60 codes/s beside its 30 fps frame rate, which covers several symbols per frame on a moving line. In a document workflow a multi-result decode returns every detectable symbol in one pass; the practical limit is symbol size and damage, not a fixed cap.

Why does my multi-result decode still return only one QR code?

Three usual causes, in order: the single-result call ran instead of the multi-result variant; the format filter does not include QR, so the second symbol is skipped; or the second symbol is too small or damaged for the detector at that render DPI. Log the result count first, widen the format filter second, then zoom into the missing code and count pixels per module.

What marks a Swiss QR payment slip payload?

A Swiss QR-bill payload begins with the letters SPD, followed by the version and the payment data, so a three-character prefix test on the decoded text separates the payment symbol from any ordinary URL code on the same page. Keep the position rule as the second filter, because a genuine second payment slip also starts with SPD and only the region test can break that tie.

Can a reader be told where to look, instead of filtering afterwards?

Yes. Most fixed code readers accept one or more read regions per image, so the detector only runs where the qualifying code sits and stray codes elsewhere never enter the result list. Regions speed the read up and remove ambiguity at the source; keep the payload test as well when a second code could ever wander into the region.

Is the first returned result ever the top-left code?

Not reliably. Detector order depends on the scan pattern inside the library or reader firmware, so the first result can be any symbol in the frame, and it can change with a library update, a firmware update or a moved code. Treat the list as unordered and always select by payload or position, never by index.