VironZizu 2 Report post Posted August 13, 2014 Приветствую! В поисках решения проблемы распознования набрел на ваш форум, очень нужен совет! Задача моя, считать поля паспорта, для распознавания, использую готовую библиотеку puma.NET, она со своей задачей отлично справляется! Но на вход ей нужно подавать естественно преобразованный скан паспорта! Нашел небольшую методику как это можно сделать, но не могу подобрать нужные методы библиотеки OpenCV, а именно использую обертку OpenCVSharp, но это уже не суть... Подскажите пожалуйста, как вырезать нужные мне области с паспорта, чтобы в распознанку не попадал всякий не нужный хлам! и как удачней затем избавиться от шума!? Конкретно: нужна область с ФИО и область "паспорт выдан" Схема того, что хочу сделать http://www.passportvision.ru/technology/ некоторые этапы понимаю как сделать, но вот выделить нужную область и избавиться от шума в виде черных точек, это проблема з.ы.Пишу для личного пользования. ИЗ просторов инета ни одной бесплатной библиотеки не нашел Share this post Link to post Share on other sites
Nuzhny 243 Report post Posted August 13, 2014 О, я как раз писал недавно про свои эксперименты со Stroke width transform. Тебе тоже можно с этим поэкспериментировать. 1 Share this post Link to post Share on other sites
VironZizu 2 Report post Posted August 13, 2014 Спасибо большое за ответ! По читал вашу тему, но решения там не нашел), скорей всего нужно что-то по проще... Может ещё есть идеи?) Я вот думаю может просто конкретные зоны вырезать? честно говоря у меня даже это не получилось,как то замысловато вырезается кусок рисунка Share this post Link to post Share on other sites
Nuzhny 243 Report post Posted August 13, 2014 Если хочешь, то я покажу, что может 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; } Share this post Link to post Share on other sites
VironZizu 2 Report post Posted August 13, 2014 Пока у меня получился вот такой результат, собственно делаю Гаусово размытие, а потом imgSrc.Threshold(imgSrc, 0, 255, ThresholdType.Otsu)(вы вроде про это и писали!) Осталось убрать шумы подчеркнутые, но каким образом, не могу понять, у меня уже идеи закончились... если учесть что эти поля есть во всех паспортах, может каким нибудь наложением сверху такой же области только в белом фоне!? Либо еще вариант опробовать tesseract может у него лучше получится распознать чем у puma.NET... Спасибо большое за помощь! сейчас скину вариант, который на гуглил, паспорт ничейный) http://i064.radikal.ru/1408/cc/d9e4fd09e628.jpg Вот, очень интересно глянуть как алгоритм отработает! Share this post Link to post Share on other sites
Nuzhny 243 Report post Posted August 14, 2014 Ага, хорошо. Завтра утром скину результат. Share this post Link to post Share on other sites
Nuzhny 243 Report post Posted August 14, 2014 Вот результат преобразования и найденных компонент. Вроде как неплохо. Что можно применить в твоём случае: самое просто - контуры. То есть найти все контуры, маленькие откинуть, а большие оставить. Если тебе в результате нужно бинарное изображение всего паспорта, то: 1. ищи все контуры? 2. для каждого найденного контура ищи bounding box (описывающий символ прямоугольник): если он маленький, то закрашивает его белым цветом. 3. Всё. 1 Share this post Link to post Share on other sites
iskees 32 Report post Posted August 14, 2014 На цветном снимке паспорта "общий текст" идет с явным оттенком красного, а заполняется черным. Можно по цвету еще отфильтровать. 2 Share this post Link to post Share on other sites
Nuzhny 243 Report post Posted August 15, 2014 Точно! Я был так поглощён всякими заумными алгоритмами, что не заметил элементарного. 1 Share this post Link to post Share on other sites
VironZizu 2 Report post Posted August 17, 2014 Вот результат преобразования и найденных компонент. Вроде как неплохо. Что можно применить в твоём случае: самое просто - контуры. То есть найти все контуры, маленькие откинуть, а большие оставить. Если тебе в результате нужно бинарное изображение всего паспорта, то: 1. ищи все контуры? 2. для каждого найденного контура ищи bounding box (описывающий символ прямоугольник): если он маленький, то закрашивает его белым цветом. 3. Всё. Вариант впринципе интересный, но боюсь у меня будут сложности с реализаций, я так понимаю он на C++ написан? у меня проект на C#... А вот с определением по цвету очень заинтересовало, но сегодня перерыл все свои sample`s по OPenCV ничего подходящего не нашел, может подскажете с каким методом стоит по ковыряться? Share this post Link to post Share on other sites
VironZizu 2 Report post Posted August 17, 2014 На цветном снимке паспорта "общий текст" идет с явным оттенком красного, а заполняется черным. Можно по цвету еще отфильтровать. Спасибо большое за совет! И в правду отличный способ, сейчас опробовал на фотошопе, отлично получается, единственно не могу метод OpenCV подобрать который сделал бы подобное Share this post Link to post Share on other sites
Nuzhny 243 Report post Posted August 18, 2014 Вариант впринципе интересный, но боюсь у меня будут сложности с реализаций, я так понимаю он на C++ написан? у меня проект на C#... Нет, от ни на чём не написан, я просто описал словами. А С++ или C# - какая разница? Emgu CV позволяет на шарпе делать ровно столько же, сколько на обычном С++ и OpenCV. А вот с определением по цвету очень заинтересовало, но сегодня перерыл все свои sample`s по OPenCV ничего подходящего не нашел, может подскажете с каким методом стоит по ковыряться? Скажи, что ты делал в Фотошопе по шагам и мы повторим это здесь. 1 Share this post Link to post Share on other sites
VironZizu 2 Report post Posted August 18, 2014 Нет, от ни на чём не написан, я просто описал словами. А С++ или C# - какая разница? Emgu CV позволяет на шарпе делать ровно столько же, сколько на обычном С++ и OpenCV. Скажи, что ты делал в Фотошопе по шагам и мы повторим это здесь. В ФШ делал так, увеличивал насыщеность почти до максимума(Изображение-Цветовой тон\Насыщеность-Насыщеность) тем самым бордовые цвета становятся более выраженными и не сливаются с черными буквами. Далее делал просто заменить цвет(Изображение-коррекция-заменить цвет) указывал на бордовые пиксели и менял их на белые! и вроде неплохо получалось! ну и далее уже стандартное: бинаризация-распознание. Это был первый вариант как избавиться от лишних надписей, пробовал сегодня второй- делать бинаризацию, потом искать мелкие скопления черных пикселей и заменять их, но видимо я как то слишком примитивно его реализовываю, что большие буквы тоже смазываются. Пробовал SURF т.е. поиск подкартинки на картинке, очень плохо срабатывает, тоже отказался от этого метода... Share this post Link to post Share on other sites
Nuzhny 243 Report post Posted August 18, 2014 Манипуляции с насыщенностью (а заодно и контрастом) в 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 меняй по вкусу. На счёт маленьких объектов повторюсь: проще всего их искать контурами, в сети примеров много и для шарпа. Если не получается, то выложи свой код - поищем ошибки на глаз или кто-то скомпилирует у себя. Share this post Link to post Share on other sites
VironZizu 2 Report post Posted August 19, 2014 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 вообще не пойму к чему применить!? Я так понимаю на плюсах вы создаете, вектор к тремя составляющими, я же пробую просто каждый отдельно создать. Share this post Link to post Share on other sites
Nuzhny 243 Report post Posted August 19, 2014 ConvertScale Share this post Link to post Share on other sites
VironZizu 2 Report post Posted August 19, 2014 Спасибо большое! сейчас опробую) Кстати сменил библиотеку для распознования с Puma.NET на Tesseract, может быть удастся как то с ней удачней по взаимодействовать! Share this post Link to post Share on other sites
Nuzhny 243 Report post Posted August 19, 2014 Может быть, там не сложно (во всяком случае на С++ очень просто). Share this post Link to post Share on other sites
VironZizu 2 Report post Posted August 20, 2014 В итоге решил по работать все таки с контурами, делаю бинаризацию, затем нахождение контуров. Здесь я на изображение 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); } Share this post Link to post Share on other sites
Nuzhny 243 Report post Posted August 20, 2014 Например: 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); } 1 Share this post Link to post Share on other sites
VironZizu 2 Report post Posted August 20, 2014 Спасибищеееееееееееееее!!! Вроде стало получаться!!! Сделал вот так! var f = c.BoundingRect();//по этому f уже отфильтровываю Если удасться механизм до ума довести, с меня печеньки Share this post Link to post Share on other sites
VironZizu 2 Report post Posted August 20, 2014 Рано я обрадовался, но прогресс уже все таки есть!) Получается контур выделяется каким то странным образом, я так понял, контур, это набор точек и не обязательно замкнутый? всё таки кажется нужно привязываться к цветам, эти контуры толку совсем не дают,они от части хаотичны( Share this post Link to post Share on other sites
VironZizu 2 Report post Posted August 21, 2014 Работа с контурами продвигается, отсортировал по размерам и вроде бы всё здорово, но туда попадают внутренние области нужных мне букв, по советуйте как тут быть? Что получилось на данный момент Share this post Link to post Share on other sites
Nuzhny 243 Report post Posted August 21, 2014 Как это сделать на шарпе, я точно сказать не могу. Но! При поискке контуров находятся как внешние, так и вложенные контуры, можно узнавать уровень иерархии, ходить по ним. См. например contours.c из OpenCV. Возможно, что тебе достаточно будет всего лишь убрать опцию CV_RETR_TREE, а вместо неё поставить CV_RETR_EXTERNAL. Если не получится, то будешь учиться определять уровень вложенности. 1 Share this post Link to post Share on other sites
VironZizu 2 Report post Posted August 21, 2014 Вот, а по какому признаку отличать внешние и внутренние!? стандартных средств OpenCV я не нашел, а эта возможность бы очень сильно помогла мне! Если делаю result.DrawContours(c, CvColor.Black, CvColor.White, 0, 1, LineType.AntiAlias); То внешние он окрашивает в белый, внутренние в черный, знчаит всё таки както отличает их, но блин как!? Share this post Link to post Share on other sites