opencv-身份证识别-java处理
 2018-09-01 07:57:16   534   0   

本文最后更新于天前,文中介绍内容及环境可能已不适用.请谨慎参考.

综合前面图像处理的相关知识,处理身份证的简单识别.

使用Java实现.

 

一.身份证识别概述

1.思路

身份证中关键字段为黑色,

直接按颜色过滤像素点二值化,过滤偏黑灰色,及rgb 为 (111,111,111)至(0,0,0)的颜色,设置为黑色.

其他颜色转换为白色,在图片比较标准,无亮点,光照均有的情况下,基本可以完美识别出文字.

然后计算文字的左右投影

根据投影切割行数据,

再对每一行,计算文字的上下垂直投影,计算文字宽度区域。

根据每一行合并区域得出最后的所有字段的区域。

根据区域切割图片。

对每一个切割的图片进行文字识别即可.

 

2.关键字段自动定位-实现结果

图片来源于网络。

折腾了几天,终究基本实现.算是优化了一下吧。。哈哈.

下方为第三行的垂直投影。

 

二.身份证识别详细过程

 1.图片二值化

直接按颜色过滤像素点二值化,过滤偏黑灰色,及rgb 为 (111,111,111)至(0,0,0)的颜色,设置为黑色.

其他颜色转换为白色

直接根据图像黑色过滤.

//空图片       
int imgheight = rectM.height();
		int imgwidth = rectM.width();

		Mat hist2 = new Mat(imgheight, imgwidth, CvType.CV_8UC1);

		double[] v2 = new double[imgwidth * imgheight];

		for (int j = 0; j < imgwidth * imgheight; j++) {
			v2[j] = 255;
		}
		hist2.put(0, 0, v2);

//过滤黑色
double max = imgheight;// 0;
		for (int j = 0; j < imgwidth; j++) {
			for (int i = 0; i < imgheight; i++) {

				try {
					// if (rectM.get(i, j)[0] <= 100+param && rectM.get(i, j)[1] <= 100+param &&
					// rectM.get(i, j)[2] <= 100+param) {

					// 对彩色图片过滤黑色阈值
					if (rectM.get(i, j)[0] <= param) {

						hist2.put(i, j, 0);
					} else {
						// int k = 0;
					}
				} catch (Exception e) {
					// TODO: handle exception
				}

			}
		}

其中param即黑色的像素值,越黑越保留,

关于这个值的计算,对不同的图片做了一下判断,需要判断图片的整体亮度,是否有亮点,对比是否清晰等,目前来说这个判断大体可用. 一些普通的图片都可以识别

//获取灰度图的中间1/3区域宽度的平均灰度值
		double avlihghtThird = getAvThirdWidthGray(bgr);
		// 获取输入bgr图片的 平均灰度值
		double avlihght = getAvGray(bgr);
		// 获取灰度图的1/3区域高度的最大灰度值
		double maxlihght = getMaxthresh(bgr);

	
		double param = (255 - maxlihght) * 2;

		// 确定过滤的灰色阈值
		if (Math.abs(avlihght - avlihghtThird) < 20) {

			// 中间区域与全图平均灰度差别不大,无高亮/图片颜色分布均衡

			if (val < 35) // 对比度比较低,整体偏亮
			{
				if (avlihghtThird < 150) // 平均灰度第,颜色清晰
					param = 115; // 过滤偏黑灰色.
				else if (avlihghtThird >= 150 && avlihghtThird < 180) // 平均灰度第,颜色清晰
					param = 135; // 过滤偏黑灰色.
				else
					param = 149; // //平均灰度第,颜色不太清晰

			} else if (val >= 35 && val < 61) // 正常图片,颜色分布均衡
			{
				param = 110; // 过滤偏黑灰色.

				if (maxlihght < 200)
					param = 85; // 过滤偏黑灰色.

			} else if (val >= 61 && val < 91) // 对比度比较高,色彩区分度高
				param = 60; // 过滤偏黑色.
			else if (val >= 91) // 对比度超高,图片有亮度不均衡或者部分区域高亮
			{
				param = 60; // 过滤偏黑色.
			}
		}

原图:

根据黑色二值化处理结果

 

 

2.图像水平垂直投影,定位关键数据位置

其实,第一版我是直接按照身份证的图,使用xy,初略给每个区域设定一个范围,然后直接截取图片,

但是每个图片拍摄的都不一定完整,导致切割的非常不准,

投影计算旨在不管图片怎么拍,都能把关键字段的位置定位出来.

原理:扫描全图的像素,统计y轴上的每一行的总像素值.然后将统计结果转化为直方图显示.根据直方图计算出每一行的位置.

然后对每一个行区域里面的数据做垂直投影,同上,统计所有x坐标上每一列的总像素,生成直方图,计算没一列的范围.

然后定位每一个区域.

水平投影行的像素计算:

//每一行的像素总和统计,只统计一半宽度,剔除右侧身份证头像区域
Mat hist = new Mat(1, imgheight, CvType.CV_32FC1);

		Core.normalize(hist, hist, 0, hist.rows(), Core.NORM_MINMAX, -1, new Mat());
		double[] v = new double[imgheight];

		for (int j = 0; j < imgheight; j++) {
			v[j] = 0;
		}
		hist.put(0, 0, v);

		double max = imgheight;// 0;

		for (int i = 0; i < imgheight; i++) {
			for (int j = 0; j < imgwidth / 2; j++) {
				if (input.get(i, j)[0] == 0) {

					double num = hist.get(0, i)[0];
					num++;
					hist.put(0, i, num);
				} else {
					int k = 0;
				}
			}
			double num = hist.get(0, i)[0];
			if (num > max)
				max = num;
		}

生成直方图

//数值统计转换为直方图,根据像素值/画布总宽度画线
Mat hist2 = new Mat(imgheight, imgwidth, CvType.CV_8UC1);

		Core.normalize(hist2, hist2, 0, hist2.rows(), Core.NORM_MINMAX, -1, new Mat());
		double[] v2 = new double[imgwidth * imgheight];

		for (int j = 0; j < imgwidth * imgheight; j++) {
			v2[j] = 255;
		}
		hist2.put(0, 0, v2);
		for (int j = 0; j < imgheight; j++) {

			double y = (hist.get(0, j)[0] / max) * imgwidth;

			// B component or gray image
			Imgproc.line(hist2, new Point(0, j), new Point(y, j), new Scalar(0, 0, 0), 2, 8, 0);
		}

		Imgproc.cvtColor(hist2, hist2, Imgproc.COLOR_GRAY2BGR);

 

行区域启动判断

	for (int j = 0; j < imgheight; j++) {
			double num = hist.get(0, j)[0];

			if (!isstart) {
				// 顶部和底部的排除
				if (num > 0 && j > 20 && j < imgheight - 20) {
					if (start_val1 == 0)
						start_val1 = num;
					else {
						start_val2 = num;

						// 找到开始
						if (start_val2 / start_val1 >= 1.4 && (start_val2 > 10)) {
							isstart = true;

						} else {
							// 重新开始计算
							start_val1 = num;
						}
					}

				}

 

行结束判断

if (num >= 0) {
					if (start_val1 == 0)
						start_val1 = num;
					else {
						start_val2 = num;

						// 找到结束,每一行至少>3%高度
						if (start_val1 / start_val2 > 1.4 && (j - startj > imgheight * 0.035) && start_val2 < 15) {
							isend = true;
							start_val1 = 0;
						} else {
							// 重新开始计算
							start_val1 = num;
						}

					}

				}

水平投影:

 

第三行,垂直投影

 

后面根据行列特定,合并区域即可.

最后切割结果:

3.调整-非标准四边形证件识别

上面是针对标准四边形证件,方方正正的拍摄情况的,要是图片不是完整的,需要识别证件外边框,可能还需要进行透视变换为标准形状.

比如原图如下:有一定的透视,拍色视角非正正方方,并且还有外部背景.

这样处理.计算边框

this.image.copyTo(m);

		// 滤波,模糊处理,消除某些背景干扰信息
		Imgproc.blur(m, filtered, new Size(3, 3));

		// savetoImg(filtered, "blur_33");

		// 腐蚀操作,消除某些背景干扰信息
		Imgproc.erode(filtered, filtered, new Mat(), new Point(-1, -1), 3);// 1, 1);
        
       //这里这个边缘检测的阈值成了关键数据,目前还没有太好方法进行判定.
    //上述使用的奥巴马的图片,使用值为17-17*3.
        double threshmin = thresh;
		double threshmax = threshmin * 3;

		// 边缘检测
		Imgproc.Canny(filtered, edges, threshmin, threshmax);
   // 膨胀操作,尽量使边缘闭合
		Imgproc.dilate(edges, dilated_edges, new Mat(), new Point(-1, -1), 3);// , 1, 1);

 

4.轮廓计算


		List<MatOfPoint> contours = new ArrayList<>();// 每一组Point点集就是一个轮廓。
		
		Mat hierarchy = new Mat();
		// RETR_EXTERNAL:表示只检测最外层轮廓,对所有轮廓设置hierarchy[i][2]=hierarchy[i][3]=-1

		// CHAIN_APPROX_SIMPLE:压缩水平方向,垂直方向,对角线方向的元素,值保留该方向的重点坐标,如果一个矩形轮廓只需4个点来保存轮廓信息
	
		// 寻找边界轮廓
		Imgproc.findContours(dilated_edges, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);

在轮廓的结果中寻找最外层的矩形,有4个点,并且为凸边形.,有一定的面积.

// For conversion later on
		MatOfPoint2f approxCurve = new MatOfPoint2f();

		Rect rect = new Rect();

		// For each contour found
		for (int i = 0; i < contours.size(); i++) {

			// Convert contours from MatOfPoint to MatOfPoint2f
			MatOfPoint2f contour2f = new MatOfPoint2f(contours.get(i).toArray());		

			// Processing on mMOP2f1 which is in type MatOfPoint2f
			// 计算轮廓的长度
			double approxDistance = Imgproc.arcLength(contour2f, true) * 0.02;

			if (approxDistance > 1) {
				// Find Polygons
				// 连续轮廓折线化
				Imgproc.approxPolyDP(contour2f, approxCurve, approxDistance, true);

				// Convert back to MatOfPoint
				MatOfPoint points = new MatOfPoint(approxCurve.toArray());

				// Rectangle Checks - Points, area, convexity
				// 矩形检测,4个顶点,凸边形,有一定的面积.
				if (points.total() == 4 && Math.abs(Imgproc.contourArea(points)) > 1000
						&& Imgproc.isContourConvex(points)) {
                    

                       //外测的四边形
                       rect = Imgproc.boundingRect(points);

					
						// 绘制边界轮廓
						Imgproc.drawContours(outContouMat, contours, i, new Scalar(255, 255, 0), 3);

如下,外侧4个顶点

 

透视变换

计算矩形变换数据,矩形-》边框的变化.

MatOfPoint2f rectMat_dest = new MatOfPoint2f(
								new Point[] { new Point(rect.x, rect.y), new Point(rect.x + rect.width, rect.y),
										new Point(rect.x + rect.width, rect.y + rect.height),
										new Point(rect.x, rect.y + rect.height), });

						// 确定 approxCurve 点的顺序
						List<Point> pts = new ArrayList<>();
						Point pt1 = new Point(approxCurve.get(0, 0));
						Point pt2 = new Point(approxCurve.get(1, 0));
						Point pt3 = new Point(approxCurve.get(2, 0));
						Point pt4 = new Point(approxCurve.get(3, 0));
						pts.add(pt1);
						pts.add(pt2);
						pts.add(pt3);
						pts.add(pt4);
						Point[] npts = sortRectPoints(pts);


						MatOfPoint2f rectMat_src = new MatOfPoint2f(new Point[] { npts[0], npts[1], npts[2], npts[3] });

						// 矩形变换 获取 规则->不规则
						Mat tp = Imgproc.getPerspectiveTransform(rectMat_dest, rectMat_src);
						tp.copyTo(transMat);

,然后对原图进行整体变换

	// 轮廓ok,变换原图

			Imgproc.warpPerspective(rectM2, realRect, transMat, rectM2.size(), Imgproc.INTER_LINEAR); // +
																										// Imgproc.WARP_INVERSE_MAP
				

这就是前文开始处理的图了>_<.

到此,所有的关键内容都切割出来了。下面就是识别的问题了.

 


 2018-09-11 16:41:45 
 2

  本文基于CC BY-NC-ND 4.0 许可协议发布,作者:野生的喵喵 固定链接: 【opencv-身份证识别-java处理】 转载请注明



发表新的评论
{{s_uid}}   , 欢迎回来.
您的称呼(*必填):
您的邮箱地址(*必填,您的邮箱地址不会公开,仅作为有回复后的消息通知手段):
您的站点地址(选填):
留言:

∑( ° △ °|||)︴

(๑•̀ㅂ•́)و✧
<( ̄) ̄)>
[]~( ̄▽ ̄)~*
( ̄ˇ ̄)
[]~( ̄▽ ̄)~*
( ̄ˇ ̄)
╮( ̄▽ ̄)╭
( ̄ε(# ̄)
(⊙ˍ⊙)
( ̄▽ ̄)~*
∑( ° △ °|||)︴

文章分类

可能喜欢 

KxのBook@Copyright 2017- All Rights Reserved
Designed and themed by 野生的喵喵   1344797   38296