私はtesseractを使用してドキュメントをテキストに変換しています。ドキュメントの品質は多岐にわたり、どのような画像処理が結果を改善するかについてのヒントを探しています。ファックスで生成されるなど、非常にピクセル化されたテキストは、テセラクトが処理するのが特に難しいことに気づきました-おそらく、文字に対するこれらのギザギザのエッジはすべて、形状認識アルゴリズムを混乱させます。
どのような画像処理技術が精度を改善しますか?ガウスぼかしを使用してピクセル化された画像を滑らかにし、いくつかの小さな改善を見てきましたが、より良い結果が得られるより具体的な手法があることを望んでいます。不規則なエッジを滑らかにする白黒画像に調整されたフィルターに続いて、コントラストを高めて文字をより明確にするフィルターを使用します。
画像処理の初心者向けの一般的なヒントはありますか?
すべての場合に適合する万能なコマンドラインはありません(画像をぼかすとシャープにする必要がある場合があります)。ただし、 FredのImageMagickスクリプトのTEXTCLEANER を試してみることができます。
コマンドラインのファンでない場合は、オープンソース scantailor.sourceforge.net または商用 bookrestorer を使用してみてください。
私は決してOCRの専門家ではありません。しかし、私は今週、jpgからテキストを変換する必要がありました。
色付きのRGB 445x747ピクセルjpgから始めました。私はすぐにこれについてtesseractを試みましたが、プログラムはほとんど何も変換しませんでした。それからGIMPに入り、次のことを行いました。 image> mode> grayscale image> scale image> 1191x2000ピクセルフィルター> enhance>半径= 6.8、量= 2.69、しきい値= 0の値を持つアンシャープマスク100%の品質で新しいjpgとして保存しました。
Tesseractは、すべてのテキストを.txtファイルに抽出することができました
Gimpはあなたの友達です。
画像の読みやすさを向上させるための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;
}
入力画像
出力画像
これはやや前ですが、まだ有用かもしれません。
私の経験では、tesseractに渡す前にメモリ内のイメージのサイズを変更すると役立つことがあります。
さまざまな補間モードを試してください。投稿 https://stackoverflow.com/a/4756906/1460 は私を大いに助けてくれました。
この方法で私にとって非常に役立ったのは、Capture2Textプロジェクトのソースコードです。 http://sourceforge.net/projects/capture2text/files/Capture2Text/ 。
ところで:そのような骨の折れるアルゴリズムを共有するための著者への称賛。
ファイルCapture2Text\SourceCode\leptonica_util\leptonica_util.cに特に注意してください。これがこのユーティリティの画像前処理の本質です。
バイナリを実行する場合、Capture2Text\Output \フォルダーでプロセスの前後に画像変換を確認できます。
追伸前述のソリューションでは、OCRにTesseractを使用し、前処理にLeptonicaを使用しています。
上記の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;
}
経験則として、通常、OpenCVライブラリを使用して次の画像前処理技術を適用します。
画像のサイズを変更します(DPIが300 dpi未満の画像を使用している場合に推奨されます)。
img = cv2.resize(img, None, fx=1.2, fy=1.2, interpolation=cv2.INTER_CUBIC)
画像をグレースケールに変換する:
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ノイズを除去するために膨張と収縮を適用します(データセットに応じてカーネルサイズを調整できます)。
kernel = np.ones((1, 1), np.uint8)
img = cv2.dilate(img, kernel, iterations=1)
img = cv2.erode(img, kernel, iterations=1)
ぼかしの適用は、次のいずれかの行を使用して実行できます(ただし、それぞれに長所と短所がありますが、中央値ぼかしとバイラテラルフィルターは通常、ガウスぼかしよりも優れたパフォーマンスを発揮します)。
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スクリプトを書いて、ドキュメントで思っていたよりも物事がはっきりしなかったときに経験したいくつかのハードルをクリアできるはずです。
それらをチェックアウトしたい場合は、ここでリンクを共有します:
照明が画像全体で不均一な場合、適応しきい値処理は重要です。 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
Tesseractのドキュメントには、 OCR品質を改善する方法 の画像処理ステップに関する詳細が含まれています。
Tesseractはある程度まで自動的にそれらを適用します。 Tesseractに検査用の中間画像を書き込むように指示することもできます。つまり、内部画像処理がどのように機能するかを確認します(上記のリファレンスでtessedit_write_images
を検索します)。
さらに重要なことは、Tesseract 4の 新しいニューラルネットワークシステム は、一般的に、特にノイズのある画像の場合、はるかに優れたOCR結果をもたらします。 --oem 1
で有効になります。次のように:
$ tesseract --oem 1 -l deu page.png result pdf
(この例はドイツ語を選択します)
したがって、カスタムの前処理画像処理ステップを適用する前に、最初に新しいTesseract LSTMモードを使用してどこまで到達するかをテストすることは理にかなっています。
(2017年後半の時点で、Tesseract 4はまだ安定版としてリリースされていませんが、開発版は使用可能です)
OCRエンジンを使用して画像ドキュメントからテキストを読み取るには、精度を高めるために多くの問題があります。すべてのケースに対する解決策はありませんが、OCRの結果を改善するために考慮すべきいくつかの事項があります。
1)背景領域の低画質/不要な要素/ブロブによるノイズの存在。これには、ガウスフィルターまたは通常のメディアンフィルター手法を使用して簡単に実行できるノイズ除去などの前処理操作が必要です。これらはOpenCVでも利用可能です。
2)画像の方向が間違っている:方向が間違っているため、OCRエンジンは画像内の行と単語を正しくセグメント化できず、最悪の精度が得られます。
3)行の存在:Wordまたは行セグメンテーションの実行中に、OCRエンジンは単語と行を一緒にマージしようとすることもあります。他の問題もありますが、これらは基本的な問題です。
この投稿 OCRアプリケーション は、OCRの結果に対する画像の前処理および後処理を適用して、OCRの精度を向上させることができる例です。
これらは、非常に小さなテキストのない画像から良い結果を得るために行いました。
それでも良い結果が得られない場合は、画像を150%または200%にスケーリングします。
テキスト認識は、さまざまな要因に依存して、高品質の出力を生成します。 OCR出力は、入力画像の品質に大きく依存します。これが、すべてのOCRエンジンが入力画像の品質とそのサイズに関するガイドラインを提供する理由です。これらのガイドラインは、OCRエンジンが正確な結果を生成するのに役立ちます。
Pythonでの画像処理に関する詳細な記事を書きました。詳細については、以下のリンクを参照してください。これらのプロセスを実装するためのpythonソースコードも追加されました。
このトピックを改善するための提案やより良いアイデアがある場合は、コメントを書いてください。
https://medium.com/cashify-engineering/improve-accuracy-of-ocr-using-image-preprocessing-8df29ec3a0