NDK OpenCV 身份证信息离线识别

 NDK系列之OpenCV 身份证信息离线识别技术实战,本节主要是通过OpenCV C++库,实现身份证信息识别,如身份证号码识别,本节使用到的技术点同样适用于车牌号识别、银行卡号码识别等。

实现效果:

 本节主要内容:

1.OpenCV库导入;

2.Java层代码实现;

3.Native层身份证识别;

4.OCR识别图片上面的文字。

源码:

NdkIdCard: NDK OpenCV 身份证信息离线识别

一、OpenCV库导入

1)复制OpenCV源文件到cpp目录下,动态库文件复制到jniLibs目录下:

2)在CMakeLists文件中,导入源文件和库文件

 二、Java层代码实现

MainActivity的从相册中选择身份证图片、查找身份证图片中的身份证号码区域、识别身份证号码区域的文字(身份证号码):

1)从手机相册中选择身份证图片,这个大家基本都会,不具体说明,最终获取到身份证图片的Bitmap对象赋值给fullImage变量;

public void search(View view) {
	if (!initPermission()) return;
	Intent intent;
	if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
		intent = new Intent();
		intent.setAction(Intent.ACTION_GET_CONTENT);
	} else {
		intent = new Intent(Intent.ACTION_PICK,
				MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
	}
	intent.setType("image/*");
	//使用选取器并自定义标题
	startActivityForResult(Intent.createChooser(intent, "选择待识别图片"), 100);
}

 2)查找身份证图片中的身份证号码区域

将Java层获取到的选择的身份证图片的Bitmap,即fullImage变量,传给Native层,通过OpenCV库,将身份证图片中的身份证号码区域找到,并裁剪出来;

public void searchId(View view) {
	tesstext.setText(null);
	ResultImage = null;
	Bitmap bitmapResult = ImageProcess.getIdNumber(fullImage, Bitmap.Config.ARGB_8888);
	fullImage.recycle();
	ResultImage = bitmapResult;
	//tesseract-ocr
	id.setImageBitmap(bitmapResult);
}

 ImageProcess.java:

public class ImageProcess {

    static {
        System.loadLibrary("native-lib");
    }

    public static native Bitmap getIdNumber(Bitmap src, Bitmap.Config config);
}

3)识别身份证号码区域的文字

将获取到的身份证号码区域图片,使用OCR识别成文字。

public void recognition(View view) {
	// 识别Bitmap中的图片
	baseApi.setImage(ResultImage);
	tesstext.setText(baseApi.getUTF8Text());
	baseApi.clear();
}

 三、Native层身份证识别

Native层工作主要是:获取Java层传递过来的身份证原图,使用OpenCV,找到身份证号码区域,裁剪该区域图片,并转化为Bitmap格式,返回Java层。

总体思路:

实现逻辑:

1)将Java层传递过来的bitmap转成的Mat,因为OpenCV支持的是Mat格式。

Mat src_img; // 身份证原图
Mat dst_img; // 身份证号码区域图
//将bitmap转换为Mat型格式数据
Java_org_opencv_android_Utils_nBitmapToMat2(env, type, src, (jlong) &src_img, 0);

2)无损压缩,压缩图片至640*400,减少后续查找身份证号码区域时间,提高效率。

#define DEFAULT_CARD_WIDTH 640
#define DEFAULT_CARD_HEIGHT 400
#define FIX_IDCARD_SIZE Size(DEFAULT_CARD_WIDTH,DEFAULT_CARD_HEIGHT)
resize(src_img, src_img, FIX_IDCARD_SIZE);

压缩后身份证图:

 3)灰度化,查找身份证号码区域跟图片颜色无关,将图片颜色值RBG 转化为 Gray,RBG: 256 *256 *256 = 2^24,Gray:256,转化后理论上效率提升提高2^16倍。转化公式:Gray f(i,j)=0.30R(i,j)+0.59G(i,j)+0.11B(i,j),这里直接使用OpenCV API即可:

cvtColor(src_img, dst, COLOR_BGR2GRAY);

灰度化后身份证图:

 4)二值化,再一步过滤,降噪;将图片颜色设置成只有黑白两种颜色,过滤掉灰色部分,如身份证背景波纹。

threshold(dst, dst, 100, 255, CV_THRESH_BINARY);

 二值化后身份证图:黑白色

5)膨胀,发酵,Native层目的是查找身份证号码区域,从身份证图中可以看出,身份证号码区域是一块有间隔连续的号码区域;我们是不是可以将像素点膨胀,使得身份证号码区域成为一块连续的号码区域;

Mat dst; // 膨胀身份证图
Mat erodeElement = getStructuringElement(MORPH_RECT, Size(20, 10));
erode(dst, dst, erodeElement);

 膨胀后身份证图:

6)轮廓检测,将连续像素点识别出来,并筛选出符合身份证号码区域的结果存放到集合中;身份证号码区域宽高比例 >  1:9,即width > height * 9的加入集合。

vector<vector<Point> > contours;
vector<Rect> rects;
findContours(dst, contours, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
for (int i = 0; i < contours.size(); i++) {
	Rect rect = boundingRect(contours.at(i));
	//rectangle(dst, rect, Scalar(0, 0, 255));  // 在dst 图片上显示 rect 矩形
	if (rect.width > rect.height * 9) {
		rects.push_back(rect);
		rectangle(dst, rect, Scalar(0, 255, 255));
		dst_img = src_img(rect);
	}
}

 轮廓检测后的身份证图:

 7)如果筛选后集合中只有1条数据,那么该数据将是身份证号码区域;否则遍历集合,取区域y坐标值最大的区域为身份证号码区域,因为身份证号码区域在身份证图中位于最底部。
注:真实场景可能存在身份证原图旋转,或者倒着等不规范的图片,我们要在拍照的时候提示用户将身份证放到识别框中拍照,尽可能从上层避免上面所说的不规范图片。

if (rects.size() == 1) {
	Rect rect = rects.at(0);
	dst_img = src_img(rect);
} else {
	int lowPoint = 0;
	Rect finalRect;
	for (int i = 0; i < rects.size(); i++) {
		Rect rect = rects.at(i);
		Point p = rect.tl();
		if (rect.tl().y > lowPoint) {
			lowPoint = rect.tl().y;
			finalRect = rect;
		}
	}
	rectangle(dst, finalRect, Scalar(255, 255, 0));
	dst_img = src_img(finalRect);
}

身份证识别框拍照:

8)将裁剪的身份证号码图dst_img(Mat格式)转化为bitmap格式,返回java层

jobject bitmap = createBitmap(env, dst_img, config);

src_img.release();
dst_img.release();
dst.release();

return bitmap;

裁剪的身份证号码图 :

四、OCR识别图片上面的文字

1)依赖OCR 库 tess-two

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

2)导入身份证号码识别训练学习的样本,训练学习的样本越多,OCR识别到的身份证号码越准确。

身份证号码识别训练学习的样本是一个二进制文件:cn.traineddata,这里可以直接使用我源码里面的身份证号码识别训练学习的样本。区别于收费的OCR识别公司,只要在于训练学习的样本数量,个人开发者很难做到使用大量的样本数完成训练学习工作;所以由于我的训练学样本很少,会存在一些号码数字识别不准确,如6可能识别到的是4等。

 3)初始化tess-two库

private TessBaseAPI baseApi;
private void initTess() {
	new AsyncTask<Void, Void, Boolean>() {
		@Override
		protected void onPreExecute() {
			showProgress();
		}

		@Override
		protected void onPostExecute(Boolean result) {
			dismissProgress();
			if (!result) {
				Toast.makeText(MainActivity.this, "load trainedData failed", Toast.LENGTH_SHORT).show();
			}
		}

		@RequiresApi(api = Build.VERSION_CODES.N)
		@Override
		protected Boolean doInBackground(Void... params) {
			baseApi = new TessBaseAPI();
			try {
				InputStream is = null;
				is = getAssets().open(language + ".traineddata");
				String path = mContext.getDataDir().getAbsolutePath();
				LogUtil.i(TAG, "initTess path " + path);
				File file = new File(path + "/tessdata/" + language + ".traineddata");
				if (!file.exists()) {
					file.getParentFile().mkdirs();
					FileOutputStream fos = new FileOutputStream(file);
					byte[] buffer = new byte[2048];
					int len;
					while ((len = is.read(buffer)) != -1) {
						fos.write(buffer, 0, len);
					}
					fos.close();
				}
				is.close();
				return baseApi.init(path, language);
			} catch (IOException e) {
				e.printStackTrace();
				LogUtil.i(TAG, "initTess err " + e.getMessage());
			}
			return false;
		}
	}.execute();
}

4)调用API识别身份证号码区域的文字

public void recognition(View view) {
	// 识别Bitmap中的图片
	baseApi.setImage(ResultImage);
	tesstext.setText(baseApi.getUTF8Text());
	baseApi.clear();
}

至此,OpenCV 身份证信息离线识别技术项目已完成;车牌号识别、银行卡号码识别等也是同样的实现思想。

源码:

NdkIdCard: NDK OpenCV 身份证信息离线识别

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sziitjin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值