身份证识别

  前面介绍了好些OpenCV基本知识之后,现在我们小试牛刀,稍微写个身份证识别功能出来,这里我们就把工程移植到安卓平台,其实核心业务逻辑是完全一样的。
项目地址:https://github.com/samychen/IDcardRecognization.git
  我们知道身份证上那么多文字,我们怎么知道去哪拿身份证号码?

  居民二代身份证除了基本信息不同,其他地方都是模板样式,那么我们可以先把敏感信息找到,也就是把身份证号码那一块区域先找到,我们可以把公民身份证那块区别作为匹配模板,找到整张图片的模板所在的区域,而OpenCV刚好提供了模板匹配的方法matchTemplate( InputArray image, InputArray templ,OutputArray result, int method, InputArray mask = noArray() )

身份证样板.png

  通过这个方法我们就找到了模板所在的区域,这下子是不是与想法了,我们拿到模板矩阵像素坐标后,是不是可以根据身份证像素坐标的宽度进行一定计算来确定真实身份证号码所在的区域范围了。

接下来我们可以确定真实号码所在区域的结构体范围

  • X:模板的左上角像素坐标x加上模板的宽
  • Y:模板的y
  • W:全图宽-(身份证(模版)X+身份证(模版)宽) - n(给个大概值)
  • H:模板的高

  有了以上参数我们就可以把号码所在的区域专门截取出来,是不是已经实现了最重要的功能了。

  当然,拿到号码之后我们还需要对号码去进行识别,这里我们采用tesseract-ocr训练的模型来识别具体号码,关于tesseract-ocr的使用可以自行去谷歌。

  这里还有许多需要优化的地方,身份证原图是彩色图片,考虑到OpenCV计算多通道图片需要耗费性能,在预处理阶段需要先转换为灰度图,之后还需要进行高斯边界模糊处理消除噪声的影响。

核心代码:

#include "common.h"


#define DEFAULT_IDCARD_WIDTH  640
#define DEFAULT_IDCARD_HEIGHT  320

#define DEFAULT_IDNUMBER_WIDTH  240
#define DEFAULT_IDNUMBER_HEIGHT  120

#define  FIX_IDCARD_SIZE Size(DEFAULT_IDCARD_WIDTH,DEFAULT_IDCARD_HEIGHT)
#define  FIX_IDNUMBER_SIZE  Size(DEFAULT_IDNUMBER_WIDTH,DEFAULT_IDNUMBER_HEIGHT)

#define FIX_TEMPLATE_SIZE  Size(150, 26)

extern "C"
JNIEXPORT jobject JNICALL
Java_com_samychen_gracefulwrapper_idcardrecognization_ImageUtils_findIdNumber(JNIEnv *env,
                                                                              jclass type,
                                                                              jobject src,
                                                                              jobject out,
                                                                              jobject tpl,
                                                                              jobject config) {

    //原始图
    Mat img_src;
    //灰度图 需要拿去模版匹配
    Mat img_gray;
    //二值图 进行轮廓检测
    Mat img_threshold;
    //高斯图 进行边界模糊
    Mat img_gaussian;
    //边界图
    Mat img_canny;
    //模版
    Mat img_tpl;
    //获得的身份证图
    Mat img_idCard;
    //获得的身份证号码图
    Mat img_idNumber;
    bitmap2Mat(env, src, img_src);
    bitmap2Mat(env, tpl, img_tpl);
    //灰度化
    cvtColor(img_src, img_gray, COLOR_BGRA2GRAY);
    //二值化
    threshold(img_gray, img_threshold, 100, 255, THRESH_BINARY);
    GaussianBlur(img_threshold,img_gaussian,Size(3,3),0);
    Canny(img_gaussian,img_canny,180,255);
    vector<vector<Point>> contours;
    vector<Vec4i> hierachy;//每个边界轮廓都由上下左右4个坐标组成,所以需要vector数据结构
    //轮廓检测 只检测外轮廓 并压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标,比如矩形就是存储四个点
    //findContours(img_threshold, contours, hierachy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
    findContours(img_canny, contours, hierachy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
    int width = img_src.cols >> 1;//图片宽的一半
    int height = img_src.rows >> 1;//图片高的一半
    if (contours.empty()) {
        //可能整张图就是身份证
        img_idCard = img_gray;
    } else {
        Rect roiArea;
        vector<Rect> roiAreas;
        Rect rectMin;
        for (auto points : contours) {
            //根据4个顶点获得区域
            Rect rect = boundingRect(points);
            //身份证轮廓的宽必须大于图片宽的一半
            //高必须大于图片高的一半
            if (rect.width >= width && rect.height >= height) {
                roiArea = rect;
                roiAreas.push_back(rect);
            }
        }
        if (roiAreas.size()>0){
            rectMin = roiAreas.at(0);//找出满足条件的所有轮廓中最小的就是正好身份证的轮廓
            for(int i=0;i<roiAreas.size();i++){
                Rect temp = roiAreas.at(i);
                if (temp.area()<rectMin.area()){
                    rectMin = temp;
                }
            }
        } else{
            rectMin = Rect(0,0,img_gray.cols,img_gray.rows);
        }
        img_idCard = img_gray(rectMin);
        //roiarea有面积
//        if (roiArea.area())
//            img_idCard = img_gray(roiArea);
    }
    resize(img_idCard, img_idCard, FIX_IDCARD_SIZE);//身份证大小640 x 400
    resize(img_tpl, img_tpl, FIX_TEMPLATE_SIZE);
    cvtColor(img_tpl, img_tpl, COLOR_BGRA2GRAY);//使用灰度图进行匹配,彩色图计算量太大
    int cols = img_idCard.cols - img_tpl.cols + 1;
    int rows = img_idCard.rows - img_tpl.rows + 1;
    //创建输出图像,输出图像的宽度 = 被查找图像的宽度 - 模版图像的宽度 + 1
    Mat match(rows, cols, CV_32F);
    //Mat match;
//        TM_SQDIFF 平方差匹配法
//        TM_CCORR 相关匹配法
//        TM_CCOEFF 相关系数匹配法
//        TM_SQDIFF_NORMED
//        TM_CCORR_NORMED
//        TM_CCOEFF_NORMED
    // 对于方法 SQDIFF 和 SQDIFF_NORMED, 越小的数值代表更高的匹配结果. 而对于其他方法, 数值越大匹配越好
    matchTemplate(img_idCard, img_tpl, match, TM_CCORR_NORMED);
    //归一化
    normalize(match, match, 0, 1, NORM_MINMAX, -1);
    Point maxLoc;
    minMaxLoc(match, 0, 0, 0, &maxLoc);
    //计算 [身份证(模版):号码区域]
    //号码区域:
    //x: 身份证(模版)的X+宽
    //y: 身份证(模版)Y
    //w: 全图宽-(身份证(模版)X+身份证(模版)宽) - n(给个大概值)
    //h: 身份证(模版)高
    Rect rect(maxLoc.x + img_tpl.cols, maxLoc.y, img_idCard.cols - (maxLoc.x + img_tpl.cols) - 40,
              img_tpl.rows);
    //拿二值的号码
    resize(img_threshold, img_threshold, FIX_IDCARD_SIZE);
    img_idNumber = img_threshold(rect);
    jobject obj=createBitmap(env,img_idNumber,config);
//    resize(img_idNumber, img_idNumber, FIX_IDNUMBER_SIZE);
//    mat2Bitmap(env, img_idNumber, out);


    img_src.release();
    img_gray.release();
    img_threshold.release();
    img_idCard.release();
    img_idNumber.release();
    img_tpl.release();
    match.release();
    return obj;
}

演示结果:


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

推荐阅读更多精彩内容

  • 这段时间项目的需求,需要在注册的时候进行身份证识别。就简单的搞了一下。 身份证识别 项目的需求是通过摄像头的采集获...
    请输入账号名阅读 7,879评论 12 24
  • 项目中需要用到身份识别,so网上扫荡了一番,封装了一个比较简单的。 干货如下 需要用到两个三方库 Tesserac...
    ___Lynn阅读 3,795评论 11 8
  • 第一次写简书,多见谅。本文运用opencv+TesseractOCR来实现身份证识别姓名和身份证号查阅并引用了很多...
    Joey91阅读 4,002评论 0 5
  • 每个人心里都有个小孩,他好像永远不会长大,在某些时候,他突然就跳出来用大人般的口吻对你说",嘿,该明白点什么了" ...
    布达拉的朝圣阅读 185评论 0 0
  • 设计场景 将具有length属性的对象转换为数组 最常见的对象是 arguments 和 NodeList 问题 ...
    adiu阅读 3,999评论 0 1