这一次Android使用opencv开发身份证大小卡片识别的原因主要还是因为之前项目中有实名认证的一个需求,因为当时没有这方面的经验,所以使用第三方的框架进行实名认证。
在我对接第三方框架时我发现这些框架普遍收费,并且收费还不低。那时我就幻想着要是有朝一日我掌握了这项技巧,并且拿来卖的话······😏我岂不是······,不好意思,走远了。
秉着学习的态度查阅了诸多资料,了解到有这么一款开源框架能帮忙处理图像的检测?识别?其实我也不清楚,反正就是大概这样的意思,那就是今天我要说的东西,当当当当——OpenCv。
项目地址:IDCardCheck
废话不多说,先上一张效果图,俗话说的好的,无图言屌。
-
开发环境:Android Studio 3.4
-
1、首先前往opencv官网选择Release 下载OpenCV – 4.1.0 Android版本。
-
2、解压到当前目录。
-
3、使用Android Studio创建一个Empty Activity的Demo。
-
4、点击File->New->Import Module,选择opencv目录下sdk/build.gradle文件进行导入(导入opencv库的目的是为了使开发的app脱离opencv-manager)
-
5、复制opencv目录下sdk/native/libs中对应cpu的文件夹到Demo的libs下
-
6、在MainActivity中onResume中执行以下代码用于初始化opencv。
if (!OpenCVLoader.initDebug()) { Log.d(TAG, "Internal OpenCV library not found. Using OpenCV Manager for initialization"); OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION, this, mLoaderCallback); } else { Log.d(TAG, "OpenCV library found inside package. Using it!"); mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS); }
-
7、在xml中设置opencv自带Camera JavaCameraView并初始化,
<org.opencv.android.JavaCameraView android:id="@+id/cameraView" android:layout_width="match_parent" android:layout_height="300dp" app:camera_id="back" />
-
MainActivity需实现CameraBridgeViewBase.CvCameraViewListener2。
mOpenCvCameraView.setVisibility(CameraBridgeViewBase.VISIBLE); mOpenCvCameraView.setCvCameraViewListener(this);
-
@Override public void onCameraViewStarted(int width, int height) { mGray = new Mat(); mRgba = new Mat(); mFilp = new Mat(width, height, CvType.CV_8UC4); mTranspose = new Mat(height, width, CvType.CV_8UC4); } @Override public void onCameraViewStopped() { mGray.release(); mRgba.release(); } @Override public Mat onCameraFrame(CameraBridgeViewBase.CvCameraViewFrame inputFrame) { //用于显示原有的画面 mRgba = inputFrame.rgba(); //灰度处理过的画面,用于后续使用 mGray = inputFrame.gray(); //因为准备进行正反面识别的(未实现)因此有判断 if (isFront&&!isIdCardFrontGet){ runOnUiThread(() -> { Bitmap bitmap = IDCardCheckUtils.getInstance().checkIDCard(isFront,inputFrame); if (bitmap != null) { imageViewF.setImageBitmap(bitmap); isIdCardFrontGet = true; } }); }else if (!isFront&&!isIdCardBackGet){ runOnUiThread(() -> { Bitmap bitmap = IDCardCheckUtils.getInstance().checkIDCard(isFront,inputFrame); if (bitmap != null) { imageViewB.setImageBitmap(bitmap); isIdCardBackGet = true; } }); } return mRgba; }
-
8、接下来就是对JavaCameraView返回的灰度的Mat即上面的mGray进行处理得到Bitmap需使用到opencv的高斯滤波和边缘检测以及轮廓检测 借鉴上述链接文章侵删。
Mat mGray = inputFrame.gray(); Mat blur = new Mat(); Mat canny = new Mat(); List
matOfPoint = new ArrayList<>(); Mat hierarchy = new Mat(); Imgproc.GaussianBlur(mGray, blur, new Size(5, 5), 0); Imgproc.Canny(blur, canny, 50, 100, 3, false); Imgproc.findContours(canny, matOfPoint, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_NONE); -
以上方法得到轮廓列表matOfPoint,使用contourArea方法计算轮廓面积取出最大面积轮廓。
double maxVal = 0; int maxValIdx = 0; for (int contourIdx = 0; contourIdx < matOfPoint.size(); contourIdx++) { double contourArea = Imgproc.contourArea(matOfPoint.get(contourIdx)); if (maxVal < contourArea) { maxVal = contourArea; maxValIdx = contourIdx; } }
-
接着使用遍历取出轮廓的位置信息,并根据具体的卡片需要在摄像头中显示的大小调试出自己想要的轮廓的point的数量范围以及轮廓的面积范围,再调用opencv中matToBitmap方法将Mat转化为bitmap并进行裁剪返回
if (matOfPoint.size() != 0) { double contourArea = Imgproc.contourArea(matOfPoint.get(maxValIdx)); List
points = matOfPoint.get(maxValIdx).toList(); double x, y, width, height; width = height = 0; x = points.get(0).x; y = points.get(0).y; for (int i = 0; i < points.size(); i++) { if (points.get(i).x > width) { width = points.get(i).x; } if (points.get(i).x < x) { x = points.get(i).x; } if (points.get(i).y > height) { height = points.get(i).y; } if (points.get(i).y < y) { y = points.get(i).y; } } width = width - x; height = height - y; int finalMaxValIdx = maxValIdx; double finalHeight = height; mFinalHeight = height; double finalY = y; double finalWidth = width; mFinalWidth = width; double finalX = x; if (points.size() > 1200 && points.size() < 1450 && contourArea > 105000f && contourArea < 130000f) { if (height > 430 && height < 466 && width > 260 && width < 286) { Bitmap bitmap2 = Bitmap.createBitmap(mRgba.cols(), mRgba.rows(), Bitmap.Config.RGB_565); //主要影响最后bitmap显示 2选1即可 可能要考虑下oom //Bitmap bitmap3 = Bitmap.createBitmap(mRgba.cols(), mRgba.rows(), Bitmap.Config.ARGB_8888); Utils.matToBitmap(mRgba, bitmap2); //Utils.matToBitmap(mRgba, bitmap3); Bitmap bitmap = Bitmap.createBitmap(bitmap2, (int) finalX, (int) finalY, (int) (finalWidth), (int) (finalHeight), null, false); bitmap2.recycle(); //bitmap3.recycle(); return bitmap; } } }
总结
以上就是我使用opencv进行身份证类大小的卡片进行检测的一个记录,本来打算使用人脸检测或者第三方ocr增加检测正反面的功能,但是懒癌当场发作差点去世,所以就没有进行了,请各位勿怪。
第一次写这种文章可能写的不是很好,而且代码可能不是很简洁,表述也不是很清楚,请大佬们指点,谢谢大家捧场。