Obtenga la ubicación de todo el texto presente en la imagen usando opencv


11

Tengo esta imagen que contiene texto (números y alfabetos). Quiero obtener la ubicación de todos los textos y números presentes en esta imagen. También quiero extraer todo el texto también.

ingrese la descripción de la imagen aquí

¿Cómo consigo los cordinates así como todo el texto (números y alfabetos) en mi imagen? Por ejemplo, 10B, 44, 16, 38, 22B, etc.


¿Cuál es su versión de tensorflow? Si su versión es 2.1, intente instalar 2.0
gellezzz

1
Lanzar recompensas a las malas preguntas no es una buena práctica. No mostró conocimiento sobre cómo hacer esto, por lo que parece que simplemente está tratando de atraer a los desarrolladores para que codifiquen una solución completa a cambio de algunos puntos de repetición. No espero ver respuestas perfectas por ese motivo, pero creo que puede obtener mejores soluciones en sitios web independientes si paga a las personas por su tiempo.
karlphillip

@karlphillip lo siento mucho, pero soy un principiante, necesito algo para comenzar, ¿verdad? ¿Me pueden ayudar con este?
Pulkit Bhatnagar

Respuestas:


13

Aquí hay un enfoque potencial que utiliza operaciones morfológicas para filtrar contornos que no son de texto. La idea es:

  1. Obtener imagen binaria. Cargar imagen, escala de grises, luego el umbral de Otsu

  2. Eliminar líneas horizontales y verticales. Cree núcleos horizontales y verticales usando cv2.getStructuringElementluego elimine líneascv2.drawContours

  3. Elimine líneas diagonales, objetos circulares y contornos curvos. Filtre utilizando el área de contorno cv2.contourArea y la aproximación de contorno cv2.approxPolyDP para aislar contornos que no sean de texto

  4. Extraer texto ROI y OCR. Encuentre contornos y filtro para ROI y luego OCR usando Pytesseract .


Se eliminaron las líneas horizontales resaltadas en verde.

ingrese la descripción de la imagen aquí

Líneas verticales eliminadas

ingrese la descripción de la imagen aquí

Se eliminaron varios contornos que no son de texto (líneas diagonales, objetos circulares y curvas)

ingrese la descripción de la imagen aquí

Regiones de texto detectadas

ingrese la descripción de la imagen aquí

import cv2
import numpy as np
import pytesseract

pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe"

# Load image, grayscale, Otsu's threshold
image = cv2.imread('1.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
clean = thresh.copy()

# Remove horizontal lines
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15,1))
detect_horizontal = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, horizontal_kernel, iterations=2)
cnts = cv2.findContours(detect_horizontal, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    cv2.drawContours(clean, [c], -1, 0, 3)

# Remove vertical lines
vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,30))
detect_vertical = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, vertical_kernel, iterations=2)
cnts = cv2.findContours(detect_vertical, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    cv2.drawContours(clean, [c], -1, 0, 3)

cnts = cv2.findContours(clean, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    # Remove diagonal lines
    area = cv2.contourArea(c)
    if area < 100:
        cv2.drawContours(clean, [c], -1, 0, 3)
    # Remove circle objects
    elif area > 1000:
        cv2.drawContours(clean, [c], -1, 0, -1)
    # Remove curve stuff
    peri = cv2.arcLength(c, True)
    approx = cv2.approxPolyDP(c, 0.02 * peri, True)
    x,y,w,h = cv2.boundingRect(c)
    if len(approx) == 4:
        cv2.rectangle(clean, (x, y), (x + w, y + h), 0, -1)

open_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2,2))
opening = cv2.morphologyEx(clean, cv2.MORPH_OPEN, open_kernel, iterations=2)
close_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,2))
close = cv2.morphologyEx(opening, cv2.MORPH_CLOSE, close_kernel, iterations=4)
cnts = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    x,y,w,h = cv2.boundingRect(c)
    area = cv2.contourArea(c)
    if area > 500:
        ROI = image[y:y+h, x:x+w]
        ROI = cv2.GaussianBlur(ROI, (3,3), 0)
        data = pytesseract.image_to_string(ROI, lang='eng',config='--psm 6')
        if data.isalnum():
            cv2.rectangle(image, (x, y), (x + w, y + h), (36,255,12), 2)
            print(data)

cv2.imwrite('image.png', image)
cv2.imwrite('clean.png', clean)
cv2.imwrite('close.png', close)
cv2.imwrite('opening.png', opening)
cv2.waitKey()

Buena idea eliminar esas líneas primero.
karlphillip

mala idea para eliminar también partes de letras ...
Walter Tross

8

Muy bien, aquí hay otra posible solución. Sé que trabajas con Python, yo trabajo con C ++. Te daré algunas ideas y espero que, si lo deseas, puedas implementar esta respuesta.

La idea principal es no utilizar el preprocesamiento en absoluto (al menos no en la etapa inicial) y en su lugar centrarse en cada carácter de destino, obtener algunas propiedades y filtrar cada blob de acuerdo con estas propiedades.

Estoy tratando de no usar el preprocesamiento porque: 1) los filtros y las etapas morfológicas podrían degradar la calidad de las manchas y 2) sus manchas objetivo parecen exhibir algunas características que podríamos explotar, principalmente: relación de aspecto y área .

Míralo, los números y las letras parecen ser más altos que anchos ... además, parecen variar dentro de un cierto valor de área. Por ejemplo, desea descartar objetos "demasiado anchos" o "demasiado grandes" .

La idea es que filtre todo lo que no se encuentre dentro de los valores precalculados. Examiné los caracteres (números y letras) y obtuve valores mínimos, máximos de área y una relación de aspecto mínima (aquí, la relación entre la altura y el ancho).

Trabajemos en el algoritmo. Comienza leyendo la imagen y redimensionándola a la mitad de las dimensiones. Tu imagen es demasiado grande. Convierta a escala de grises y obtenga una imagen binaria a través de otsu, aquí hay un pseudocódigo:

//Read input:
inputImage = imread( "diagram.png" );

//Resize Image;
resizeScale = 0.5;

inputResized = imresize( inputImage, resizeScale );

//Convert to grayscale;
inputGray = rgb2gray( inputResized );

//Get binary image via otsu:
binaryImage = imbinarize( inputGray, "Otsu" );

Frio. Trabajaremos con esta imagen. Debe examinar cada burbuja blanca y aplicar un "filtro de propiedades" . Estoy usando componentes conectados con estadísticas para recorrer cada blob y obtener su área y relación de aspecto, en C ++ esto se hace de la siguiente manera:

//Prepare the output matrices:
cv::Mat outputLabels, stats, centroids;
int connectivity = 8;

//Run the binary image through connected components:
int numberofComponents = cv::connectedComponentsWithStats( binaryImage, outputLabels, stats, centroids, connectivity );

//Prepare a vector of colors  color the filtered blobs in black
std::vector<cv::Vec3b> colors(numberofComponents+1);
colors[0] = cv::Vec3b( 0, 0, 0 ); // Element 0 is the background, which remains black.

//loop through the detected blobs:
for( int i = 1; i <= numberofComponents; i++ ) {

    //get area:
    auto blobArea = stats.at<int>(i, cv::CC_STAT_AREA);

    //get height, width and compute aspect ratio:
    auto blobWidth = stats.at<int>(i, cv::CC_STAT_WIDTH);
    auto blobHeight = stats.at<int>(i, cv::CC_STAT_HEIGHT);
    float blobAspectRatio = (float)blobHeight/(float)blobWidth;

    //Filter your blobs

};

Ahora, aplicaremos el filtro de propiedades. Esto es solo una comparación con los umbrales calculados previamente. Usé los siguientes valores:

Minimum Area: 40  Maximum Area:400
MinimumAspectRatio:  1

Dentro de su forciclo, compare las propiedades de blob actuales con estos valores. Si las pruebas son positivas, "pintas" la mancha negra. Continuando dentro del forbucle:

    //Filter your blobs

    //Test the current properties against the thresholds:
    bool areaTest =  (blobArea > maxArea)||(blobArea < minArea);
    bool aspectRatioTest = !(blobAspectRatio > minAspectRatio); //notice we are looking for TALL elements!

    //Paint the blob black:
    if( areaTest || aspectRatioTest ){
        //filtered blobs are colored in black:
        colors[i] = cv::Vec3b( 0, 0, 0 );
    }else{
        //unfiltered blobs are colored in white:
        colors[i] = cv::Vec3b( 255, 255, 255 );
    }

Después del bucle, construya la imagen filtrada:

cv::Mat filteredMat = cv::Mat::zeros( binaryImage.size(), CV_8UC3 );
for( int y = 0; y < filteredMat.rows; y++ ){
    for( int x = 0; x < filteredMat.cols; x++ )
    {
        int label = outputLabels.at<int>(y, x);
        filteredMat.at<cv::Vec3b>(y, x) = colors[label];
    }
}

Y ... eso es todo. Filtró todos los elementos que no son similares a lo que está buscando. Al ejecutar el algoritmo obtienes este resultado:

ingrese la descripción de la imagen aquí

Además, he encontrado los cuadros de límite de los blobs para visualizar mejor los resultados:

ingrese la descripción de la imagen aquí

Como puede ver, algunos elementos son detectados erróneamente. Puede refinar el "filtro de propiedades" para identificar mejor los caracteres que está buscando. Una solución más profunda, que implica un poco de aprendizaje automático, requiere la construcción de un "vector de características ideal", extraer características de los blobs y comparar ambos vectores a través de una medida de similitud. También puede aplicar algo de procesamiento posterior para mejorar los resultados ...

Lo que sea, hombre, tu problema no es trivial ni fácil de escalar, y solo te estoy dando ideas. Con suerte, podrá implementar su solución.


Cualquier posibilidad de que pueda convertir el mismo programa a python
Pulkit Bhatnagar

@PulkitBhatnagar Sí, por supuesto. Solo agárrate fuerte, tendré un puerto perfecto listo en unos minutos.
eldesgraciado

?? lo hiciste, para que pueda otorgarle la recompensa
Pulkit Bhatnagar

Ah, sí. Lo siento mucho, mi señor, me encontré con algunos problemas, pero la conversión está yendo muy bien. Solo espera. Gracias.
eldesgraciado

Nunca me pregunté que podría ser un sarcasmo.
Pulkit Bhatnagar

4

Un método es usar una ventana deslizante (es costoso).

Determine el tamaño de los caracteres en la imagen (todos los caracteres son del mismo tamaño que se ve en la imagen) y establezca el tamaño de la ventana. Pruebe tesseract para la detección (la imagen de entrada requiere un procesamiento previo). Si una ventana detecta caracteres consecutivamente, almacene las coordenadas de la ventana. Combinar las coordenadas y obtener la región en los personajes.


Creo que 100bounty es la respuesta
Himanshu Poddar
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.