OCR для определения номеров кредитных кaрт в iOS-приложении

IT копирайтер
Время чтения: 10 минут
Недавно мы начали разрабатывать прототип приложения, которое станет шагом к новому, более удобному поколению платежных систем — систем, использующих мгновенный ввод пользовательских данных. Идея приложения пришла во время работы с card.io, уникальным в своем роде проектом, разработки которого позволяют считывать номер и дату окончания срока действия кредитных карт через камеру мобильного устройства. Перспективы нас захватили: если добавить эту функцию в приложения для оплаты, насколько легче и быстрее станет процесс покупки. И мы решили разработать свой аналог.
На текущий момент сделано многое, но проект еще не закончен: на этапе локализации цифр мы столкнулись с определенными сложностями, над решением которых сейчас продолжаем работу.
Обзор проекта
Задача проекта — разработать алгоритм, позволяющий распознавать номера и даты банковских карт через камеру мобильных устройств.
Работа проходила в четыре этапа:
- захват изображения карты
- локализация — поиск предполагаемой области с текстом на изображении
- сегментация — нарезка локализованного текста на отдельные цифры
- распознавание цифр
Главная сложность задачи заключается в том, что, в отличие от распознавания текста, эмбоссированные цифры не контрастируют с фоном. Кроме того, на многие карты нанесены рисунки, пересекающие и «зашумляющие» текст. Новые карты, цифры на которых покрыты серебряной краской, читаются относительно неплохо. Старые, затертые — хуже.
Другая проблема — сильное влияние освещения: при резко направленном боковом свете исходная картинка получается одна, при рассеянном фронтальном — другая.
Для глаза и мозга человека эти особенности не являются проблемой, но, к сожалению, у нас нет лишних 100 миллиардов нейронов человеческого мозга, а для цифрового оптического распознавания эти особенности оказалось серьезным вызовом.
Положительные моменты тоже есть: небольшой диапазон символов — от 0 до 9, букв нет; на всех картах используется один и тот же шрифт, где каждая цифра хорошо отличаются от другой, т. е. семерка не похожа на единицу и т. д.
Реализация
Захват изображения карты: обработка кадров с камеры
GPU, OpenGL
При запуске приложения мы включаем камеру телефона и начинаем захватывать кадры. Используя CoreVideo OpenGLES Texture Cache, создаем из кадра текстуру OpenGL и передаем ее для обработки. Каждый захваченный кадр — цветное изображение (RGB) — отправляется на обработку.
Этап 1. Получаем изображение в градациях серого и сохраняем эти значения в альфа-канал получившейся текстуры.
varying highp vec2 textureCoordinate; precision highp float; uniform sampler2D Source; uniform float PhotoRatioCoef; const vec3 coef = vec3(0.299, 0.587, 0.114); void main() { vec4 textureValue = texture2D(Source, textureCoordinate); float gray = dot(textureValue.rgb, coef); gl_FragColor = vec4(textureValue.rgb, gray); }
Исходные RGB-каналы тоже оставляем, чтобы в конце показать пользователю цветное изображение.
Этап 2. Выделяем границы.
Из воркфлоу приложения пользователю для захвата необходимо поймать карту в кадре в определенных заданных областях. Для ускорения захвата области имеют некий запас, «люфт», т. е. пользователю не обязательно размещать карту точно по меткам, поэтому в захваченном изображении возможны некоторые отклонения карты от меток, как перспективные, так и по углу наклона и масштабу.
Вид из приложения: захват банковской карты через камеру iPhone
Необходимые области задаем с помощью текстуры с маской областей: области вертикальных границ — зеленый канал, горизонтальных границ — красный.
Пример маски
Для нахождения границ карты мы выделяем все границы в заданных областях, используя оператор Собеля. В вертикальных областях используем горизонтальную производную яркости, а в горизонтальных — вертикальную. Тем самым мы выделяем в соответствующих областях только горизонтальные или вертикальные границы.
Код шейдера:
varying highp vec2 textureCoordinate; precision highp float; uniform highp sampler2D Source; uniform highp sampler2D Mask; uniform float Offset; void main() { vec4 NE = texture2D(Source, textureCoordinate + vec2(Offset, Offset)); vec4 SW = texture2D(Source, textureCoordinate + vec2(-Offset, -Offset)); vec4 NW = texture2D(Source, textureCoordinate + vec2(-Offset, Offset)); vec4 SE = texture2D(Source, textureCoordinate + vec2(Offset, -Offset)); vec4 S = texture2D(Source, textureCoordinate + vec2(0, -Offset)); vec4 N = texture2D(Source, textureCoordinate + vec2(0, Offset)); vec4 E = texture2D(Source, textureCoordinate + vec2(Offset, 0)); vec4 W = texture2D(Source, textureCoordinate + vec2(-Offset, 0)); vec2 gradientDirection; gradientDirection.x = 0.0 — SW.a — S.a*2.0 — SE.a + NW.a + N.a*2.0 + NE.a; gradientDirection.y = 0.0 — SW.a + SE.a — W.a*2.0 + E.a*2.0 — NW.a + NE.a; gradientDirection.x = gradientDirection.x*gradientDirection.x; gradientDirection.y = gradientDirection.y*gradientDirection.y; gl_FragColor = vec4(sqrt(gradientDirection.x), sqrt(gradientDirection.y), 1, 1); }
Результат работы шейдера
Захват изображения карты: поиск линий
CPU, OpenCV
После подготовительного этапа по выявлению границ нам необходимо найти границы карты и по этим границам выравнить захваченную карту, т. к. захват, как было описано выше, для удобства пользователя делается грубо.
Задача этого этапа — найти линии границ карты и получить их уравнения, а также рассчитать точки пересечения. Зная уравнения линий и точки пересечения, можно исправить перспективу карты, смещение и масштаб, выровнять карту относительно оси абсцисс, т. е. программно разместить ее точно по меткам.
Для целей прототипа было решено использовать готовую библиотеку OpenCV и ее методы нахождения линий с использованием пространства Хафа (Hough Line Transform). Кроме того, эту библиотеку мы также использовали для адаптивной бинаризации полученных выше границ. В перспективе мы планируем написать алгоритмы для этих задач вручную с использованием GPU для получения приемлемой производительности.
Мобильный порт (iOS) OpenCV не поддерживает GPU, и производительность этого фреймворка относительно предыдущего этапа обработки на GPU достаточна мала. Поэтому для обработки с помощью OpenCV мы уменьшили исходное изображение по линейным размерам. После получения информации о линиях в декартовых координатах нам не составит труда привести их к размерам исходного изображения.
Процесс захвата изображения карты строится на поиске линий в четырех областях, где предположительно находятся границы карты, т. е. обрабатываем мы не всю картинку.
Важно правильно выбрать ширину области предположительного нахождения границ карты: если области будут слишком узкие, нам будет труднее поймать границы, а пользователю придется долго «попадать». Но чем шире область, тем выше шанс, что за границу карты будет принят элемент фона. В областях боковых сторон нас интересуют только вертикальные линии, а в областях верха и низа — горизонтальные.
Каждая из 4 областей подвергается адаптивной бинаризации (Adaptive thresholding), т. к. алгоритмы, которые мы будем использовать для нахождения линий цифр, работают только с бинарными изображениями.
Применяем адаптивную бинаризацию, затем используем метод Хафа — метод нахождения линий на растровых изображениях.
Результат работы методов Adaptive thresholding и Hough Line Transform
После применения метода Хафа мы получаем массив линий для каждой области. Производим грубую фильтрацию линий, например, оставляем только те, которые удовлетворяют заданной пороговой длине. После фильтрации выбираем наиболее явную линию. Такая процедура производится над каждой из 4 областей. Если финальная линия обнаружена в каждой из областей, то считаем, что это потенциально пригодный захваченный кадр, иначе — пропускам.
Все линии мы получаем в декартовых координатах, т. е. для каждой линии есть уравнение прямой. Мы можем найти точки пересечения и сделать преобразования для выравнивания карты. Само выравнивание карты делаем с помощью вертексного шейдера (Vertex Shader), вершины которого смещаем в соответствии с параметрами выравнивания, выравнивая изображение, «натянтутое» на вершины.
Локализация и сегментация
GPU, OpenGL
Благодаря тому, что цифры на карте расположены по определенному стандарту, т. е. смещение положения цифр на картах даже разных производителей не больше миллиметра, мы можем с относительно высокой точностью предположить, в каком месте на карте будет расположен номер.
Люфт, безусловно, есть, потому что выравнивание карты на предыдущем этапе могло пройти неидеально и возможны смещения выравненной карты относительно «эталонных меток». Кроме того, возможен люфт эмбоссирования относительно границ карты. Поэтому 4 блока (areas) с 4 цифрами мы вырезаем с небольшим запасом к краям цифр, прихватывая по 2-3 миллиметра. В данном описании мы рассматриваем карты формата Visa, MasterCard и подобные, но группировка цифр банковского номера других карт, например American Express, может отличаться.
![]() |
![]() |
![]() |
![]() |
Пример блоков с цифрами
Итак, в выделенной области потенциально содержится блок из 4 цифр, который нам нужно максимально точно локализовать. Расположение цифр нам не известно: цифра может находиться выше/ниже, правее/левее. Нам же для качественно распознавания нужно вырезать каждую цифру наиболее четко по ее границам.
Задачу сегментации цифр можно разбить на подзадачи:
1. Сегментация цифр по оси Y
Строим гистограмму горизонтальных производных яркости на ось Y. Далее на гистограмме ищем участок максимума определенной длины, который и будет соответствовать предполагаемой области текста по оси Y. Используя полученную картину, отсекаем верхние и нижние поля.
2. Сегментация цифр по оси Х
Строим гистограмму вертикальных производных яркости по оси X. Минимумы (или нижние экстремумы) должны соответствовать интервалам между цифрами. Согласно этому, определяем границы каждой цифры в блоке. Однако, в интервалах между цифрами могут встречаться различные шумы, поэтому, для достижения лучшего результата, необходима их фильтрация. В нашей работе мы использовали метод штрихов, который показал наилучшие результаты из всех опробованных нами методов. Однако он также не дает 100% точности, поэтому проблема остается открытой.
Сегментация цифр по оси X
Сегментация цифр — это основная проблема данного этапа. Точность сегментации по Y довольно высока — 95%, а по Х — всего 70-80%. Значительная разница объясняется тем, что для оси Y используются 4 цифры, для X — одна. Т. е., если в первом случае какая-то цифра локализована не точно, соседние цифры ее выручат и суммарно дадут достаточно точный результат. При локализации по X возможны потери элементов цифр, к примеру палочки семерки. Для оптимальной же работы алгоритма точность локализации должна быть достаточно высокой.
Фоновый рисунок карт, освещение, а также ряд других факторов — это основные причины того, что границы цифры сливаются и определяются неточно; кроме того, некоторые помехи могут ложно определяться как границы цифр.
Распознавание цифр: работа с нейронными сетями
GPU, OpenGL
Для окончательной сегментации мы применяем нейронную сеть. Распознавание изображений или образов — это одно из направлений применения нейросетей. Нейросеть представляет собой систему простых процессоров, искусственных нейронов, каждый из которых преобразует набор сигналов, поступающих к нему, в выходной сигнал. Главные преимущества использования НС заключаются в гибком механизме обучения и хорошей обобщающей способностью, т. е. нейросети могут использовать опыт, полученный на обучающих образцах, при дальнейшей работе.
Множество нейронов, не связанных между собой, но связанных с другими нейронами, образует нейронный слой. Нейросети бывают однослойные и многослойные; наша задача требовала работу именно с многослойными — сверточными — нейросетями. Многослойная нейронная сеть представляет собой последовательность соединенных слоев, где нейрон каждого слоя своими входами связан со всеми нейронами предыдущего слоя, а выходами — последующего. Обучаются многослойные нейросети при помощи алгоритма обратного распространения ошибки методом градиентного спуска.
На заключительном этапе проекта, когда сегментация цифр завершена, мы получаем 16 блоков с цифрами, которые и передаем сверточной нейронной сети, построенной на основе работ Йэна ЛеКан, Леона Вотту, Йошуа Бенджио и Патрика Хаффнер. Для ее обучения мы написали специальную версию приложения, которое находит символы на реальных картах, вырезает, создает растровые изображения и группирует их по цифрам. На текущий момент наша база включает 3 500 обучающих изображений. Немного, но для прототипа и тестирования достаточно. В будущем мы планируем довести базу до 60-100 000 изображений. Точность распознавания даже с такой небольшой обучающей базой достаточно велика: при обучении на 3500 образах, на 100 тестовых образцах мы получили 2 ошибки.
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Примеры изображений базы обучения
После распознавания для каждого блока мы получаем массив вероятностей той или иной цифры для данного блока.
Пример вектора вероятностей для верно распознанного образца «6»:
0 : -1.021
1 : -1.000
2 : -1.016
3 : -0.945
4 : -1.022
5 : -1.033
6 : +1.002
7 : -0.992
8 : -0.995
9 : -1.009
Не всегда получается определить все цифры с большой вероятностью на одном кадре, поэтому мы фиксируем точно распознанные цифры, а нераспознанные цифры будут уточнены после обработки следующего кадра; точно распознанной считается цифра, максимальное значение вероятности для которой в массиве вероятностей выше некого порога.
Заключение
Неверная локализации цифр, особенно по оси Х, — главная проблема текущего этапа. Именно в ней кроется причина большинства неверно распознанных нейронной сетью цифр.
Неверная сегментация цифр по оси X
Пример результата неверной сегментации на рабочем прототипе: последний блок сегментирован неверно, в результате чего, последний блок был неверно распознан.
Мы продолжаем работу над проектом и по мере поступления новых данных, будем делиться результатами. Возможно, вы тоже работали над этой или схожими задачами, и мы будем признательны, если вы поделитесь своим подходом к локализации цифр и расскажете, какие результаты получали.
Авторы статьи: Владимир Черницкий и Антон Витвицкий
Комментарии