天天看點

Java如何使用Tessdata做OCR圖檔文字說明,看這篇就夠了~

作者:悠悠做神仙​

說到文字識别,目前除了用一些現成的api,大概就是 ​

​tessdata​

​​、​

​canvas​

​​或者 ​

​ocrad​

​等。

1、百度接口用過(可以自己去百度開發者申請,免費的),識别率吧,還可以,但也不是百分百的,但是次數使用有限制,雖然也是夠用,但是被限制總是害怕超過不讓用。 2、​

​canvas​

​​的話是需要對圖檔做具體的處理,涉及到圖檔的翻轉、置灰、文字間隔的設定等等,成功率很高,但是公司産品驗證碼是各式各樣的,沒辦法用這種方法處理,是以暫時放棄了。 3、​

​ocrad​

​​這個目前用過其.js版本,識别率還是比較低的,具體使用後面會再寫一篇文章介紹一下的。 雖然,網上對于 ​

​Tessdata​

​的技術介紹文章一搜一大片,但是其實小仙真正用起來的時候,還是費了點周折的。:fendou:

**思路:**截全圖–截取元素圖檔–處理–識别–輸出

​注意:圖檔截取格式統一為.jpg,用png會出問題。​

1、添加項目依賴

在項目的pom.xml檔案中,添加以下依賴

<!--<tess4j圖檔識别>-->
<dependency>
  <groupId>net.java.dev.jna</groupId>
  <artifactId>jna</artifactId>
  <version>4.1.0</version>
</dependency>
<dependency>
  <groupId>net.sourceforge.tess4j</groupId>
  <artifactId>tess4j</artifactId>
  <version>2.0.1</version>
  <exclusions>
    <exclusion>
      <groupId>com.sun.jna</groupId>
      <artifactId>jna</artifactId>
    </exclusion>
  </exclusions>
</dependency>      

2、從全圖中截取元素圖檔

// 元素截圖

public static String[] elementscreenShot(WebElement element )
    throws Exception {
  WrapsDriver wrapsDriver = (WrapsDriver) element;
  long time = System.currentTimeMillis();

  // 截圖整個頁面
  File screen = ((TakesScreenshot) wrapsDriver.getWrappedDriver())
      .getScreenshotAs(OutputType.FILE);
  BufferedImage img = ImageIO.read(screen);
  // 獲得元素的高度和寬度
  int width = element.getSize().getWidth();
  int height = element.getSize().getHeight();
  // 建立一個矩形使用上面的高度,和寬度
  Rectangle rect = new Rectangle(width, height);
  // 得到元素的坐标
  Point p = element.getLocation();
  BufferedImage dest = img.getSubimage(p.getX(), p.getY(),
      (int) rect.getWidth(), (int) rect.getHeight());
  // 存為png格式
  ImageIO.write(dest, "png", screen);
  DateFormat dateFormat = new SimpleDateFormat("yyyyMMddhhmmss");
  FileSystemView fsv = FileSystemView.getFileSystemView();
  File com = fsv.getHomeDirectory(); // 這便是讀取桌面路徑的方法了
  String url = com.getPath() + "/test";
  File location = new File(url);
  if (!location.exists()) {
    location.mkdirs();
  }

  String imgPath = location.getAbsolutePath() + File.separator + "pic_"
      + time + ".jpg";
  String cleanPath = location.getAbsolutePath();
  //存了原圖檔和清楚後圖檔的位址
  String[] imgpath = { imgPath, cleanPath };
  File targetFile = new File(imgPath);
  try {
    FileUtils.copyFile(screen, targetFile);
  } catch (IOException e1) {
    e1.printStackTrace();
  }
  //元素圖檔路徑
  return imgpath;
}      

3、對截取圖檔進行處理:灰階化、二值化、去除幹擾線等

以下是圖像處理的類,其中對于去除幹擾線的操作還是慎用,可能會把文字也剔除掉。

public class CleanElementImage {
    /**
     *
     * @param sfile
     *            需要去噪的圖像
     * @param destDir
     *            去噪後的圖像儲存位址
     * @throws IOException
     */
    public static void handlImage(File sfile, String destDir)  throws IOException {
        File destF = new File(destDir);
        if (!destF.exists())
        {
            destF.mkdirs();
        }

        BufferedImage bufferedImage = ImageIO.read(sfile);
        int h = bufferedImage.getHeight();
        int w = bufferedImage.getWidth();

        // 灰階化
        int[][] gray = new int[w][h];
        for (int x = 0; x < w; x++)
        {
            for (int y = 0; y < h; y++)
            {
                int argb = bufferedImage.getRGB(x, y);
                // 圖像加亮(調整亮度識别率非常高)
                int r = (int) (((argb >> 16) & 0xFF) * 1.1 + 30);
                int g = (int) (((argb >> 8) & 0xFF) * 1.1 + 30);
                int b = (int) (((argb >> 0) & 0xFF) * 1.1 + 30);
                if (r >= 255)
                {
                    r = 255;
                }
                if (g >= 255)
                {
                    g = 255;
                }
                if (b >= 255)
                {
                    b = 255;
                }
                gray[x][y] = (int) Math
                        .pow((Math.pow(r, 2.2) * 0.2973 + Math.pow(g, 2.2)
                                * 0.6274 + Math.pow(b, 2.2) * 0.0753), 1 / 2.2);
            }
        }

        // 二值化
        int threshold = ostu(gray, w, h);
        BufferedImage binaryBufferedImage = new BufferedImage(w, h, BufferedImage.TYPE_BYTE_BINARY);
        for (int x = 0; x < w; x++)
        {
            for (int y = 0; y < h; y++)
            {
                if (gray[x][y] > threshold)
            {
                gray[x][y] |= 0x00FFFF;
            } else
            {
                gray[x][y] &= 0xFF0000;
            }
            binaryBufferedImage.setRGB(x, y, gray[x][y]);
        }
    }

        //去除幹擾線條
//        for(int y = 1; y < h-1; y++){
//            for(int x = 1; x < w-1; x++){
//                boolean flag = false ;
//                if(isBlack(binaryBufferedImage.getRGB(x, y))){
//                    //左右均為空時,去掉此點
//                    if(isWhite(binaryBufferedImage.getRGB(x-1, y)) && isWhite(binaryBufferedImage.getRGB(x+1, y))){
//                        flag = true;
//                    }
//                    //上下均為空時,去掉此點
//                    if(isWhite(binaryBufferedImage.getRGB(x, y+1)) && isWhite(binaryBufferedImage.getRGB(x, y-1))){
//                        flag = true;
//                    }
//                    //斜上下為空時,去掉此點
//                    if(isWhite(binaryBufferedImage.getRGB(x-1, y+1)) && isWhite(binaryBufferedImage.getRGB(x+1, y-1))){
//                        flag = true;
//                    }
//                    if(isWhite(binaryBufferedImage.getRGB(x+1, y+1)) && isWhite(binaryBufferedImage.getRGB(x-1, y-1))){
//                        flag = true;
//                    }
//                    if(flag){
//                        binaryBufferedImage.setRGB(x,y,-1);
//                    }
//                }
//            }
//        }
    ImageIO.write(binaryBufferedImage, "jpg", new File(destDir, sfile
            .getName()));

}

public static boolean isBlack(int colorInt)
{
    Color color = new Color(colorInt);
    if (color.getRed() + color.getGreen() + color.getBlue() <= 300)
    {
        return true;
    }
    return false;
}

public static boolean isWhite(int colorInt)
{
    Color color = new Color(colorInt);
    if (color.getRed() + color.getGreen() + color.getBlue() > 300)
    {
        return true;
    }
    return false;
}

public static int isBlackOrWhite(int colorInt)
{
    if (getColorBright(colorInt) < 30 || getColorBright(colorInt) > 730)
    {
        return 1;
    }
    return 0;
}

public static int getColorBright(int colorInt)
{
    Color color = new Color(colorInt);
    return color.getRed() + color.getGreen() + color.getBlue();
}

public static int ostu(int[][] gray, int w, int h)
{
    int[] histData = new int[w * h];
    // Calculate histogram
    for (int x = 0; x < w; x++)
    {
        for (int y = 0; y < h; y++)
        {
            int red = 0xFF & gray[x][y];
            histData[red]++;
        }
    }

    // Total number of pixels
    int total = w * h;

    float sum = 0;
    for (int t = 0; t < 256; t++){
        sum += t * histData[t];}

    float sumB = 0;
    int wB = 0;
    int wF = 0;

    float varMax = 0;
    int threshold = 0;

    for (int t = 0; t < 256; t++)
    {
        wB += histData[t]; // Weight Background
        if (wB == 0) {
            continue;
        }

        wF = total - wB; // Weight Foreground
        if (wF == 0) {
            break;
        }

        sumB += (float) (t * histData[t]);

        float mB = sumB / wB; // Mean Background
        float mF = (sum - sumB) / wF; // Mean Foreground

        // Calculate Between Class Variance
        float varBetween = (float) wB * (float) wF * (mB - mF) * (mB - mF);

        // Check if new maximum found
        if (varBetween > varMax)
        {
            varMax = varBetween;
            threshold = t;
        }
    }

    return threshold;
}
}      

4、準備識别的語言包

預設是英文(識别字母和數字),如果要識别中文(數字 + 中文),需要制定語言包。 語言包可以指定一個路徑,有就可以了。 ​​源碼下載下傳位址​​​ 可以下載下傳源碼,然後到下面這個路徑找到語言包,把語言包放到一個路徑: 例如:​

​XXX/tessdata/​

​下面。

tesseract.js-master.zip\tesseract.js-master\tests\assets\traineddata      
Java如何使用Tessdata做OCR圖檔文字說明,看這篇就夠了~

5、對圖檔進行識别

/**
* 圖檔識别
* @author wangy
* @date 2019-08-26
* @param parameter
*/
public static  String  ocrResult(WebElement element ) throws Exception {

  FileSystemView fsv = FileSystemView.getFileSystemView();
  File com=fsv.getHomeDirectory();    //這便是讀取桌面路徑的方法了
  String url = "";
  String os = System.getProperty("os.name");
  //識别系統,找不同的語言包路徑
  if (os.indexOf("Windows") == -1) {
    url = "/opt/google/";
  } else {
    url = com.getPath();
  }
  //擷取元素截圖的路徑
        String path[]=Screenshot.elementscreenShot(element);
        //擷取未處理的截圖路徑
        String imgpath=path[0];
  String result = null;
  File imageFile = new File(imgpath);
  //要對圖檔處理
        CleanElementImage.handlImage(imageFile,path[1]);
  ITesseract instance = new Tesseract();
  //讀取語言包的路徑位址
  instance.setDatapath(url + File.separator + "test" + File.separator
        + "tessdata");
  // 預設是英文(識别字母和數字),如果要識别中文(數字 + 中文),需要制定語言包,這裡是數字,是以沒用語言包
        // instance.setLanguage("chi_sim");
        //為了防止沒截完圖檔就識别,做了一個簡單的循環
  try{
    String ocrResult=instance.doOCR(imageFile);
    if(imageFile.exists()&&ocrResult!=""){
      result=ocrResult;
    }else {
      while(true){
        Thread.sleep(1000);
        if(imageFile.exists()&&ocrResult!=""){
          result=ocrResult;
          break;
        }
      }
    }

  }catch(TesseractException e){
    System.out.println(e.getMessage());
  }
  return result;
}      

6、成果