OCR за час? — Не думаю
О задаче
Одним прекрасным вечером коллега попросил подумать над алгоритмом поворота серийных номеров на металлических брусках — бруски овальные, серийные номера выбиты на их торцах, и ориентация надписи может быть произвольной (пример на картинке ниже).
Предполагалось, что если повернуть брусок так, чтобы номер располагался горизонтально, это поможет распознаванию. Один из возможных вариантов поворота придумать несложно, об этом ниже. Но глобально задача предполагала именно распознавание. Ранее задачами OCR глубоко я не занимался, и мне стало интересно, на что способны имеющиеся коробочные решения. Казалось бы, они должны легко решить нашу задачу за час. О том, что было на самом деле, и пойдет речь в этой статье.
Paddle OCR
Одним из самых популярных репозиториев на Github и Papers With Code является PaddleOCR. Обычно OCR-пайплайны сначала детектируют области с текстом, а потом, с помощью так называемого recognizer’a, извлекают текст. PaddleOCR тут не исключение. Единственное усложнение - добавление классификатора ориентации текста после детекции, что позволяет модели отрабатывать на текстах с ориентацией, кратной
Сразу перейдем к примерам:
Например, на такой картинке (Рисунок 2) PaddleOCR отлично справился с распознаванием номера 84612000 (confidence 0.98), а 281 не нашел вообще.
А вот на этом изображении (Рисунок 3) нашел:
('612', 0.988)
(значение в кавычках - распознанный текст, число рядом - confidence модели, далее результаты распознавания буду предоставлять в таком формате);('C', 0.902)
;('*8', 0.834)
.
Получаем лишь куски номеров или вовсе не то, что нужно.
Поворачиваем текст
Окей, PaddleOCR не завелся идеально из коробки, пришлось поворачивать бруски. Идейно подход такой:
сегментируем область с номером, получаем маску. Нестрого говоря, она имеет вид эллипса - то есть вытянута вдоль одной из осей.
Далее через точки маски строится линейная регрессия.
Зная коэффициент наклона прямой, находим угол поворота.
Тест Open Source решений
Для примера в статье возьму часть фотографии, на которой PaddleOCR не отработал, а именно часть фотографии с номером 281 (Рисунок 5)
Подход №1. "В лоб"
PaddleOCR
Тестируем PaddleOCR на повернутом тексте. Ничего не обнаруживается. Кстати, расположение текста мы уже "детектировали" с предыдущего этапа с поворотом номера и возникает вопрос: может быть детектор косячит, и попробовать отдать номер сразу в recognizer?Окей, recognizer что-то распознал, но на номер это не похоже:
('HEi', 0.567)
.Tesseract
Возьмем представителя старой школы - Tesseract (python wrapper).
Ожидаемо, он справляется не очень хорошо - ничего не видит.И действительно, Tesseract довольно требователен к входным данным, есть даже отдельный раздел в документации по улучшению данных на входе.
EasyOCR
Рисунок 6. Схема работы EasyOCR (Github).
EasyOCR использует в качестве детектора текста CRAFT, а в качестве recognizer'a - ResNet50 + LSTM + CTC Loss. Реализовано все на PyTorch. Надо сказать, что документации у EasyOCR простая и понятная, а recognizer довольно просто дообучить на своих данных.
Но вернёмся к результату на нашем примере. EasyOCR находит что-то более осмысленное:
С детектором видит
('201', 0.728)
;Без детектора
('3281', 0.237)
- нашел даже больше, чем надо.
Замечу, что с настройками по умолчанию, EasyOCR производит вычисления для 4 изображений, повернутых на 0, 90, 180 и 270 градусов. Можно задать свои настройки для углов поворота. Если вы уверены, что текст ориентирован горизонтально,можно указать лишь один угол для экономии времени.
Подход №2. Бинаризация
Как оказалось, для моделей OCR довольно сложно распознавать текст, имеющий сложную форму и структуру (у нас символы состоят из светлых и темных частей за счет преломления света) на произвольном фоне. Попробуем избавиться от фона и минимизировать переливы света на выбитых цифрах за счет бинаризации фотографии. Для примера возьмем всё то же изображение.
PaddleOCR
А вот на бинаризованной фотографии paddle отработал исправно -('281', 0.964)
Recognizer без детектора нашел только('t', 0.541)
Tesseract
Still nothing.EasyOCR
EasyOCR с детектором тоже справился -('281', 0.983)
А вот без детектора все еще находит лишнее -('7281', 0.396)
(возможно, потому что на нашей фотографии 2 сильно похожа на 7)
Подход №2.1 Инвертированние
В качестве гипотезы, проверили, что будет, если инвертировать бинаризованное изображение так, чтобы текст стал черным, а фон белым - кажется, что модели должны были чаще видеть именно такой формат.
Ниже в таблице приведены результаты моделей на рисунках 6-8.
Таблица 1. Результаты из подходов №1, №2, №2.1.
Модель | Результат подхода №1 | Результат подхода №2 | Результат подхода №2.1 |
---|---|---|---|
PaddleOCR | _ | ('281', 0.964) | ('8 2', 0.823) |
PaddleOCR (recognizer only) | ('HEi', 0.567) | ('t', 0.541) | ('zal', 0.409) |
Tesseract | - | - | - |
EasyOCR | ('201', 0.728) | ('281', 0.983) | ('8281', 0.354) |
EasyOCR (recognizer only) | ('3281', 0.237) | ('7281', 0.396) | ('7281', 0.199) |
Telegram OCR
Казалось, что современные open source решения должны отрабатывать более стабильно. Поэтому, немного разочаровавшись в результатах, мы решили протестить OCR, встроенный в какое нибудь приложение. Как оказалось, в Telegram для iOS есть встроенный OCR, о котором узнали случайно, просто пересылая фотографию. Загрузили нашу картинку в "телегу" и опа! - он нашел "281" и для оригинальной фотографии, и для бинаризованной версии, а вот для инвертированной версии тоже выдал нечто - "2. 8 Fa" (хотя с "2. 8" можно согласиться).
Выводы и варианты улучшения распознавания
Данная статья не претендует на какую-либо серьезность, скорее показывает на что можно расчитывать, запустив практически втупую современные коробочные решения на вашей задаче.
Как и следовало ожидать, решения из коробки не дают отличных результатов на специфичных задачах. Без "докручивания" кастомными алгоритмами обойтись сложно.
Скорее всего, модели в обучающей выборке видели не очень много выдавленных в металле цифр, и fine tuning моделей должен сильно улучшить результаты - эксперимент с бинаризацией это подтверждает. C другой стороны, Telegram справился, то есть существуют модели, которые "могут".
Возможно, для конкретно такой задачи использованные модели OCR это overkill и следует использовать детекцию каждой отдельной цифры.