java实现的⾝份证照⽚脸部识别头像截图)以及OCR字体
断断续续地折腾了⼤半个⽉,终于把⾝份证照⽚脸部识别以及OCR字体识别功能⽤Java实现了,需求很简单:通过摄像头所照的⼀张放在⿊⾊底板上的⾝份证照,识别照⽚上⾝份证⾥⾯的⼈名和地址(OCR中⽂),再截取⾝份证上的头像⽤Base64编码。⽣成⼀个规定格式的XML然后把⼈名,地址和头像照⽚的编码放到XML⾥⾯。
其中⽤到了OpenCV, Tesseract-OCR 还有⼀些对BufferedImage进⾏图像处理的东西。代码倒也不算很复杂,但是其中⼏个问题的确很烧脑细胞,花了不少时间才解决(Jedi本⼈⾟苦开发+原创码字博⽂共享,希望能):
1.  相⽚⾥⾯⾝份证的位置不确定问题: 底板⽐较⼤⾝份证可以在上⾯随意位置摆放
解决⽅法很简单,⼈脸识别时候把⼈脸的位置坐标返回出来,利⽤这个坐标来确定⾝份证位置也⼤⼤缩⼩需要字体OCR识别的区域。不需要整张照⽚做OCR也节省了许多运算时间。
2.  OpenCV⼈脸识别的容错率问题(有时候⼀张照⽚可以识别出三个头像来, 连个模糊的⾊块也能识别为⼀个头像,汗啊),当然了,识别⼈脸时候需要的l是必须的。上⽹就有
不少地⽅有的下载。运⾏时需要这个xml位于你的可运⾏jar所在的同个路径下
解决⽅法是思路就是指定进⾏⼈脸识别的最⼤和最⼩像素范围minSize和maxSize(就是多⼤尺⼨以内才去分析是不是⼈脸,当然要具体情况具体设置,minsize最好设⼤⼀点点,不然⼀个模糊⼩⾊块都会可能被误当作是⼈脸,T_T),然后设置参数
scaleFactor,minNeighbors和flags来提⾼识别正确率,具体代码如下:
public int[] detectFace(String imageFileName) {
int[] RectPosition = new int[4];
CascadeClassifier faceDetector = new CascadeClassifier("l");
倬怎么读Mat image = Highgui.imread(imageFileName);
MatOfRect faceDetections = new MatOfRect();
//指定⼈脸识别的最⼤和最⼩像素范围
3分25秒痞幼吃鸡Size minSize = new Size(120, 120);
Size maxSize = new Size(250, 250);
//参数设置为scaleFactor=1.1f, minNeighbors=4, flags=0 以此来增加识别⼈脸的正确率
faceDetector.detectMultiScale(image, faceDetections, 1.1f, 4, 0, minSize, maxSize);
//对识别出来的头像画个⽅框,并且返回这个⽅框的位置坐标和⼤⼩
for (Rect rect : Array()) {
+ rect.width, rect.y + rect.height), new Scalar(0, 255, 0));
RectPosition[0]=rect.x;
RectPosition[1]=rect.y;
刘美含的图片
RectPosition[2]=rect.width;
RectPosition[3]=rect.height;
System.out.println(rect.x +" "+ rect.y + " "+rect.width+" "+rect.height);
}
// 下⾯注释掉的三⾏可以⽤来⽣成识别出的⼈脸图像,保存下来以便Debug⽤
//          String filename = "face.png";
//          System.out.println(String.format("Writing %s", filename));
//          Highgui.imwrite(filename, image);
return RectPosition;
}
3.  Tesseract-OCR对⾝份证上的字体识别率⽐较低的问题。Tesseract⾃带的aineddatad(识别库)对⾝份证上字体的识别率偏低,想了很多办法,也⽤了很多时间精⼒去⽤⾝份证专⽤的华⽂细⿊字体库来做训练 (关于如何使⽤jTessBoxEditor-1.2来⽣成tif图⽚和矫正每个字体的识别,还有如何⽤命令⾏进⾏训练⽣成tessdata识别库的问题,学到的经验多到⼏乎可以写⼀⼤篇⽂档了。这个
要等有时间再总结了),捣腾了很多次之后发现出来的tessdata并没有⽐原⽣⾃带的chi_sim识别率提⾼多少,可能是需要从⼤到⼩不同字体都进⾏训练才⾏。
4.  ⾝份证照⽚解析度不⾼,1600x1200的摄像头照出来的效果不敢恭维⽽且⾝份证相⽚上⾯有很多弧形⼲扰线导致识别率更加低
为了提⾼解析度消除⼲扰线,我⽤了Tess4J⾃带的vertImageToBinary()把图像处理成⿊⽩照⽚,⼀次性解决了字体受⼲扰的问题,不过由照⽚上笔画⽐较多的字体笔画间隙出现了模糊,我在转换⿊背之前加上了⼀个图像处理的步骤,消除字体笔画间隙的⼲扰让字体更加清晰,⽅法如下:
public BufferedImage replaceWithWhiteColor(BufferedImage bi) {
int[] rgb = new int[3];
int width = bi.getWidth();
int height = bi.getHeight();
int minx = bi.getMinX();
int miny = bi.getMinY();
/**
miss a fei
* 遍历图⽚的像素,为处理图⽚上的杂⾊,所以要把指定像素上的颜⾊换成⽬标⽩⾊ ⽤⼆层循环遍历长和宽上的每个像素
*/
int hitCount = 0;
for (int i = minx; i < width-1; i++) {
for (int j = miny; j < height; j++) {
/**
* 得到指定像素(i,j)上的RGB值,
*/
int pixel = bi.getRGB(i, j);
int pixelNext = bi.getRGB(i+1, j);
/**
* 分别进⾏位操作得到 r g b上的值
*/
rgb[0] = (pixel & 0xff0000) >> 16;
rgb[1] = (pixel & 0xff00) >> 8;
rgb[2] = (pixel & 0xff);
/**
* 进⾏换⾊操作,我这⾥是要换成⽩底,那么就判断图⽚中rgb值是否在范围内的像素
*/
朱茵晒新照被赞//经过不断尝试,RGB数值相互间相差15以内的都基本上是灰⾊,
/
/对以⾝份证来说特别是介于73到78之间,还有⼤于100的部分RGB值都是⼲扰⾊,将它们⼀次性转变成⽩⾊
if ((Math.abs(rgb[0] - rgb[1]) < 15)
&& (Math.abs(rgb[0] - rgb[2]) < 15)
&& (Math.abs(rgb[1] - rgb[2]) < 15) &&
(((rgb[0] > 73)&& (rgb[0] < 78))||(rgb[0] > 100))) {
//进⾏换⾊操作,0xffffff是⽩⾊
bi.setRGB(i, j, 0xffffff);
}
}
}
return bi;
}
5.  关于Java调⽤DLL的问题,OpenCV和Tesseract-OCR都不是Java原⽣的,需要去load外部的DLL动态链接库⽂件
OpenCV需要⽤到opencv_java2410.dll (这个DLL和相关的opencv-2410.jar可以下载并安装opencv-2.之后在openCV的安装路径下到opencv\build\java\, ⾥⾯有64位和32位不同的版本,需要根据你运⾏时的JRE/JDK是否为64位来决定),⽽且需要在运⾏时候⽤System.load("C:\\opencv_java2410.dll") 导⼊,这⾥我⽤了绝对路径去load这个opencv_java2410.dll. 所以在发布的时候可以⽤⼀个bat判断是否在C:\根⽬录下是否存在这个dll,没有的话就⽴马copy⼀个过去再运⾏java。(bat批处理来调⽤java的时候可以指定jre,也可以避免java版本64位或者32位的问题)
6.  打包jar包发布的时候如果⽤⼀个⼤的jar包⾥⾯包含了所有的依赖包(包括jai-imageio.jar)就会出现
sun.misc.ServiceConfigurationError: javax.imageio.spi.ImageOutputStreamSpi: Provider d
ia.imageioimpl.stream.ChannelImageOutputStreamSpi could not be instantiated: java.lang.IllegalArgumentException: vendorName == null!
这个是由于imageIO的调⽤失败导致的,我尝试了很多做法,譬如修改Manifest .MF⽂件都还是不⾏T_T,最后还是选择分开单独打包jar 和依赖包的⽅法才⾏。⼀个可运⾏jar包⾛天下的愿望泡汤了。
7.  调试中发现⼀张⼤照⽚来做OCR识别字体的结果⽐你限制⼀个⼩的区域单独做OCR的效果差很多,所以如果有可能的话还是尽量缩⼩你要OCR识别字体的范围,不断识别率提⾼了⽽且处理起来速度也快很多。我就是通过定位⾝份证头像⼤致位置来估算⾝份证⽂字位置⼤⼩区域来实现的。
8.  在开发中我为了识别完每张图⽚⽣成XML之后删除图⽚,⽤了file.deleteOnExit(); 结果发现⼏张图处理完之后全部图都删除了剩下最后⼀张图没法删除。
研究了⼤半天才发现问题所在:BufferedImage image = ad(new FileInputStream(imgPath));
这样写的代码是存在问题的,因为这个FileInputStream在被GC之前是没有办法去close掉。没有close就会导致了⽂件⽆法被删除⽽且file.deleteOnExie()是不会有IOException抛出的.  查BUG会相当的confuse费时费⼒.  正确的写法应该是:
FileInputStream fins = new FileInputStream(imgPath);张敬轩王菀之
BufferedImage image = ad(fins);
最后⽤fins.close(); 关闭这个FileInputStream, ⽂件就能删除了,良好的编程代码习惯还是要慢慢养成,不然要花很多时间在Debug上⾯。
9.  另外我也试过了⼿机上的App扫描全能王和PC上的汉王OCR,感觉都是挺不错的成熟产品识别率挺⾼的,可惜没有提供第三⽅可调⽤的接⼝(当然不开放了,不然⼈家怎么赚钱^_^)Google的Tesseract可提升的空间还很⼤,如果有时间的话多⽤⽤不同⼤⼩的字体来训练可以获得更⾼的识别率。