tesseract OCRの精度を向上させる画像処理


145

私は文書をテキストに変換するためにtesseractを使用しています。ドキュメントの品質は非常に幅が広​​いので、どのような画像処理で結果が改善されるかについてのヒントを探しています。ピクセル化されたテキスト(FAXマシンによって生成されたテキストなど)は、テッセラクトの処理が特に困難であることに気づきました。おそらく、文字のギザギザのエッジすべてが形状認識アルゴリズムを混乱させます。

どんな種類の画像処理技術が精度を向上させるでしょうか?私はピクセル化された画像を滑らかにするためにガウスぼかしを使用していて、いくつかの小さな改善を見てきましたが、より良い結果をもたらすより具体的な手法があることを期待しています。白黒の画像に合わせて調整されたフィルターを言います。これにより、不規則なエッジが滑らかになり、その後にコントラストを高めて文字をよりはっきりさせるフィルターが続きます。

画像処理の初心者のための一般的なヒントはありますか?

回答:


103
  1. DPIを修正(必要な場合)最小300 DPI
  2. テキストサイズを修正します(たとえば、12 ptで問題ありません)
  3. テキスト行の修正を試みます(テキストのデスキューとデワープ)
  4. 画像の照明を修正してみてください(例:画像の暗い部分がない)
  5. 画像の二値化とノイズ除去

すべてのケースに適合する汎用コマンドラインはありません(画像をぼかして鮮明にする必要がある場合があります)。しかし、FredのImageMagickスクリプトからTEXTCLEANERを試すことができます。

あなたは、コマンドラインのファンでない場合は、多分あなたはオープンソースを使用しようとすることができますscantailor.sourceforge.netや商用bookrestorerを


6
そして、これを行う方法のイラスト付きガイドがあります:code.google.com/p/tesseract-ocr/wiki/ImproveQuality
iljau

2
リンクされたスクリプトはLinux専用であるように見えます。
Zoran Pavlovic

1
これは真実ではありません-これはbashスクリプトです。bashとImageMagickをインストールしている場合は、Windowsでも動作します。バッシュは、例えば他の有用なソフトウェアの一部としてインストールできgitのmsys2 ...
user898678

6
@iljau githubに移動してから。wikiページは次の場所にあり
hometoast

2
Tesseractのドキュメントが再びtesseract-ocr.github.io/tessdoc/ImproveQualityに
誰も

73

私は決してOCRの専門家ではありません。しかし、私は今週、jpgからテキストを変換する必要がありました。

私は、カラー化されたRGB 445x747ピクセルjpgから始めました。私はすぐにこれについてテッセラクトを試しましたが、プログラムはほとんど何も変換しませんでした。それからGIMPに入り、次のことを行いました。image> mode> grayscale image> scale image> 1191x2000 pixel filters> enhance> unsharp mask with values of radius = 6.8、amount = 2.69、threshold = 0次に、新しいjpgとして100%の品質で保存しました。

その後、Tesseractはすべてのテキストを.txtファイルに抽出することができました

Gimpはあなたの友達です。


11
+1私はあなたの手順に従いましたが、私は大きな改善を得ました。ありがとう
2012

1
また、入力をTIFFファイルに変換してTesseractにTIFFを与えると(Tesseractに変換を依頼するのではなく)、Tesseractがよりうまく機能するという印象もあります。ImageMagickが変換を行います。これは逸話的な印象ですが、慎重にテストしていないため、間違っている可能性があります。
DW

+1「アンシャープマスク」フィルターは本当に私の一日を作りました。私を助けたもう1つのステップ:「ファジー選択」ツールを使用して背景を選択し、Delを押してそれを引き締めます
Davide

tesseract認識の前にこの画像処理の問題に悩まされていますstackoverflow.com/questions/32473095/…ここで私を助けてくれませんか?
フセイン

いいえ。私はそれをより大きなサイズにしようとしました、そしてそれをグレースケールに設定することは私に良い結果を与えないようです。ため息:(このターゲットをチェックしてください:freesms4us.com/…
gumuruh

30

画像を読みやすくするための3つのポイント:1)可変の高さと幅で画像のサイズを変更します(画像の高さと幅で0.5と1と2を掛けます)。2)画像をグレースケールフォーマット(白黒)に変換します。3)ノイズピクセルを削除し、より明確にします(画像をフィルターします)。

以下のコードを参照してください:

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

入力画像
入力画像

出力画像 出力画像


はい、必要なパラメーターをResizeメソッドに渡す必要があります。これにより、サイズ変更、SetGrayscale、およびRemoveNoise操作が実行され、読みやすい出力画像が返されます。
Sathyaraj Palanisamy 2017

このアプローチを一連のファイルで試して、最初の結果と比較しました。一部の限られたケースでは、より良い結果が得られます。ほとんどの場合、出力テキストの品質がわずかに低下しました。したがって、それは普遍的な解決策のようには見えません。
ブライン

これは実際にはかなりうまくいきました。確かに、Tesseractから返される意味不明な量を取り除く画像前処理の出発点となります。
2018年

22

経験則として、私は通常、OpenCVライブラリを使用して次の画像前処理手法を適用します。

  1. 画像のサイズを変更します(DPIが300 dpi未満の画像を扱う場合にお勧めします)。

    img = cv2.resize(img, None, fx=1.2, fy=1.2, interpolation=cv2.INTER_CUBIC)
    
  2. 画像をグレースケールに変換する:

    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
  3. ノイズを除去するために膨張と収縮を適用します(データセットによってはカーネルサイズで遊んでもかまいません)。

    kernel = np.ones((1, 1), np.uint8)
    img = cv2.dilate(img, kernel, iterations=1)
    img = cv2.erode(img, kernel, iterations=1)
    
  4. ぼかしの適用。これは次のいずれかの行を使用して実行できます(ただし、それぞれに長所と短所がありますが、中央値のぼかしとバイラテラルフィルターは通常、ガウスぼかしよりも優れています)。

    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)
    

私は最近、Tesseractのかなり単純なガイドを書きましたが、それにより、最初のOCRスクリプトを記述して、ドキュメントで思っていたよりも不明確なときに経験したいくつかのハードルをクリアできるはずです。

それらをチェックアウトしたい場合に備えて、ここでリンクを共有します。


なぜ画像をグレースケールに変換するのですか?より具体的には、画像検出プロセスで見てきたように、画像は最初にグレースケールに変換され、次にsobel-> MSER-> SWTに変換されます。詳しく説明してもらえますか?私はIP分野の新人です。
OnePunchMan

私の理解に関しては、それはアルゴリズムに依存し、一部はまったく変換する必要がないかもしれません。ピクセルは、デジタルで保存されたいくつかのカラー値(RGB、赤、緑、青の場合)と考えてください。ピクセルが白黒スケールに変換されると、アルゴリズムは3ではなく2次元でのみ動作する必要があります。これにより、ピクセルでアルゴリズムを1つずつ実行する場合の速度が明らかに向上します。さらに、グレースケールに変換すると、ノイズを除去して画像のエッジを検出する方が簡単だと言う人もいます。
bkaankuguoglu

ご回答ありがとうございます。そして、あなたのブログについて、ローマ字以外のスクリプト用にTESSERACTを使用してSCRATCHからOCRを構築する方法に1つ書いてください。私はどこでも検索していますが、利用できるものはすべて明確ではありません。
OnePunchMan

16

これは少し前のことですが、それでも役に立つかもしれません。

私の経験では、tesseractに渡す前にメモリ内で画像のサイズを変更すると役立つ場合があることを示しています。

さまざまな補間モードを試してください。https://stackoverflow.com/a/4756906/146003の投稿は私を大いに助けてくれました。


15

この方法で私にとって非常に有益だったのは、Capture2Textプロジェクトのソースコードです。 http://sourceforge.net/projects/capture2text/files/Capture2Text/

ところで:そのような骨の折れるアルゴリズムを共有するための作者への称賛。

Capture2Text \ SourceCode \ leptonica_util \ leptonica_util.cファイルに特に注意してください。これが、このユーティリティの画像前処理の本質です。

バイナリを実行する場合は、Capture2Text \ Output \フォルダーでプロセスの前後の画像変換を確認できます。

PSで言及されたソリューションは、OCRにはTesseractを、前処理にはLeptonicaを使用しています。


1
Capture2Textツールをご利用いただきありがとうございます。それは私のプロジェクトのすべてのOCR問題を完全に解決します!
LêクアンDuyと

12

上記のSathyarajのコードのJavaバージョン:

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

ビットマップのクラスは何ですか?Javaにはビットマップがありません(ネイティブのAndroidにあります)。
私たちはボルグ

このメソッドは例外を通過します。原因:java.lang.IllegalArgumentException:yは<bitmap.height()でなければなりません
Nativ

9

Tesseractのドキュメントには、OCRの品質を改善する方法に関するいくつかの優れた詳細が含まれています画像処理ステップを介します。

ある程度まで、Tesseractはそれらを自動的に適用します。Tesseractに検査用の中間画像を書き込むように指示することもできます。つまり、内部画像処理がどのように機能するかを確認します(tessedit_write_images上記の参照で検索します)。

さらに重要なことに、Tesseract 4 の新しいニューラルネットワークシステムは、一般的に、特にノイズのある画像に対して、はるかに優れたOCR結果をもたらします。--oem 1たとえば、次のように有効にします。

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

(この例ではドイツ語を選択しています)

したがって、カスタムの前処理画像処理ステップを適用する前に、新しいTesseract LSTMモードでどのくらい遠くまで到達できるかを最初にテストすることは理にかなっています。


6

照明が画像全体で不均一である場合、適応しきい値処理は重要です。GraphicsMagicを使用した前処理については、この投稿で説明しています:https : //groups.google.com/forum/#!topic / tesseract-ocr / jONGSChLRv4

GraphicsMagicには、線形時間適応しきい値の-lat機能もあります。

OpenCVを使用したしきい値の別の方法については、こちらをご覧ください:http : //docs.opencv.org/trunk/doc/py_tutorials/py_imgproc/py_thresholding/py_thresholding.html


2
OpenCVリンクが変更されました。OpenCVドキュメントでは、 OpenCV-Pythonチュートリアル> OpenCVでの画像処理>画像のしきい値処理
richk

2

非常に小さなテキストではない画像から良い結果を得るためにこれらを行いました。

  1. 元の画像にぼかしを適用します。
  2. 適応しきい値を適用します。
  3. シャープ効果を適用します。

それでも良い結果が得られない場合は、画像を150%または200%にスケーリングします。


2

任意のOCRエンジンを使用して画像ドキュメントからテキストを読み取るには、精度を高めるために多くの問題があります。すべてのケースに対する解決策はありませんが、OCRの結果を改善するために考慮すべき点をいくつか紹介します。

1)画質の低下、背景領域の不要な要素/ブロブによるノイズの存在。これには、ガウスフィルターまたは通常の中央値フィルターメソッドを使用して簡単に実行できるノイズ除去などのいくつかの前処理操作が必要です。これらはOpenCVでも利用できます。

2)画像の向きが間違っている:向きが間違っているため、OCRエンジンは画像内の行と単語を正しくセグメント化できず、最悪の精度になります。

3)行の存在:単語または行のセグメンテーションを実行している間、OCRエンジンは時々単語と行をマージして、間違ったコンテンツを処理し、それによって間違った結果を出そうとします。他の問題もありますが、これらは基本的な問題です。

このポストOCRアプリケーションは、画像の前処理とOCR結果の後処理を適用してOCRの精度を高めることができる例です。


1

テキスト認識は、高品質の出力を生成するためのさまざまな要因に依存しています。OCR出力は、入力画像の品質に大きく依存します。これが、すべてのOCRエンジンが入力画像の品質とそのサイズに関するガイドラインを提供する理由です。これらのガイドラインは、OCRエンジンが正確な結果を生成するのに役立ちます。

Pythonでの画像処理に関する詳細な記事を書きました。詳細については、以下のリンクをたどってください。これらのプロセスを実装するためのpythonソースコードも追加しました。

このトピックを改善するための提案またはより良いアイデアがある場合は、コメントを書き込んでください。

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


2
ブログの概要として、ここに回答を追加してください。そのため、リンクが切れたとしても、答えは役に立たなくなります。
Nithin

0

ノイズ低減を行ってからしきい値を適用できますが、-psmと--oemの値を変更することで、OCRの構成を試すことができます

試してください:--psm 5 --oem 2

詳細については、次のリンクもご覧ください。 ください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.