your tutorial recognition not installed python c++ opencv tesseract cv2

python - tutorial - Eliminar los subrayados horizontales



tesseract ocr tutorial (4)

Estoy intentando extraer texto de unos pocos cientos de JPG que contienen información sobre los registros de la pena capital; los JPG son recibidos por el Departamento de Justicia Criminal de Texas (TDCJ). A continuación se muestra un fragmento de ejemplo con información de identificación personal eliminada.

He identificado los subrayados como el impedimento para un OCR adecuado. Si entro, captura una imagen de un sub-snippet y líneas en blanco, el OCR resultante a través de pytesseract es muy bueno. Pero con los subrayados presentes, es extremadamente pobre.

¿Cómo puedo eliminar mejor estas líneas horizontales? Lo que he intentado:

Etiquetando esta pregunta con c ++ con la esperanza de que alguien pueda ayudar a traducir el Paso 5 de la documentación a Python. He intentado un lote de transformaciones como Hugh Line Transform, pero me siento en la oscuridad dentro de una biblioteca y un área con la que no tengo experiencia previa.

import cv2 # Inverted grayscale img = cv2.imread(''rsnippet.jpg'', cv2.IMREAD_GRAYSCALE) img = cv2.bitwise_not(img) # Transform inverted grayscale to binary th = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 15, -2) # An alternative; Not sure if `th` or `th2` is optimal here th2 = cv2.threshold(img, 170, 255, cv2.THRESH_BINARY)[1] # Create corresponding structure element for horizontal lines. # Start by cloning th/th2. horiz = th.copy() r, c = horiz.shape # Lost after here - not understanding intuition behind sizing/partitioning


Algunas sugerencias:

  • Dado que está comenzando con un JPEG, no componga la pérdida. Guarda tus archivos intermedios como PNGs. Tesseract hace frente a los que están bien.
  • cv2.resize la imagen 2x (usando cv2.resize ) entregando a Tesseract.
  • Intenta detectar y eliminar el subrayado negro. ( Esta pregunta puede ayudar). Hacer eso mientras preservar a los descendientes puede ser complicado.
  • Explore las opciones de línea de comandos de Tesseract, de las cuales hay muchas (y están horriblemente documentadas, algunas requieren inmersiones en la fuente de C ++ para tratar de entenderlas). Parece que las ligaduras están causando algo de dolor. IIRC (ha pasado un tiempo), hay una configuración o dos que pueden ayudar.

Como la mayoría de las líneas a ser detectadas en su fuente son líneas horizontales, similares a mi otra respuesta, es encontrar un solo color, espacios horizontales en la imagen

Esta es la imagen de origen:

Aquí están mis dos pasos principales para eliminar la línea horizontal larga:

  1. Haga morph-close con el kernel de línea larga en la imagen gris

kernel = np.ones((1,40), np.uint8) morphed = cv2.morphologyEx(gray, cv2.MORPH_CLOSE, kernel)

luego, obtener la imagen transformada contiene las líneas largas:

  1. Invierta la imagen transformada y agregue a la imagen de origen:

dst = cv2.add(gray, (255-morphed))

luego obtener la imagen con largas líneas eliminadas:

Bastante simple, ¿verdad? Y también existen small line segments , creo que tiene pocos efectos en OCR. Note, casi todos los caracteres se mantienen originales, excepto g , j , p , q , y , Q , tal vez un poco diferente. Pero las herramientas de OCR más modernas, como Tesseract (con tecnología LSTM ) tienen la capacidad de lidiar con una confusión tan simple.

0123456789abcdef g hi j klmno pq rstuvwx y zABCDEFGHIJKLMNOP Q RSTUVWXYZ

Código total para guardar la imagen eliminada como line_removed.png :

#!/usr/bin/python3 # 2018.01.21 16:33:42 CST import cv2 import numpy as np ## Read img = cv2.imread("img04.jpg") gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ## (1) Create long line kernel, and do morph-close-op kernel = np.ones((1,40), np.uint8) morphed = cv2.morphologyEx(gray, cv2.MORPH_CLOSE, kernel) cv2.imwrite("line_detected.png", morphed) ## (2) Invert the morphed image, and add to the source image: dst = cv2.add(gray, (255-morphed)) cv2.imwrite("line_removed.png", dst)

Actualización @ 2018.01.23 13:15:15 CST:

Tesseract es una poderosa herramienta para hacer OCR. Hoy instalo el tesseract-4.0 y pytesseract. Luego hago ocr usando pytesseract en mi resultado line_removed.png .

import cv2 import pytesseract img = cv2.imread("line_removed.png") print(pytesseract.image_to_string(img, lang="eng"))

Este es el reuslt, bien para mí.

Convicted as the triggerman in the murder—for—hire of 29—year—old . shot once in the head with a 357 Magnum revolver in the garage of her home at .. she stepped from her car. Police discovered that the victim‘s husband, brother—in—law, _ ______ paid _ $2,000 to kill her, apparently so .. _ collect on life insurance policies totaling $250,000. Before the killing, . applied for additional life insurance policies of $150,000 each on himself and his wife to the scheme in three different statements to police. was and could had also . confessed


Todas las respuestas hasta ahora parecen estar utilizando operaciones morfológicas. Aquí hay algo un poco diferente. Esto debería dar resultados bastante buenos si las líneas son horizontales .

Para esto uso una parte de la imagen de muestra que se muestra a continuación.

Cargue la imagen, conviértala a escala de grises e inviértala.

import cv2 import numpy as np import matplotlib.pyplot as plt im = cv2.imread(''sample.jpg'') gray = 255 - cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)

Imagen en escala de grises invertida:

Si escanea una fila en esta imagen invertida, verá que su perfil se ve diferente según la presencia o la ausencia de una línea.

plt.figure(1) plt.plot(gray[18, :] > 16, ''g-'') plt.axis([0, gray.shape[1], 0, 1.1]) plt.figure(2) plt.plot(gray[36, :] > 16, ''r-'') plt.axis([0, gray.shape[1], 0, 1.1])

El perfil en verde es una fila donde no hay subrayado, el rojo es para una fila con subrayado. Si tomas el promedio de cada perfil, verás que el rojo tiene un promedio más alto.

Entonces, utilizando este enfoque puedes detectar los subrayados y eliminarlos.

for row in range(gray.shape[0]): avg = np.average(gray[row, :] > 16) if avg > 0.9: cv2.line(im, (0, row), (gray.shape[1]-1, row), (0, 0, 255)) cv2.line(gray, (0, row), (gray.shape[1]-1, row), (0, 0, 0), 1) cv2.imshow("gray", 255 - gray) cv2.imshow("im", im)

Aquí están los subrayados detectados en rojo, y la imagen limpia.

Salida de tesseract de la imagen limpiada:

Convthed as th( shot once in the she stepped fr< brother-in-lawii collect on life in applied for man to the scheme i|

La razón para usar parte de la imagen ya debe estar clara. Dado que la información de identificación personal se ha eliminado en la imagen original, el umbral no habría funcionado. Pero esto no debería ser un problema cuando se aplica para el procesamiento. A veces es posible que tenga que ajustar los umbrales (16, 0.9).

El resultado no se ve muy bien con partes de las letras eliminadas y algunas de las líneas débiles aún permanecen. Se actualizará si puedo mejorarlo un poco más.

ACTUALIZAR:

Dis algunas mejoras; Limpie y vincule las partes faltantes de las letras. He comentado el código, así que creo que el proceso es claro. También puede comprobar las imágenes intermedias resultantes para ver cómo funciona. Los resultados son un poco mejores

Salida de tesseract de la imagen limpiada:

Convicted as th( shot once in the she stepped fr< brother-in-law. ‘ collect on life ix applied for man to the scheme i|

Salida de tesseract de la imagen limpiada:

)r-hire of 29-year-old . revolver in the garage ‘ red that the victim‘s h {2000 to kill her. mum 250.000. Before the kil If$| 50.000 each on bin to police.

código python:

import cv2 import numpy as np import matplotlib.pyplot as plt im = cv2.imread(''sample2.jpg'') gray = 255 - cv2.cvtColor(im, cv2.COLOR_BGR2GRAY) # prepare a mask using Otsu threshold, then copy from original. this removes some noise __, bw = cv2.threshold(cv2.dilate(gray, None), 128, 255, cv2.THRESH_BINARY or cv2.THRESH_OTSU) gray = cv2.bitwise_and(gray, bw) # make copy of the low-noise underlined image grayu = gray.copy() imcpy = im.copy() # scan each row and remove lines for row in range(gray.shape[0]): avg = np.average(gray[row, :] > 16) if avg > 0.9: cv2.line(im, (0, row), (gray.shape[1]-1, row), (0, 0, 255)) cv2.line(gray, (0, row), (gray.shape[1]-1, row), (0, 0, 0), 1) cont = gray.copy() graycpy = gray.copy() # after contour processing, the residual will contain small contours residual = gray.copy() # find contours contours, hierarchy = cv2.findContours(cont, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE) for i in range(len(contours)): # find the boundingbox of the contour x, y, w, h = cv2.boundingRect(contours[i]) if 10 < h: cv2.drawContours(im, contours, i, (0, 255, 0), -1) # if boundingbox height is higher than threshold, remove the contour from residual image cv2.drawContours(residual, contours, i, (0, 0, 0), -1) else: cv2.drawContours(im, contours, i, (255, 0, 0), -1) # if boundingbox height is less than or equal to threshold, remove the contour gray image cv2.drawContours(gray, contours, i, (0, 0, 0), -1) # now the residual only contains small contours. open it to remove thin lines st = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)) residual = cv2.morphologyEx(residual, cv2.MORPH_OPEN, st, iterations=1) # prepare a mask for residual components __, residual = cv2.threshold(residual, 0, 255, cv2.THRESH_BINARY) cv2.imshow("gray", gray) cv2.imshow("residual", residual) # combine the residuals. we still need to link the residuals combined = cv2.bitwise_or(cv2.bitwise_and(graycpy, residual), gray) # link the residuals st = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (1, 7)) linked = cv2.morphologyEx(combined, cv2.MORPH_CLOSE, st, iterations=1) cv2.imshow("linked", linked) # prepare a msak from linked image __, mask = cv2.threshold(linked, 0, 255, cv2.THRESH_BINARY) # copy region from low-noise underlined image clean = 255 - cv2.bitwise_and(grayu, mask) cv2.imshow("clean", clean) cv2.imshow("im", im)


Uno puede intentar esto.

img = cv2.imread(''img_provided_by_op.jpg'', 0) img = cv2.bitwise_not(img) # (1) clean up noises kernel_clean = np.ones((2,2),np.uint8) cleaned = cv2.erode(img, kernel_clean, iterations=1) # (2) Extract lines kernel_line = np.ones((1, 5), np.uint8) clean_lines = cv2.erode(cleaned, kernel_line, iterations=6) clean_lines = cv2.dilate(clean_lines, kernel_line, iterations=6) # (3) Subtract lines cleaned_img_without_lines = cleaned - clean_lines cleaned_img_without_lines = cv2.bitwise_not(cleaned_img_without_lines) plt.imshow(cleaned_img_without_lines) plt.show() cv2.imwrite(''img_wanted.jpg'', cleaned_img_without_lines)

Manifestación

El método se basa en la respuesta de Zaw Lin. Él / ella identificó líneas en la imagen y simplemente hizo una resta para deshacerse de ellas. Sin embargo , no podemos simplemente restar líneas aquí porque también tenemos las letras e , t , E , T , ¡que también contienen líneas! Si solo restamos líneas horizontales de la imagen, e será casi idéntico a c . - se habrá ido ...

P: ¿Cómo encontramos las líneas?

Para encontrar líneas, podemos hacer uso de la función de erode . Para hacer uso de erode , necesitamos definir un kernel. (Puedes pensar en un kernel como una ventana / forma en la que funcionan las funciones).

El kernel se desliza a través de la imagen (como en la convolución 2D). Un píxel en la imagen original (1 o 0) se considerará 1 solo si todos los píxeles debajo del kernel son 1, de lo contrario se erosionará (se pondrá a cero). - (Source).

Para extraer líneas, definimos un kernel, kernel_line como np.ones((1, 5)) , [1, 1, 1, 1, 1] . Este kernel se deslizará a través de la imagen y erosionará los píxeles que tienen 0 debajo del kernel.

Más específicamente, mientras que el núcleo se aplica a un píxel, capturará los dos píxeles a su izquierda y los dos a su derecha.

[X X Y X X] ^ | Applied to Y, `kernel_line` captures Y''s neighbors. If any of them is not 0, Y will be set to 0.

Las líneas horizontales se conservarán bajo este núcleo, mientras que los píxeles que no tienen vecinos horizontales desaparecerán. Así es como capturamos líneas con la siguiente línea.

clean_lines = cv2.erode(cleaned, kernel_line, iterations=6)

P: ¿Cómo evitamos extraer líneas dentro de e, E, t, T y -?

Combinaremos la erosion y la dilation con el parámetro de iteración .

clean_lines = cv2.erode(cleaned, kernel_line, iterations=6)

Es posible que hayas notado las iterations=6 parte. El efecto de este parámetro hará que la parte plana en e, E, t, T, - desaparezca. Esto se debe a que, si bien aplicamos la misma operación varias veces, la parte del límite de estas líneas se reduciría. (Aplicando el mismo kernel, solo la parte del límite se encontrará con 0 y se convertirá en 0 como resultado). Usamos este truco para hacer que las líneas en estos caracteres desaparezcan.

Esto, sin embargo, viene con un efecto secundario que la parte de subrayado larga que queremos eliminar también se reduce. ¡Podemos cultivarlo con dilate !

clean_lines = cv2.dilate(clean_lines, kernel_line, iterations=6)

Contrariamente a la erosión que encoge una imagen, la dilatación hace que la imagen sea más grande. Si bien aún tenemos el mismo kernel, kernel_line , si alguna parte debajo del kernel es 1, el píxel objetivo será 1. Aplicando esto, el límite volverá a crecer. (La parte en e, E, t, T, - no volverá a crecer si seleccionamos el parámetro con cuidado para que desaparezca en la parte de erosión).

Con este truco adicional, podemos eliminar con éxito las líneas sin dañar e, E, t, T y -.