前言
在当前的文字识别技术应用中,除了采用现有的API服务之外,常见的解决方案包括利用Tessdata、Canvas或OCRAD等工具。以下是对几种技术的简要分析:
-
百度API的使用体验表明,虽然其识别率令人满意,但并非完美无误。此外,该服务对使用次数有所限制,尽管对于一般需求而言足够,但存在超额使用导致服务受限的风险。
-
Canvas技术要求对图像执行预处理步骤,如旋转、灰度化以及调整字符间距等,这些处理能显著提升识别成功率。然而,由于公司产品中的验证码类型多样,难以通过统一方法处理,因此该方案被暂时搁置。
-
OCRAD的JavaScript版本已进行尝试,但识别准确率相对较低。有关此工具的具体应用将在未来的文章中进一步探讨。
尽管互联网上关于Tessdata技术的介绍文章众多,但实际应用过程中仍面临一定挑战。操作流程概述如下:首先截取全屏图像,接着提取特定元素图片,然后进行图像预处理,最后执行文字识别并输出结果。
通过这一流程,可以有效地实现对图像中文字的准确识别和解读。
注意:图片截取格式统一为.jpg,用png会出问题。
Tessdata介绍
Tessdata是用于光学字符识别(OCR)的软件包,专为与Tesseract OCR引擎配合使用,提供必要的语言数据文件以支持文本识别的多种语言。它包含了预先训练好的模型,这些模型帮助Tesseract识别和转换不同语言的文本图像为可编辑和可搜索的文件格式。
在Tesseract中,Tessdata的作用不可小觑,它是让Tesseract能够识别特定语言文本的关键。Tessdata文件夹通常存放在Tesseract安装目录下,每个语言模型都以“.traineddata”为扩展名的文件形式存在。例如,英语的模型文件是“eng.traineddata”,中文简体是“chi_sim.traineddata”。这些文件包含了每种语言特定的字符集和相关的训练信息,这些信息使得Tesseract能够准确地将图像中的文本转换为文本文件。
Tessdata不仅提供了官方支持的语言包,还允许用户生成自定义的traineddata文件,从而训练出适应特定需求的OCR模型。例如,如果现有的语言包无法满足某些特殊场景的需求,用户可以自行收集数据并按照Tesseract的训练流程创建新的语言模型。这一过程可能包括图像预处理、地面实况标注、模型训练和测试等步骤。通过这种方式,Tesseract可以不断扩展其应用范围,适应更多样的应用场景。
总的来说,Tessdata是Tesseract OCR引擎的核心部分,提供了使Tesseract能够识别并处理多种语言文本的能力。通过合理地使用和配置Tessdata文件,用户可以显著提高文字识别的准确率和效率。同时,Tesseract社区持续对Tessdata进行更新和优化,以适应不断变化的应用需求和提升用户体验。
实践案例
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]);
}
}
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%以上吧: