Почему художественный QR-код может не прочитаться, если поднести телефон вплотную
Все ожидают, что QR-код откажет, если он слишком мал, слишком далеко или слишком тускло освещён. У фото-кодов есть и обратный отказ: они прекрасно читаются с расстояния и перестают читаться, когда телефон подносят вплотную. Ниже объяснено почему — с цифрами того измерения, которое это обнаружило.
Как сканер решает, что считать тёмным
Прежде чем искать модули, читатель обязан превратить фотографию в чёрно-белое. Глобальный порог не выживает в реальном освещении — тень, легшая на один угол, зачернила бы половину кода, — поэтому большинство декодеров вычисляет порог локально. ZXing, открытая библиотека, от которой до сих пор происходит немалая часть сканирования штрихкодов, делит изображение на блоки 8×8 пикселей, считает среднее каждого блока, сглаживает его с соседними блоками и использует результат как порог для этих 64 пикселей. Пиксель темнее порога своего блока считается тёмным, остальные — светлыми. Для обычного QR-кода это почти бесплатно. Каким бы ни был размер блока, модуль сплошной, поэтому среднее блока либо близко к тёмному, либо к светлому, либо лежит между ними, когда блок попал на границу, и решение в любом случае простое. Схема надёжна, дёшева и широко скопирована — именно поэтому она и важна: декодер, с которым встретится ваш напечатанный код в поле, вполне может считать порог именно так.
Что меняется, когда модули состоят из дизеринга
В коде с дизерингом модуль не сплошной. Это небольшое принудительное ядро, окружённое пикселями, которые несут фотографию. Его среднее верно; его текстура неоднородна. Теперь посмотрим на дистанцию сканирования в единственной значимой единице — пикселях камеры на модуль. Отодвиньте телефон настолько, чтобы модуль занимал 5—8 пикселей: блок 8×8 будет перекрывать границы модулей. Локальное среднее — это примерно «данный модуль и его соседи», порог оказывается между тёмным и светлым, и код читается. Поднесите телефон так, чтобы модуль занимал 20 пикселей, и блок 8×8 целиком помещается внутрь одного модуля. Средним блока становится собственная локальная текстура этого модуля. Порог начинает следовать за дизерингом вместо модуля, и декодер восстанавливает крап, а не код. В самом отпечатке ничего не изменилось; изменилось лишь число пикселей матрицы, приходящихся на модуль, а вместе с ним — отношение между блоком декодера и модулем. Поэтому отказ является свойством дистанции, а не размера или освещения.
Цифра
Мы измерили это при разработке фоторежима SWAPQR: три длины полезной нагрузки, шесть типов изображений, обе полярности цвета, три положения баланса, семь смоделированных дистанций сканирования, декодирование двумя независимыми библиотеками. Граница получилась чистой. При примерно 20% принудительной доли модуля — положение с упором на фото — и при примерно 44% коды читались на 5—8 пикселях на модуль, а читатели класса ZXing становились ненадёжны вплотную, на 10—28 пикселях на модуль. При примерно 60% и выше они читались на любой дистанции. Как только более половины площади модуля сплошная, любой блок 8×8, попавший внутрь модуля, ложится в основном на сплошные пиксели, и эффект исчезает. Ниже этой доли — не исчезает, и никакая подстройка тонового крена не помогает, потому что локальный порог всё равно вычитает локальное среднее. В какую бы сторону вы ни кренили тон модуля, блок целиком внутри него измерит этот крен как свою базовую линию и вычтет его. Единственный работающий рычаг — доля модуля, которая сделана сплошной, ради чего регулятор баланса и существует.
Новые декодеры и почему на этом нельзя успокоиться
Новые декодеры справляются лучше. В той же серии измерений jsQR, использующий другую стратегию бинаризации, прочитал все конфигурации на всех дистанциях, а сканеры, встроенные в современные телефоны, ещё сильнее. Но «большинство телефонов справится» — не то же самое, что «справится любой читатель», а напечатанный код встречается с тем читателем, который на него направили: старый телефон, кассовый сканер, киоск, приложение с библиотекой, происходящей от ZXing. Поэтому положение баланса с упором на фото честно сообщает о своём компромиссе — студия отмечает, что современные телефонные сканеры читают его на любой дистанции, тогда как более старые и простые читатели могут спотыкаться вплотную, — и выбор точки на балансе зависит от того, кто и с какого расстояния будет сканировать код. Зависит он и от того, где телефон оказывается естественным образом: никто не держит телефон на вытянутой руке перед афишей, но к визитке его подносят вплотную все, и именно там фото-код и простой читатель могут встретиться.
Что с этим делать
Если код едет туда, где его сканируют с обычной дистанции — афиша, настольная табличка, полочный ценник, — положения с упором на фото годятся: на такой дистанции модуль занимает несколько пикселей камеры, и блоки перекрывают границы, как и должны. Если код едет туда, где телефон приложат вплотную — маленькая наклейка, визитка, экран, — сдвиньте баланс к «Коду», чтобы более половины каждого модуля стало сплошным, и эффект близкого сканирования исчезнет для любого класса читателей. В обоих случаях напечатайте образец и просканируйте его телефоном. Не снимок экрана на мониторе, а настоящий отпечаток, в настоящем размере, с той дистанции, с которой люди будут им пользоваться, и желательно не одним телефоном. SWAPQR показывает это предупреждение в самой студии, а не мелким шрифтом, потому что код, отказавший в поле, хуже обычного кода, который работает, и потому что, если поймать это до тиража, лечится оно движением ползунка, а не перепечаткой.
| Принудительная доля модуля | Далеко / обычно (5—8 пкс на модуль) | Вплотную (10—28 пкс на модуль) |
|---|---|---|
| ~20% (упор на фото) | Читается | Читатели класса ZXing ненадёжны |
| ~44% (середина) | Читается | Читатели класса ZXing ненадёжны |
| ~60% и выше (упор на код) | Читается | Читается |
| Любая настройка, класс jsQR и современные телефонные сканеры | Читается | Читается в той же серии измерений |
Часто задаваемые вопросы
Мой телефон читает фото-код с любого расстояния. К чему предупреждение?
Современные телефонные сканеры используют более сильную бинаризацию и прочитали все конфигурации в нашей серии. Предупреждение касается более старых и простых читателей, считающих порог блоками 8×8, с которыми напечатанный код всё ещё может встретиться.
Почему помогает отодвинуть телефон?
Дальше от кода на каждый модуль приходится меньше пикселей камеры, поэтому блок 8×8 декодера перекрывает несколько модулей и его среднее оказывается между тёмным и светлым. Вплотную блок попадает внутрь одного модуля и следует за текстурой дизеринга.
Поможет ли усиление тонового крена при съёмке вплотную?
Нет. Локальный порог вычитает локальное среднее, поэтому любой равномерный крен внутри модуля вычитается вместе с ним. Результат меняет только доля модуля, сделанная сплошной.
Какой баланс безопасен для визитки?
Сдвиньте его к «Коду», чтобы сплошным стало больше примерно половины каждого модуля. С этого момента читатели класса ZXing в нашем измерении читали код на всех дистанциях, а картинка остаётся видимой, просто бледнее.
Напечатайте квадрат один раз. Куда он ведёт — решите потом. SWAPQR создаёт статические QR-коды бесплатно, со всеми вариантами оформления, без аккаунта и без водяного знака. Платный план делает код динамическим: напечатанный квадрат остаётся прежним, а вы меняете его адрес назначения и видите, как часто его сканировали, по дням и по устройствам.
Создать QR-код в браузере