今天想着把以前做过的一个Android的文字检测识别应用好好的回顾一下,因为以前写java程序,目的就是能用就行,不会仔细看每一个部分代码,也不会记他们的用法,不回会去查API,借鉴别人的例程,用过就忘了,现在想着要改变,于是就回顾了一番。
之前检测用到的是Tesseract_OCR,之所以能在Android的上运行,是因为黑暗伯爵大神已经把tess-two(为android写的tesseract-tools)编译好了,然后我直接用的。我还是小白,完全不懂编译那些,如果让我自己搞.... 反正最后编译成so文件(这个是linux平台下的动态链接库,可以类比dll),然后我用编译好的so文件,以及jar包导入到工程,照葫芦画瓢把文字识别部分和自己之前看论文写的文字检测部分合到一起,然后百度了怎么调用摄像头,然后写了一个摄像头拍照,然后检测文字所在区域,处理,然后识别的应用。以后有时间会详细的重新把应用梳理一遍,然后记录,当然这不是今天的重点。如果对Android上如何做OCR感兴趣,可以参考 这里 。
回到正题,前几天同学问我以前做的文字识别是咋搞得,他想用用,我于是就想把程序移回来到java上,结果我百度才发现原来还有另外一种思路,那就是执行exe进程。先在pc上安装好tesseract_orc,然后jvm运行命令行跑识别程序,完成识别后结果写入txt,最后读取txt把内容返回到java程序中。这么想想也行,于是便试了试。(PS:我是后来才发现也有Java的API tess4j,参考 这里)
然后就是讲自己具体怎么实现:
工程也就两部分,一部分是GUI,反正我现在对于Java的基础知识薄弱,然后常用类也用的不多,所以借此机会正好熟悉。
GUI主要用到javax.swing包和java.awt包,swing主要写组件,awt是事件(其实awt也可以写组件),但是说swing是对awt中组建的优化,所以现在常用swing。GUI里面比较重要的概念还是容器,组件必须放到容器里面才能显示,常用frame和dialog。我这次写GUI用到jframe,jbutton,filedialog,JLabel,imageicon这几个类
界面如下
- 其中的按钮用的JButton:"push" 按钮的实现,可以设置监听器。
- 按钮设置了监听器,“打开图片”弹出Filedoalog,选择图片文件,“识别”新建文字识别类,进行识别返回结果:
FileDialog
类显示一个对话框窗口,用户可以从中选择文件。 - 图片显示和文字显示用的JLabel:
JLabel
对象可以显示文本、图像或同时显示二者。可以通过设置垂直和水平对齐方式,指定标签显示区中标签内容在何处对齐。默认情况下,标签在其显示区内垂直居中对齐。默认情况下,只显示文本的标签是开始边对齐;而只显示图像的标签则水平居中对齐。 - Jframe建立整个容器
- Imageicon用来加载图像。Imageicon:一个 Icon 接口的实现,它根据 Image 绘制 Icon。可使用 MediaTracker 预载根据 URL、文件名或字节数组创建的图像,以监视该图像的加载状态。
界面的具体代码我就不贴上来了,值得注意的是图片压缩显示有一个小trick。
imagePath = new String(dirPath+fileName); imageico = new ImageIcon(imagePath); int w = imageico.getIconWidth(); int h = imageico.getIconHeight(); double ratio = (double)w/(double)h; if(ratio>4/3){ h = (int)(640*h/w); w = 640; }else{ w = (int)(480*w/h); h = 480; } imageico.setImage(imageico.getImage().getScaledInstance(w,h,imageico.getImage().SCALE_DEFAULT));
另外,就是文字识别的部分,主要还是一个执行进程的过程,那么首先得下载安装Tesseract_OCR:
安装下载的工作参考 这里
那么这边主要的挑战就是使用java执行进程和做文件io,主要分一下几点:
1.java操作命令行主要用到processbuilder & process 类,出自java.lang
一般都是ProcessBuilder.start() 和 Runtime.exec(ArrayList<String>) 方法创建一个本机进程,并返回 Process 子类的一个实例,该实例可用来控制进程并获得相关信息。Process 类提供了执行从进程输入、执行输出到进程、等待进程完成、检查进程的退出状态以及销毁(杀掉)进程的方法。
Runtime.getRuntime.exec(ArrayList<String> cmd)
processbuilder pb; pb.command(ArrayList<String> cmd);
这里就是用的processbuilder pb,新建一个实例,并且把运行参数保存到字符串表单cmd里,然后pb.command(ArrayList<String> cmd)执行,结果保存到指定txt中;
2.表示文件的类file类 出自java.io
它是文件和目录路径名的抽象表示形式。
主要方法getName(),getPath(),getParentPath();
在执行命令行时,表示输入的图片,表示输出txt都可以用到file类。
3.读取字节流过程 出自java.io
FileInputStream 从文件系统中的某个文件中获得输入字节。哪些文件可用取决于主机环境,这里可以读取图片。
new FileInputStream(outputFile.getAbsolutePath()) 新建一个文件输入字节流
new InputStreamReader(new FileInputStream(outputFile.getAbsolutePath()),"UTF-8")。文件输入字节流变成文件输入字符流
BufferedReader从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。
主要是两种用法还可以这么使用
BufferedReader in = new BufferedReader(new FileReader("foo.in"));
或者
BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream("foo.in"),"UTF-8"));
前者不能指定编码,而后者可以。
在最后把txt中文本读取到java程序,进而显示在GUI中用到
识别的代码如下
import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileReader; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; public class textRecognizer{ private String textResult; /** * 输出的结果 */ private final String EOL = System.getProperty("line.separator"); //回车 private String tessPath = "D:\Tesseract-OCR"; //tessocr程序所在目录 public textRecognizer(String path) { try { File imagefile = new File(path); textResult = this.recognizeText(imagefile); } catch (Exception e) { e.printStackTrace(); } } public String getResult(){ return textResult; } private String recognizeText(File imageFile) throws Exception { /** * 设置输出文件的保存的文件目录 */ File outputFile = new File(imageFile.getParentFile(),"output"); StringBuffer strB = new StringBuffer(); //设置cmd命令行字符串形式 List<String> cmd = new ArrayList<String>(); cmd.add(tessPath + "\tesseract"); cmd.add(imageFile.getName()); cmd.add(outputFile.getName()); cmd.add("-l"); cmd.add("eng"); //启动exe进程 ProcessBuilder pb = new ProcessBuilder(); pb.directory(imageFile.getParentFile()); pb.command(cmd); pb.redirectErrorStream(true); Process process = pb.start(); //等待此进程完成 int w = process.waitFor(); if (w == 0){// 0代表正常退出 BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(outputFile.getAbsolutePath()+ ".txt"),"UTF-8")); String str; while ((str = in.readLine()) != null) { strB.append(str).append(EOL); } in.close(); } else{ String msg; switch (w){ case 1: msg = "Errors accessing files. There may be spaces in your image's filename."; break; case 29: msg = "Cannot recognize the image or its selected region."; break; case 31: msg = "Unsupported image format."; break; default: msg = "Errors occurred."; } throw new RuntimeException(msg); } new File(outputFile.getAbsolutePath()+ ".txt").delete(); /** * 如果做验证码 * return strB.toString().replaceAll("\s*", ""); */ return strB.toString(); } }
可惜的是最后检测的结果一般。
今天写程序期间还有其他的有意思的地方我也有记录。
- java foreach 遍历,for(File file :testDataDir.listFiles()),jdk1.6后支持。
- private final String EOL = System.getProperty("line.separator"); 回车换行的字符串表示