traitement d'image pour améliorer la précision de l'OCR tesseract

147

J'utilise tesseract pour convertir des documents en texte. La qualité des documents varie énormément, et je cherche des conseils sur le type de traitement d'image qui pourrait améliorer les résultats. J'ai remarqué que le texte hautement pixellisé - par exemple celui généré par les télécopieurs - est particulièrement difficile à traiter pour tesseract - vraisemblablement tous ces bords irréguliers des caractères confondent les algorithmes de reconnaissance de forme.

Quel type de techniques de traitement d'image améliorerait la précision? J'ai utilisé un flou gaussien pour lisser les images pixellisées et j'ai constaté une petite amélioration, mais j'espère qu'il existe une technique plus spécifique qui donnerait de meilleurs résultats. Disons un filtre adapté aux images en noir et blanc, qui lisserait les bords irréguliers, suivi d'un filtre qui augmenterait le contraste pour rendre les caractères plus distincts.

Des conseils généraux pour quelqu'un qui est novice en traitement d'image?

user364902
la source

Réponses:

105
  1. fixer DPI (si nécessaire) 300 DPI est minimum
  2. corriger la taille du texte (par exemple 12 pt devrait être ok)
  3. essayer de corriger les lignes de texte (redresser et déformer le texte)
  4. essayez de corriger l'éclairage de l'image (par exemple, aucune partie sombre de l'image)
  5. image binarisée et antibruit

Il n'y a pas de ligne de commande universelle qui conviendrait à tous les cas (parfois vous devez rendre l'image floue et nette). Mais vous pouvez essayer TEXTCLEANER à partir des scripts ImageMagick de Fred .

Si vous n'êtes pas fan de la ligne de commande, vous pouvez peut-être essayer d'utiliser l'opensource scantailor.sourceforge.net ou le bookrestorer commercial .

user898678
la source
6
Et il y a un guide illustré sur la façon de procéder: code.google.com/p/tesseract-ocr/wiki/ImproveQuality
iljau
2
Notez que le script lié semble être uniquement Linux.
Zoran Pavlovic
1
Ce n'est pas vrai - c'est un script bash. Si vous avez installé bash et ImageMagick, il fonctionnera également sous Windows. Bash pourrait être installé dans le cadre d'autres logiciels utiles, par exemple git ou msys2 ...
user898678
6
@iljau Depuis déménagé sur github. la page wiki est à: github.com/tesseract-ocr/tesseract/wiki/ImproveQuality
hometoast le
2
Les documents Tesseract ont de nouveau été déplacés vers tesseract-ocr.github.io/tessdoc/ImproveQuality
Nobody le
73

Je ne suis en aucun cas un expert en OCR. Mais cette semaine, j'ai eu besoin de convertir du texte à partir d'un jpg.

J'ai commencé avec un jpg RVB colorisé de 445x747 pixels. J'ai immédiatement essayé tesseract là-dessus, et le programme n'a presque rien converti. Je suis ensuite entré dans GIMP et j'ai fait ce qui suit. image> mode> image en niveaux de gris> image à l'échelle> filtres de 1191x2000 pixels> améliorer> masque flou avec des valeurs de rayon = 6,8, montant = 2,69, seuil = 0 J'ai ensuite enregistré comme nouveau jpg à 100% de qualité.

Tesseract a ensuite pu extraire tout le texte dans un fichier .txt

Gimp est votre ami.

John
la source
11
+1 J'ai suivi vos pas et j'ai obtenu une grande amélioration. Merci
OnOF
1
J'ai aussi l'impression que Tesseract fonctionne mieux si vous convertissez l'entrée en fichier TIFF et donnez à Tesseract le TIFF (plutôt que de demander à Tesseract de faire la conversion pour vous). ImageMagick peut effectuer la conversion pour vous. C'est mon impression anecdotique, mais je ne l'ai pas testé avec soin, donc ça pourrait être faux.
DW
+1 Le filtre "masque flou" a vraiment fait ma journée. Une autre étape qui m'a aidé: à l'aide de l'outil "sélection floue" sélectionnez l'arrière-plan puis appuyez sur Suppr pour le faire disparaître
Davide
Je suis bloqué sur ce problème de traitement d'image avant la reconnaissance de tesseract stackoverflow.com/questions/32473095/... Pouvez-vous m'aider ici?
Hussain
Nan. J'ai essayé de le rendre plus grand, et le mettre en niveaux de gris ne semble rien me donner un résultat positif. Soupir :( Vérifiez cette cible: freesms4us.com
...
30

Trois points pour améliorer la lisibilité de l'image: 1) Redimensionner l'image avec une hauteur et une largeur variables (multiplier 0,5 et 1 et 2 avec la hauteur et la largeur de l'image). 2) Convertissez l'image au format Échelle de gris (noir et blanc). 3) Supprimez les pixels de bruit et rendez plus clair (Filtrez l'image).

Référez-vous au code ci-dessous:

//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;
        }

IMAGE ENTRÉE
IMAGE ENTRÉE

IMAGE DE SORTIE IMAGE DE SORTIE

Sathyaraj Palanisamy
la source
Oui, nous devons passer le paramètre requis à la méthode Resize, cela pré-traitera le redimensionnement, l'opération SetGrayscale et RemoveNoise, puis retournera l'image de sortie avec une meilleure lisibilité.
Sathyaraj Palanisamy
J'ai essayé cette approche sur un ensemble de fichiers et comparé au résultat initial. Dans certains cas limités, cela donne de meilleurs résultats, la plupart du temps il y a eu une légère diminution de la qualité du texte de sortie. Donc, cela ne ressemble pas à une solution universelle.
Bryn
Cela a plutôt bien fonctionné pour moi. Cela donne certainement un point de départ pour le prétraitement de l'image qui supprime la quantité de charabia que vous récupérez de Tesseract.
ses
22

En règle générale, j'applique généralement les techniques de prétraitement d'image suivantes à l'aide de la bibliothèque OpenCV:

  1. Redimensionner l'image (il est recommandé si vous travaillez avec des images dont le DPI est inférieur à 300 dpi):

    img = cv2.resize(img, None, fx=1.2, fy=1.2, interpolation=cv2.INTER_CUBIC)
    
  2. Conversion d'image en niveaux de gris:

    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
  3. Application de la dilatation et de l'érosion pour supprimer le bruit (vous pouvez jouer avec la taille du noyau en fonction de votre jeu de données):

    kernel = np.ones((1, 1), np.uint8)
    img = cv2.dilate(img, kernel, iterations=1)
    img = cv2.erode(img, kernel, iterations=1)
    
  4. Appliquer un flou, ce qui peut être fait en utilisant l'une des lignes suivantes (dont chacune a ses avantages et ses inconvénients, cependant, le flou médian et le filtre bilatéral fonctionnent généralement mieux que le flou gaussien.):

    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)
    

J'ai récemment écrit un guide assez simple sur Tesseract, mais il devrait vous permettre d'écrire votre premier script OCR et d'éliminer certains obstacles que j'ai rencontrés lorsque les choses étaient moins claires que je ne l'aurais souhaité dans la documentation.

Au cas où vous souhaiteriez les consulter, je partage ici les liens avec vous:

bkaankuguoglu
la source
pourquoi convertissons-nous l'image en échelle de gris? Pour être plus précis, j'ai vu dans le processus de détection d'image, l'image est d'abord convertie en échelle de gris, puis sobel-> MSER -> SWT. pourriez-vous s'il vous plaît l'élaborer? Je suis nouveau dans le domaine de la propriété intellectuelle.
OnePunchMan
Quant à ma compréhension, cela dépend de l'algorithme, certains peuvent ne pas avoir besoin de convertir du tout. Considérez les pixels comme quelques valeurs de couleur stockées numériquement - dans le cas de RVB, rouge, vert et bleu -. Lorsqu'un pixel est converti à l'échelle N / B, alors votre algorithme ne doit travailler que sur 2 dimensions, au lieu de 3. Cela présente des avantages évidents en termes de vitesse lors de l'exécution de votre algorithme sur les pixels un par un. En outre, certains peuvent également dire qu'il est plus facile de supprimer le bruit et de détecter les bords d'une image lorsqu'elle est convertie en niveaux de gris.
bkaankuguoglu
Merci pour votre réponse. Et à propos de votre blog, pourriez-vous s'il vous plaît en écrire un sur COMMENT CRÉER UNE OCR À PARTIR DE SCRATCH EN UTILISANT TESSERACT pour un script non romain. J'ai des recherches partout, tout ce qui est disponible n'est pas clair.
OnePunchMan
16

C'est il y a un peu mais cela pourrait encore être utile.

Mon expérience montre que redimensionner l'image en mémoire avant de la passer à tesseract aide parfois.

Essayez différents modes d'interpolation. Le message https://stackoverflow.com/a/4756906/146003 m'a beaucoup aidé.

Atmocréations
la source
15

Ce qui m'a été extrêmement utile de cette façon, ce sont les codes sources du projet Capture2Text. http://sourceforge.net/projects/capture2text/files/Capture2Text/ .

BTW: Félicitations à son auteur pour le partage d'un algorithme aussi minutieux.

Faites particulièrement attention au fichier Capture2Text \ SourceCode \ leptonica_util \ leptonica_util.c - c'est l'essence même de la préprocession d'image pour cet utilitaire.

Si vous exécutez les binaires, vous pouvez vérifier la transformation d'image avant / après le processus dans le dossier Capture2Text \ Output \.

La solution mentionnée par PS utilise Tesseract pour l'OCR et Leptonica pour le prétraitement.

Homme sage
la source
1
Merci pour l'outil Capture2Text. Cela résout parfaitement tous les problèmes d'OCR dans mon projet!
Lê Quang Duy le
12

Version Java pour le code de Sathyaraj ci-dessus:

// 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
la source
Quelle est votre classe pour Bitmap? Bitmap n'est pas trouvé dans Java (il est nativement sous Android).
Nous sommes Borg
Cette méthode passe par une exception: Causée par: java.lang.IllegalArgumentException: y doit être <bitmap.height ()
Nativ
9

La documentation Tesseract contient quelques bons détails sur la façon d'améliorer la qualité de l'OCR via les étapes de traitement d'image.

Dans une certaine mesure, Tesseract les applique automatiquement. Il est également possible de dire à Tesseract d'écrire une image intermédiaire pour l'inspection, c'est-à-dire de vérifier le bon fonctionnement du traitement d'image interne (recherche tessedit_write_imagesdans la référence ci-dessus).

Plus important encore, le nouveau système de réseau neuronal de Tesseract 4 donne de bien meilleurs résultats OCR - en général et en particulier pour les images avec un peu de bruit. Il est activé avec --oem 1, par exemple comme dans:

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

(cet exemple sélectionne la langue allemande)

Ainsi, il est logique de tester d'abord jusqu'où vous en êtes avec le nouveau mode Tesseract LSTM avant d'appliquer certaines étapes de traitement d'image de prétraitement personnalisées.

maxschlepzig
la source
6

Le seuillage adaptatif est important si l'éclairage est inégal sur l'image. Mon prétraitement à l'aide de GraphicsMagic est mentionné dans cet article: https://groups.google.com/forum/#!topic/tesseract-ocr/jONGSChLRv4

GraphicsMagic a également la fonction -lat pour le seuil adaptatif de temps linéaire que je vais essayer bientôt.

Une autre méthode de seuillage utilisant OpenCV est décrite ici: http://docs.opencv.org/trunk/doc/py_tutorials/py_imgproc/py_thresholding/py_thresholding.html

rleir
la source
2
Le lien OpenCV est modifié. Dans la documentation OpenCV, il y a Tutoriels OpenCV-Python> Traitement d'image dans OpenCV> Seuil d'image
richk
2

Je l'ai fait pour obtenir de bons résultats avec une image qui n'a pas de très petit texte.

  1. Appliquez un flou à l'image d'origine.
  2. Appliquer le seuil adaptatif.
  3. Appliquer un effet de netteté.

Et si vous n'obtenez toujours pas de bons résultats, redimensionnez l'image à 150% ou 200%.

Hamza Iqbal
la source
2

La lecture de texte à partir de documents image à l'aide de n'importe quel moteur OCR pose de nombreux problèmes pour obtenir une bonne précision. Il n'y a pas de solution fixe à tous les cas, mais voici quelques éléments à prendre en compte pour améliorer les résultats de l'OCR.

1) Présence de bruit en raison d'une mauvaise qualité d'image / d'éléments / gouttes indésirables dans la région d'arrière-plan. Cela nécessite des opérations de prétraitement comme la suppression du bruit qui peuvent être facilement effectuées en utilisant un filtre gaussien ou des méthodes de filtre médian normales. Ceux-ci sont également disponibles dans OpenCV.

2) Mauvaise orientation de l'image: En raison d'une mauvaise orientation, le moteur OCR ne parvient pas à segmenter correctement les lignes et les mots de l'image, ce qui donne la pire précision.

3) Présence de lignes: Lors de la segmentation de mots ou de lignes, le moteur OCR essaie parfois de fusionner les mots et les lignes ensemble, traitant ainsi le mauvais contenu et donnant ainsi de mauvais résultats. Il y a aussi d'autres problèmes, mais ce sont les problèmes de base.

Cette application post OCR est un exemple de cas où certains pré-traitement d'image et post-traitement sur le résultat OCR peuvent être appliqués pour obtenir une meilleure précision OCR.

flamélite
la source
1

La reconnaissance de texte dépend de divers facteurs pour produire une sortie de bonne qualité. La sortie OCR dépend fortement de la qualité de l'image d'entrée. C'est pourquoi chaque moteur OCR fournit des directives concernant la qualité de l'image d'entrée et sa taille. Ces directives aident le moteur OCR à produire des résultats précis.

J'ai écrit un article détaillé sur le traitement d'image en python. Veuillez suivre le lien ci-dessous pour plus d'explications. Ajout du code source python pour implémenter ces processus.

Veuillez écrire un commentaire si vous avez une suggestion ou une meilleure idée sur ce sujet pour l'améliorer.

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

Brijesh Gupta
la source
2
Veuillez ajouter une réponse ici comme résumé de votre blog. De sorte que même si le lien est mort, la réponse ne sera pas rendue inutile.
Nithin
0

vous pouvez réduire le bruit puis appliquer un seuillage, mais vous pouvez jouer avec la configuration de l'OCR en modifiant les valeurs --psm et --oem

essayez: --psm 5 --oem 2

vous pouvez également consulter le lien suivant pour plus de détails ici

Sameer Maurya
la source