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

Введение

В настоящее время существует тенденция сокращения расходов автотранспортных предприятий и предприятий, сфера деятельности которых связана с охраной и безопасностью. Достигается это в том числе и путем внедрения современных информационных систем. Одной из таких систем является система автоматического распознавания номеров авто (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 прописываем пути до заголовочных файлов и библиотек. К примеру, у меня это выглядит так:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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 и изменим его для наших нужд. Алгоритм ищет прямоугольники на картинке, затем отсекает ненужные. Критерием отсеивания будет соотношение сторон авто номера, а так же его наполненность черными пикселями после процедуры бинаризации. Как показывает практика, хорошо работает уже первый критерий, поэтому оставим только его. Процедура бинаризации переводит изображение из серого в черно-белое. Делается это в связи с тем, что многие алгоритмы компьютерного зрения адаптированы для работы с черно-белым изображением.

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
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 – входное изображение номера авто.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
cv::Mat src(txt);
cv::Mat dst = cvCreateImage( cvSize(txt-&gt;width, txt-&gt;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-&gt;width, txt-&gt;height), 8, 1);
  IplImage* rgb_img = cvCreateImage(cvSize(txt-&gt;width, txt-&gt;height), 8, 3);
  cvCvtColor(smooth, gray, CV_RGB2GRAY);
  binary = cvCreateImage( cvSize(gray-&gt;width, gray-&gt;height), gray-&gt; 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.

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

1
2
3
4
5
6
7
8
9
10
11
12
13
    cvCanny(gray, canny, 10, 100, 3);
    CvMemStorage* storage = cvCreateMemStorage(0);
    CvSeq* contours=0;
    int contoursCont = cvFindContours( canny, storage,&amp;contours,
sizeof(CvContour), CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE,cvPoint(0,0));
    std::vector seqvec;
    std::vector recvec;
    cv::Mat _img(binary);
    std::map pic_list;
    cvCvtColor(erode, rgb_img, CV_GRAY2RGB);
    IplImage* arealOfText = rgb_img;
    tesseract::TessBaseAPI tessApi;
    cv::Mat image1(arealOfText);

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    for (CvSeq* seq0 = contours; seq0 != 0; seq0 = seq0-&gt;h_next){
          seqvec.push_back(seq0);
          double area = fabs(cvContourArea(seq0));
          double perim = cvContourPerimeter(seq0);
          CvRect rec1 = cvBoundingRect(seq0);
          qDebug() &lt;&lt; "Heigth" &lt;&lt; rec1.height;             
          if (rec1.height &gt; 0.5*gray-&gt;height){
              IplImage* dst1 = cvCreateImage(cvSize(rec1.width, rec1.height), 
binary-&gt;depth, 1);;
              cv::Mat dst_mat = _img(rec1);
              *dst1 = dst_mat;
              IplImage* rgb_img1 = cvCreateImage(cvSize(dst1-&gt;
width, dst1-&gt;height), 8, 3);
              cvCvtColor(dst1, rgb_img1, CV_GRAY2RGB);
              pic_list[rec1.x] = rgb_img1;
           }
   }

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  std::string str;
  std::map::iterator iter = pic_list.begin();
  while (iter != pic_list.end()){
         IplImage* rgb_img1 = iter-&gt;second;
         cv::Mat image2(rgb_img1);
         int x = iter-&gt;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() &lt;&lt; str.c_str();
         iter++;
}

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

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

1
2
3
4
5
6
7
8
9
CvCapture* capture = cvCreateCameraCapture(CV_CAP_ANY);
assert( capture );
IplImage* frame=0;
while(true){
     //получаем кадр
     frame = cvQueryFrame( capture );
     //Далее распознаем полученный кадр}

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

Заключение

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

Оригинал статьи http://www.ibm.com/developerworks/ru/library/os-avto/

 

У нас есть похожие новости по этим темам:
Наверх

1 комментарий