Издевательски точный, быстрый и легковесный поиск баркодов через семантическую сегментацию

    Поиск объектов на изображениях? Имея обучающую выборку и минимальный набор знаний о нейросетях, любой студент сегодня может получить решение определенной точности. Однако большинство нейросетей, использующихся для решения этой задачи, достаточно глубокие, а соответственно, требуют много данных для обучения, сравнительно медленно работают на этапе inference (особенно если на устройстве отсутствует GPU), много весят и достаточно энергозатратны. Все вышеперечисленное может быть весьма критично в определенных случаях, в первую очередь, для мобильных приложений.


    Баркоды — объекты с достаточно простой структурой. В ходе исследований у нас получилось с помощью сравнительно оригинального подхода искать такие простые объекты весьма точно (мы побили state-of-the-art) и достаточно быстро (real-time на среднем CPU). Плюс наш детектор очень легкий, имеющий всего 30к весов. О результатах нашего исследования мы и расскажем в этой статье.


    Но прежде чем говорить о решении, стоит все же немного погрузиться в предметную область, и начать, пожалуй, стоит с того, что такое баркоды.


    Что есть баркод и зачем его искать?


    Баркоды (Barcodes) — общее название для группы форматов машиночитаемых данных. Вы наверняка слышали про штрих-коды и QR-коды — это как раз частные случаи баркодов. В современном мире их можно обнаружить на билетах, на товарах в магазине, в официальных документах и во многих других местах.



    Баркоды принято разделять на одномерные, представленные набором черных и белых полос, и двумерные, состоящие из мелких квадратных или прямоугольных блоков. Как можно видеть на рисунке, в целом различные типы весьма похожи между собой.


    Сложность могут представлять баркоды некоторых типов, у которых не специфицирована длина. Подобные баркоды могут быть сколь угодно длинными и при этом очень тонкими.



    Пример выше искусственный, однако баркоды с аналогичными пропорциями встречаются в реальных документах, в частности в инвойсах.


    И раз уж это машиночитаемые форматы, они должны сравнительно легко распознаваться аппаратурой. Однако первый шаг на пути к распознаванию — это поиск, и именно о нем и пойдет речь в данной статье.


    Стоит отметить, что лазерные сканеры и многие приложения подразумевают активное участие человека — требуется явно навести камеру/сканер на баркод, дабы тот распознался, в таком случае необходимость в поиске, естественно, отпадает. Однако в данном сценарии пользователи вынуждены затрачивать дополнительные усилия со своей стороны, что не есть хорошо. Такой подход также не позволяет распознавать несколько объектов на изображении, ограничиваясь только одним.


    Постановка задачи и метрики качества


    Что бы нам хотелось от создаваемого детектора баркодов?


    1. Находить все баркоды на изображении достаточно точно*
    2. Находить в том числе длинные и очень узкие баркоды
    3. Real-time на cpu

    *В качестве основной метрики точности предлагается использовать f-меру по объектам при пороге IoU=0.5. Также будем смотреть на precision, recall и detection rate.


    Формальное определение метрик

    Для начала введем понятие IoU — Intersection over Union (другое название — Jaccard Index). Имея истинную ($G$) и предсказанную ($F$) маски объекта, IoU вычисляется как площадь пересечения, деленная на площадь объединения.

    $J(G,F) = \frac{|G \cap F|}{|G \cup F|}$



    Легко заметить, что IoU принимает значения от 0 до 1. Теперь, основываясь на этой мере, введем понятия precision, recall, f-мера и detection rate.


    Пусть в выборке N объектов. Установим порог IoU равным некоторому фиксированному числу $t$ (например, 0.5). Для некоторого объекта $G$, и детекции $F$ считаем, что если $J(G, F) \ge t$, объект найден (1), иначе считаем, что объект не найден (0). Тогда $recall(t)$ определяется как доля найденных объектов выборки, $precision(t)$ — доля детекций, соответствующих хотя бы одному объекту выборки. f-мера определяется стандартно как среднее гармоническое $2*precision(t)*recall(t)/(precision(t)+recall(t))$.


    Осталось разобраться с тем, что такое detection rate. Пусть в выборке M картинок с объектами. Посчитаем IoU между истинными и предсказанными масками по картинке, аналогично отсечем по порогу картинки. Detection rate называется доля картинок, по которым общий IoU всех истинных объектов с общим IoU всех предсказанных объектов больше заданного порога t.


    В предыдущих исследованиях (по детектированию баркодов) для оценки качества принято использовать detection rate. Однако рассмотрим такую ситуацию — пусть на изображении есть один очень большой и один очень маленький объекты, а детектор нашел большой и не нашел маленький. В таком случае detection rate просигнализирует об ошибке только в случае очень большого порога $t$ близком к 1. В то время как $recall$ будет не больше 0.5 при произвольных значениях порога $t$, что также сделает f-меру меньше 1. То есть для такого примера f-мера может просигнализировать ошибку раньше (в смысле меньшего порога $t$) чем detection rate.


    Поэтому в своих экспериментах мы опирались на f-меру, а detection rate использовали для сравнения с работами прошлых лет.


    Вдохновляемся детекторами текста


    Пожалуй, самый известный и популярный детектор объектов в случае, когда важна скорость — YOLO (You Only Look Once). Существуют также более поздние версии YOLOv2 и YOLOv3, но в целом данный подход хорош для более-менее квадратных объектов, а вот в нахождении узких, но сильно вытянутых объектов данный подход уже не так силен.


    Так что мы решили пойти другим путем. Как легко заметить, строчки с текстом на изображении также весьма вытянутые и узкие, а задача поиска текста на изображениях в научном сообществе достаточно популярна (уж точно популярнее поиска баркодов) и для нее придуманы различные интересные архитектуры, основанные на графовой структуре. Наше решение мотивировано как раз одной из работ по поиску текста, в которой предложена архитектура PixelLink.



    Суть подхода в следующем — давайте решать задачу instance segmentation так:


    1. Для каждого пикселя будем решать задачу бинарной классификации (текст/не текст).
    2. Для каждого пикселя будем предсказывать 8 связей (links) с его соседями. Связь означает, что данная пара пикселей принадлежат одному объекту (instance).
    3. Получив по изображению карту сегментации текст/не текст и карты со связями, соберем граф, в котором вершины будут пикселями, у которых предсказан класс текста, а ребра — линки.
    4. Находим в этом графе связные компоненты.
    5. Вокруг каждой связной компоненты выделяем минимальный охватывающий прямоугольник (например, используя OpenCV), который и будет итоговой детекцией.

    Данный процесс наглядно изображен на схеме, рисунок взят из оригинальной статьи.



    Данный подход для поиска текста дает достаточно приличные результаты, сравнимые со state-of-the-art.



    Возвращаемся теперь к баркодам. Мы задались вопросом — а так ли нам нужны эти линки? Ведь баркоды, в отличие от текста, в подавляющем большинстве случаев находятся далеко друг от друга. Да и вообще по спецификации у многих типов баркодов обязательно должен быть некий зазор до ближайших соседних объектов.
    В общем мы решили, что линки нам особо не нужны, и модифицировали подход следующим образом:


    1. Решаем задачу семантической сегментации — для каждого пикселя определяем класс баркод/не баркод.
    2. Строим граф с вершинами в пикселях, у которых определился тип баркод. В качестве ребер вместо того чтобы искать линки, считаем, что между любой парой соседних пикселей типа баркод есть линк.
    3. Находим связные компоненты в данном графе.
    4. Вокруг каждой связной компоненты выделяем минимальный охватывающий прямоугольник, который и будет итоговой детекцией.

    Итак, мы определились с общей схемой решения. Пункты 2-4 можно считать тривиальными, а вот как именно справиться с семантической сегментацией, давайте обсудим далее.


    Архитектура сети для семантической сегментации


    Немного истории, что обычно используют


    В целом, семантическая сегментация с помощью нейросетей ведет свою историю примерно с 2013 года с появления U-Net. В U-Net пространственное разрешение сначала постепенно уменьшалось в Encoder, затем постепенно увеличивалось в Decoder, также присутствовали связи (skip connections) из промежуточных признаков Encoder'а в промежуточные признаки Decoder'a. Чуть позже появились dilated свертки (см. рисунок ниже), которые дали лучшее качество, но требовали больше памяти и вычислений для обработки. Ну и, в конце концов, появился подход, известный как Deeplabv3+, комбинирующий оба эти подхода и являющийся state-of-the-art среди human-designed архитектур на момент написания этой статьи (на самом деле, благодаря Neural Architecture Search, уже были получены более эффективные решения, например).


    Остановимся немного подробнее на dilated свертке, т.к. в итоговом решении мы будем полагаться на ее свойства.


    Обычная свертка работает примерно так



    В то время как dilated свертка изображена ниже (на изображении dilated фактор равен 2)



    Можно заметить что обычная свертка на самом деле является частным случаем dilated свертки (с dilation фактором 1).


    Вообще обсуждение решений для семантической сегментации — тема для отдельной немаленькой статьи, я лишь хотел сделать небольшое напоминание, в котором самое важное — разобраться с устройством dilated свертки. Если вы плохо знакомы с семантической сегментацией, здесь лучший известный автору обзор, впрочем, для понимания того, что будет происходить дальше, ознакомление с ним не обязательно.


    Если бы все было так просто...


    Главное требование к нашей нейросети, помимо качества, — скорость. В наших данных встречаются баркоды настолько мелкие относительно размера изображения, что минимальное разрешение, на котором имеет смысл запускать нейросеть, должно быть хотя бы 512x512, а лучше 1024х1024. При этом есть требование работать на CPU не больше 100 мс на изображении (причем еще и на одном ядре!). По совокупности требований ко входному разрешению и общему времени работы задача выходит весьма не тривиальной. При таких жестких ограничениях использование сильных, глубоких архитектур не представляется возможным. Справедливости ради, требование про 100 мс на одном ядре мы так и не выполнили, но итоговое решение работает 40 мс на 4х ядрах (Intel Core i5, 3.2 ГГц).


    К сожалению, все известные нам архитектуры для семантической сегментации категорически не укладывались в данные ограничения. Так что перед нами стоял выбор: или неизвестно как придумывать что-то совсем новое, или попытаться максимально упростить одну из популярных архитектур. Гениальных идей у нас не появилось, так что мы выбрали последнее.


    Вдохновляемся Context Aggregation Network


    У dilated сверток имеется следующее полезное свойство — если применить их последовательно с экспоненциально возрастающим dilated фактором, receptive field будет расти экспоненциально (тогда как в случае с обычными свертками он растет линейно).



    На рисунке (source) показано экспоненциальное увеличение receptive field нейронной сети при последовательном применении dilated сверток. (a) F1 получается из F0 применением свертки с dilation=1, receptive field = 3x3 для каждого элемента в F1 (b) F2 получается из F1 применением свертки с dilation=2, receptive field = 7x7 для каждого элемента в F2 (c ) F3 получается из F2 применением свертки с dilation=4, receptive field = 15x15 для каждого элемента в F3


    В статье "Multi-Scale Context Aggregation by Dilated Convolutions" на основе этого свойства придуман специальный блок (называемый авторами Context Module), использующийся для сбора информации из контекста текущего пикселя. В статье этот блок используется поверх сети, которая уже умеет неплохо решать задачу сегментации для уточнения этих предсказаний с помощью информации о контексте.


    Мы решили взять этот Context Module, но использовать его не для улучшения предсказаний уже имеющейся хорошей модели, а в качестве основной части сверточной сети, которая и будет решать эту проблему. В целом же, наша архитектура устроена следующим образом (см. таблицу):


    1. Downscale Module — начальные свертки для извлечения простейших признаков и уменьшения разрешения.
    2. Context Module — по сути набор dilated сверток, которые быстро увеличат receptive field, таким образом собрав признаки с большего контекста.
    3. Финальный слой (1x1 свертка), получающий карту вероятностей для семантической сегментации баркод/не баркод. Также, в случае если мы хотим различать тип детектируемых баркодов, предсказывается дополнительно N каналов (смотреть далее).


    То есть, в полученной архитектуре в каждом слое используется константное небольшое число каналов C=24, первые 3 свертки уменьшают разрешение, последующие свертки (Context Module) наращивает receptive field каждого элемента карты признаков.


    В таблице гиперпараметров архитектуры, помимо прочего, указано, является ли свертка на текущем слое сепарабельной. Сепарабельные свертки теоретически дают ускорение порядка k^2 раз по сравнению с обычной сверткой, где k — размер фильтра. На практике ускорение может быть намного меньше (все зависит от разрешения изображения, количества входных и выходных фильтров).


    Изначально мы пробовали сделать все свертки сепарабельными, но от этого качество сильно упало. Тогда мы решили сделать сепарабельными только первые 3 свертки, которые работают с бОльшими разрешениями изображения, и, соответственно, требуют больше вычислений по сравнению с последующими свертками. При этом качество от такой замены упало уже совсем не значительно. Итого за счет сепарабельности, нейросеть ускорилась еще на 20%.


    Остался последний неразъясненный момент — что значит число N в последнем слое. N отвечает за количество различных типов объектов (в нашем случае баркодов), которые мы хотим различать. Если же у нас рассматривается простейший случай — когда надо только находить объекты, а определять их тип не надо (это баркод и не важно какой) — можно считать N=0. Если же мы все же хотим различать типы баркодов (это баркод типа [такого-то]), то в последнем слое добавляется N каналов, в которых предсказываются вероятности быть какого-то определенного типа. Теперь, получив детекцию в виде прямоугольника, в котором находится баркод, можно усреднить вероятности классов внутри этого найденного прямоугольника, из чего узнать тип найденного баркода.


    Результаты


    Получив какое-то решение, всегда стоит посмотреть вокруг и сравнить качество полученного решения с более ранними исследованиями. В целом задача поиска баркодов не пользуется особой популярностью, за последние лет 10 выходило только 1-2 статьи в год (как известно, простейший способ побить state-of-the-art — найти непопулярную задачу, что у нас в итоге и получилось...). В таблице ниже сравнение с другими подходами на двух датасетах, на одном из которых у нас получились лучшие результаты.



    Превосходство, конечно, не супер впечатляющее, но все же присутствует. Однако главная фишка найденного решения не в точности, а в скорости — по сравнению с тем же YOLO на том же GPU (GTX 1080), причем на большем разрешении изображения, наш метод работает в ~3.5 раза быстрее.



    Помимо преимущества в скорости, есть и преимущество в весе итоговой модели. Детектор имеет ~30 тысяч весов, в то время как подавляющее большинство современных сверточных сетей (даже заточенных под мобильные устройства) имеют миллионы параметров. Итоговое решение весит даже меньше LeNet, который имел ~60 тысяч параметров.


    Заключение


    По сути, данная работа основана на двух простых идеях:


    1. Детектирование через семантическую сегментацию.
    2. Использования последовательности dilated сверток с небольшим числом каналов для максимально быстрого наращивания receptive field.

    В результате была получена очень легкая и быстрая архитектура, тем не менее показывающая вполне достойные результаты (и даже побившая SOTA в непопулярной задаче поиска баркодов). Кроме того, применение данного подхода не ограничивается баркодами. Мы использовали эту же архитектуру для нахождения текста, поиска визиток и паспортов, получив хорошие результаты и для этих задач.


    Стоит, тем не менее, отметить, что данный подход в таком виде имеет ограничения. Если совсем рядом находятся 2 объекта одного типа, они будут слипаться в один. Возможно, в следующих сериях, мы расскажем, как бороться с такой проблемой.


    По результатам экспериментов с баркодами мы написали статью, которая будет представлена на ICDAR2019 в Сиднее.


    Литература


    1. Статья с результатами наших экспериментов и большим числом подробностей. Universal Barcode Detector via Semantic Segmentation
    2. Прекрасная обзорная статья про развитие архитектур для семантической сегментации. link
    3. PixelLink: Detecting Scene Text via Instance Segmentation
    4. Статья про использование dilated сверток для улучшения предсказаний с помощью сбора информации из контекста. Multi-Scale Context Aggregation by Dilated Convolutions

    Computer Vision Research Group

    ABBYY
    Решения для интеллектуальной обработки информации

    Комментарии 10

      0

      Маленький вопрос: насколько это распознавание устойчиво к повороту баркода? Например, на 20 или 45 градусов.

        +1
        В статье обсуждается именно детектирование, не распознавание. При обучении в аугментацию добавлялись повороты изображения на произвольный угол, так что можно считать что модель достаточно устойчива к поворотам. На реальных изображениях проблем с повернутыми баркодами также не возникает.
          0
          давным-давно делали детектор бар кодов (причем без нейронных сетей). так самой большой проблемой был не поворот, а перспектива. даже не знаю, какие современный детекторы нормально работают при наклонном изображении.
            0

            В нейронных сетях такой проблемы не должно возникнуть, если в тренировочную базу добавить изображений баркодов с наклонной проекции.

          0
          Сразу отмечу как немного практик. Баркоды стараются распределять не рядом. То есть максимально разнести по наклейке, часто повернув вторую на 90 градусов. Или же они чётко отделены друг от друга по вертикали довольно таки чётким пустым местом.
            0
            100ms и real-time несколько разные вещи. Когда я занимался распознованием у нас был предел 30ms на кадр. В общем без нейронок все это на порядок быстрее.
            Перспективные искажения можно и легко исправить с помощью преобразования Хафа.
              +1
              Наш детектор работает 44мс на 512х512, соответственно, если хочется действительно real-time, можно немного уменьшить размер входного изображения. Нейронки работают медленнее классических алгоритмов, здесь вы правы, зато они позволяют выиграть в качестве.

              В приложениях, где действительно нужен real-time, задача как правило достаточно простая (обычно один большой баркод на страницу). В таком простом случае можно без потерь качества уменьшить разрешение в несколько раз.
                –1
                Я десять лет назад делал распознование любого из 3-4 обьектов за 30ms в картинке 320х240 в один поток без нейронок. Но делал хитро.
                Вам бы для распознования баркодов рекомендовал бы посмотреть на 2D DCT и FFT. Для баркодов там будут интересные артефакты. Можно также посмотреть на вейвлеты. И обрабатывать это простой статистикой.
              0
              Отличная статья! Очень вовремя — делаю распознавалку для PDF417.

              Кто-нибудь может подсказать python пакет для декодирования PDF417? pyzbar упорно отказывается распознавать именно этот формат ((
                0
                «Получив какое-то решение, всегда стоит посмотреть вокруг и сравнить качество полученного решения с более ранними исследованиями.» — эпично :))
                Хорошая статья на самом деле, отдельное спасибо за 4 ссылку — весьма интересная работа.

                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                Самое читаемое