procesamiento de imágenes para mejorar la precisión de tesseract OCR

146

He estado usando tesseract para convertir documentos en texto. La calidad de los documentos varía enormemente, y estoy buscando consejos sobre qué tipo de procesamiento de imágenes podría mejorar los resultados. Me di cuenta de que el texto que está altamente pixelado, por ejemplo, el generado por las máquinas de fax, es especialmente difícil de procesar para tesseract, presumiblemente todos esos bordes irregulares a los caracteres confunden los algoritmos de reconocimiento de formas.

¿Qué tipo de técnicas de procesamiento de imágenes mejorarían la precisión? He estado usando un desenfoque gaussiano para suavizar las imágenes pixeladas y he visto algunas pequeñas mejoras, pero espero que haya una técnica más específica que produzca mejores resultados. Digamos un filtro que se ajustó a las imágenes en blanco y negro, que suavizaría los bordes irregulares, seguido de un filtro que aumentaría el contraste para hacer que los caracteres sean más distintos.

¿Algún consejo general para alguien que es un novato en el procesamiento de imágenes?

usuario364902
fuente

Respuestas:

104
  1. fijar DPI (si es necesario) 300 DPI es mínimo
  2. corregir el tamaño del texto (por ejemplo, 12 pt debería estar bien)
  3. intenta arreglar líneas de texto (desviar y desviar texto)
  4. intente arreglar la iluminación de la imagen (por ejemplo, no hay parte oscura de la imagen)
  5. binarizar y eliminar ruido de imagen

No existe una línea de comando universal que se ajuste a todos los casos (a veces es necesario desenfocar y enfocar la imagen). Pero puedes probar TEXTCLEANER desde las secuencias de comandos ImageMagick de Fred .

Si no eres fanático de la línea de comandos, tal vez puedas intentar usar opensource scantailor.sourceforge.net o un bookrestorer comercial .

user898678
fuente
66
Y hay una guía ilustrada sobre cómo hacer esto: code.google.com/p/tesseract-ocr/wiki/ImproveQuality
iljau
2
Tenga en cuenta que el script vinculado parece ser solo de Linux.
Zoran Pavlovic
1
Esto no es cierto, este es un script bash. Si ha instalado bash e ImageMagick, también se ejecutará en Windows. Bash podría instalarse como parte de otro software útil, por ejemplo, git o msys2 ...
user898678
66
@iljau Desde que se mudó a github. la página wiki está en: github.com/tesseract-ocr/tesseract/wiki/ImproveQuality
hometoast el
2
Los documentos de Tesseract se mudaron nuevamente a tesseract-ocr.github.io/tessdoc/ImproveQuality
Nadie
73

De ninguna manera soy un experto en OCR. Pero esta semana tuve que convertir texto de un jpg.

Comencé con un color jpg RGB 445x747 píxeles. Inmediatamente intenté tesseract en esto, y el programa no convirtió casi nada. Luego entré en GIMP e hice lo siguiente. imagen> modo> imagen en escala de grises> imagen en escala> filtros de 1191x2000 píxeles> mejorar> máscara de enfoque con valores de radio = 6.8, cantidad = 2.69, umbral = 0 Luego lo guardé como un nuevo jpg al 100% de calidad.

Tesseract pudo extraer todo el texto en un archivo .txt

Gimp es tu amigo.

Juan
fuente
11
+1 Seguí tus pasos y obtuve una gran mejora. Gracias
onof
1
También tengo la impresión de que Tesseract funciona mejor si convierte la entrada a un archivo TIFF y le da a Tesseract el TIFF (en lugar de pedirle a Tesseract que haga la conversión por usted). ImageMagick puede hacer la conversión por usted. Esta es mi impresión anecdótica, pero no la he probado cuidadosamente, por lo que podría estar equivocado.
DW
+1 El filtro "máscara de enfoque" realmente me alegró el día. Otro paso que me ayudó: usando la herramienta "selección difusa", seleccione el fondo y luego presione Supr para aclararlo
Davide
Estoy atrapado en este problema de procesamiento de imágenes antes del reconocimiento de tesseract stackoverflow.com/questions/32473095/… ¿Me pueden ayudar aquí?
Hussain
no Traté de hacerlo más grande, y configurarlo en escala de grises parece que nada me da un resultado positivo. Suspiro :( Verifique este objetivo: freesms4us.com/…
gumuruh
30

Tres puntos para mejorar la legibilidad de la imagen: 1) Cambiar el tamaño de la imagen con alto y ancho variables (multiplique 0.5 y 1 y 2 con alto y ancho de la imagen). 2) Convierta la imagen al formato de escala de grises (blanco y negro). 3) Elimine los píxeles de ruido y haga más claro (Filtre la imagen).

Consulte el siguiente código:

//Resize
  public Bitmap Resize(Bitmap bmp, int newWidth, int newHeight)
        {

                Bitmap temp = (Bitmap)bmp;

                Bitmap bmap = new Bitmap(newWidth, newHeight, temp.PixelFormat);

                double nWidthFactor = (double)temp.Width / (double)newWidth;
                double nHeightFactor = (double)temp.Height / (double)newHeight;

                double fx, fy, nx, ny;
                int cx, cy, fr_x, fr_y;
                Color color1 = new Color();
                Color color2 = new Color();
                Color color3 = new Color();
                Color color4 = new Color();
                byte nRed, nGreen, nBlue;

                byte bp1, bp2;

                for (int x = 0; x < bmap.Width; ++x)
                {
                    for (int y = 0; y < bmap.Height; ++y)
                    {

                        fr_x = (int)Math.Floor(x * nWidthFactor);
                        fr_y = (int)Math.Floor(y * nHeightFactor);
                        cx = fr_x + 1;
                        if (cx >= temp.Width) cx = fr_x;
                        cy = fr_y + 1;
                        if (cy >= temp.Height) cy = fr_y;
                        fx = x * nWidthFactor - fr_x;
                        fy = y * nHeightFactor - fr_y;
                        nx = 1.0 - fx;
                        ny = 1.0 - fy;

                        color1 = temp.GetPixel(fr_x, fr_y);
                        color2 = temp.GetPixel(cx, fr_y);
                        color3 = temp.GetPixel(fr_x, cy);
                        color4 = temp.GetPixel(cx, cy);

                        // Blue
                        bp1 = (byte)(nx * color1.B + fx * color2.B);

                        bp2 = (byte)(nx * color3.B + fx * color4.B);

                        nBlue = (byte)(ny * (double)(bp1) + fy * (double)(bp2));

                        // Green
                        bp1 = (byte)(nx * color1.G + fx * color2.G);

                        bp2 = (byte)(nx * color3.G + fx * color4.G);

                        nGreen = (byte)(ny * (double)(bp1) + fy * (double)(bp2));

                        // Red
                        bp1 = (byte)(nx * color1.R + fx * color2.R);

                        bp2 = (byte)(nx * color3.R + fx * color4.R);

                        nRed = (byte)(ny * (double)(bp1) + fy * (double)(bp2));

                        bmap.SetPixel(x, y, System.Drawing.Color.FromArgb
                (255, nRed, nGreen, nBlue));
                    }
                }



                bmap = SetGrayscale(bmap);
                bmap = RemoveNoise(bmap);

                return bmap;

        }


//SetGrayscale
  public Bitmap SetGrayscale(Bitmap img)
        {

            Bitmap temp = (Bitmap)img;
            Bitmap bmap = (Bitmap)temp.Clone();
            Color c;
            for (int i = 0; i < bmap.Width; i++)
            {
                for (int j = 0; j < bmap.Height; j++)
                {
                    c = bmap.GetPixel(i, j);
                    byte gray = (byte)(.299 * c.R + .587 * c.G + .114 * c.B);

                    bmap.SetPixel(i, j, Color.FromArgb(gray, gray, gray));
                }
            }
            return (Bitmap)bmap.Clone();

        }
//RemoveNoise
   public Bitmap RemoveNoise(Bitmap bmap)
        {

            for (var x = 0; x < bmap.Width; x++)
            {
                for (var y = 0; y < bmap.Height; y++)
                {
                    var pixel = bmap.GetPixel(x, y);
                    if (pixel.R < 162 && pixel.G < 162 && pixel.B < 162)
                        bmap.SetPixel(x, y, Color.Black);
                    else if (pixel.R > 162 && pixel.G > 162 && pixel.B > 162)
                        bmap.SetPixel(x, y, Color.White);
                }
            }

            return bmap;
        }

IMAGEN DE ENTRADA
IMAGEN DE ENTRADA

IMAGEN DE SALIDA IMAGEN DE SALIDA

Sathyaraj Palanisamy
fuente
Sí, tenemos que pasar el parámetro requerido al método de cambio de tamaño, procesará el cambio de tamaño, la escala SetGrayscale y RemoveNoise y luego devolverá la imagen de salida con una mejor legibilidad.
Sathyaraj Palanisamy
Probé este enfoque en un conjunto de archivos y lo comparé con el resultado inicial. En algunos casos limitados da un mejor resultado, principalmente hubo una ligera disminución de la calidad del texto de salida. Por lo tanto, no parece una solución universal.
Bryn
Esto realmente funcionó bastante bien para mí. Ciertamente, proporciona un punto de partida para el preprocesamiento de imágenes que elimina la cantidad de tonterías que obtienes de Tesseract.
ses
22

Como regla general, generalmente aplico las siguientes técnicas de preprocesamiento de imágenes usando la biblioteca OpenCV:

  1. Reescalar la imagen (se recomienda si está trabajando con imágenes que tienen un DPI de menos de 300 ppp):

    img = cv2.resize(img, None, fx=1.2, fy=1.2, interpolation=cv2.INTER_CUBIC)
    
  2. Convertir imagen a escala de grises:

    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
  3. Aplicando dilatación y erosión para eliminar el ruido (puede jugar con el tamaño del núcleo dependiendo de su conjunto de datos):

    kernel = np.ones((1, 1), np.uint8)
    img = cv2.dilate(img, kernel, iterations=1)
    img = cv2.erode(img, kernel, iterations=1)
    
  4. Aplicación de desenfoque, que se puede hacer utilizando una de las siguientes líneas (cada una de las cuales tiene sus ventajas y desventajas, sin embargo, el desenfoque medio y el filtro bilateral generalmente funcionan mejor que el desenfoque gaussiano):

    cv2.threshold(cv2.GaussianBlur(img, (5, 5), 0), 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
    
    cv2.threshold(cv2.bilateralFilter(img, 5, 75, 75), 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
    
    cv2.threshold(cv2.medianBlur(img, 3), 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
    
    cv2.adaptiveThreshold(cv2.GaussianBlur(img, (5, 5), 0), 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 31, 2)
    
    cv2.adaptiveThreshold(cv2.bilateralFilter(img, 9, 75, 75), 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 31, 2)
    
    cv2.adaptiveThreshold(cv2.medianBlur(img, 3), 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 31, 2)
    

Recientemente escribí una guía bastante simple para Tesseract, pero debería permitirle escribir su primer script de OCR y resolver algunos obstáculos que experimenté cuando las cosas estaban menos claras de lo que me hubiera gustado en la documentación.

En caso de que desee consultarlos, aquí estoy compartiendo los enlaces con usted:

bkaankuguoglu
fuente
¿Por qué convertimos la imagen a escala de grises? Para ser más específico, he visto en el proceso de detección de imágenes, la imagen se convierte primero a escala de grises, luego sobel-> MSER -> SWT. ¿podría por favor elaborarlo? Soy nuevo en el campo de IP.
OnePunchMan
En mi opinión, depende del algoritmo, algunos pueden no necesitar convertir en absoluto. Piense en los píxeles como unos pocos valores de color almacenados digitalmente, en el caso de RGB, rojo, verde y azul. Cuando un píxel se convierte a la escala B / N, entonces su algoritmo necesita trabajar solo en 2 dimensiones, en lugar de 3. Esto tiene ventajas obvias en velocidad al ejecutar su algoritmo en píxeles uno por uno. Además, algunos también pueden decir que es más fácil eliminar el ruido y detectar los bordes de una imagen cuando se convierte en escala de grises.
bkaankuguoglu
Gracias por la respuesta. Y sobre su blog, ¿podría escribir uno sobre CÓMO CONSTRUIR OCR DESDE SCRATCH UTILIZANDO TESSERACT para una escritura no romana? He buscado en todas partes, todo lo que está disponible no está claro.
OnePunchMan
16

Esto fue hace un tiempo, pero aún podría ser útil.

Mi experiencia muestra que cambiar el tamaño de la imagen en la memoria antes de pasarla a tesseract a veces ayuda.

Pruebe diferentes modos de interpolación. La publicación https://stackoverflow.com/a/4756906/146003 me ayudó mucho.

Atmocreations
fuente
15

Lo que fue EXTREMADAMENTE ÚTIL para mí de esta manera son los códigos fuente del proyecto Capture2Text. http://sourceforge.net/projects/capture2text/files/Capture2Text/ .

Por cierto: Felicitaciones a su autor por compartir un algoritmo tan minucioso.

Preste especial atención al archivo Capture2Text \ SourceCode \ leptonica_util \ leptonica_util.c: esa es la esencia de la preprocesión de imágenes para esta utilidad.

Si va a ejecutar los archivos binarios, puede verificar la transformación de la imagen antes / después del proceso en la carpeta Capture2Text \ Output \.

La solución mencionada por PS utiliza Tesseract para OCR y Leptonica para el preprocesamiento.

Hombre sabio
fuente
1
Gracias por la herramienta Capture2Text. ¡Resuelve perfectamente todos los problemas de OCR en mi proyecto!
Lê Quang Duy
12

Versión de Java para el código de Sathyaraj anterior:

// Resize
public Bitmap resize(Bitmap img, int newWidth, int newHeight) {
    Bitmap bmap = img.copy(img.getConfig(), true);

    double nWidthFactor = (double) img.getWidth() / (double) newWidth;
    double nHeightFactor = (double) img.getHeight() / (double) newHeight;

    double fx, fy, nx, ny;
    int cx, cy, fr_x, fr_y;
    int color1;
    int color2;
    int color3;
    int color4;
    byte nRed, nGreen, nBlue;

    byte bp1, bp2;

    for (int x = 0; x < bmap.getWidth(); ++x) {
        for (int y = 0; y < bmap.getHeight(); ++y) {

            fr_x = (int) Math.floor(x * nWidthFactor);
            fr_y = (int) Math.floor(y * nHeightFactor);
            cx = fr_x + 1;
            if (cx >= img.getWidth())
                cx = fr_x;
            cy = fr_y + 1;
            if (cy >= img.getHeight())
                cy = fr_y;
            fx = x * nWidthFactor - fr_x;
            fy = y * nHeightFactor - fr_y;
            nx = 1.0 - fx;
            ny = 1.0 - fy;

            color1 = img.getPixel(fr_x, fr_y);
            color2 = img.getPixel(cx, fr_y);
            color3 = img.getPixel(fr_x, cy);
            color4 = img.getPixel(cx, cy);

            // Blue
            bp1 = (byte) (nx * Color.blue(color1) + fx * Color.blue(color2));
            bp2 = (byte) (nx * Color.blue(color3) + fx * Color.blue(color4));
            nBlue = (byte) (ny * (double) (bp1) + fy * (double) (bp2));

            // Green
            bp1 = (byte) (nx * Color.green(color1) + fx * Color.green(color2));
            bp2 = (byte) (nx * Color.green(color3) + fx * Color.green(color4));
            nGreen = (byte) (ny * (double) (bp1) + fy * (double) (bp2));

            // Red
            bp1 = (byte) (nx * Color.red(color1) + fx * Color.red(color2));
            bp2 = (byte) (nx * Color.red(color3) + fx * Color.red(color4));
            nRed = (byte) (ny * (double) (bp1) + fy * (double) (bp2));

            bmap.setPixel(x, y, Color.argb(255, nRed, nGreen, nBlue));
        }
    }

    bmap = setGrayscale(bmap);
    bmap = removeNoise(bmap);

    return bmap;
}

// SetGrayscale
private Bitmap setGrayscale(Bitmap img) {
    Bitmap bmap = img.copy(img.getConfig(), true);
    int c;
    for (int i = 0; i < bmap.getWidth(); i++) {
        for (int j = 0; j < bmap.getHeight(); j++) {
            c = bmap.getPixel(i, j);
            byte gray = (byte) (.299 * Color.red(c) + .587 * Color.green(c)
                    + .114 * Color.blue(c));

            bmap.setPixel(i, j, Color.argb(255, gray, gray, gray));
        }
    }
    return bmap;
}

// RemoveNoise
private Bitmap removeNoise(Bitmap bmap) {
    for (int x = 0; x < bmap.getWidth(); x++) {
        for (int y = 0; y < bmap.getHeight(); y++) {
            int pixel = bmap.getPixel(x, y);
            if (Color.red(pixel) < 162 && Color.green(pixel) < 162 && Color.blue(pixel) < 162) {
                bmap.setPixel(x, y, Color.BLACK);
            }
        }
    }
    for (int x = 0; x < bmap.getWidth(); x++) {
        for (int y = 0; y < bmap.getHeight(); y++) {
            int pixel = bmap.getPixel(x, y);
            if (Color.red(pixel) > 162 && Color.green(pixel) > 162 && Color.blue(pixel) > 162) {
                bmap.setPixel(x, y, Color.WHITE);
            }
        }
    }
    return bmap;
}
Fábio Silva
fuente
¿Cuál es tu clase para Bitmap? El mapa de bits no se encuentra en Java (está en Android de forma nativa).
Somos Borg
Este método pasa por una excepción: Causado por: java.lang.IllegalArgumentException: y debe ser <bitmap.height ()
Nativ
9

La documentación de Tesseract contiene algunos buenos detalles sobre cómo mejorar la calidad de OCR a través de pasos de procesamiento de imágenes.

Hasta cierto punto, Tesseract los aplica automáticamente. También es posible decirle a Tesseract que escriba una imagen intermedia para su inspección, es decir, para verificar qué tan bien funciona el procesamiento interno de la imagen (busque tessedit_write_imagesen la referencia anterior).

Más importante aún, el nuevo sistema de red neuronal en Tesseract 4 produce resultados OCR mucho mejores, en general y especialmente para imágenes con algo de ruido. Está habilitado con --oem 1, por ejemplo, como en:

$ tesseract --oem 1 -l deu page.png result pdf

(este ejemplo selecciona el idioma alemán)

Por lo tanto, tiene sentido probar primero hasta qué punto llega con el nuevo modo Tesseract LSTM antes de aplicar algunos pasos de procesamiento de imágenes de preprocesamiento personalizados.

maxschlepzig
fuente
6

El umbral adaptativo es importante si la iluminación es desigual en la imagen. Mi preprocesamiento con GraphicsMagic se menciona en esta publicación: https://groups.google.com/forum/#!topic/tesseract-ocr/jONGSChLRv4

GraphicsMagic también tiene la función -lat para Umbral adaptativo de tiempo lineal que intentaré pronto.

Aquí se describe otro método de umbral usando OpenCV: http://docs.opencv.org/trunk/doc/py_tutorials/py_imgproc/py_thresholding/py_thresholding.html

rleir
fuente
2
El enlace OpenCV ha cambiado. En la documentación de OpenCV se encuentran los Tutoriales de OpenCV-Python> Procesamiento de imágenes en OpenCV> Umbral de imagen
richk
2

Hice esto para obtener buenos resultados de una imagen que no tiene texto muy pequeño.

  1. Aplicar desenfoque a la imagen original.
  2. Aplicar umbral adaptativo.
  3. Aplicar efecto de afilado.

Y si todavía no obtiene buenos resultados, escale la imagen al 150% o 200%.

Hamza Iqbal
fuente
2

La lectura de texto de documentos de imagen utilizando cualquier motor de OCR tiene muchos problemas para obtener una buena precisión. No existe una solución fija para todos los casos, pero aquí hay algunas cosas que deben considerarse para mejorar los resultados de OCR.

1) Presencia de ruido debido a la mala calidad de imagen / elementos / blobs no deseados en la región de fondo. Esto requiere algunas operaciones de preprocesamiento, como la eliminación de ruido, que se puede hacer fácilmente utilizando un filtro gaussiano o métodos de filtro de mediana normal. Estos también están disponibles en OpenCV.

2) Orientación incorrecta de la imagen: debido a la orientación incorrecta, el motor de OCR no puede segmentar correctamente las líneas y palabras en la imagen, lo que da la peor precisión.

3) Presencia de líneas: mientras se realiza la segmentación de palabras o líneas, el motor OCR a veces también intenta fusionar las palabras y las líneas y, por lo tanto, procesar contenido incorrecto y, por lo tanto, dar resultados incorrectos. También hay otros problemas, pero estos son los básicos.

Esta aplicación posterior a OCR es un caso de ejemplo en el que se puede aplicar algo de procesamiento previo de imagen y procesamiento posterior en el resultado de OCR para obtener una mejor precisión de OCR.

flamelita
fuente
1

El reconocimiento de texto depende de una variedad de factores para producir una salida de buena calidad. La salida de OCR depende en gran medida de la calidad de la imagen de entrada. Es por eso que cada motor de OCR proporciona pautas con respecto a la calidad de la imagen de entrada y su tamaño. Estas pautas ayudan al motor OCR a producir resultados precisos.

He escrito un artículo detallado sobre el procesamiento de imágenes en python. Siga amablemente el siguiente enlace para obtener más explicaciones. También se agregó el código fuente de Python para implementar esos procesos.

Escriba un comentario si tiene una sugerencia o una mejor idea sobre este tema para mejorarlo.

https://medium.com/cashify-engineering/improve-accuracy-of-ocr-using-image-preprocessing-8df29ec3a033

Brijesh Gupta
fuente
2
Agregue una respuesta aquí como resumen de su blog. De modo que incluso si el enlace está muerto, la respuesta no será inútil.
Nithin
0

puede reducir el ruido y luego aplicar umbrales, pero puede hacerlo con la configuración del OCR cambiando los valores --psm y --oem

prueba: --psm 5 --oem 2

También puede consultar el siguiente enlace para obtener más detalles aquí.

Sameer Maurya
fuente