Android(安卓)通过opencv实现人脸识别,追踪
title: Android 通过opencv实现人脸识别,追踪
categories:
- Android
tags: - opencv
- 人脸识别
- 人脸追踪
date: 2020-05-29 10:11:41
本人博客转载去标明原文
前言
好了,上篇文章讲了如何进行原生的人脸识别,检测,追踪等,相信玩过的肯定已经有了感觉,今天我们用opencv来实现,
那么很多人会问,原生都实现了,为什么还要接opencv的方式来实现,那么下面看完大家应该就会清楚
正文
导入opencv引用
首先,opencv的接入方式有几种
1.自己编译需要的模块生成so库,然后ndk接入
2.接入官网编译好的ndk,用C/C++来写功能
3.直接接入官网library sdk,
今天我们讲第三种,后续研究下载opencv2d转3d,目标是实现所有机型,前置摄像头精确出人脸到屏幕的距离
opencv 认准android-sdk.zip下载就好了
下载后解压
讲该图片中java导入项目中,作为library
更改build
apply plugin: 'com.android.library'android { compileSdkVersion 29 buildToolsVersion "29.0.2" defaultConfig { minSdkVersion 21 targetSdkVersion 29 } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' } }}
Sdk版本与项目的保持一直即可
然后在app中引用
implementation project(path: ':CVLibrary430')
opencv初始化
我这里是写在onResume 里面需要用initDebug
@Override public void onResume() { super.onResume(); //初始化opencv资源 if (!OpenCVLoader.initDebug()) { Log.d("OpenCV", "Internal OpenCV library not found. Using OpenCV Manager for initialization"); boolean success = OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION, this, openCVLoaderCallback); if (!success) Log.e("OpenCV", "Asynchronous initialization failed!"); else Log.d("OpenCV", "Asynchronous initialization succeeded!"); } else { Log.d("OpenCV", "OpenCV library found inside package. Using it!"); openCVLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS); } }
然后是监听部分的
LoaderCallbackInterface openCVLoaderCallback = new LoaderCallbackInterface() { @Override public void onManagerConnected(int status) { if (status == LoaderCallbackInterface.SUCCESS) { Log.i(TAG, "OpenCV loaded successfully"); initOpencv(); } } @Override public void onPackageInstall(int operation, InstallCallbackInterface callback) { Log.d("OpenCV", "onPackageInstall " + operation); } };
但是你可能会发现你初始化失败了,此处我们还需要修改app下面的build----android{}内
externalNativeBuild { cmake {// 我们配置cmake命令// cppFlags "" arguments "-DANDROID_STL=c++_shared" } } ndk { abiFilters 'armeabi-v7a', 'x86' }
然后这里用cmake但是,不用c++的可能不需要配置
externalNativeBuild { cmake { path "src/main/cpp/CMakeLists.txt" version "3.10.2" } } sourceSets { main {// jni.srcDirs = [] jniLibs.srcDirs = ['libs'] } }
dummy.cpp 是空的,暂时没用到 到这我们可以发现opencv已经初始化成功了,我们可以愉快的开始使用了
初始化分类起initOpcv
protected void initOpencv() { try { //OpenCV的人脸模型文件: haarcascade_frontalface_alt InputStream is = getResources().openRawResource(R.raw.haarcascade_frontalface_alt); File cascadeDir = getDir("cascade", Context.MODE_PRIVATE); File mCascadeFile = new File(cascadeDir, "haarcascade_frontalface_alt.xml"); FileOutputStream os = new FileOutputStream(mCascadeFile); byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = is.read(buffer)) != -1) { os.write(buffer, 0, bytesRead); } is.close(); os.close(); // 加载 人脸分类器 mFrontalFaceClassifier = new CascadeClassifier(mCascadeFile.getAbsolutePath()); } catch (Exception e) { Log.e(TAG, e.toString()); } openCvCameraView.enableView(); }
这里面我们看到用了一个R.raw.haarcascade_frontalface_alt, 这里我们可以去刚才下载的opencv包里面找到,具体位置在第一篇
文章里面可以看到截图,此处是为了加载分类器,也就是我理解的所谓人脸模型数据,用来对我们的图片做对比
代码引用
布局代码需要引用
<?xml version="1.0" encoding="utf-8"?>
然后初始化布局后,初始摄像头,代码如下
protected void initCamera() { openCvCameraView.setCameraPermissionGranted(); //该方法用于判断权限后,自行设置,opencv430版本新改的逻辑 openCvCameraView.setCameraIndex(CameraBridgeViewBase.CAMERA_ID_FRONT); //摄像头索引 设置 openCvCameraView.setCvCameraViewListener(this);//监听 openCvCameraView.setVisibility(SurfaceView.VISIBLE); openCvCameraView.setCameraDistance(1.5f); // 设置焦距 openCvCameraView.setMaxFrameSize(640, 480);//设置帧大小 }
监听是CameraBridgeViewBase.CvCameraViewListener2的方法在回调中我们可以收到相机获取到的数据,以此来做处理
首先是start
@Override public void onCameraViewStarted(int width, int height) { Log.d("camera","---onCameraViewStarted" + width); mRgba = new Mat(); mGray = new Mat(); Matlin = new Mat(width, height, CvType.CV_8UC4); gMatlin = new Mat(width, height, CvType.CV_8UC4); matWidth = width; absoluteFaceSize = (int)(height * 0.2); }
然后记得在stop的时候释放,我们创建的mat(opencv中)对象
@Override public void onCameraViewStopped() { Log.d("camera","---onCameraViewStopped"); mRgba.release(); mGray.release(); Matlin.release(); gMatlin.release(); }
然后是onCameraFrame return 的mat是你画面显示的mat此处的灰度通道十分简单,直接个可以获取
但需要注意的是mat的方向如果不是正向会导致检测不到人脸,所以此处需要做一个旋转
@Override public Mat onCameraFrame(CameraBridgeViewBase.CvCameraViewFrame inputFrame) { mRgba = inputFrame.rgba(); //RGBA mGray = inputFrame.gray(); //单通道灰度图 int rotation = openCvCameraView.getDisplay().getRotation(); double area = 0; double width = 0; MatOfRect frontalFaces = new MatOfRect(); switch (rotation){ case Surface.ROTATION_0: mRgba = Matutils.rotate(mRgba,90); mGray = Matutils.rotate(mGray,90); break; case Surface.ROTATION_90: break; case Surface.ROTATION_180: mRgba = Matutils.rotate(mRgba,270); mGray = Matutils.rotate(mGray,270); break; case Surface.ROTATION_270: mRgba = Matutils.rotate(mRgba,180); mGray = Matutils.rotate(mGray,180); break; } if (mFrontalFaceClassifier != null) { //这里2个 Size 是用于检测人脸的,越小,检测距离越远,1.1, 5, 2, m65Size, mDefault着四个参数可以提高检测的准确率,5表示确认五次,具体百度 detectMultiScale 这个方法 mFrontalFaceClassifier.detectMultiScale(mGray, frontalFaces, 1.1, 2, 2, new Size(absoluteFaceSize, absoluteFaceSize), mDefault); mFrontalFacesArray = frontalFaces.toArray(); if (mFrontalFacesArray.length > 0) { area = mFrontalFacesArray[0].area(); width = mFrontalFacesArray[0].width; Log.i(TAG, "1 : " + mFrontalFacesArray.length); Log.i(TAG, "1 : " + mFrontalFacesArray[0].size()); Log.i(TAG, "1 : " + mFrontalFacesArray[0].area()); Log.i(TAG, "1 : " + mFrontalFacesArray[0].tl()); Log.i(TAG, "1 : " + mFrontalFacesArray[0].br()); } mCurrentFaceSize = mFrontalFacesArray.length; } if (mCurrentFaceSize > 0){ for (int i = 0; i < mFrontalFacesArray.length; i++) { //用框标记 Imgproc.rectangle(mRgba, mFrontalFacesArray[i].tl(), mFrontalFacesArray[i].br(), new Scalar(0, 255, 0, 255), 3); } } //显示检测到的人数 double distence = (1 + 153 * openCvCameraView.getWidth() / width / 36 ) * 30 * 1.5; double areas = area/openCvCameraView.getScale(); Log.i(TAG, "openCvCameraView : " + openCvCameraView.getWidth()); Log.i(TAG, "openCvCameraView : " + openCvCameraView.getHeight()); Log.i(TAG, "openCvCameraView : " + openCvCameraView.getScaleX()); Log.i(TAG, "openCvCameraView : " + openCvCameraView.getScaleY()); mHandler.postDelayed(new Runnable() { @SuppressLint("SetTextI18n") @Override public void run() { mFrontalFaceNumber.setText(areas + "mm2"); mProfileFaceNumber.setText("CameraDistance:" + mRgba.width() + mRgba.height()); mCurrentNumber.setText("distence:" + distence + "mm"); mWaitTime.setText( ""); } }, 0); return mRgba; }
此处也用到一个旋转的工具类
public static Mat rotate(Mat src, double angele) { Mat dst = src.clone(); Point center = new Point(src.width() / 2.0, src.height() / 2.0); Mat affineTrans = Imgproc.getRotationMatrix2D(center, angele, 1.0); Imgproc.warpAffine(src, dst, affineTrans, dst.size(), Imgproc.INTER_NEAREST); return dst; }
然后你就可以跑起来看效果了
结语
笔者做这个目的是做人脸到屏幕距离的检测,但是这里我们可以获取到双额的距离,但是对于测算公式需要用到,焦距,全画幅
有效焦距等,由于没有api的提供,获取不到实际焦距,而安卓机型太多所以此处中断了
后续会更新使用arcroe实现测距,还有opencv的2d模型转3d来实现测算的思路
有问题的欢迎评论或者私聊笔者
更多相关文章
- 自定义控件:onDraw 方法实现仿 iOS 的开关效果
- 【高通SDM660平台 Android(安卓)10.0】(20) --- Actuator 与 Ker
- android ListView内容无限循环显示
- Android(安卓)TTS 使用教程
- tess_two Android图片文字识别
- Android(安卓)OpenCV java.lang.UnsatisfiedLinkError n_mat
- 1. Android启动过程
- Android菜单定制总结
- 实现导航栏的左右滑动效果