说到文字识别,目前除了用一些现成的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
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、成果
这里简单放个对照,图片将就看一下效果,识别结果大概90%以上吧: