Борода22 1 Жалоба Опубликовано July 31, 2020 Приветствую, ребята. Направьте пожалуйста в нужное русло. Задача у меня такая. Распознать реквизиты на платежном документе. Есть скан платежного документа. Т.к. это унифицированная форма документа, то каждый реквизит находится на определенном месте. Подскажите пожалуйста, можно ли как-то извлечь данные по определенным координатам и распознать текст ? Использую OpenCvSharp4. В интернетах находил код (при необходимости могу выложить) и общее описание алгоритма, но совсем не понимаю что, куда и откуда. Буду признателен если кто-то проведет ликбез. Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Nuzhny 243 Жалоба Опубликовано July 31, 2020 Предлагаю начать либо с голого tesseract-ocr, либо с примеров из opencv_text. А потом по результатам. 1 Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Борода22 1 Жалоба Опубликовано July 31, 2020 У меня есть такой код на шарпе: public void RunTextRecog() { List<Rect> boundRect = new List<Rect>(); using (Mat img = new Mat(PathToImage)) using (Mat img_gray = new Mat()) using (Mat img_sobel = new Mat()) using (Mat img_threshold = new Mat()) { Cv2.CvtColor(img, img_gray, ColorConversionCodes.BGR2GRAY); Cv2.Sobel(img_gray, img_sobel, MatType.CV_8U, 1, 0, 3, 1, 0, BorderTypes.Default); //Cv2.AdaptiveThreshold(img_sobel, img_threshold, 250, AdaptiveThresholdTypes.GaussianC, ThresholdTypes.Binary, 3, 1); Cv2.Threshold(img_sobel, img_threshold, 0, 255, ThresholdTypes.Otsu | ThresholdTypes.Binary); using (Mat element = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(20, 20))) { Cv2.MorphologyEx(img_threshold, img_threshold, MorphTypes.Close, element); Point[][] edgesArray = img_threshold.Clone().FindContoursAsArray(RetrievalModes.External, ContourApproximationModes.ApproxNone); foreach (Point[] edges in edgesArray) { Point[] normalizedEdges = Cv2.ApproxPolyDP(edges, 17, true); Rect appRect = Cv2.BoundingRect(normalizedEdges); boundRect.Add(appRect); } } for (int ind = 0; ind < boundRect.Count; ind++) { Cv2.Rectangle(img, new Point(boundRect[ind].X, boundRect[ind].Y), new Point(boundRect[ind].X + boundRect[ind].Width, boundRect[ind].Y + boundRect[ind].Height), new Scalar(100, 200, 0), 3); } Mat resize_img = new Mat(); Cv2.ImShow("Результат", img); Cv2.ImShow("Собель", img_sobel); Cv2.ImShow("Threshold", img_threshold); Cv2.ImShow("Grey", img_gray); Cv2.ImWrite("segmented.jpg", img); } } Результаты на скриншотах. Как я понял опытным путем (изменяя параметры), выделение объектов происходит с помощью GetStructuringElement(). У меня тут еще идея возникла. Тупо вырезать определенные области на сканере, и скармливать их tesseract-у Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Smorodov 579 Жалоба Опубликовано July 31, 2020 Прислушайтесь к тому, что советует Nuzhny, начните с tesseract-ocr, есть даже бинды под шарп https://github.com/charlesw/tesseract например. Пример применения : https://github.com/charlesw/tesseract-samples Но если есть много энергии и желание повозиться, пишите, посоветуем по шагам, но это длинный путь ) 1 Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Борода22 1 Жалоба Опубликовано August 3, 2020 @Smorodov Спасибо большое. Я уже пробовал использовать tesseract sdk, распознает кириллицу, но как я понял, распознавание идет "потоком". Т.е. распознает все, но в одну строку. Полагаю что разбор такой строки будет еще тот геморрой. Либо я подключал не ту библиотеку, либо что-то не так настроил, но tesseract дает распознать картинку максимальным разрешением в 500 px. Как я понимаю, у меня два пути - поиск областей с данными с помощью OpenCVSharp и потом скармливать эти области tesseract-у, либо определиться с координатами фиксированных областей, и потом опять же, скармливать их tesseract-у для распознавания. Кто что может подсказать ? Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Борода22 1 Жалоба Опубликовано August 3, 2020 Установил из Nu-get`а библиотеку Tesseract 4.1. А дальше что ? Как я понял, в проект еще необходимо добавить какие-то библиотеки ? Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Борода22 1 Жалоба Опубликовано August 3, 2020 Разобрался. Все было проще. Устанавливается все из ну-гета, подключается к проекту папка tessdata, в свойствах выставляем - копировать всегда. Теперь склоняюсь ко второму варианту - фиксированные области скармливать tesseract-у. Как по мне - проще и надежнее, чем определять эти области с помощью OpenCV Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Smorodov 579 Жалоба Опубликовано August 3, 2020 В OpenCv ecть встроенные детекторы текста, гляньте здесь: https://www.pyimagesearch.com/2018/08/20/opencv-text-detection-east-text-detector/ 1 Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Борода22 1 Жалоба Опубликовано August 4, 2020 @Smorodov Спасибо большое за ссылочку, хорошее чтиво. Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Борода22 1 Жалоба Опубликовано August 4, 2020 Использовал tesseract, результаты вполне пригодны. Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Борода22 1 Жалоба Опубликовано August 5, 2020 И вот столкнулся с проблемой. Не хочет распознавать одиночные символы. Например, вот как на скрине. Что можно попытаться предпринять ? Увеличить изображение ? Причем, если "дописать" цифру слева или справа, то распознает корректно. Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Smorodov 579 Жалоба Опубликовано August 5, 2020 Может там фильтр по минимальной площади текста стоит, попробуйте увеличить размер изображения, или посмотрите параметры тессеракта, помнится таи их огромная куча была. Вот, кстати: https://www.sk-spell.sk.cx/tesseract-ocr-parameters-in-302-version 1 Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Борода22 1 Жалоба Опубликовано August 5, 2020 Я использую вот такой код: var img = Pix.LoadFromFile(pathToImage); var page = tessEngine.Process(img); string result = page.GetText(); page.Dispose(); 47 минут назад, Smorodov сказал: попробуйте увеличить размер изображения Для этого нужно использовать метод Scale() ? Как я понял, нужно для page segmentation mode установить режим single char. Как это сделать ? Установил свойство DefaultPageSegMode = PageSegMode.SingleChar, теперь распознает, но не корректно распознает строки Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Борода22 1 Жалоба Опубликовано August 5, 2020 Выставил в SingleBlock и все корректно распозналось 1 Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Smorodov 579 Жалоба Опубликовано August 5, 2020 На CPP это cv::resize , на шарпе не знаю. Ну если все получилось, поздравляю! )) Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Борода22 1 Жалоба Опубликовано August 6, 2020 15 часов назад, Smorodov сказал: Ну если все получилось, поздравляю! )) Да, кажется все нормально, спасибо большое! Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Борода22 1 Жалоба Опубликовано August 6, 2020 Все-таки рано радовался. На "эталонном" документе все отрабатывает как нужно, т.к. я распознаю заранее фиксированные области Но предстоит работать со сканированными изображениями, причем, сама форма документа может отличаться от "эталонного" в визуальном плане. Поэтому работа с OpenCV, думаю, все-таки предстоит. Нужно как-то определять границы текста, далее его "вырезать" и скармливать tesseract-у. Походу возвращаюсь к сообщению. Как корректно распознать области(границы) с текстом ? Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Борода22 1 Жалоба Опубликовано August 6, 2020 За что отвечает функция GetStructuringElement() и что за параметры используются ? Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Smorodov 579 Жалоба Опубликовано August 6, 2020 На английском конечно, но по теме ) : https://www.pyimagesearch.com/2014/09/01/build-kick-ass-mobile-document-scanner-just-5-minutes/ GetStructuringElement создает структурный элемент для морфологических операций (эрозия, дилатация, открытие, закрытие .... ) элемент представляет собой маленький шаблончик в виде прямоугольника заполненного единицами, реже используется эллипс, крест, и т.д. Посмотрите про морфологические операции и все станет ясно. https://ru.wikipedia.org/wiki/Математическая_морфология Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Борода22 1 Жалоба Опубликовано August 7, 2020 12 часа назад, Smorodov сказал: На английском конечно, но по теме ) Это просто жуть, тут не то что бутылка нужна, нужно заново родиться, и впитывать это с молоком матери Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Борода22 1 Жалоба Опубликовано August 7, 2020 Вот я обработал контуры, с помощью чего можно их объединить? Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Smorodov 579 Жалоба Опубликовано August 7, 2020 dilate - утолщит все с сольет в блоки. Структурный элемент взять высотой с символ, шириной примерно в 1.5 - 2.5 от ширины символа. после него erode с тем же ядром, чтобы вернуть размеры блоков. Повторить поиск контуров. Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Борода22 1 Жалоба Опубликовано August 10, 2020 @Smorodov, Приветствую, уважаемый! А можно пример в виде используемых функций ? Простите уж за такое наглое пожелание Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Борода22 1 Жалоба Опубликовано August 11, 2020 Все-таки видимо нужно идти другим путем. Убрать все прямые вертикальные и горизонтальные линии, т.к. они мешают для утолщения. Написал (поправил чужой код) вот такой код для поиска и отображения прямых линий: Mat original_img = new Mat(PathToImage); Mat gray_img = original_img.CvtColor(ColorConversionCodes.BGR2GRAY); Mat thresh_img = gray_img.Threshold(0, 255, ThresholdTypes.Otsu | ThresholdTypes.BinaryInv); var lines = Cv2.HoughLines(thresh_img, 200, Math.PI/180, 2); for(int x = 0; x < lines.Count(); x++) { float rho = lines[x].Rho; float theta = lines[x].Theta; double a = Math.Cos(theta); double b = Math.Sin(theta); double x0 = a * rho; double y0 = b * rho; LineSegmentPoint point = new LineSegmentPoint(); point.P1.X = (int)Math.Round(x0 + (1000 * (-b))); point.P1.Y = (int)Math.Round(y0 + (1000 * (a))); point.P2.X = (int)Math.Round(x0 - (1000 * (-b))); point.P2.Y = (int)Math.Round(y0 - (1000 * (a))); Cv2.Line(original_img, point.P1, point.P2, new Scalar(0, 200, 20)); } Но линии рисуются вообще не правильно (рисуются диагоналями, под углом), я так понимаю, что накосячил где-то в преобразовании координат ? И что это за магические числа в виде 1000 ? Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Smorodov 579 Жалоба Опубликовано August 12, 2020 Извиняюсь что запоздал с ответом, был в отъезде. Сишный пример из стандартного набора: #include <opencv2/imgproc.hpp> #include <opencv2/highgui.hpp> using namespace cv; using namespace std; int main(int argc, char** argv) { Mat src, dst, color_dst; if( argc != 2 || !(src=imread(argv[1], 0)).data) return -1; Canny( src, dst, 50, 200, 3 ); cvtColor( dst, color_dst, COLOR_GRAY2BGR ); vector<Vec4i> lines; HoughLinesP( dst, lines, 1, CV_PI/180, 80, 30, 10 ); for( size_t i = 0; i < lines.size(); i++ ) { line( color_dst, Point(lines[i][0], lines[i][1]), Point( lines[i][2], lines[i][3]), Scalar(0,0,255), 3, 8 ); } namedWindow( "Source", 1 ); imshow( "Source", src ); namedWindow( "Detected Lines", 1 ); imshow( "Detected Lines", color_dst ); waitKey(0); return 0; } По поводу морфологических операций, cv::dilate затем cv::erode. Для детектора линий, нужно белое изображение на черном фоне. Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах