Pull to refresh

Система учета на базе OCR системы

Reading time5 min
Views7.5K

Пролог


По ходу своей трудовой деятельности получил задачу придумать и реализовать систему учета рекламной информации. Учет заключался в проверке наличия нужной информации на нужном рекламном щите. Щит и полиграфия пронумерованы.
В качестве исходной информации для системы предлагалось использовать фото. После торговли согласования с дизайнерами было оговорено, что оба номера будут располагаться внутри одной рамки. Единственное, что рамка могла быть в любом месте щита.
Собственно на этом постановка задачи заканчивается и начинается повествование о реализации.
Задача решается в три действия:
  1. Нахождение нужного прямоугольника на изображении.
  2. Распознавание текста.
  3. Проверка правильности распознавания.


Действие первое — поисковое


Чтобы найти нужный прямоугольник на картинке проще всего найти все куски, которые можно назвать прямоугольниками, а затем по определенным параметрам отфильтровать их. Для поиска прямоугольников на изображении был использован немного допиленный стандартный пример из OpenCV — squares.cpp, из которого взята функция поиска прямоугольников.
Процедура поиска фигур достаточно примитивная и при наличии на входе сложной картинки с множеством цветовых границ и переходов выдает кучу прямоугольников, из которых еще до процедуры распознавания, нужно повыкидывать ненужное.

Ненужное фильтруется нескольким критериям:
1. Соотношение ширины и высоты.
В программе стоит критерий отсечки (r.width < 5*r.height), его можно усовершенствовать и использовать более точно условие условие с дельтой.
Тут главное, чтобы фотограф не проявлял фантазию и не снимал объект, повернув камеру на 90o (сфотографируй меня с ногами).
2. Убрать приблизительно одинаковые фигуры.

Еще один момент: перед фильтрацией спрямляем прямоугольники, так как рука у фотографа может дрогнуть и искомый прямоугольник может иметь на фотографии не горизонтально вертикальные границы.

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


<source lang="cpp">
#include "cv.h"
#include "highgui.h"
#include <iostream>
#include <math.h>
#include <string.h>
#include <stdio.h>

using namespace cv;
using namespace std;

typedef vector<Point> polygon;
typedef vector<polygon> polygonList;

...
//Сравнение для фильтрации схожих фигур
bool compareRect(const CvRect &r1, const CvRect &r2)
{    
    if (!r1.width || !r1.height) return false;
    
    if ((float)abs(r1.width- r2.width)/(float)r1.width > 0.05) return false;
    if ((float)abs(r1.height - r2.height)/(float)r1.height > 0.05) return false;
    if ((float)abs(r1.x - r2.x)/(float)r1.width > 0.02) return false;        
    if ((float)abs(r1.y - r2.y)/(float)r1.height > 0.02) return false;

    return true;
}

//Спрямляем прямоугольник
CvRect getRect(const polygon& poly)
{
    CvPoint p1 = cvPoint(10000,10000);
    CvPoint p2 = cvPoint(-10000,-10000);
    for (size_t i=0; i < poly.size(); i++) 
    {
        const Point p = poly[i];
        if (p1.x > p.x) p1.x = p.x;
        if (p1.y > p.y) p1.y = p.y;
        if (p2.x < p.x) p2.x = p.x;
        if (p2.y < p.y) p2.y = p.y;
    }
    return cvRect(p1.x,p1.y,p2.x-p1.x,p2.y-p1.y);    
}


int main(int argc, char** argv)
{
    if(argc <= 3)
    {
        cout << "Wrong Param Count: " << argc << endl;
        cout << "Usage: findrect infile extension outfolder" << endl;
        return 1;
    }
        
    char *fileIn = argv[1];
    char *fileExt = argv[2];
    char *dirOut = argv[3];    
    char fileOut[128];    
    polygonList squares;    
    IplImage *Img = cvLoadImage(fileIn,1);
        
    Mat image(Img);
    if(image.empty())
    {
        cout << "Couldn't load " << fileIn << endl;
        return 1;
    }
    
    findSquares(image, squares);      
    vector<CvRect> rectList;
    int p = 0;
    
    int adaptive_method = CV_ADAPTIVE_THRESH_GAUSSIAN_C;
    int threshold_type = CV_THRESH_BINARY;
    int block_size = 65;
    double offset = 10;
    
    for (int j=0; j<squares.size(); j++)
    {
        //спрямляем прямоугольник
        CvRect r = getRect(squares[j]);
        if (r.width < 5*r.height) continue;
        
        //не добавляем похожие по размерам 
        bool doContinue = false;
        for (int k=0; k<rectList.size(); k++)            
            if (compareRect(r, rectList[k])) {
                doContinue = true;
                break;                                        
            }
        if (doContinue) continue;
        
        rectList.push_back(r);            
        
        //копируем нужный участок с исходника
        cvSetImageROI(Img, r);
        IplImage *dst = cvCreateImage(cvSize(r.width, r.height), Img->depth, Img->nChannels);            
        IplImage *gray = cvCreateImage(cvSize(r.width, r.height), 8, 1);            
        IplImage *bw = cvCreateImage(cvSize(r.width, r.height), 8, 1);            
        cvCopy(Img, dst, NULL);        
        cvResetImageROI(Img);        
        
        //выводим информацию о файле, она будет нужна для последующей обработки в php
        sprintf(fileOut,"%s/%d.%s",dirOut, p, fileExt);
        cout << fileOut << endl;
        p++;        
        
        //преобразуем в черно-белый
        cvCvtColor(dst,gray,CV_RGB2GRAY);             
        cvAdaptiveThreshold(gray, bw, 255, adaptive_method,threshold_type,block_size,offset);    
        cvSaveImage(fileOut, bw);        
        cvReleaseImage(&dst);        
        cvReleaseImage(&gray);        
        cvReleaseImage(&bw);        
    }          
    return 0;
}


Действие второе — распознавательное


На вход утилитке распознавания поступает как нормальный контент так и мусор.
image

image

image

image

Как и было заявлено ранее, для распознавания используем утилиту от Google — tesseract.
Можно было использовать и другие средства для распознавания, тестировалось также cuniform.
Но tesseract был выбран по причине того, что по нему много информации и была понятная инструкция по его тренировке на свой набор символов.

Тренировка на свой алфавит была сделана с несколькими целями:
  1. Словарь для распознавания цифр — должен состоять из 10 символов, не нужны буквы и другие символы. Короткий набор вероятность ошибки.
  2. В принципе, на 1-м можно было и остановиться — у tesseract есть режим распознавания только цифр. Можно было бы использовать его и не заморачиваться созданием своего словаря.
    Но результаты тестирования подвигли еще к одной идее и причина в следующем: обычные шрифты (входящие в стандартный набор), имеют символы цифр с точки зрения OCR похожие друг на друга: цифра «7» при определенных условиях похожа на «1», цифра «3» на «8», и т.д.
    Поэтому и было принято решение использовать шрифт, в котором символ цифр не будут похожи друг на друга. В качестве подсказки для поиска шрифта было название оного — «OCR A Std». Этот шрифт как раз и использован на приведенных выше вырезках.
    Таким образом, имеем еще один фактор для снижения вероятности ошибки.

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

Действие третье — собирательное


Работа системы тестировалась под Ubuntu. Запуск утилит нарезки и распознавания выполняется php.
Здесь же осуществляется окончательная проверка распознанных данных методом контрольной суммы.
Используется алгоритм crc-8.


$imagesout = '/home/toor/www/out';
$findrect = '/home/toor/OCR/OpenCV-2.2.0/samples/cpp/findrect';
$uploaddir = '/home/toor/www/uploads/';
$rectdir = '/home/toor/www/out/';
$tesseract = '/home/toor/OCR/tesseract-3.00/api/tesseract';

...

if (isset($_FILES['userfile']['tmp_name'])) 
{
    $uploadfile = $uploaddir. $_FILES['userfile']['name'];
    if (!move_uploaded_file($_FILES['userfile']['tmp_name'], $uploaddir . $_FILES['userfile']['name'])) 
    {
        echo "Есть ошибки!";
        exit(1);
    } 
    echo "Файл {$_FILES['userfile']['name']} успешно загружен!";
    $cmd = "$findrect $uploadfile tif $imagesout";    
    exec($cmd, $output);
    echo count($output)." фрагментов";
    $datas = array();
    foreach($output as $k => $f)
    {         
         $recognized = "$rectdir$k.txt";
         $cmd = "$tesseract $f $rectdir$k -l nums.ocr";             
         exec($cmd);         
         if (!file_exists($recognized)) continue;
         
         echo "file: $recognized";
         
         $data = file_get_contents($recognized);         
                           
         $data = preg_replace('/\D/','',$data);
         $data = trim($data);
         if (!strlen($data)) continue;
         if (!array_key_exists($data,$datas))  $datas[$data] = 1; else $datas[$data]++;
    }
    
    foreach ($datas as $d => $v)
    {
              if ($r = crc_check($d, NUMBER_LEN_1, NUMBER_LEN_CRC_1))  {
                  echo 'Найден номер: '.$r;
             }
            if ($r = crc_check($d, NUMBER_LEN_2, NUMBER_LEN_CRC_2))  {
                  echo 'Найден номер: '.$r;
             }

    }
}


В целом в тестовом режиме система показала себя достаточно неплохо.
Отрабатываются картинки с самых простых телефонов как эта

и до нескольких мегабайт c цифровых фотоаппаратов.

Ссылки


Tesseract
OpenCV
OCR A Std Шрифт
Tags:
Hubs:
+17
Comments15

Articles