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