【问题标题】:image processing to improve tesseract OCR accuracy图像处理以提高 tesseract OCR 准确性
【发布时间】:2012-03-17 19:27:54
【问题描述】:

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

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

对于图像处理新手有什么一般性提示吗?

【问题讨论】:

    标签: image-processing ocr tesseract


    【解决方案1】:
    1. 修复 DPI(如果需要)最低 300 DPI
    2. 修正文本大小(例如 12 pt 应该没问题)
    3. 尝试修复文本行(去歪斜和扭曲文本)
    4. 尝试修复图像的照明(例如,图像没有暗部)
    5. 二值化和去噪图像

    没有适合所有情况的通用命令行(有时您需要模糊和锐化图像)。不过你可以试试TEXTCLEANER from Fred's ImageMagick Scripts

    如果你不是命令行爱好者,也许你可以尝试使用开源scantailor.sourceforge.net或商业bookrestorer

    【讨论】:

    【解决方案2】:

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

    我从一个彩色的 RGB 445x747 像素 jpg 开始。 我立即对此进行了 tesseract 尝试,该程序几乎没有转换任何内容。 然后我进入 GIMP 并执行以下操作。

    • 图像 > 模式 > 灰度
    • 图像 > 缩放图像 > 1191x2000 像素
    • 过滤器 > 增强 > 不锐化蒙版,值为
      半径 = 6.8,数量 = 2.69,阈值 = 0

    然后我以 100% 的质量另存为新的 jpg。

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

    Gimp 是你的朋友。

    【讨论】:

    • +1 我按照您的步骤进行操作,取得了很大的进步。谢谢
    • 我也有这样的印象,如果将输入转换为 TIFF 文件并为 Tesseract 提供 TIFF(而不是要求 Tesseract 为您进行转换),Tesseract 的效果会更好。 ImageMagick 可以为您进行转换。这是我的轶事印象,但我没有仔细测试过,所以可能是错误的。
    • +1 “锐化蒙版”滤镜真的让我很开心。另一个帮助我的步骤:使用“模糊选择”工具选择背景,然后按 Del 将其变白
    • 我在 tesseract 识别之前被困在这个图像处理问题上stackoverflow.com/questions/32473095/…你能帮我吗?
    • 不。我试图让它更大的尺寸,并将其设置为灰度似乎没有给我积极的结果。叹息:(检查这个目标:freesms4us.com/…
    【解决方案3】:

    根据经验,我通常使用 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 领域的新手。
    • 据我了解,这取决于算法,有些可能根本不需要转换。将像素视为以数字方式存储的一些颜色值(在 RGB、红色、绿色和蓝色的情况下)。当一个像素转换为黑白比例时,您的算法只需要在 2 维而不是 3 维上工作。这在逐个像素上运行算法时具有明显的速度优势。此外,也有人会说,将图片转为灰度后,更容易去除噪点和检测边缘。
    • 感谢您的回复。关于你的博客,你能否写一篇关于如何使用 TESSERACT 从零开始构建 OCR 用于非罗马脚本的文章。我到处搜索,所有可用的都不清楚。
    【解决方案4】:

    提高图片可读性的三点:

    1. 使用可变高度和宽度调整图像大小(将图像高度和宽度乘以 0.5 和 1 和 2)。

    2. 将图像转换为灰度格式(黑白)。

    3. 去除噪声像素,使图像更清晰(过滤图像)。

    参考以下代码:

    调整大小

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

    设置灰度

    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();
        
                }
    
    

    去除噪音

    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 操作,然后返回具有更好可读性的输出图像。
    • 在一组文件上尝试了这种方法,并与初始结果进行了比较。在某些有限的情况下,它会提供更好的结果,主要是输出文本质量略有下降。所以,它看起来不像是一个通用的解决方案。
    • 这对我来说实际上效果很好。当然,它为图像预处理提供了一个起点,可以消除从 Tesseract 返回的大量乱码。
    【解决方案5】:

    这有点早了,但它仍然可能有用。

    我的经验表明,在将图像传递给 tesseract 之前调整内存中的图像大小有时会有所帮助。

    尝试不同的插值模式。 https://stackoverflow.com/a/4756906/146003 的帖子对我帮助很大。

    【讨论】:

      【解决方案6】:

      Capture2Text 项目的源代码对我来说非常有帮助。 http://sourceforge.net/projects/capture2text/files/Capture2Text/.

      顺便说一句:感谢它的作者分享了如此艰苦的算法。

      特别注意文件 Capture2Text\SourceCode\leptonica_util\leptonica_util.c - 这是该实用程序图像预处理的精髓。

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

      附:提到的解决方案使用 Tesseract 进行 OCR 和 Leptonica 进行预处理。

      【讨论】:

      • 感谢 Capture2Text 工具。它完美解决了我项目中的所有 OCR 问题!
      【解决方案7】:

      上述 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 中原生)。
      • 该方法通过异常:Caused by: java.lang.IllegalArgumentException: y must be
      【解决方案8】:

      Tesseract 文档包含一些关于 how to improve the OCR quality 通过图像处理步骤的详细信息。

      在某种程度上,Tesseract 会自动应用它们。也可以告诉 Tesseract 编写一个中间图像进行检查,即检查内部图像处理的工作情况(在上述参考中搜索tessedit_write_images)。

      更重要的是,Tesseract 4 中的 new neural network system 产生了更好的 OCR 结果——一般来说,尤其是对于有一些噪点的图像。它通过--oem 1 启用,例如如:

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

      (本例选择德语)

      因此,在应用一些自定义预处理图像处理步骤之前,首先测试您使用新的 Tesseract LSTM 模式能走多远是有意义的。

      【讨论】:

        【解决方案9】:

        如果整个图像的光照不均匀,自适应阈值就很重要。 这篇文章中提到了我使用 GraphicsMagic 进行的预处理: https://groups.google.com/forum/#!topic/tesseract-ocr/jONGSChLRv4

        GraphicsMagic 还具有线性时间自适应阈值的 -lat 功能,我将很快尝试。

        这里描述了另一种使用 OpenCV 进行阈值处理的方法: https://docs.opencv.org/4.x/d7/d4d/tutorial_py_thresholding.html

        【讨论】:

          【解决方案10】:

          我这样做是为了从文本不是很小的图像中获得良好的效果。

          1. 对原始图像应用模糊。
          2. 应用自适应阈值。
          3. 应用锐化效果。

          如果仍然没有得到好的结果,请将图像缩放到 150% 或 200%。

          【讨论】:

            【解决方案11】:

            为了获得良好的准确性,使用任何 OCR 引擎从图像文档中读取文本存在许多问题。所有情况都没有固定的解决方案,但这里有一些应该考虑的事情来改善 OCR 结果。

            1) 由于图像质量差/背景区域中存在不需要的元素/斑点而存在噪点。这需要一些预处理操作,例如可以使用高斯滤波器或正常中值滤波器方法轻松完成的噪声去除。这些在 OpenCV 中也可用。

            2) 图像方向错误:由于方向错误,OCR 引擎无法正确分割图像中的线条和文字,导致准确度最差。

            3) 行的存在:在进行分词或分行时,OCR 引擎有时也会尝试将词和行合并在一起,从而处理错误的内容,从而给出错误的结果。还有其他问题,但这些是基本问题。

            这篇帖子OCR application 是一个示例案例,可以对 OCR 结果应用一些图像预处理和后处理以获得更好的 OCR 精度。

            【讨论】:

              【解决方案12】:

              文本识别取决于多种因素来产生高质量的输出。 OCR 输出高度依赖于输入图像的质量。这就是为什么每个 OCR 引擎都提供有关输入图像质量及其大小的指南。这些指南有助于 OCR 引擎生成准确的结果。

              我写了一篇关于python图像处理的详细文章。请点击以下链接以获取更多说明。还添加了python源代码来实现这些过程。

              如果您对此主题有任何建议或更好的想法以改进它,请写评论。

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

              【讨论】:

              • 请在此处添加答案作为您博客的摘要。这样即使链接失效,答案也不会变得无用。
              【解决方案13】:

              您可以进行降噪然后应用阈值,但是您可以通过更改 --psm 和 --oem 值来玩弄 OCR 的配置

              尝试: --psm 5 --oem 2

              您还可以查看以下链接了解更多详情 here

              【讨论】:

                【解决方案14】:

                到目前为止,我已经玩了很多 tesseract 3.x、4.x 和 5.0.0。 tesseract 4.x 和 5.x 似乎产生了完全相同的精度。

                有时,我使用旧引擎(使用 --oem 0)获得更好的结果,有时我使用 LTSM 引擎 --oem 1 获得更好的结果。 一般来说,我在使用 LTSM 引擎的放大图像上得到最好的结果。后者与我早期的引擎(ABBYY CLI OCR 11 for Linux)相当。

                当然,traineddata 需要从 github 下载,因为大多数 linux 发行版只会提供快速版本。 可以使用以下命令在https://github.com/tesseract-ocr/tessdata 下载适用于旧版和 LTSM 引擎的训练数据。不要忘记下载 OSD 训练数据。

                curl -L https://github.com/tesseract-ocr/tessdata/blob/main/eng.traineddata?raw=true -o /usr/share/tesseract/tessdata/eng.traineddata
                curl -L https://github.com/tesseract-ocr/tessdata/blob/main/eng.traineddata?raw=true -o /usr/share/tesseract/tessdata/osd.traineddata
                

                我最终使用 ImageMagick 作为我的图像预处理器,因为它很方便并且可以轻松运行脚本。您可以使用 yum install ImageMagickapt install imagemagick 安装它,具体取决于您的发行版风格。

                所以这是我的 oneliner 预处理器,它适合我提供给 OCR 的大部分内容:

                convert my_document.jpg -units PixelsPerInch -respect-parenthesis \( -compress LZW -resample 300 -bordercolor black -border 1 -trim +repage -fill white -draw "color 0,0 floodfill" -alpha off -shave 1x1 \) \( -bordercolor black -border 2 -fill white -draw "color 0,0 floodfill" -alpha off -shave 0x1 -deskew 40 +repage \) -antialias -sharpen 0x3 preprocessed_my_document.tiff
                

                基本上我们:

                • 使用 TIFF 格式,因为 tesseract 比 JPG 更喜欢它(与解压缩器相关,谁知道)
                • 使用无损 LZW TIFF 压缩
                • 将图像重新采样为 300dpi
                • 使用一些黑魔法去除不需要的颜色
                • 如果可以检测到旋转,请尝试旋转页面
                • 图像抗锯齿
                • 锐化文本

                后一个图像可以通过以下方式输入到 tesseract:

                tesseract -l eng preprocessed_my_document.tiff - --oem 1 -psm 1
                

                顺便说一句,几年前我编写了“穷人的 OCR 服务器”,它检查给定目录中的更改文件,并对所有尚未 OCR 的文件启动 OCR 操作。 pmocr 与 tesseract 3.x-5.x 和 abbyyocr11 兼容。 见pmocr project on github

                【讨论】:

                  猜你喜欢
                  • 2021-06-30
                  • 2017-04-03
                  • 2019-11-02
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2018-07-03
                  相关资源
                  最近更新 更多