图像处理以提高tesseract OCR精度


145

我一直在使用tesseract将文档转换为文本。文档的质量千差万别,我正在寻找有关哪种图像处理可以改善结果的提示。我注意到,像素化程度很高的文本(例如,由传真机生成的文本)对于tesseract来说尤其难以处理-大概字符的所有那些锯齿状边缘都会混淆形状识别算法。

哪种图像处理技术可以提高准确性?我一直在使用高斯模糊对像素化的图像进行平滑处理,并且看到了一些小的改进,但是我希望有一种更具体的技术可以产生更好的结果。说一个调整为黑白图像的滤镜,它将平滑不规则的边缘,然后说一个滤镜,它将增加对比度以使字符更加清晰。

对图像处理新手有何一般提示?

Answers:


103
  1. 修复DPI(如果需要)最低300 DPI
  2. 固定文字大小(例如12磅应该可以)
  3. 尝试修复文本行(歪斜和变形文本)
  4. 尝试固定图像的照明度(例如,图像无暗部)
  5. 对图像进行二值化和去噪

没有适用于所有情况的通用命令行(有时您需要模糊和锐化图像)。但是您可以尝试使用Fred的ImageMagick Scripts尝试使用TEXTCLEANER

如果您不喜欢命令行,也许您可​​以尝试使用开源scantailor.sourceforge.net或商用Bookrestorer


6
并提供了有关如何执行此操作的插图指南:code.google.com/p/tesseract-ocr/wiki/ImproveQuality
iljau 2014年

2
注意,链接脚本似乎仅适用于linux。
Zoran Pavlovic

1
这是不正确的-这是bash脚本。如果您已经安装了bash和ImageMagick,它也将在Windows上运行。Bash可以作为其他有用的软件的一部分安装,例如gitmsys2 ...
user898678

6
@iljau自从移至github。Wiki页面位于:github.com/tesseract-ocr/tesseract/wiki/ImproveQuality
hometoast


73

我绝不是OCR专家。但是我这周需要将文本转换为jpg。

我从彩色RGB 445x747像素jpg开始。我立即对此尝试了tesseract,该程序几乎没有转换。然后,我进入GIMP并执行以下操作。图像>模式>灰度图像>比例图像> 1191x2000像素滤镜>增强>锐化蒙版,其半径值为6.8,数量= 2.69,阈值= 0然后我以100%的质量保存为新的jpg。

然后Tesseract能够将所有文本提取到.txt文件中

金普是你的朋友。


11
+1我按照您的步骤进行了操作,我得到了很大的进步。谢谢
onof

1
我还给人一种印象,如果将输入转换为TIFF文件并给Tesseract TIFF(而不是要求Tesseract为您进行转换),则Tesseract会更好地工作。ImageMagick可以为您完成转换。这是我的轶事印象,但是我没有仔细测试,因此可能是错误的。
DW

+1“不清晰的蒙版”过滤器确实让我很开心。帮了我大忙的另一步:使用“模糊选择”工具选择背景,然后按Del收紧背景
Davide

在进行tesseract识别之前,我一直陷在这个图像处理问题上stackoverflow.com/questions/32473095 / ...您能在这里帮助我吗?
侯赛因

不。我试图将其放大,然后将其设置为灰度,似乎没有给我带来积极的结果。叹:(检查这一目标:freesms4us.com/...
gumuruh

30

要提高图像的可读性,有三点: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方法,它将先进行resize,SetGrayscale和RemoveNoise操作,然后以更好的可读性返回输出图像。
Sathyaraj Palanisamy

在一组文件上尝试了这种方法,并与初始结果进行了比较。在某些有限的情况下,它可以提供更好的结果,大多数情况下输出文本的质量会略有下降。因此,它看起来不像是通用解决方案。
布林

这实际上对我来说很好。当然,它为图像预处理提供了一个起点,从而消除了您从Tesseract获得的胡言乱语。
ses

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,红色,绿色和蓝色的情况下。将像素转换为黑白比例时,您的算法只需要在2维而不是3维上工作。在逐个像素上运行算法时,这在速度上具有明显的优势。此外,有些人可能还会说,将图像转换为灰度时,可以更轻松地消除噪声并检测图像的边缘。
bkaankuguoglu

感谢您的答复。关于您的博客,您能否写一本关于如何使用TESSERACT对非罗马脚本进行刮擦来构建OCR的文章。我到处搜索,尚不清楚所有可用的内容。
OnePunchMan


15

用这种方式对我非常有用的是Capture2Text项目的源代码。 http://sourceforge.net/projects/capture2text/files/Capture2Text/

顺便说一句:对于作者分享如此艰苦的算法,我深表歉意。

要特别注意文件Capture2Text \ SourceCode \ leptonica_util \ leptonica_util.c-这是此实用程序进行图像预处理的本质。

如果要运行二进制文件,则可以在Capture2Text \ Output \文件夹中检查处理之前/之后的图像转换。

PS提到的解决方案将Tesseract用于OCR,将Leptonica用于预处理。


1
感谢您使用Capture2Text工具。它完美解决了我项目中的所有OCR问题!
黎光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;
}

您的Bitmap课程是什么?在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

你也可以看看下面的链接了解更多详情 点击这里

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.