Возникла производственная необходимость научиться читать QR-коды. Из спортивного интереса было решено написать алгоритм самостоятельно. К тому же, те несколько сторонних сканеров, что я попробовал, предъявляли очень жесткие требования к расположению картинки и ее качеству. В идеале, хочется, что б сканировалась картинка под любым углом, в любом месте, искаженная перспективой и переменным освещением. Примерно, как сканируются штрих-коды в супермаркетах. Думал, справлюсь быстро, но не тут то было, вторую неделю ковыряюсь.
Собственно, самая интересная часть - это найти паттерн на изображении и привести его к квадратному нормализованному виду. При этом, хочется, что б работало все на мобильном телефоне - т.е. имеют место быть определенные требования по производительности.
В виду отсутствия специализированных знаний, решил атаковать в лоб. Для начала, решил победить двухмерную задачу. Взял примерно следующий паттерн с альфа-каналом:
Ввел четыре степени свободы - два смещения, поворот и масштаб и функцию ошибки, как попиксельную сумму модулей разницы яркости. Получившаяся четырехмерная функция оказалось, имеет очень локализованный глобальный минимум и несколько ложных локальных на фоне сплошного мусора. Различные спуски отказались работать. Работает перебор гиперкуба первым проходом с последующим уточнением методами спуска или дихотомией в локальных минимумах. Но это очень медленно, даже для двухмерной задачи.
Затем решил попробовать поискать отдельно квадраты-маркеры тем же методом:
Получилось немного быстрее, но недостаточно, т.к. первый проход все равно нужно погулять по большому объему. Плюс возникла подзадача приведения трех решений к одному, усложненная соответсвием истинных опорных квадратов часто локальным минимумам, а не глобальным.
Затем заметил, что середина каждого квадрата представляет собой симметричную конструкцию по обоим срезам вдоль X/Y, вроде такой:
Последовательный поиск подобных паттернов вдоль обеих осей позволил уменьшить пр-во функции ошибки до двух измерений. А с учетом симметричности паттерна и его специфики - до одного. Качество упало, но не критично.
Для очистки результатов поиска от шумов придумал влоб скрестить результаты двух проходов по обеим осям и "отбросить" непересекающиеся на общей двухмерной картинке отрезки, а затем прогнать результаты через медианный фильтр, чтобы удалить случайные полоски.
Скорости сразу поднялись, ищет опорные квадраты относительно неплохо (картинка кликабельная)
Открытые вопросы, до которых еще не добрался, но которые уже созрели:
- как дешево выполнить нормализацию сигнала (оцифрование в два уровня), особенно с учетом того, что черный (да и белый в принципе) может быть градиентом и перелазить за статичную середину 0x80 (в примере захардкожено 0xC0)
- как развернуть по позициям трех опорных квадратов центральную проекцию квадратного рисунка в общем случае (с учетом искажений перспективы), что б 'на костылях' относительно неплохо работало, или проще поискать четвертый маленький опорный квадрат, на базе позиций трех основных? (он умеет сливаться с данными кода, на глаз я его вообще не сразу заметил)
- опорные квадраты ищутся в своих областях картинки, потому перевернутые исходники и далеко уехавшие не работают. Хотелось попробовать последовательно замалевать каждый квадрат после обнаружения и сканировать всю область, но положение двух квадратов на одной оси все портит.
- непонятно, можно ли что-нибудь сделать с размыванием картинки при ее движении. Специфика многих мобильных камер такова, что яркие пиксели засвечивают темные и при движении и вышеописанный метод плывет. Тут есть идеи как попробовать обойти.
Может быть, кто-нибудь имеет определенный опыт и сразу скажет, куда копать во избежание попыток изобрести велосипед с квадратными колесами?
P.S. Про динамическое программирование и нейронные сети имею представление, но лет на 15 устаревшее. На мой взгляд, и то и другое тут будет слишком медленным.