Здесь на Хабре часто встречаются статьи про распознавание капчи. Всегда с большим интересом их читал, а сегодня настал и мой черёд написать. Путь от наивной реализации с Тессерактом до веб-сервиса со сложной нейросетью занял у меня около года. Количество ошибок распознавания удалось снизить с 90 до менее 1%.
Однажды один довольно известный файлообменник в очередной раз что-то поменял в алгоритме, и перед закачкой в программе для скачивания стало появляться окно ввода капчи. Это раздражало, а старые методы обхода по-видимому уже не работали. Стал думать, как решить проблему. С подключением плагина к программе всё оказалось просто, дело за распознаванием самой капчи. Она представляла собой 4 цветных символа (буквы, цифры) разного размера с поворотом до 30 градусов в обе стороны на цветном фоне с тонкими прямыми линиями. В результате поисков наткнулся на OCR программу Tesseract, сохранил несколько файлов с капчами и попытался их распознать. Наивное решение дало около 10% правильных результатов, довольно быстро обнаружилась возможность задать список разрешённых символов - это повысило процент попаданий до 20. С этим уже можно работать - написал на Пайтоне программу, которая отправляет капчи на распознавание и возвращает результат программе закачек. Попутно стал экспериментировать с графической обработкой картинок, чтобы улучшить точность распознавания. Сначала пытался привести их к чёрно-белому изображению, но из-за низкого разрешения и некоторого градиента цвета края символа оказывались обтравленными. Остановился на понижении цветности путём отбрасывания 6 младших бит цвета. Также пришёл к идее обрабатывать изображение посимвольно, разбивая картинку на части и производя несколько попыток при разных углах поворота. Вращение от -30 до 30 с шагом 5 градусов с выбором наиболее часто встречающегося результата давало точность 30-40%, но время на одну капчу увеличилось до 12 секунд.
Я хотел иметь возможность запускать систему на любой машине без необходимости установки Tesseract и его прописывания в переменных окружения. Потратив некоторое время на эксперименты, смог добиться работы Тессеракта без установки - необходимые настройки передавались ему в строке запуска. Весь путь начиная с поиска OCR программ занял три месяца.
Система работала, я не планировал к ней возвращаться, но спустя ещё 3 месяца решил проанализировать ошибки распознавания и произвести рефакторинг системы. Оказалось, что часто возникает ситуация когда некоторые символы устойчиво распознаются как другие. Основная причина - большой угол поворота плохо воспринимается Тессерактом и он выдаёт не тот символ, иногда пару символов. Возникла идея обучить Тессеракт распознавать повёрнутые символы, провёл серию экспериментов по нарезке и очистке изображений, но результат улучшить не удалось. Проанализировал статистику ошибок и добавил в программу весовые коэффициенты и списки замен, в результате точность выросла до 40-50%. Заодно вынес настройки в отдельный файл для возможности управлять многочисленными параметрами.
Эксперименты с изображениями не прошли даром - применил OpenCV и NumPy для предварительной обработки капчи. При помощи масштабирования, размытия и последующего перевода в оттенки серого удалось избавиться от части тонких фоновых линий, поиск контура и заливка по маске позволили точнее выделять символы. Точность повысилась до 70-80%, но размер программы вырос до 85 Мбайт. Поменял компоновку системы - вынес Тессеракт и программу в отдельную папку, в программе закачки остался только лоадер с файлом настроек. За этими развлечениями прошёл ещё месяц.
Потом я добрался до машинного обучения и обнаружил, что известный набор рукописных цифр MNIST не сильно отличается по сложности от наклонных символов капчи. Собрал все капчи какие были (около 2500), с помощью уже работающей у меня системы распознал и нарезал их на картинки 28х28 с отдельными символами, отсеял ошибки разметки и исправил неправильно распознанные 25% - получилось чуть меньше 9000 файлов. Обучил на них нейросеть, используя Keras и Tensorflow, получил точность практически 100% на этих файлах и 75% на реальных капчах. Веселье началось при попытке скомпилировать программу - папка со всем этим добром весила 1,8 Гб. Стал искать альтернативу, оказалось что простую сеть можно сделать на голом NumPy, который у меня уже был для обработки картинок. За основу взял учебный пример из книги "Python Machine Learning" Себастьяна Рашки, про который я уже писал в статье про Доббль.
Дальше пришлось опять играть в игры с нарезкой на части и попытки убрать линии. Главными проблемами оказались соприкасающиеся символы, шум, малоконтрастные символы. Пытался обрабатывать цвета раздельно, ничего хорошего из этого не вышло. Пытался искать и затирать остатки тонких линий, сколько-то процентов удалось выиграть. Собрал промежуточную версию, которая весила как старая для Тессеракта, только без Тессеракта. Запустил на обкатку, настроил сохранение файлов с капчей под именем, содержащим распознанный код и метку времени. Точность оказалась около 90%.
Почти полгода не возвращался к этой теме, накопилось чуть больше 6000 капч. Понял, что если разница по времени между файлами меньше 2 минут, первый скорее всего распознался неправильно. Решил попробовать что-то из этого выжать, поискал - нашел пример распознавания полной капчи без какой-либо предварительной обработки картинки под названием "How to implement an OCR model using CNNs, RNNs and CTC loss". Оказалось что его реализация есть в примерах к Keras.
Переделал под свои параметры, обучил только на "хороших" файлах, ошибок оказалось около 2%. Вручную разметил "плохие", обучил еще раз, получил 20 ошибок на всей папке. Основные проблемы - пропускает один из идущих подряд одинаковых символов, в основном первый (учел это в постобработке), иногда спотыкается без видимых причин. Собрал ещё капч, включая старые нераспознанные, распознал и добавил к тренировочному набору - получилось около 15000 файлов. Провёл несколько раундов в стиле тренируем сеть - ищем с её помощью ошибки в наборе - исправляем, в результате получил 2 ошибки с пропуском повторяющихся символов на всей папке. В реальных условиях оказалась 1 ошибка на 250 попыток. Но без Keras и Tensorflow тут было уже не обойтись. Запуск сети занимал 3 секунды и ещё 2 уходило на обработку первой капчи, поэтому пришлось организовать работу в фоновом режиме. Сделал веб-приложение на Flask, которое принимает файл с капчей и возвращает текстовый результат.
Если бы я нашёл этот пример раньше, возможно применение кода упростило бы задачу. Но сбор необходимого для тренировки сети датасета потребовал бы много усилий, да и настройку некоторых моментов было полезно сначала отработать на более простых примерах.
Выводы:
Развитие как всегда движется по спирали
Если чего-то не мог найти вчера - не факт, что это не найдётся сегодня
Для тренировки сложной сети нужен большой набор данных, и хорошо бы заставить работать над его разметкой сеть попроще
Лучший способ чему-то научиться - применить это на практике
"Каждая мечта тебе дается вместе с силами, необходимыми для ее осуществления. Однако, тебе, возможно, придется ради этого потрудиться" (с) Ричард Бах