Android基于tess-two的离线身份证等图片文字识别

转载请注明出处,本文地址:https://blog.csdn.net/Weiye__Lee/article/details/80952724

写在前面

  最近因为业务需要,要在Android端实现个扫描身份证识别其中文字的功能,网上溜达了一圈。
  Android比较推荐的是:
  GitHub:https://github.com/rmtheis/tess-two
  当然也有第三方提供的解决方案,比如百度提供的文字识别:http://ai.baidu.com/tech/ocr。
  咱做技术的还是先折腾折腾,特此记录下过程,也希望能帮助到同样折腾的人

效果图(名字打了马赛克)

Android基于tess-two的离线身份证等图片文字识别_第1张图片 Android基于tess-two的离线身份证等图片文字识别_第2张图片

过程

先理下要实现这样的效果,我们需要做些什么

  1. 在Android端需要自定义个含取景框的自定义相机
  2. 对拍摄的图片进行灰度化、二值化处理
  3. 引入tess-two 库,调用相应api将步骤2处理后的图片传入处理

1、自定义相机

  这一步主要是写个布局,使用Camera和SurfaceView做个自定义相机,具体实现可看demo代码,里面有个取景框是专门用来截取出我们关心的区域的

2、图片处理

  机器视觉分为三个阶段 : 图像转化、图像分析、图像理解。若要将一幅图像转化为方便分析理解的格式,有一个很关键的过程就是“图像二值化”。一幅图像能否分析理解的准确很大程度上来说取决于二值化(非黑即白)效果的好坏。而在二值化之前,有一个重要步骤,那便是“图像灰度化”

  所以,先将图片灰度化,这里有个公式:f(x) = R*0.3+G*0.51+B*0.11,实际需要做的就是将图片的每个像素(这里假定android里用ARGB表示一个像素点)的red、green、blue取出并代入此公式算出每个点的灰度值,这样便实现了灰度化
  Android基于tess-two的离线身份证等图片文字识别_第3张图片 Android基于tess-two的离线身份证等图片文字识别_第4张图片
  再来看看二值化,二值化的原理就是取一个阈值,然后将每个像素点的灰度值和这个阈值进行比较,如果大于阈值则定为白色,反之为黑色(这里假定要识别的图像是白底黑字),如此一来便实现了二值化。可以看到,最重要的是这个阈值,该怎么取值才合理,最简单的阈值取定便是取整幅图画的均值了:
  Android基于tess-two的离线身份证等图片文字识别_第5张图片 Android基于tess-two的离线身份证等图片文字识别_第6张图片
  效果看上去还不错,实际上用到身份证识别或文字识别上,受阴影等因素的影响,效果就差很多了,因此,优化算法还是很有必要的,网上流传着多种二值化算阈值的算法,这里目前尝试了以下几种:
   
1. 阈值迭代算法(效果不理想,字体的笔画容易站在一起,阴影影响大)
  https://www.cnblogs.com/gxclmx/p/6916515.html
  
2. 基于Otsu阈值二值化(跟上面的迭代算法效果差不多,阴影影响大)
  https://blog.csdn.net/mao0514/article/details/47041597
  
3. 矩阵二值化算法
  https://blog.csdn.net/lj501886285/article/details/52425157
  这个算法的阈值是自适应的,对于每个像素点都构造了一个小矩阵来衡量阈值的取值,也就是说每个点都跟它周围的细节相关,这样比起用单一的整体阈值去衡量每个点,显然更具说服力。而事实证明,这个算法应用后的二值化测试效果确实杠杠的
  

3、tess-two api识别

  这一步就比较简单了,直接上步骤:
1. 引入依赖

dependencies {
    implementation 'com.rmtheis:tess-two:9.0.0'
}
  1. 调用api识别
TessBaseAPI tessBaseAPI = new TessBaseAPI();
tessBaseAPI.init(DATAPATH, "chi_sim");//传入训练文件目录和训练文件
tessBaseAPI.setImage(bitmap);
String text = tessBaseAPI.getUTF8Text();    

  就这样短短4行代码便可识别出文字了,这里有个坑要注意,看下tessBaseAPI.init这个方法源码:

public boolean init(String datapath, String language) {
        return init(datapath, language, OEM_DEFAULT);
    }

可以看到又调用了另一个init方法

 public boolean init(String datapath, String language, @OcrEngineMode int ocrEngineMode) {
        if (datapath == null)
            throw new IllegalArgumentException("Data path must not be null!");
        if (!datapath.endsWith(File.separator))
            datapath += File.separator;

        File datapathFile = new File(datapath);
        if (!datapathFile.exists())
            throw new IllegalArgumentException("Data path does not exist!");

        File tessdata = new File(datapath + "tessdata");
        if (!tessdata.exists() || !tessdata.isDirectory())
            throw new IllegalArgumentException("Data path must contain subfolder tessdata!");

        //noinspection deprecation
        if (ocrEngineMode != OEM_CUBE_ONLY) {
            for (String languageCode : language.split("\\+")) {
                if (!languageCode.startsWith("~")) {
                    File datafile = new File(tessdata + File.separator + 
                            languageCode + ".traineddata");
                    if (!datafile.exists())
                        throw new IllegalArgumentException("Data file not found at " + datafile);

                    // Catch some common problematic initialization cases.
                    if (languageCode.equals("ara") || (languageCode.equals("hin") &&
                            ocrEngineMode == OEM_DEFAULT)) {
                        boolean sampleCubeFileExists = new File(tessdata +
                                File.separator + languageCode + ".cube.params").exists();
                        if (!sampleCubeFileExists) {
                            throw new IllegalArgumentException("Cube data files not found." +
                                    " See https://github.com/rmtheis/tess-two/issues/239");
                        }
                    }
                }
            }
        }

        boolean success = nativeInitOem(mNativeData, datapath, language, ocrEngineMode);

        if (success) {
            mRecycled = false;
        }

        return success;
    }

这里注意下面这一段代码,api要求DATAPATH目录下,必须有tessdata这样一个子目录,也就是说,训练文件必须放在这个子目录下

File tessdata = new File(datapath + "tessdata");
        if (!tessdata.exists() || !tessdata.isDirectory())
            throw new IllegalArgumentException("Data path must contain subfolder tessdata!");

  一个文字识别的demo就此完成了,稍后会传上demo到github,demo做的是身份证,所以对图片的截取处理是针对身份证的,当然也可应用到其他营业执照啥的了。
  最后,在查资料的时候发现一个身份证识别demo:IdCardReconition,处理效果挺快,效果也蛮不错的,但是查看代码后发现处理都是在so文件里并且处理貌似只针对身份证,也不清楚是怎么做的,有了解的望告知

附上github:TextOcrExample

待完善

  1. 图像处理的算法是有待完善的,特别是速度上
  2. tess-two api识别的速度上,也可考虑针对特定场景定制训练文件,这样速度上也会有所提升
  3. 去除噪点

PS:对于速度上的要求,个人觉得可以在网络畅通的情况下,采用上传图片到服务器去处理,然后再反馈回给客户端。在服务端可做的事就多了,单台服务器计算资源就好过手机太多,况且我们还可以做分布式并发处理呢?速度相信是杠杠的,像百度这些第三方一般也是提供服务上传图片来识别的,速度那是相当快。最后感谢阅读,如果有什么不对的望大神指正,喜欢就star一下呗,谢谢!

参考

  1. tesseract ocr训练样本库 识别字库
  2. tess-two

你可能感兴趣的:(Android,java)