2012-02-28 80 views
99

我一直在使用tesseract将文档转换为文本。文档质量非常好,我正在寻找关于什么样的图像处理可以改善结果的提示。我注意到,高度像素化的文本(例如由传真机生成的文本)对于tesseract处理来说特别困难 - 大概所有那些锯齿状的边缘与字符混淆了形状识别算法。改善tesseract OCR精度的图像处理

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

对于在图像处理方面是新手的人的任何一般提示?

回答

65
  1. 修复DPI(如果需要)300 DPI是最小
  2. 修复文字大小(例如12磅应该确定)
  3. 尝试修复文本行(抗扭斜和dewarp文本)
  4. 尝试修复图像的照明(图像如无暗部分
  5. 二值化和去噪图像

有能够适合于所有情况(有时你需要b无通用的命令行lur和锐化图像)。但你可以试试TEXTCLEANER from Fred's ImageMagick Scripts

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

+5

而且还有关于如何做到这一点说明指南:https://code.google.com/p/tesseract-ocr/wiki/ImproveQuality – iljau 2014-01-26 13:54:28

+2

注,链接脚本似乎仅限Linux。 – 2014-09-24 16:40:38

+0

@ZoranPavlovic你是对的。链接仅适用于Linux。 – Prashant 2015-07-17 07:19:47

56

我决不是一个OCR专家。但是我本周需要将文本转换为jpg格式。

我开始使用彩色RGB 445x747像素jpg。 我立即尝试了tesseract,并且该程序几乎没有任何转换。 然后我进入GIMP并做了以下工作。 图像>模式>灰度 图像>缩放图像> 1191x2000像素 过滤器>增强>半径为6.8,金额= 2.69,阈值= 0的锐化蒙版然后我保存为100%质量的新jpg。

正方体,然后才得以所有文本提取到一个.txt文件

GIMP是你的朋友。

+7

+1我按照你的步骤进行了改进。谢谢 – onof 2012-09-05 22:02:18

+1

我还有一个印象,如果您将输入转换为TIFF文件并将Tesseract设置为TIFF(而不是要求Tesseract为您进行转换),则Tesseract会更好。 ImageMagick可以为你做转换。这是我的轶事印象,但我没有仔细测试过,所以可能是错误的。 – 2013-11-18 02:31:12

+0

+1“不清晰的面具”过滤器真的让我的一天。另一个帮助我的步骤是:使用“模糊选择”工具选择背景,然后按下Del键将其消除 – Davide 2015-04-13 10:30:39

15

这是有点前,但它仍然可能是有用的。

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

尝试不同的插值模式。 https://stackoverflow.com/a/4756906/146003后帮助了我很多。

+1

如果我可能会问:为什么downvote? – Atmocreations 2014-09-28 07:15:37

13

这对我来说极其有用的是Capture2Text项目的源代码。 http://sourceforge.net/projects/capture2text/files/Capture2Text/

顺便说一句:荣誉是它的作者共享这样一个艰苦的算法。

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

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

P.S.上述解决方案使用Tesseract用于OCR和Leptonica进行预处理。

22

改善图像可读性的三点: 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; 
     } 

输入图像
INPUT IMAGE

输出图像 OUTPUT IMAGE

+0

它是整个***代码***? – Kiquenet 2017-04-06 22:11:58

+0

是的,我们必须将所需的参数传递给Resize方法,它会调整大小,SetGrayscale和RemoveNoise操作,然后返回具有更好可读性的输出图像。 – 2017-04-07 13:25:51

+0

在一组文件上尝试了这种方法,并与初始结果进行了比较。在一些有限的情况下,它会给出更好的结果,大多数情况下输出文本质量略有下降。所以,它看起来不是一个通用的解决方案。 – Bryn 2017-05-27 22:22:28

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中的图像处理>图像阈值处理](http://docs.opencv.org/master/d7/d4d/tutorial_py_thresholding.html) – richk 2016-10-06 08:50:22

12

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

您的Bitmap类是什么?在Java中找不到位图(它本身在Android中)。 – 2017-08-17 08:18:57

+0

此方法通过一个异常:引起:java.lang.IllegalArgumentException:y必须是 Nativ 2017-12-05 15:24:48

1

我做这些,以获得良好的效果出来它有没有非常小的图像文本。

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

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

3

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模式首先在多大程度上得到应用一些自定义的预处理的图像处理步骤之前, 。

(截至2017年年底,正方体4没有被释放的稳重又不失,但是开发版本可用)使用任何OCR引擎图像文件

2

阅读文本有很多的问题,以便获得良好的精度。对于所有情况没有固定的解决方案,但这里有几件事情应该考虑改进OCR结果。

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

2)图像的方向错误:由于错误的方向OCR引擎的失败段在图像这给最差精确度的线和字正确。线的

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

这篇文章OCR application是一个示例情况,其中一些图像预处理和OCR结果后处理可应用于获得更好的OCR准确性。