共计 6427 个字符,预计需要花费 17 分钟才能阅读完成。
作者:悠悠做神仙
起源:恒生 LIGHT 云社区
说到文字辨认,目前除了用一些现成的 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% 以上吧:
正文完