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

Пролог


По ходу своей трудовой деятельности получил задачу придумать и реализовать систему учета рекламной информации. Учет заключался в проверке наличия нужной информации на нужном рекламном щите. Щит и полиграфия пронумерованы.
В качестве исходной информации для системы предлагалось использовать фото. После торговли согласования с дизайнерами было оговорено, что оба номера будут располагаться внутри одной рамки. Единственное, что рамка могла быть в любом месте щита.
Собственно на этом постановка задачи заканчивается и начинается повествование о реализации.
Задача решается в три действия:
  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 Шрифт

Средняя зарплата в IT

113 000 ₽/мес.
Средняя зарплата по всем IT-специализациям на основании 10 037 анкет, за 2-ое пол. 2020 года Узнать свою зарплату
Реклама
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее

Комментарии 15

    0
    А сделайте какой-нибудь perfomance_test.cpp для сравнения со стандартными алгоритмами из OpenCV, было бы интересно посмотреть, что там с быстродействием и точностью обработки на тестовом наборе данных
      0
      Сравнить быстродействия двух алгоритмов будет достаточно сложно, так как в имеющемся решении присутствуют операции записи\чтения из файла. Если реализовывать решение полностью на C++ с использованием OpenCV, то это решение будет скорее всего быстрее за счет экономии времени на этих операциях.
      Единственный момент — создание базы изображений, тут tesseract мне показался более удобен.
      +1
      «не горизонтально вертикальные границы» :)
        0
        Да, не совсем корректно выразился :)
        Не горизонтальные и вертикальные границы. На первой вырезке как раз такая картинка.
        +1
        А кто фотографировал щиты и каким образом? Просто если это делалось вручную, то какой смысл в автоматическом распознавании?
          0
          Совершенно справедливый вопрос. Разработка средства доставки изображения — следующая задача.
          Вариантов множество: MMS, E-mail или некое собственное приложение, в котором будут жестко зашиты координаты сервера, чтоб персонал лишний раз не думал.
            0
            Так а смысл-то какой? Зачем делать распознавание, если цифры сразу могут быть прочтены человеком?
              0
              Смысл как раз в том, чтобы человек в этом участвовал минимально и правильность установки проверялась автоматически. А цифры, в отличии от тех же самых QR кодов, как раз позволят проверять и в ручном режиме.
                0
                Трудно назвать минимальным участием случай, когда человек ездит от щита к щиту и фотографирует их. Имхо, автоматизировать следовало как раз это звено.
          0
          В питере подобную систему запускали еще пару лет назад.
          Агенты объезжали рекламные щиты и снимали их наладониками с GPS, по инету фотка отправлялась на центральный сервер вместе с координатами.
          В центре картинка распознавалась путем сравнения с «эталонном» (без всяких дополнительных цифр).
          На основании рекламных контрактов (они все были в базе) принималось решение о том, «правильная» висит реклама или чужая (просроченная).
          Так как на система автоматически выписывала «штрафы» обслуживающим организациям, начальника подразделения разработавшего и внедрившего все это через несколько месяцев «убрали с повышением»
            0
            Решение красивое, но обладает требованием по наличию GPS.
            Здесь же задача привязки к местности решается за счет сопоставления 2-х номеров.
            Еще одна проблема заключается в том, что для разных щитов полиграфия может отличаться незначительно и при не самом удачном снимке и плохой камере можно получить ошибку.
            С контрольной суммой же, вероятность ошибиться ничтожна: либо есть, либо нет.
              0
              GPS — это не сложно ;)
              А по поводу влияния полиграфии на качество распознавания, так для этого и есть такая дисциплина — распознавание образов. Изучение которой и позволило создать систему.
            +1
            А QR кодами не проще было бы? =)
              0
              Такой вариант рассматривался, однако у него есть свои недостатки.
              Если посмотреть на последнее фото, его размер 320 на 240, сделан он на древний Sony Erricson.
              Неизвестно как выглядел бы QR код после сжатия в jpeg на этом фото.
              Изначально информация в арабских цифрах обладает большей избыточностью и помехоустойчивостью.
                0
                Вы недооцениваете QR Code и его помехоустойчивость, а также то, с какими дефектами справляются распознающие алгоритмы. На той же площади, которую занимает текст (и который довольно часто распознаётся с ошибками), можно разместить QR код с большей информацией и защитой от дефектов.

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое