Skip to content

decoder: read mirror images by retrying on the transposed grid - #40

Merged
paulmillr merged 1 commit into
paulmillr:mainfrom
shreeve:mirror
Sep 21, 2026
Merged

paulmillr merged 1 commit into
paulmillr:mainfrom
shreeve:mirror

Conversation

@shreeve

@shreeve shreeve commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

A mirror image locates like any symbol, since finders and timing are symmetric, and fails only at the format and data bits. When the sampled grid does not decode, transpose it in place and try once more: the transpose of a mirrored symbol is the upright one. ISO/IEC 18004 expects a reader to read mirror images, and a camera plane can arrive mirrored with no metadata saying so.

Why

On iOS (WebKit), VideoFrame.copyTo of the rear camera hands the reader a plane that is a mirror image of the scene, and the frame reports neither flip nor rotation. Every finder is found, and every decode fails. We hit this in production with the Rip port of this decoder and confirmed it with ZXingReader on the same bytes (IsMirrored: true).

Checks

Real iPhone frames (2592x2160 NV12, rotated coded frame, fed to the decoder unchanged), plus a mirrored raster and a mirrored BoofCV photo:

input before after
encoded raster, mirrored timing ok
BoofCV blurred/image007, mirrored timing ok (same text as upright)
iPhone rear-camera frame 1 timing ok
iPhone rear-camera frame 2 timing ok

Upright inputs are unchanged. The retry runs only after a failed read, so successful decodes pay nothing; a 1080p frame with no symbol costs the same (blank 2.73 → 2.70 ms, noise 8.37 → 8.21 ms), and the worst case is one extra grid decode per failed projection. BoofCV accuracy is unchanged (57.5%, every category identical). One test added: decodeQR reads mirror images.

@paulmillr

Copy link
Copy Markdown
Owner

@copilot resolve the merge conflicts on this branch.

@paulmillr

Copy link
Copy Markdown
Owner

Copilot doesn't work. So this needs a rebase, since the first PR got merged.

A mirror image locates like any symbol, since finders and timing are
symmetric, and fails only at the format and data bits. When the sampled
grid does not decode, transpose it in place and try once more: the
transpose of a mirrored symbol is the upright one. ISO/IEC 18004 expects
a reader to read mirror images, and a camera plane can arrive mirrored
with no metadata saying so (iOS WebKit hands VideoFrame.copyTo a mirrored
rear-camera plane and reports neither flip nor rotation).

The retry runs only after a failed read. BoofCV accuracy is unchanged
(57.5%, every category identical); a 1080p frame with no symbol costs
the same (blank 2.73 → 2.70 ms, noise 8.37 → 8.21 ms); raster and photo
decodes are within noise.
@shreeve

shreeve commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

Rebased

@paulmillr
paulmillr merged commit e90a06b into paulmillr:main Sep 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants