texto tecnicas software reconocimiento opticos letras lectora imágenes imagenes deteccion caracteres java c++ opencv image-processing ocr

java - tecnicas - Procesamiento de imágenes y extracción de caracteres



software reconocimiento de caracteres (4)

Estoy tratando de descubrir qué tecnologías necesitaría para procesar imágenes para los personajes.

Específicamente, en este ejemplo, necesito extraer el hashtag que está en un círculo. Puedes verlo aqui:

Cualquier implementación sería de gran ayuda.


Es posible resolver este problema con OpenCV + Tesseract

aunque creo que podría haber formas más fáciles. OpenCV es una biblioteca de código abierto utilizada para construir aplicaciones de visión por computadora y Tesseract es un motor de OCR de código abierto.

Antes de comenzar, permítanme aclarar algo: eso no es un círculo, es un rectángulo redondeado .

Estoy compartiendo el código fuente de la aplicación que escribí para demostrar cómo se puede resolver el problema, así como algunos consejos sobre lo que está sucediendo. Se supone que esta respuesta no debe educar a nadie sobre el procesamiento de imágenes digitales y se espera que el lector tenga una comprensión mínima en este campo.

Describiré muy brevemente qué hacen las secciones más grandes del código. La mayor parte del siguiente fragmento de código proviene de squares.cpp , una aplicación de muestra que se envía con OpenCV para detectar cuadrados en las imágenes.

#include <iostream> #include <vector> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> // angle: helper function. // Finds a cosine of angle between vectors from pt0->pt1 and from pt0->pt2. double angle( cv::Point pt1, cv::Point pt2, cv::Point pt0 ) { double dx1 = pt1.x - pt0.x; double dy1 = pt1.y - pt0.y; double dx2 = pt2.x - pt0.x; double dy2 = pt2.y - pt0.y; return (dx1*dx2 + dy1*dy2)/sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10); } // findSquares: returns sequence of squares detected on the image. // The sequence is stored in the specified memory storage. void findSquares(const cv::Mat& image, std::vector<std::vector<cv::Point> >& squares) { cv::Mat pyr, timg; // Down-scale and up-scale the image to filter out small noises cv::pyrDown(image, pyr, cv::Size(image.cols/2, image.rows/2)); cv::pyrUp(pyr, timg, image.size()); // Apply Canny with a threshold of 50 cv::Canny(timg, timg, 0, 50, 5); // Dilate canny output to remove potential holes between edge segments cv::dilate(timg, timg, cv::Mat(), cv::Point(-1,-1)); // find contours and store them all as a list std::vector<std::vector<cv::Point> > contours; cv::findContours(timg, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE); for( size_t i = 0; i < contours.size(); i++ ) // Test each contour { // Approximate contour with accuracy proportional to the contour perimeter std::vector<cv::Point> approx; cv::approxPolyDP(cv::Mat(contours[i]), approx, cv::arcLength(cv::Mat(contours[i]), true)*0.02, true); // Square contours should have 4 vertices after approximation // relatively large area (to filter out noisy contours) // and be convex. // Note: absolute value of an area is used because // area may be positive or negative - in accordance with the // contour orientation if( approx.size() == 4 && fabs(cv::contourArea(cv::Mat(approx))) > 1000 && cv::isContourConvex(cv::Mat(approx)) ) { double maxCosine = 0; for (int j = 2; j < 5; j++) { // Find the maximum cosine of the angle between joint edges double cosine = fabs(angle(approx[j%4], approx[j-2], approx[j-1])); maxCosine = MAX(maxCosine, cosine); } // If cosines of all angles are small // (all angles are ~90 degree) then write quandrange // vertices to resultant sequence if( maxCosine < 0.3 ) squares.push_back(approx); } } } // drawSquares: function draws all the squares found in the image void drawSquares( cv::Mat& image, const std::vector<std::vector<cv::Point> >& squares ) { for( size_t i = 0; i < squares.size(); i++ ) { const cv::Point* p = &squares[i][0]; int n = (int)squares[i].size(); cv::polylines(image, &p, &n, 1, true, cv::Scalar(0,255,0), 2, CV_AA); } cv::imshow("drawSquares", image); }

Ok, entonces nuestro programa comienza en:

int main(int argc, char* argv[]) { // Load input image (colored, 3-channel) cv::Mat input = cv::imread(argv[1]); if (input.empty()) { std::cout << "!!! failed imread()" << std::endl; return -1; } // Convert input image to grayscale (1-channel) cv::Mat grayscale = input.clone(); cv::cvtColor(input, grayscale, cv::COLOR_BGR2GRAY); //cv::imwrite("gray.png", grayscale);

Cómo se ve la escala de grises :

// Threshold to binarize the image and get rid of the shoe cv::Mat binary; cv::threshold(grayscale, binary, 225, 255, cv::THRESH_BINARY_INV); cv::imshow("Binary image", binary); //cv::imwrite("binary.png", binary);

Cómo se ve el binario :

// Find the contours in the thresholded image std::vector<std::vector<cv::Point> > contours; cv::findContours(binary, contours, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE); // Fill the areas of the contours with BLUE (hoping to erase everything inside a rectangular shape) cv::Mat blue = input.clone(); for (size_t i = 0; i < contours.size(); i++) { std::vector<cv::Point> cnt = contours[i]; double area = cv::contourArea(cv::Mat(cnt)); //std::cout << "* Area: " << area << std::endl; cv::drawContours(blue, contours, i, cv::Scalar(255, 0, 0), CV_FILLED, 8, std::vector<cv::Vec4i>(), 0, cv::Point() ); } cv::imshow("Countours Filled", blue); //cv::imwrite("contours.png", blue);

Cómo se ve el azul :

// Convert the blue colored image to binary (again), and we will have a good rectangular shape to detect cv::Mat gray; cv::cvtColor(blue, gray, cv::COLOR_BGR2GRAY); cv::threshold(gray, binary, 225, 255, cv::THRESH_BINARY_INV); cv::imshow("binary2", binary); //cv::imwrite("binary2.png", binary);

Cómo se ve el binario en este punto:

// Erode & Dilate to isolate segments connected to nearby areas int erosion_type = cv::MORPH_RECT; int erosion_size = 5; cv::Mat element = cv::getStructuringElement(erosion_type, cv::Size(2 * erosion_size + 1, 2 * erosion_size + 1), cv::Point(erosion_size, erosion_size)); cv::erode(binary, binary, element); cv::dilate(binary, binary, element); cv::imshow("Morphologic Op", binary); //cv::imwrite("morpho.png", binary);

Cómo se ve el binario en este punto:

// Ok, let''s go ahead and try to detect all rectangular shapes std::vector<std::vector<cv::Point> > squares; findSquares(binary, squares); std::cout << "* Rectangular shapes found: " << squares.size() << std::endl; // Draw all rectangular shapes found cv::Mat output = input.clone(); drawSquares(output, squares); //cv::imwrite("output.png", output);

Cómo se ve la salida :

¡Bien! Solucionamos la primera parte del problema que fue encontrar el rectángulo redondeado. Puede ver en la imagen de arriba que se detectó la forma rectangular y se dibujaron líneas verdes sobre la imagen original con fines educativos.

La segunda parte es mucho más fácil. Comienza creando un ROI (Región de interés) en la imagen original para que podamos recortar la imagen en el área dentro del rectángulo redondeado. Una vez hecho esto, la imagen recortada se guarda en el disco como un archivo TIFF, que luego se alimenta a Tesseract para que sea mágico:

// Crop the rectangular shape if (squares.size() == 1) { cv::Rect box = cv::boundingRect(cv::Mat(squares[0])); std::cout << "* The location of the box is x:" << box.x << " y:" << box.y << " " << box.width << "x" << box.height << std::endl; // Crop the original image to the defined ROI cv::Mat crop = input(box); cv::imshow("crop", crop); //cv::imwrite("cropped.tiff", crop); } else { std::cout << "* Abort! More than one rectangle was found." << std::endl; } // Wait until user presses key cv::waitKey(0); return 0; }

Cómo se ve el cultivo :

Cuando esta aplicación finaliza su trabajo, crea un archivo llamado cropped.tiff en el disco. Vaya a la línea de comandos e invoque Tesseract para detectar el texto presente en la imagen recortada:

tesseract cropped.tiff out

Este comando crea un archivo llamado out.txt con el texto detectado:

Tesseract tiene una API que puede usar para agregar la función de OCR a su aplicación.

Esta solución no es sólida y probablemente tendrá que hacer algunos cambios aquí y allá para que funcione en otros casos de prueba.


El OCR funciona bien con documentos escaneados. A lo que se refiere es a la detección de texto en imágenes generales, lo que requiere otras técnicas (a veces el OCR se usa como parte del flujo)

No estoy al tanto de ninguna implementación de "producción preparada".

para información general prueba google scholar con: "detección de texto en imágenes"

un método específico que funcionó bien para mí es ''transformación del ancho de trazo'' (SWT), no es difícil de implementar, y creo que también hay algunas implementaciones disponibles en línea.


Hay algunas alternativas: implementación de OCR de Java

Mencionan las siguientes herramientas:

Y algunos otros

Esta lista de enlaces también puede ser útil: http://www.javawhat.com/showCategory.do?id=2138003

En general, este tipo de tarea requiere muchas pruebas y pruebas. Probablemente, la mejor herramienta depende mucho más del perfil de sus datos de entrada que cualquier otra cosa.