Содержание


Библиотеки для автоматического распознавания номеров авто и их практическое использование

Comments

В настоящее время существует тенденция сокращения расходов автотранспортных предприятий и предприятий, сфера деятельности которых связана с охраной и безопасностью. Достигается это в том числе и путем внедрения современных информационных систем. Одной из таких систем является система автоматического распознавания номеров авто (ANPR).

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

Обзор существующих библиотек для распознавания номеров авто

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

  1. Проект Opos. Реализация – С#. Проект располагается по адресу http://opos.codeplex.com. Анализ исходного кода показал, что для распознавания номера авто используется порт библиотеки компьютерного зрения opencv на C#, а для, собственно, распознавания символов номера используется обертка на C# над библиотекой OCR CuneiFrom – Puma.NET. Библиотека имеет один серьезный недостаток – изображения для последующей передачи в Puma.NET сначала сохраняется на диск, что затрудняет использование этой библиотеки в системах реального времени.
  2. Проект JavaANPR. Реализация – Java. Проект располагается по адресу http://javaanpr.sourceforge.net. Основное преимущество этой библиотеки в ее кроссплатформенности. Кроме этого, все алгоритмы написаны на Java без использования нативных библиотек, что сильно упрощает использование. Так же эту библиотеку с небольшой доработкой можно использовать на устройствах под управлением OS Android. Скорость распознавания одной картинки с автомобильным номером порядка 0.2 – 0.8 сек, что позволяет использовать ее в системах реального времени. Библиотека очень хорошо документирована. Один из недостатков этой библиотеки это то, что библиотека хорошо работает на примерах, которые идут с ней в комплекте. В случае зашумленных картинок или картинок с плохой освещенностью библиотека иногда дает не очень хороший результат.
  3. Проект Automatic License Plate Recognition. Поект располагается по адресу http://sourceforge.net/projects/licenseplate. Реализация – С#. Данный проект использует две библиотеки – порт библиотеки opencv на C# - Emgu для поиска номера и порт библиотеки tesseract OCR – tessnet для распознавания автомобильного номера. Среди преимуществ данной библиотеки то, что она работает с кириллическими символами. Среди недостатков можно отметить, что отсутствуют примеры для использования библиотеки в системах реального времени.

Существует так же еще несколько проектов в сети в той или иной степени законченности. Многие из них так или иначе используют библиотеку компьютерного зрения opencv и библиотеку tesseract OCR. Хотя некоторые библиотеки используют свой алгоритм распознавания символов – например, JavaANPR. Как правило, эти алгоритмы основаны на нейронных сетях, либо на анализе контуров символов. Стоит отметить, что алгоритмы, основанные на нейронных сетях бывают чувствительны к выбору шрифта. Самым эффективным решением, очевидно, было бы использование в своем проекте библиотеки opencv для локализации номера и библиотеки Tesseract OCR для распознавания номера. Этот подход позволит наиболее гибко использовать весь потенциал этих библиотек. Opencv написана на C и хорошо оптимизирована для использования в системах реального времени. Tesseract OCR в настоящее время является лучшей открытой библиотекой для распознавания символов, обладает хорошей скоростью работы и хорошо документирована.

В качестве среды разработки выберем библиотеку Qt и среду разработки Qt Creator для OS Windows 7. А в качестве компилятора выберем компилятор mingw, который идет в комплекте с бесплатной средой разработки Qt Creator. Версия библиотеки opencv, используемая в данной статье – 2.4.2. Версия библиотеки Tesseract OCR – 3.02.

Установка необходимых библиотек и настройка проекта

Для начала установим последнюю версию среды разработки Qt со всеми библиотеками. В комплекте уже идут скомпилированные библиотеки для компилятора mingw. Используемая в данном проекте версия библиотек Qt – 4.8.2.

Библиотеку opencv можно скачать с официального сайта - http://opencv.org/downloads.html . Распаковываем библиотеку в папку на диске, например, C:\opencv24.

Библиотеку Tesseract OCR 3.02 можно скачать из svn репозитория в виде исходного кода, выполнив команду svn checkout http://tesseract-ocr.googlecode.com/svn/trunk/ tesseract-ocr-read-only . Для этого сначала необходимо установить SVN клиент. Получив исходники, компилируем их, как это показано здесь http://www.sk-spell.sk.cx/compiling-leptonica-and-tesseract-ocr-with-mingwmsys . Стоит отметить очень важный момент, а именно - версию библиотеки 3.02 удалось собрать лишь динамически. Статическая сборка отказалась работать.

После установки библиотек создаем проект Qt. В файле pro прописываем пути до заголовочных файлов и библиотек. К примеру, у меня это выглядит так:

INCLUDEPATH += C:\opencv24\build\include
INCLUDEPATH += C:\opencv24\build\include\opencv
CONFIG(release,debug|release)
{
LIBS += C:\opencv24\build\x86\vc9\lib\opencv_calib3d242.lib \
C:\opencv24\build\x86\vc9\lib\opencv_contrib242.lib \
C:\opencv24\build\x86\vc9\lib\opencv_core242.lib \
C:\opencv24\build\x86\vc9\lib\opencv_features2d242.lib \
C:\opencv24\build\x86\vc9\lib\opencv_flann242.lib \
C:\opencv24\build\x86\vc9\lib\opencv_gpu242.lib \
C:\opencv24\build\x86\vc9\lib\opencv_haartraining_engine.lib \
C:\opencv24\build\x86\vc9\lib\opencv_highgui242.lib \
C:\opencv24\build\x86\vc9\lib\opencv_imgproc242.lib \
C:\opencv24\build\x86\vc9\lib\opencv_legacy242.lib \
C:\opencv24\build\x86\vc9\lib\opencv_ml242.lib \
C:\opencv24\build\x86\vc9\lib\opencv_objdetect242.lib \
C:\opencv24\build\x86\vc9\lib\opencv_ts242.lib \
C:\opencv24\build\x86\vc9\lib\opencv_video242.lib \
}
CONFIG(debug,debug|release)
{
LIBS += C:\opencv24\build\x86\vc9\lib\opencv_calib3d242d.lib \
C:\opencv24\build\x86\vc9\lib\opencv_contrib242d.lib \
C:\opencv24\build\x86\vc9\lib\opencv_core242d.lib \
C:\opencv24\build\x86\vc9\lib\opencv_features2d242d.lib \
C:\opencv24\build\x86\vc9\lib\opencv_flann242d.lib \
C:\opencv24\build\x86\vc9\lib\opencv_gpu242d.lib \
C:\opencv24\build\x86\vc9\lib\opencv_haartraining_engined.lib \
C:\opencv24\build\x86\vc9\lib\opencv_highgui242d.lib \
C:\opencv24\build\x86\vc9\lib\opencv_imgproc242d.lib \
C:\opencv24\build\x86\vc9\lib\opencv_legacy242d.lib \
C:\opencv24\build\x86\vc9\lib\opencv_ml242d.lib \
C:\opencv24\build\x86\vc9\lib\opencv_objdetect242d.lib \
C:\opencv24\build\x86\vc9\lib\opencv_ts242d.lib \
C:\opencv24\build\x86\vc9\lib\opencv_video242d.lib \
}

INCLUDEPATH += C:\libtesseract\tesseract302\api
INCLUDEPATH += C:\libtesseract\tesseract302\ccmain
INCLUDEPATH += C:\libtesseract\tesseract302\ccutil
INCLUDEPATH += C:\libtesseract\tesseract302\ccstruct
INCLUDEPATH += C:\libtesseract\tesseract302\include

LIBS += C:\libtesseract\tesseract302\DLL_Release\libtesseract302.lib
LIBS += C:\libtesseract\lib\liblept168.lib
LIBS += C:\libtesseract\lib\liblept168-static-mtdll.lib
LIBS += C:\libtesseract\lib\giflib416-static-mtdll.lib
LIBS += C:\libtesseract\lib\libjpeg8c-static-mtdll.lib
LIBS += C:\libtesseract\lib\libtiff394-static-mtdll.lib
LIBS += C:\libtesseract\lib\zlib125-static-mtdll.lib

Реализация алгоритма локализации автомобильного номера и его распознавания

Существует несколько подходов к локализации автомобильного номера. Один из них это анализ гистограммы картинки и выявление контрастных областей. Однако, в случае если автомобиль покрашен яркой краской или в условиях плохого освещения этот алгоритм не работает. Поэтому мы используем более ресурсоемкий, но при этом более надежный алгоритм поиска ограничивающего номер прямоугольника. Для этого возьмем пример, который поставляется с opencv findSquares и изменим его для наших нужд. Алгоритм ищет прямоугольники на картинке, затем отсекает ненужные. Критерием отсеивания будет соотношение сторон авто номера, а так же его наполненность черными пикселями после процедуры бинаризации. Как показывает практика, хорошо работает уже первый критерий, поэтому оставим только его. Процедура бинаризации переводит изображение из серого в черно-белое. Делается это в связи с тем, что многие алгоритмы компьютерного зрения адаптированы для работы с черно-белым изображением.

Загрузим тестовую картинку:

cv::Mat img_m = cv::imread(fileName.toStdString().c_str());

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

void CarNum::findSquares(std::vector<std::vector<cv::Point> >& squares){
         squares.clear();
         cv::Mat image = img_m;
         cv::Mat pyr, timg, gray0(image.size(), CV_8U), gray;
         int thresh = 50, N = 11;
         // down-scale and upscale the image to filter out the noise
         pyrDown(image, pyr, cv::Size(image.cols/2, image.rows/2));
         pyrUp(pyr, timg, image.size());
         std::vector<std::vector<cv::Point> > contours;
         // find squares in every color plane of the image
         for( int c = 0; c < 3; c++ )
         {
             int ch[] = {c, 0};
             mixChannels(&timg, 1, &gray0, 1, ch, 1);
             // try several threshold levels
             for( int l = 0; l < N; l++ )
             {
                 // hack: use Canny instead of zero threshold level.
                 // Canny helps to catch squares with gradient shading
                 if( l == 0 )
                 {
                     // apply Canny. Take the upper threshold from slider
                     // and set the lower to 0 (which forces edges merging)
                     Canny(gray0, gray, 0, thresh, 5);
                     // dilate canny output to remove potential
                     // holes between edge segments
                     cv::dilate(gray, gray, cv::Mat(), cv::Point(-1,-1));
                 }
                 else
                 {
                     // apply threshold if l!=0:
                     //     tgray(x,y) = gray(x,y) < (l+1)*255/N ? 255 : 0
                     gray = gray0 >= (l+1)*255/N;
                 }
                 // find contours and store them all as a list
                 findContours(gray, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);
                 std::vector<cv::Point> approx;
                 // test each contour
                 for( size_t i = 0; i < contours.size(); i++ )
                 {
                     // approximate contour with accuracy proportional
                     // to the contour perimeter
               approxPolyDP(cv::Mat(contours[i]), 
approx, arcLength(cv::Mat(contours[i]), true)*0.02, true);
                     // square contours should have 4 vertices after approximation
                     // relatively large area (to filter out noisy contours)
                     // and be convex.
                     // Note: absolute value of an area is used because
                     // area may be positive or negative - in accordance with the
                     // contour orientation
                     if( approx.size() == 4 &&
                         fabs(cv::contourArea(cv::Mat(approx))) > 1000 &&
                         cv::isContourConvex(cv::Mat(approx)) )
                     {
                         double maxCosine = 0;
                         for( int j = 2; j < 5; j++ )
                         {
                             // find the maximum cosine of the angle between joint edges
                             double cosine = fabs(angle(approx[j%4],
 approx[j-2], approx[j-1]));
                             maxCosine = MAX(maxCosine, cosine);
                         }
                         // if cosines of all angles are small
                         // (all angles are ~90 degree) then write quandrange
                         // vertices to resultant sequence
                         cv::Rect rec = cv::boundingRect(cv::Mat(approx));
                         int h = image.size().height;
                         int w = image.size().width;
                         if ((rec.height > 0.2*h)||(rec.height < 20)             continue;
                         if ((rec.width > 0.2*w)||(rec.height < 20)) continue;
                         if (rec.height > rec.width) continue;
                         if( maxCosine < 0.1 )
                             squares.push_back(approx);
                     }
                 }
             }
         }
 }

std::vector<std::vector<cv::Point> > squares;
findSquares(squares);
if (squares.size() == 0) return NULL;

...

После того, как мы найдем подходящий нам по критериям регион, нам необходимо передать его для анализа в библиотеку Tesseract OCR. На этом этапе мы столкнемся с проблемой шумов на картинке. Tesseract OCR очень чувствительна к шумам. А на полученном регионе обязательно будут присутствовать пятна. Для того, что бы избавится от них нам необходимо бинаризовать изображение номера и применить операции эрозии и дилатации. Операции эрозии и дилатации — это морфологические операции. Эрозия делает объекты более тонкими, а дилатация, напротив утолщает их. При этом в изображении убираются шумы и остаются лишь значимые детали. В нашем случае этими деталями будут символы автомобильного номера. Более подробно ознакомится с морфологическими операциями можно здесь: Математическая_морфология. Остановимся на это более подробно. Вот код, который делает вышеописанное:

txt – входное изображение номера авто.

cv::Mat src(txt);
    cv::Mat dst = cvCreateImage( cvSize(txt->width, txt->height),
 IPL_DEPTH_8U, 1);
    IplImage* binary = 0;
    IplImage* gray = 0;
    IplImage* smooth = cvCloneImage(txt);
    cvSmooth(txt, smooth, CV_GAUSSIAN,11);
    gray = cvCreateImage(cvSize(txt->width, txt->height), 8, 1);
    IplImage* rgb_img = cvCreateImage(cvSize(txt->width, txt->height), 8, 3);
    cvCvtColor(smooth, gray, CV_RGB2GRAY);
    binary = cvCreateImage( cvSize(gray->width, gray->height), gray->depth, 1);
    cvThreshold(gray, binary, 100, 250, CV_THRESH_BINARY);
    int radius = 1;
    IplConvKernel* Kern = cvCreateStructuringElementEx(radius*2+1, radius*2+1, 
radius, radius, 1);
    IplImage* img1;
    IplImage* erode;
    IplImage* dilat;
    IplImage* canny;
    erode = cvCloneImage(binary);
    dilat = cvCloneImage(binary);
    canny = cvCloneImage(gray);
    cvErode(binary, erode, Kern, 1);
        cvDilate(erode, erode, Kern, 1);

...

Однако, даже после этих операций останутся пятна. Поэтому для надежного распознавания символов на картинке применим оператор Кэнни для поиска внешних контуров букв, найдем все буквы, вырежем их из изображения и передадим в Tesseract. Оператор Кэнни предназначен для поиска контуров изображения. Контуром объекта на изображении может быть область, где градиент яркости изменяется наиболее сильно. В нашем случае мы будет искать лишь внешние контуры символов. Более подробно об операторе Кэнни можно узнать здесь Оператор_Кэнни . В opencv для поиска контуров служит функция cvCanny.

Поиск контуров:

    cvCanny(gray, canny, 10, 100, 3);
    CvMemStorage* storage = cvCreateMemStorage(0);
    CvSeq* contours=0;
    int contoursCont = cvFindContours( canny, storage,&contours,sizeof(CvContour),
CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE,cvPoint(0,0));
    std::vector<CvSeq*> seqvec;
    std::vector<CvRect> recvec;
    cv::Mat _img(binary);
    std::map<nt, IplImage*> pic_list;
    cvCvtColor(erode, rgb_img, CV_GRAY2RGB);
    IplImage* arealOfText = rgb_img;
    tesseract::TessBaseAPI tessApi;
    cv::Mat image1(arealOfText);

Инициализация библиотеки Tesseract и ее настройка (важным моментом здесь является использование параметра PSM_SINGLE_CHAR, который указывает библиотеке, что мы будем распознавать по одному символу):

      int c = tessApi.Init(NULL,"eng");
    tessApi.SetPageSegMode(PSM_SINGLE_CHAR);
    tessApi.SetVariable("tessedit_char_whitelist",
 "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz");

Далее – цикл по контурам и выбор букв из них

    for (CvSeq* seq0 = contours; seq0 != 0; seq0 = seq0->h_next){
            seqvec.push_back(seq0);
            double area = fabs(cvContourArea(seq0));
            double perim = cvContourPerimeter(seq0);
            CvRect rec1 = cvBoundingRect(seq0);
            qDebug() << "Heigth" << rec1.height;
            if (rec1.height > 0.5*gray->height){
                IplImage* dst1 = cvCreateImage(cvSize(rec1.width, rec1.height),
 binary->depth, 1);;
                cv::Mat dst_mat = _img(rec1);
                *dst1 = dst_mat;
                IplImage* rgb_img1 = cvCreateImage(cvSize(dst1->
width, dst1->height), 8, 3);
                cvCvtColor(dst1, rgb_img1, CV_GRAY2RGB);
                pic_list[rec1.x] = rgb_img1;
            }
    }

Этап распознавания символов и формирования строки:

     std::string str;
   std::map<int, IplImage*>::iterator iter = pic_list.begin();
   while (iter != pic_list.end()){
        IplImage* rgb_img1 = iter->second;
        cv::Mat image2(rgb_img1);
        int x = iter->first;
        tessApi.SetRectangle(0,0,image2.size().width, image2.size().height);
        tessApi.SetImage((uchar*)image2.data, image2.size().
width, image2.size().height, image2.channels(), image2.step1());
        const char* out = tessApi.GetUTF8Text();
        str += out[0];
        qDebug() << str.c_str();
        iter++;
    }

Использование данных библиотек в системах реального времени

Для использование данных библиотек в системе видео наблюдения, необходимо добавить код для получения кадра видео из видео потока. Выглядит это так:

CvCapture* capture = cvCreateCameraCapture(CV_CAP_ANY);
assert( capture );
IplImage* frame=0;
while(true){
                //получаем кадр
   frame = cvQueryFrame( capture );
//Далее распознаем полученный кадр
…
}

Получив кадр, мы распознаем его описанным выше алгоритмом.

Заключение

Как показывают эксперименты, скорость обработки картинок связкой Opencv + Tesseract OCR достаточно высока. Это позволяет использовать данные библиотеки в системах распознавания номеров авто в режиме реального времени. Данный код абсолютно кроссплатформенный. Для построения интерфейса бала использована кроссплатформенная библиотека Qt и свободный компилятор mingw.


Ресурсы для скачивания


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Open source
ArticleID=844726
ArticleTitle=Библиотеки для автоматического распознавания номеров авто и их практическое использование
publish-date=11062012