欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android Ocr文字识别 身份证识别 实时扫描

程序员文章站 2024-03-15 23:43:48
...

遇到一个需求需要扫描身份证,识别身份证号并进行查询,在网上百度需要用到文字识别技术,ocr tess-two,看到网上有关于中英文实时扫描和手机号实时扫描的功能,于是在这两者的基础上进行了改进,感谢顾_小白和Si_Kang的贡献,基于分享的思想,将本次的开发思路写出来,以供大家参考。

顾_小白 地址:https://blog.csdn.net/g_ying_jie/article/details/73849492

Si_kang地址:https://blog.csdn.net/mr_sk/article/details/72877492

我的Demo下载地址:https://download.csdn.net/download/weixin_41632509/10324727

先来看一下效果,图片中身份证仅供学习参考使用,不做其他用途。

Android Ocr文字识别 身份证识别 实时扫描

Android Ocr文字识别 身份证识别 实时扫描

Android Ocr文字识别 身份证识别 实时扫描


用到的技术是tess-two,语言识别库chi_sim.traineddata

下载chi_sim.traineddata集成到自己代码中,地址:https://github.com/tesseract-ocr/tessdata

在自己的项目中,AndroidManifest.xml文件中加入如下权限配置,安卓6.0需要动态权限申请

<uses-permission android:name="android.permission.CAMERA" />
<!-- 声明使用Camera意图 -->
<uses-feature android:name="android.hardware.camera" />
<!-- 声明调用Camera自动对焦功能 -->
<uses-feature android:name="android.hardware.camera.autofocus" />

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

集成:


compile 'com.rmtheis:tess-two:7.0.0'

获取读写权限后把字库从项目的assets目录复制到手机存储中,注意存储的路径一定要包含tessdata目录,因为在TessBaseAPI初始化的时候回去查找该目录

/**

 * 请求到权限后在这里复制识别库
 *
 * @param requestCode
 * @param permissions
 * @param grantResults
 */
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    switch (requestCode) {
        case 0:
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                copyToSD(LANGUAGE_PATH, DEFAULT_LANGUAGE_NAME);
            }
            break;
        default:
            break;
    }
}

/**
 * 将assets中的识别库复制到SD卡中
 *
 * @param path 要存放在SD卡中的 完整的文件名。这里是"/storage/emulated/0//tessdata/chi_sim.traineddata"
 * @param name assets中的文件名 这里是 "chi_sim.traineddata"
 */
public void copyToSD(String path, String name) {

    //如果存在就删掉
    File f = new File(path);
    if (f.exists()) {
        f.delete();
    }
    if (!f.exists()) {
        File p = new File(f.getParent());
        if (!p.exists()) {
            p.mkdirs();
        }
        try {
            f.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    InputStream is = null;
    OutputStream os = null;
    try {
        is = this.getAssets().open(name);
        File file = new File(path);
        os = new FileOutputStream(file);
        byte[] bytes = new byte[2048];
        int len = 0;
        while ((len = is.read(bytes)) != -1) {
            os.write(bytes, 0, len);
        }
        os.flush();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            if (is != null)
                is.close();
            if (os != null)
                os.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

其他问题请具体查看代码,下面说一说身份证扫描要用到的部分

1、需要截取身份证号区域,此图片为截取身份证号部分图片,用tess-two识别该部分文字

代码:


//截取透明框内照片(身份证)
bmp = Bitmap.createBitmap(bmp,275,430,625,140);
2、将图片经过灰化处理,二值化处理,旋转以后,交给tess-two进行识别,需要注意的是是实时识别
身份证,一帧一帧扫描,如果不是身份证号格式,我们要摒弃,重新进行扫描识别

//灰化处理
bmp = ImageFilter.grayScale(bmp);
//二值化处理
bmp = ImageFilter.binaryzation(bmp);
//开始识别
TessBaseAPI baseApi = new TessBaseAPI();
//初始化OCR的字体数据,DATA_PATH为路径,DEFAULT_LANGUAGE指明要用的字体库(不用加后缀)
if (baseApi.init(DATA_PATH, DEFAULT_LANGUAGE)) {
    //设置识别模式
    baseApi.setPageSegMode(TessBaseAPI.PageSegMode.PSM_AUTO);
    //设置要识别的图片
    baseApi.setImage(bmp);
    //开始识别
    String result = baseApi.getUTF8Text();
    baseApi.clear();
    baseApi.end();
    isQualified(result);
}
3、身份证号判断部分,调用
IDCard.IDCardValidate( str) 进行判断

public static class IDCard {
    /**
     * 功能:身份证的有效验证
     * @param IDStr 身份证号
     * @return true 有效:false 无效
     * @throws ParseException
     */
    public static boolean IDCardValidate(String IDStr) throws ParseException {
        String[] ValCodeArr = { "1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2" };
        String[] Wi = { "7", "9", "10", "5", "8", "4", "2", "1", "6", "3", "7", "9", "10", "5", "8", "4", "2" };
        String Ai = "";
        // ================ 号码的长度18位 ================
        if (IDStr.length() != 18) {
            return false;
        }
        // ================ 数字 除最后1位都为数字 ================
        if (IDStr.length() == 18) {
            Ai = IDStr.substring(0, 17);
        }
        if (isNumeric(Ai) == false) {
            //errorInfo = "身份证15位号码都应为数字 ; 18位号码除最后一位外,都应为数字。";
            return false;
        }
        // ================ 出生年月是否有效 ================
        String strYear = Ai.substring(6, 10);// 年份
        String strMonth = Ai.substring(10, 12);// 月份
        String strDay = Ai.substring(12, 14);// 日
        if (isDate(strYear + "-" + strMonth + "-" + strDay) == false) {
            // errorInfo = "身份证生日无效。";
            return false;
        }
        GregorianCalendar gc = new GregorianCalendar();
        SimpleDateFormat s = new SimpleDateFormat("yyyy-MM-dd");
        try {
            if ((gc.get(Calendar.YEAR) - Integer.parseInt(strYear)) > 150 || (gc.getTime().getTime() - s.parse(strYear + "-" + strMonth + "-" + strDay).getTime()) < 0) {
                //errorInfo = "身份证生日不在有效范围。";
                return false;
            }
        } catch (NumberFormatException e) {
            e.printStackTrace();
        } catch (java.text.ParseException e) {
            e.printStackTrace();
        }
        if (Integer.parseInt(strMonth) > 12 || Integer.parseInt(strMonth) == 0) {
            //errorInfo = "身份证月份无效";
            return false;
        }
        if (Integer.parseInt(strDay) > 31 || Integer.parseInt(strDay) == 0) {
            //errorInfo = "身份证日期无效";
            return false;
        }
        // ================ 地区码时候有效 ================
        Hashtable h = GetAreaCode();
        if (h.get(Ai.substring(0, 2)) == null) {
            //errorInfo = "身份证地区编码错误。";
            return false;
        }
        // ================ 判断最后一位的值 ================
        int TotalmulAiWi = 0;
        for (int i = 0; i < 17; i++) {
            TotalmulAiWi = TotalmulAiWi + Integer.parseInt(String.valueOf(Ai.charAt(i))) * Integer.parseInt(Wi[i]);
        }
        int modValue = TotalmulAiWi % 11;
        String strVerifyCode = ValCodeArr[modValue];
        Ai = Ai + strVerifyCode;

        if (IDStr.length() == 18) {
            if (Ai.equals(IDStr) == false) {
                //errorInfo = "身份证无效,不是合法的身份证号码";
                return false;
            }
        } else {
            return true;
        }
        return true;
    }

    /**
     * 功能:设置地区编码
     * @return Hashtable 对象
     */
    @SuppressWarnings("unchecked")
    private static Hashtable GetAreaCode() {
        Hashtable hashtable = new Hashtable();
        hashtable.put("11", "北京");
        hashtable.put("12", "天津");
        hashtable.put("13", "河北");
        hashtable.put("14", "山西");
        hashtable.put("15", "内蒙古");
        hashtable.put("21", "辽宁");
        hashtable.put("22", "吉林");
        hashtable.put("23", "黑龙江");
        hashtable.put("31", "上海");
        hashtable.put("32", "江苏");
        hashtable.put("33", "浙江");
        hashtable.put("34", "安徽");
        hashtable.put("35", "福建");
        hashtable.put("36", "江西");
        hashtable.put("37", "山东");
        hashtable.put("41", "河南");
        hashtable.put("42", "湖北");
        hashtable.put("43", "湖南");
        hashtable.put("44", "广东");
        hashtable.put("45", "广西");
        hashtable.put("46", "海南");
        hashtable.put("50", "重庆");
        hashtable.put("51", "四川");
        hashtable.put("52", "贵州");
        hashtable.put("53", "云南");
        hashtable.put("54", "*");
        hashtable.put("61", "陕西");
        hashtable.put("62", "甘肃");
        hashtable.put("63", "青海");
        hashtable.put("64", "宁夏");
        hashtable.put("65", "*");
        //hashtable.put("71", "*");
        //hashtable.put("81", "香港");
        //hashtable.put("82", "澳门");
        //hashtable.put("91", "国外");
        return hashtable;
    }

    /**
     * 功能:判断字符串是否为数字
     * @param str
     * @return
     */
    private static boolean isNumeric(String str) {
        Pattern pattern = Pattern.compile("[0-9]*");
        Matcher isNum = pattern.matcher(str);
        if (isNum.matches()) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 功能:判断字符串是否为日期格式
     * @return
     */
    public static boolean isDate(String strDate) {
        Pattern pattern = Pattern
                .compile("^((\\d{2}(([02468][048])|([13579][26]))[\\-\\/\\s]?((((0?[13578])|(1[02]))[\\-\\/\\s]?((0?[1-9])|([1-2][0-9])|(3[01])))|(((0?[469])|(11))[\\-\\/\\s]?((0?[1-9])|([1-2][0-9])|(30)))|(0?2[\\-\\/\\s]?((0?[1-9])|([1-2][0-9])))))|(\\d{2}(([02468][1235679])|([13579][01345789]))[\\-\\/\\s]?((((0?[13578])|(1[02]))[\\-\\/\\s]?((0?[1-9])|([1-2][0-9])|(3[01])))|(((0?[469])|(11))[\\-\\/\\s]?((0?[1-9])|([1-2][0-9])|(30)))|(0?2[\\-\\/\\s]?((0?[1-9])|(1[0-9])|(2[0-8]))))))(\\s(((0?[0-9])|([1-2][0-3]))\\:([0-5]?[0-9])((\\s)|(\\:([0-5]?[0-9])))))?$");
        Matcher m = pattern.matcher(strDate);
        if (m.matches()) {
            return true;
        } else {
            return false;
        }
    }

}

4、需要注意的是,本demo是用线程接收识别的字符串,并且是在自己自定义的surfaceVIew中,
如果有需要跳转到其他界面,需要借助context,具体如下代码:

Intent intent = new Intent(getContext(), MainActivity.class);//跳转
intent.putExtra("idCard",(String) msg.obj);
getContext().startActivity(intent);

同学们可以根据自己需求跳转到相应页面,并将参数带进去。

改进:目前使用的语言库是中文的,由于文件较大,有五十多兆,并且识别速率不是特别快,由于是帧扫描,相对扫描结果还是比较准确的,如果有兴趣的同学,可以训练自己的身份证识别库,识别速率也会大大加快。