Создаём приложение на С++ с использованием Tesseract-ocr, MinGW и напильника

Так случилось, что понадобилось нам внедрить в своё приложение возможность распознавания текста, поэтому начались поиски подходящей библиотеки. В конечном счёте остановились на двух опенсорсных проектах CuneiForm Linux и Tesseract-ocr. Внимательное изучение проекта CuneiForm показало, что это просто порт продукта компании Cognitive Technologies, исходники которого они открыли в 2008 году и благополучно забили получив свою порцию внимания (во всяком случае такое сложилось впечатление). По сути весь проект состоял в портировании, а о новых фичах даже речи не шло. Всё это, вкупе с печальной новостью на страничке проекта, заставило нас отказаться от CuneiForm в пользу Tesseract, который в данный момент принадлежит Google, что даёт некоторую уверенность в будущем проекта. Под катом опыт сборки Tesseract-ocr под Windows с использованием MinGW и последующего создания простейшего приложения на С++.

Подготовка


Я постараюсь описать всё что нужно сделать, чтобы собрать tesseract с минимальной головной болью, при этом постараюсь не углубляться в банальности.

Установка и настройка MinGW

Скачиваем и устанавливаем последний доступный инсталлятор с официального сайта проекта, не забываем выставить галочки для C++ Compiler и MSYS Basic System. После этого заходим в MinGW Shell и устанавливаем дополнительные пакеты, которые понадобятся нам позже, следующей командой:
mingw-get install mingw32-automake mingw32-autoconf mingw32-autotools mingw32-libz
Сразу заметим, что в /mingv примонтирован каталог, в который установлен MinGW, это нам также пригодится при сборке библиотек.

Устанавливаем библиотеку Leptonica

Tesseract-ocr использует для работы с изображениями библиотеку Leptonica, я опишу как собрать и установить её из исходных кодов, которые можно взять с официального сайта, но перед этим нам нужно установить библиотеки libJpeg, libPng и libTiff, которые в свою очередь использует Leptonica (сделаем это также сборкой из исходных кодов).

Сборка libJpeg

Скачиваем архив с исходными кодами с официального сайта и распаковываем в отдельный каталог (для простоты будем считать, что это D:\lib\jpeg). Возвращаемся в MinGW Shell и лёгким движением руки собираем и устанавливаем библиотеку в каталоги, в которых gcc ищет по умолчанию. Флаги переопределяем чтобы отключить вывод отладочных символов.
cd /D/lib/jpeg
./configure CFLAGS='-O2' CXXFLAGS='-O2' --prefix=/mingw
make
make install


Сборка libPng

Также скачиваем архив с исходными кодами со странички проекта и распаковываем в каталог D:\lib\png (Вы, естественно, можете выбрать другой). Возвращаемся в MinGW Shell и повторяем то же самое, что и для libJpeg.

Сборка libTiff

Архив с исходными кодами берём с рекомендуемого ftp и распаковываем в D:\lib\tiff. И собираем аналогично предыдущим двум.

Сборка Leptonica

Архив с исходными кодами у нас уже есть, осталось его распаковать в D:\lib\leptonica. А дальше впору вспомнить про напильник, сборка с поддержкой Zlib не удастся из-за небольшого бага, который впрочем легко исправить самостоятельно. Для этого открываем файл src/pngio.c, расположенный в каталоге, куда мы распаковали исходники Leptonica. Там необходимо найти строку #include «png.h» и вставить после неё директивы, чтобы получилось примерно вот так:
#include "png.h"

#ifdef HAVE_LIBZ
#include "zlib.h"
#endif

/* ----------------Set defaults for read/write options ----------------- */

После этого собираем Leptonica так же как и предыдущие библиотеки.

Сборка и установка Tesseract-ocr


Теперь у нас есть все необходимые зависимости. Скачивать исходники на этот раз будем из транка svn разработчиков:
svn checkout ht tp://tesseract-ocr.googlecode.com/svn/trunk/ tesseract-ocr-read-only
*пробел между t преднащначен исключительно для хабрапарсера, уберите его.

После чего опять берёмся за напильник, я же предварительно сэкспортировал исходники в D:\lib\tesseract.
Пути к файлам я буду писать относительно каталога, в котором находятся исходники tesseract (напомню, что в моём случае это D:\lib\tesseract).
  • Редактируем файл ccutil/platform.h. Нам нужно закомментировать повторное объявление типа BLOB, которое уже есть в winsock2.h. Должно получиться, что-то вроде:
    /*typedef struct _BLOB {
    unsigned int cbSize;
    char *pBlobData;
    } BLOB, *LPBLOB;*/
  • Из vs2008/port копируем файлы strtok_r.h и strtok_r.cpp в каталог ccutil и добавляем strtok_r.cpp в переменную libtesseract_ccutil_la_SOURCES в файле ccutil/Makefile.am.
  • Комментируем объявление класса PBLOB в api/baseapi.h.
  • В файле api/Makefile.am дополняем переменную AM_CPPFLAGS значением -I$(top_srcdir)/vs2008/port
    или просто копируем файл vs2008/port/version.h в каталог api
  • Дополняем переменную AM_CPPFLAGS в файле viewer/Makefile.am значением -I$(top_srcdir)/ccutil

После этих манипуляций можно перейти в MinGW Shell и приступить к непосредственной сборке библиотеки:
cd /D/lib/tesseract
./runautoconf
./configure CFLAGS='-D__MSW32__ -O2' CXXFLAGS='-D__MSW32__-O2' LIBS='-lws2_32' LIBLEPT_HEADERSDIR='/mingw/include' --prefix=/mingw
make
make install

Пока он собирался я успел попить чаю, а после обнаружил пачку заголовочных файлов в /mingw/include/tesseract, заголовочные файлы Leptonica расположились в /mingw/include/leptonica, все библиотеки закономерно оказались в /mingw/lib.

Простое приложение


Я приведу код целиком, так как он весьма мал:
#include <stdio.h>
#include <string.h>
#include <tesseract/baseapi.h>
#include <leptonica/allheaders.h>

int main(int argc, char* argv[]) {
    tesseract::TessBaseAPI tessApi;
    tessApi.Init("data", "rus");// тут data каталог в котором лежат файлы *.traineddata,
    // а rus указывает какой именно из них использовать
    if(argc > 1) {
        PIX *pix = pixRead(argv[1]);// считываем картинку из файла с именем,
        // переданным первым аргументом, это функционал Leptonica
        tessApi.SetImage(pix);// говорим tesseract, что распознавать нужно эту картинку
        char *text = tessApi.GetUTF8Text();//распознаём
        //---генерируем имя файла в который будет записан распознанный текст
        char *fileName = NULL;
        long prefixLength;
        const char* lastDotPosition = strrchr(argv[1], '.');
        if(lastDotPosition != NULL) {
            prefixLength = lastDotPosition - argv[1];
            fileName = new char[prefixLength + 5];
            strncpy(fileName, argv[1], prefixLength);
            strcpy(fileName + prefixLength, ".txt\0");
        } else {
            exit(1);
        }
        //---
        FILE *outF = fopen(fileName, "w");
        fprintf(outF, "%s", text);
        fclose(outF);
        //---
        pixDestroy(&pix);
        delete [] fileName;
        delete [] text;
    }
    return 0;
}

Собрать наше приложение можно командой:
g++ -O2 test.cpp -o test.exe -ltesseract_api -ltesseract_main -ltesseract_textord -ltesseract_wordrec -ltesseract_ccstruct -ltesseract_ccutil -ltesseract_classify -ltesseract_dict -ltesseract_image -ltesseract_viewer -ltesseract_cutil -ltesseract_cube -ltesseract_neural -llept -lws2_32
Линковка статическая, так как текущая версия tesseract не поддерживает создание DLL.

Заключение


По своему опыту знаю, что сложнее всего начать, поэтому надеюсь, что мой рассказ будет кому-нибудь полезен, особенно учитывая, что документации по Tesseract в сети преступно мало и основную, пожалуй, можно вытянуть из самих исходников с помощью doxygen.
PS: некоторые идеи для фиксов были почерпнуты в этом посте за что автору огромное спасибо.
Share post

Similar posts

Comments 13

    +1
    Спасибо за статью. Хотелось бы в дополнение узнать ответы на пару вопросов.
    1) Насколько данная библиотека удовлетворила ваши требования и как бы вы оценили качество распознавания?
    2) На странице Readme в разделе установка в Windows для версии 3.0 есть ссылка на инсталлятор, а так же рекомендации собрать библиотеку из солюшена в самой vs2008. По какой причине вы выбрали сборку при помощи MinGW?
      0
      1) Распознавать пока пробовал только с дефолтными данными (rus.traineddata), при относительно чистом скане печатного текста распознавание почти 100%, сейчас как раз экспериментирую с методами очистки картинок. С использованием словаря и whitelist'а думаю точность распознавания будет ещё выше.
      2) Инсталлятор ставится без проблем, но библиотек в комплекте нет как и заголовочных файлов (ну или я не нашёл, тогда придётся посыпать голову пеплом). Со сборкой студией у меня возникли примерно аналогичные проблемы и решения там думаю должны быть примерно аналогичные. MinGW был выбран для единообразия с остальными библиотеками и просто из-за того, что обычно я использую NetBeans.
      –1
      И ещё вопрос — насколько я помню, у Tesseract были проблемы с распознаванием русского. Они в прошлом или с русским всё так же печально?
        0
        Не заметил особых проблем с распознаванием, он даже мой рукопечатный «привет» понял, единственно что букву 'в' пришлось с примерно равными ушками написать.
        Если речь о специфических шрифтах, то обучение может стать решением.
          –1
          Проблем нет.
          Можете обучить его даже мандаринскому, если у вас будет изображение символов алфавита. А коды запишете сами какие надо.
            0
            Насколько я помню, проблемы были с тем, что там были захардкожены символы латиницы, так что даже в русских словах буквы схожего написания (такие, как 'о', 'А') он распознавал как латинские. Что же, если проблемы больше нет — это здорово.
              0
              Лично я использовал tesseract с целью распознавания специфичных символов, и у меня был свой алфавит и свои коды.
              Я бы в этом случае решил вопрос также — составил словарь для обучения утилиты состоящий из символов русского алфавита и записал туда нужные коды win-1251 или любой другой кодовой страницы. Это, кстати, уменьшит вероятность ошибки распознавания.
              Если же натаскивать утилитку лень, то str_replace вам в помощь.

        0
        Скажите, а умеет ли библиотека распознавать заголовки или таблицы?
          0
          Насколько помню он умеет распознавать многоколоночный текст, насчёт форматирования вывода именно таблицей не уверен, у меня пока не было необходимости в таком функционале.
          Насчёт заголовков не очень понял если честно что подразумевается.
          0
          Этот «напильник» напоминает страшный сон любого юниксоида — C:/etc/X11.
            0
            Какая дикая смесь СИ и С++. И когда уже люди отучаться ТАК писать на С++?..
              0
              Если критика в адрес моего тестового приложения, то всегда рад если меня поправят увы не являюсь гуру в С/С++, пишу так как удобней
              Если это в адрес самого tesseract, то это наследие HP, разработка была начата если не ошибаюсь в 1985 (по сути страшно сказать он мой ровесник) и именно на С, а полное портирование на С++ не такое уж простое дело
              0
              Я имел в виду код, который написан Вами. А суть проста: если Вы пишете на С++ — то и используйте инструментарий С++, а не СИ. Например, используйте строковые контейнеры (std::basic_string) для работы со строками, и std::basic_stream для работы с файлами. А если Вы пишите на СИ — то в приведенном выше коде замените вызовы операторов new [] и delete [] на calloc/malloc и free соответсвенно.

              Only users with full accounts can post comments. Log in, please.