Tengo un montón de fechas en las que estoy tratando de OCR usando tesseract. Sin embargo, muchos de los dígitos en las fechas se combinan con las líneas en los cuadros de fecha de la siguiente manera:


Digits intersecting boxes Digits intersecting boxes Digits intersecting boxes Digits intersecting boxes


Además, aquí hay una buena imagen con la que puedo probar bien: Imagen de buena fecha


Y aquí está mi código:

import os
import cv2
from matplotlib import pyplot as plt
import subprocess
import numpy as np
from PIL import Image

def show(img):
    plt.figure(figsize=(20,20))
    plt.imshow(img,cmap='gray')
    plt.show()

def sort_contours(cnts, method="left-to-right"):
    # initialize the reverse flag and sort index
    reverse = False
    i = 0

    # handle if we need to sort in reverse
    if method == "right-to-left" or method == "bottom-to-top":
        reverse = True

    # handle if we are sorting against the y-coordinate rather than
    # the x-coordinate of the bounding box
    if method == "top-to-bottom" or method == "bottom-to-top":
        i = 1

    # construct the list of bounding boxes and sort them from top to
    # bottom
    boundingBoxes = [cv2.boundingRect(c) for c in cnts]

    cnts, boundingBoxes = zip(*sorted(zip(cnts, boundingBoxes),
        key=lambda b:b[1][i], reverse=reverse))

    # return the list of sorted contours and bounding boxes
    return cnts, boundingBoxes


def tesseract_it(contours,main_img, label,delete_last_contour=False):
    min_limit, max_limit = (1300,1700)
    idx =0 
    roi_list = []
    slist= set()
    for cnt in contours:
        idx += 1
        x,y,w,h = cv2.boundingRect(cnt)
        if label=='boxes':
            roi=main_img[y+2:y+h-2,x+2:x+w-2]
        else:
            roi=main_img[y:y+h,x:x+w]

        if w*h > min_limit and w*h < max_limit and w>10 and w< 50 and h>10 and h<50:
            if (x,y,w,h) not in slist: # Stops from identifying repeted contours

                roi = cv2.resize(roi,dsize=(45,45),fx=0 ,fy=0, interpolation = cv2.INTER_AREA)
                roi_list.append(roi)
                slist.add((x,y,w,h))

    if not delete_last_contour:
        vis = np.concatenate((roi_list),1)
    else:
        roi_list.pop(-1)
        vis = np.concatenate((roi_list),1)

    show(vis)

    # Tesseract the final image here
    # ...


image = 'bad_digit/1.jpg'
# image = 'bad_digit/good.jpg'
specimen_orig = cv2.imread(image,0)


specimen = cv2.fastNlMeansDenoising(specimen_orig)
#     show(specimen)
kernel = np.ones((3,3), np.uint8)

# Now we erode
specimen = cv2.erode(specimen, kernel, iterations = 1)
#     show(specimen)
_, specimen = cv2.threshold(specimen, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
#     show(specimen)
specimen_canny = cv2.Canny(specimen, 0, 0)
#     show(specimen_canny)

specimen_blank_image = np.zeros((specimen.shape[0], specimen.shape[1], 3))
_,specimen_contours, retr = cv2.findContours(specimen_canny.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE )
# print(len(specimen_contours))
cv2.drawContours(specimen_blank_image, specimen_contours, -1, 100, 2)
#     show(specimen_blank_image)
specimen_blank_image = np.zeros((specimen.shape[0], specimen.shape[1], 3))

specimen_sorted_contours, specimen_bounding_box = sort_contours(specimen_contours)

output_string = tesseract_it(specimen_sorted_contours,specimen_orig,label='boxes',)
# return output_string

El resultado de la buena imagen adjunta es así: Buen resultado


La prueba de esta imagen me da resultados precisos.

Sin embargo, para aquellos en los que las líneas se fusionan con los dígitos, mi salida se ve así: bad1 bad2 bad3 bad4

Estos no funcionan bien con Tesseract en absoluto. Me preguntaba si había una manera de eliminar las líneas y conservar solo los dígitos.

También probé lo siguiente: https://docs.opencv.org/3.2.0/d1/dee /tutorial_moprh_lines_detection.html

Lo que realmente no parece ser excelente en las imágenes que adjunto.

También he intentado usar imagemagick:

convert original.jpg \
\( -clone 0 -threshold 50% -negate -statistic median 200x1 \)  \
-compose lighten -composite                                    \
\( -clone 0 -threshold 50% -negate -statistic median 1x200 \)  \
-composite output.jpg

Sus resultados son justos, pero la línea eliminada corta algo los dígitos de la siguiente manera:

imagemagick1 imagemagick2 imagemagick3 imagemagick4

¿Hay alguna forma mejor de abordar este problema? Mi objetivo final es testear los dígitos, por lo que la imagen final debe ser bastante clara.

12
MetalloyD 2 mar. 2018 a las 11:29

3 respuestas

La mejor respuesta

Aquí hay un código que parece funcionar bastante bien. Hay dos fases:

  • Se puede observar que los números son ligeramente más audaces que las cajas. Además, toda la imagen tiene una fuerte horizontalidad. Entonces podemos aplicar una dilatación más fuerte horizontalmente para deshacernos de la mayoría de las líneas verticales.
  • En este punto, los OCR, por ejemplo, Google's one, pueden detectar la mayoría de los números. Desafortunadamente, es algo demasiado bueno y ve otras cosas, por lo que he agregado otra fase que es más compleja y bastante relacionada con su contexto particular.

Aquí está el resultado de una imagen después de la primera fase:

enter image description here

Y aquí están todos los resultados después de la segunda fase:

enter image description here

Como ves, no es perfecto, 8 puede verse como B (bueno, incluso un humano como yo lo ve como B ... pero se puede arreglar fácilmente si solo tienes números en tu mundo). También hay un carácter ":" (un legado de una línea vertical que se ha eliminado) que no puedo eliminar sin ajustar demasiado el código ...

El código C #:

static void Unbox(string inputFilePath, string outputFilePath)
{
    using (var orig = new Mat(inputFilePath))
    {
        using (var gray = orig.CvtColor(ColorConversionCodes.BGR2GRAY))
        {
            using (var dst = orig.EmptyClone())
            {
                // this is what I call the "horizontal shake" pass.
                // note I use the Rect shape here, this is important
                using (var dilate = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(4, 1)))
                {
                    Cv2.Dilate(gray, dst, dilate);
                }

                // erode just a bit to get back some numbers to life
                using (var erode = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(2, 1)))
                {
                    Cv2.Erode(dst, dst, erode);
                }

                // at this point, good OCR will see most numbers
                // but we want to remove surrounding artifacts

                // find countours
                using (var canny = dst.Canny(0, 400))
                {
                    var contours = canny.FindContoursAsArray(RetrievalModes.List, ContourApproximationModes.ApproxSimple);

                    // compute a bounding rect for all numbers w/o boxes and artifacts
                    // this is the tricky part where we try to discard what's not related exclusively to numbers
                    var boundingRect = Rect.Empty;
                    foreach (var contour in contours)
                    {
                        // discard some small and broken polygons
                        var polygon = Cv2.ApproxPolyDP(contour, 4, true);
                        if (polygon.Length < 3)
                            continue;

                        // we want only numbers, and boxes are approx 40px wide,
                        // so let's discard box-related polygons, if any
                        // and some other artifacts that passed previous checks
                        // this quite depends on some context knowledge...
                        var rect = Cv2.BoundingRect(polygon);
                        if (rect.Width > 40 || rect.Height < 15)
                            continue;

                        boundingRect = boundingRect.X == 0 ? rect : boundingRect.Union(rect);
                    }

                    using (var final = dst.Clone(boundingRect))
                    {
                        final.SaveImage(outputFilePath);
                    }
                }
            }
        }
    }
}
12
Simon Mourier 7 mar. 2018 a las 09:31

En particular, mire su 1 en 2018 a continuación en el caso de Yves Daoust casus ... eso es casi un "n" o como tres cuartos del entero 0 y 8 se convierte en la letra B. El 2 puede leerse como invertido 6. En algunos casos, 0 también se puede leer como 6, etc. Incluso algunos pueden terminar como "irreconocibles" si deja la cuadrícula en su lugar. Por lo tanto, mi enfoque sería:

  1. Sacar la información de cuadrícula redundante ayuda a identificar mejor los enteros que tienen líneas rectas dentro de ellos, como 0,1, 2, 4, 5 y 7.
  2. Seguido por entrenamiento de personajes usando el clasificador Cascade.

Las curvaturas en algunos dígitos se detectan fácilmente una vez que se retira la cuadrícula y se realiza el entrenamiento. Esto reducirá 90-95 por ciento de sus falsos negativos en enteros reales (verdaderos positivos) o bogies (verdaderos negativos). Entonces solo tiene que preocuparse por ese 5-10 por ciento.

Los documentos y la información del código de muestra se pueden encontrar aquí en OpenCV, aquí en Code-Robin y aquí en github.

Valores de imagen 02032018022018:

values 02032018022018

0
ZF007 6 mar. 2018 a las 12:30

Solo una sugerencia, nunca lo intenté.

En lugar de tratar de quitar las barras, guárdelas y entrene en todas las posiciones posibles de la barra. Recorte las barras a los límites de caracteres para una alineación adecuada.

enter image description here enter image description here

Entrene a estos como 02032018022018. Supongo que es mejor simular las barras en caracteres limpios.

2
Yves Daoust 2 mar. 2018 a las 10:10