Перейти к содержимому
Compvision.ru
VironZizu

Распознавание текста паспорта, OpenCV!

Recommended Posts

Приветствую! В поисках решения проблемы распознования набрел на ваш форум, очень нужен совет!

Задача моя, считать поля паспорта, для распознавания, использую готовую библиотеку puma.NET, она со своей задачей отлично справляется! Но на вход ей нужно подавать естественно преобразованный скан паспорта! Нашел небольшую методику как это можно сделать, но не могу подобрать нужные методы библиотеки OpenCV, а именно использую обертку OpenCVSharp, но это уже не суть...

Подскажите пожалуйста, как вырезать нужные мне области с паспорта, чтобы в распознанку не попадал всякий не нужный хлам!

и как удачней затем избавиться от шума!?

Конкретно: нужна область с ФИО и область "паспорт выдан"

Схема того, что хочу сделать http://www.passportvision.ru/technology/

некоторые этапы понимаю как сделать, но вот выделить нужную область и избавиться от шума в виде черных точек, это проблема

з.ы.Пишу для личного пользования. ИЗ просторов инета ни одной бесплатной библиотеки не нашел

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

О, я как раз писал недавно про свои эксперименты со Stroke width transform. Тебе тоже можно с этим поэкспериментировать.

  • Like 1

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Спасибо большое за ответ! По читал вашу тему, но решения там не нашел), скорей всего нужно что-то по проще... Может ещё есть идеи?)

Я вот думаю может просто конкретные зоны вырезать? честно говоря у меня даже это не получилось,как то замысловато вырезается кусок рисунка

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Если хочешь, то я покажу, что может Stroke width transform в твоём случае. Тебе ничего компилировать и запускать не придётся, просто выложи здесь произвольный скан паспорта, а я запущу и выложу изображения результата. Номер паспорта, фамилию можешь затереть для конфиденциальности (серию и имя/отчество лучше оставить, чтобы убедиться, что они находятся).

А вообще тут надо пробовать со всякой разной бинаризацией (Оцу, адаптивной), выравнивания освещённости, поиска контуров и/или связных компонент, медианной фильтрации... Посмотри, что делает Leptonica, которая используется для предобработки изображений в открытой OCR системе tesseract.

Вдогонку, если интересная бинаризация, то посмотри на статью "Binarising Camera Images for OCR" Mauritius Seeger and Christopher Dance (легко гуглится).

Вот исходник по ней на С++ (легко конвертируется на С#, но если не сможешь, то я запущу и покажу результат на твоём скане):

#include <iostream>

#include <vector>

#include <stdio.h>

#include <stdarg.h>

#include "opencv2/opencv.hpp"

#include "fstream"

#include "iostream"

using namespace std;

using namespace cv;


//-----------------------------------------------------------------------------------------------------

//

//-----------------------------------------------------------------------------------------------------

void CalcBlockMeanVariance(Mat& Img,Mat& Res,float blockSide=21)

{

    Mat I;

    Img.convertTo(I,CV_32FC1);

    Res=Mat::zeros(Img.rows/blockSide,Img.cols/blockSide,CV_32FC1);

    Mat inpaintmask;

    Mat patch;

    Mat smallImg;

    Scalar m,s;


    for(int i=0;i<Img.rows-blockSide;i+=blockSide)

    {      

        for (int j=0;j<Img.cols-blockSide;j+=blockSide)

        {

            patch=I(Range(i,i+blockSide+1),Range(j,j+blockSide+1));

            cv::meanStdDev(patch,m,s);

            if(s[0]>0.01)

            {

                Res.at<float>(i/blockSide,j/blockSide)=m[0];

            }else

            {

                Res.at<float>(i/blockSide,j/blockSide)=0;

            }          

        }

    }


    cv::resize(I,smallImg,Res.size());


    cv::threshold(Res,inpaintmask,0.02,1.0,cv::THRESH_BINARY);


    Mat inpainted;

    smallImg.convertTo(smallImg,CV_8UC1,255);


    inpaintmask.convertTo(inpaintmask,CV_8UC1);

    inpaint(smallImg, inpaintmask, inpainted, 5, INPAINT_TELEA);


    cv::resize(inpainted,Res,Img.size());

    Res.convertTo(Res,CV_32FC1,1.0/255.0);


}

//-----------------------------------------------------------------------------------------------------

//

//-----------------------------------------------------------------------------------------------------

int main( int argc, char** argv )

{

    namedWindow("Img");

    namedWindow("Edges");

    //Mat Img=imread("D:\\ImagesForTest\\BookPage.JPG",0);

    Mat Img=imread("Test2.JPG",0);

    Mat res;

    Img.convertTo(Img,CV_32FC1,1.0/255.0);

    CalcBlockMeanVariance(Img,res);

    res=1.0-res;

    res=Img+res;

    imshow("Img",Img);

    cv::threshold(res,res,0.85,1,cv::THRESH_BINARY);

    cv::resize(res,res,cv::Size(res.cols/2,res.rows/2));

    imwrite("result.jpg",res*255);

    imshow("Edges",res);

    waitKey(0);


    return 0;

}

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Пока у меня получился вот такой результат, собственно делаю Гаусово размытие, а потом imgSrc.Threshold(imgSrc, 0, 255, ThresholdType.Otsu)(вы вроде про это и писали!) :)

97e702a66e68.png

Осталось убрать шумы подчеркнутые, но каким образом, не могу понять, у меня уже идеи закончились... если учесть что эти поля есть во всех паспортах, может каким нибудь наложением сверху такой же области только в белом фоне!?

Либо еще вариант опробовать tesseract может у него лучше получится распознать чем у puma.NET...

Спасибо большое за помощь! сейчас скину вариант, который на гуглил, паспорт ничейный)

http://i064.radikal.ru/1408/cc/d9e4fd09e628.jpg

Вот, очень интересно глянуть как алгоритм отработает!

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Ага, хорошо. Завтра утром скину результат.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

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

Что можно применить в твоём случае: самое просто - контуры. То есть найти все контуры, маленькие откинуть, а большие оставить.

Если тебе в результате нужно бинарное изображение всего паспорта, то:

1. ищи все контуры?

2. для каждого найденного контура ищи bounding box (описывающий символ прямоугольник): если он маленький, то закрашивает его белым цветом.

3. Всё.

post-391-0-52806700-1408041016_thumb.png

post-391-0-68084700-1408041024_thumb.png

  • Like 1

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

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

  • Like 2

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Точно! Я был так поглощён всякими заумными алгоритмами, что не заметил элементарного.

  • Like 1

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

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

Что можно применить в твоём случае: самое просто - контуры. То есть найти все контуры, маленькие откинуть, а большие оставить.

Если тебе в результате нужно бинарное изображение всего паспорта, то:

1. ищи все контуры?

2. для каждого найденного контура ищи bounding box (описывающий символ прямоугольник): если он маленький, то закрашивает его белым цветом.

3. Всё.

Вариант впринципе интересный, но боюсь у меня будут сложности с реализаций, я так понимаю он на C++ написан? у меня проект на C#...

А вот с определением по цвету очень заинтересовало, но сегодня перерыл все свои sample`s по OPenCV ничего подходящего не нашел, может подскажете с каким методом стоит по ковыряться?

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

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

Спасибо большое за совет! И в правду отличный способ, сейчас опробовал на фотошопе, отлично получается, единственно не могу метод OpenCV подобрать который сделал бы подобное :blink:

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Вариант впринципе интересный, но боюсь у меня будут сложности с реализаций, я так понимаю он на C++ написан? у меня проект на C#...

Нет, от ни на чём не написан, я просто описал словами. А С++ или C# - какая разница? Emgu CV позволяет на шарпе делать ровно столько же, сколько на обычном С++ и OpenCV.

А вот с определением по цвету очень заинтересовало, но сегодня перерыл все свои sample`s по OPenCV ничего подходящего не нашел, может подскажете с каким методом стоит по ковыряться?

Скажи, что ты делал в Фотошопе по шагам и мы повторим это здесь.

  • Like 1

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Нет, от ни на чём не написан, я просто описал словами. А С++ или C# - какая разница? Emgu CV позволяет на шарпе делать ровно столько же, сколько на обычном С++ и OpenCV.

Скажи, что ты делал в Фотошопе по шагам и мы повторим это здесь.

В ФШ делал так, увеличивал насыщеность почти до максимума(Изображение-Цветовой тон\Насыщеность-Насыщеность) тем самым бордовые цвета становятся более выраженными и не сливаются с черными буквами.

Далее делал просто заменить цвет(Изображение-коррекция-заменить цвет) указывал на бордовые пиксели и менял их на белые! и вроде неплохо получалось! ну и далее уже стандартное: бинаризация-распознание.

Это был первый вариант как избавиться от лишних надписей, пробовал сегодня второй- делать бинаризацию, потом искать мелкие скопления черных пикселей и заменять их, но видимо я как то слишком примитивно его реализовываю, что большие буквы тоже смазываются. Пробовал SURF т.е. поиск подкартинки на картинке, очень плохо срабатывает, тоже отказался от этого метода...

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Манипуляции с насыщенностью (а заодно и контрастом) в OpenCV делаются довольно просто:


    std::vector<cv::Mat> chans;

    cv::split(img, chans);


    cv::imshow("B", chans[0]);

    cv::imshow("G", chans[1]);

    cv::imshow("R", chans[2]);


    double contrast = 30;

    double brightness = 50;

    double a, b;

    if (contrast > 0)

    {

        double delta = 127. * contrast / 100;

        a = 255. / (255. - delta * 2);

        b = a * (brightness - delta);

    }

    else

    {

        double delta = -128. * contrast / 100;

        a = (256. - delta * 2) / 255.;

        b = a * brightness + delta;

    }

    chans[2].convertTo(chans[2], CV_8UC1, a, ;


    cv::imwrite("br_contr.png", chans[2]);[/code]

В этом коде исходное цветное изображение раскладывается на три канала RGB, канал R изменяет свою насыщенность и контраст и сохраняется. Результат в аттаче. Результат похож на твой? Думаю, что этот код ты легко перепишешь на C#. Константы double contrast = 30 и double brightness = 50 меняй по вкусу.

На счёт маленьких объектов повторюсь: проще всего их искать контурами, в сети примеров много и для шарпа. Если не получается, то выложи свой код - поищем ошибки на глаз или кто-то скомпилирует у себя.

post-391-0-12156000-1408385716_thumb.png

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах


var R = new CvMat();

    var G = new CvMat();

    var B = new CvMat();


    Cv.Split(imgDst, imgDst, B, G, R);

    double contrast = 30;

    double brightness = 50;

    double a, b;

    if (contrast > 0)

    {

        double delta = 127*contrast / 100;

        a = 255 / (255 - delta * 2);

        b = a * (brightness - delta);

    }

    else

    {

        double delta = -128 * contrast / 100;

        a = (256 - delta * 2) / 255;

        b = a * brightness + delta;

    }

    chans[2].convertTo(chans[2], CV_8UC1, a, ;[/code]

Проблемы с написанием кода под C#, как последнюю строчку написать?

Да и вообще как то странно он переписывается, метод сплит допустим требует 5 параметров на вход, ConvertTo вообще не пойму к чему применить!?

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

ConvertScale

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Спасибо большое! сейчас опробую)

Кстати сменил библиотеку для распознования с Puma.NET на Tesseract, может быть удастся как то с ней удачней по взаимодействовать!

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Может быть, там не сложно (во всяком случае на С++ очень просто).

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

В итоге решил по работать все таки с контурами, делаю бинаризацию, затем нахождение контуров.

Здесь я на изображение result эти контуры рисую, а как можно к ним обратится и отсеить по размеру в моем цикле foreach?

              Cv.CvtColor(src, gray, ColorConversion.BgrToGray);

                    gray.Smooth(gray, SmoothType.Gaussian);

                    gray.Threshold(gray, 0, 255, ThresholdType.Otsu);

                    Cv.Canny(gray, canny, 50, 200);


                    // find all contours

                    CvMemStorage storage = new CvMemStorage();

                        CvContourScanner scanner = new CvContourScanner(canny, storage, CvContour.SizeOf, ContourRetrieval.Tree, ContourChain.ApproxSimple);


                            foreach (CvSeq<CvPoint> c in scanner)

                            {

                                result.DrawContours(c, CvColor.White, CvColor.White, 0, 1, LineType.AntiAlias);

                            }

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Например:

         using (MemStorage store = new MemStorage())

         for (Contour<Point> contours1= grayImage.FindContours(Emgu.CV.CvEnum.CHAIN_APPROX_METHOD.CV_CHAIN_APPROX_NONE, Emgu.CV.CvEnum.RETR_TYPE.CV_RETR_TREE, store); contours1 != null; contours1 = contours1.HNext)

         {

             Rectangle r = CvInvoke.cvBoundingRect(contours1, 1); // Вот и размер!

             canny.Draw(r, new Gray(255), 1);

         }

  • Like 1

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Спасибищеееееееееееееее!!! Вроде стало получаться!!!

Сделал вот так!

var f = c.BoundingRect();//по этому f уже отфильтровываю

Если удасться механизм до ума довести, с меня печеньки :rolleyes:

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Рано я обрадовался, но прогресс уже все таки есть!)

Получается контур выделяется каким то странным образом, я так понял, контур, это набор точек и не обязательно замкнутый?

всё таки кажется нужно привязываться к цветам, эти контуры толку совсем не дают,они от части хаотичны(

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Работа с контурами продвигается, отсортировал по размерам и вроде бы всё здорово, но туда попадают внутренние области нужных мне букв, по советуйте как тут быть?

Что получилось на данный момент

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Как это сделать на шарпе, я точно сказать не могу. Но! При поискке контуров находятся как внешние, так и вложенные контуры, можно узнавать уровень иерархии, ходить по ним. См. например contours.c из OpenCV. Возможно, что тебе достаточно будет всего лишь убрать опцию CV_RETR_TREE, а вместо неё поставить CV_RETR_EXTERNAL. Если не получится, то будешь учиться определять уровень вложенности.

  • Like 1

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Вот, а по какому признаку отличать внешние и внутренние!? стандартных средств OpenCV я не нашел, а эта возможность бы очень сильно помогла мне!

Если делаю

result.DrawContours(c, CvColor.Black, CvColor.White, 0, 1, LineType.AntiAlias);

То внешние он окрашивает в белый, внутренние в черный, знчаит всё таки както отличает их, но блин как!?

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Создайте учётную запись или войдите для комментирования

Вы должны быть пользователем, чтобы оставить комментарий

Создать учётную запись

Зарегистрируйтесь для создания учётной записи. Это просто!

Зарегистрировать учётную запись

Войти

Уже зарегистрированы? Войдите здесь.

Войти сейчас


  • Сейчас на странице   0 пользователей

    Нет пользователей, просматривающих эту страницу

×