Android离线身份证等图片文字识别

博客地址:https://blog.csdn.net/Weiye__Lee/article/details/80952724

写在前面

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

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

拍照
拍照

识别
识别

过程

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

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

1、自定义相机

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

2、图片处理

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

所以,先将图片灰度化,这里有个公式:f(x) = R0.3+G0.51+B*0.11,实际需要做的就是将图片的每个像素(这里假定android里用ARGB表示一个像素点)的red、green、blue取出并代入此公式算出每个点的灰度值,这样便实现了灰度化
  

灰度化之前
灰度化之前
灰度化之后
灰度化之后

  再来看看二值化,二值化的原理就是取一个阈值,然后将每个像素点的灰度值和这个阈值进行比较,如果大于阈值则定为白色,反之为黑色(这里假定要识别的图像是白底黑字),如此一来便实现了二值化。可以看到,最重要的是这个阈值,该怎么取值才合理,最简单的阈值取定便是取整幅图画的均值了:
  
  
二值化之前
二值化之前
二值化之后
二值化之后

  效果看上去还不错,实际上用到身份证识别或文字识别上,受阴影等因素的影响,效果就差很多了,因此,优化算法还是很有必要的,网上流传着多种二值化算阈值的算法,这里目前尝试了以下几种:

  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识别的速度上,也可考虑针对特定场景定制训练文件,这样速度上也会有所提升

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

参考

  1. tesseract ocr训练样本库 识别字库
  2. tess-two
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容

  • 不同图像灰度不同,边界处一般会有明显的边缘,利用此特征可以分割图像。需要说明的是:边缘和物体间的边界并不等同,边缘...
    大川无敌阅读 13,836评论 0 29
  • 本篇文章十分的长,大概有2万7千字左右。 一、发展史 1、人脸识别的理解: 人脸识别(Face Recogniti...
    放飞人夜阅读 19,929评论 8 122
  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    阳明先生_X自主阅读 15,969评论 3 119
  • 生存还是毁灭,这是一个困扰了人类千百年的问题。我们在生命中度过一天又一天,陷入漫长的生活中,也许正是因为生...
    白羊先生阅读 542评论 0 1
  • 现在才突然觉得,变化莫测原来是明白人唬不明白人的把戏。说到底,哪有什么变幻莫测。就像魔术一揭秘,你会有小小的失望,...
    路痴橘阅读 310评论 0 0