最近公司须要作一我的脸检测的新功能,在网上找了找,有不少开源的第三方库均可以用,例如OpenCV,虹软,Face++,百度,阿里等等。java
因为在APP的需求,只能本地检测,因此Face++,百度,阿里这些须要用HTTP进行网络请求返回结果的,只能舍弃了。集中研究OpenCV以及虹软。android
首先介绍一下虹软,这家公司开源了so库以及jar包,可直接下载集成到项目中,简单配置以后就可检测人脸,并且识别率仍是不错的。可借鉴此博客点击打开连接。详细教你在Android Studio中使用虹软检测以及识别人脸。网络
接下来说一下使用OpenCV开源库检测人脸。其实OpenCV很是强大,有兴趣的同窗能够去查阅一下。目前只讲一下使用OpenCV经过Camera动态检测人脸。OpenCV搭建流程可百度,内容不少,这里仅作简单说明。app
首先下载OpenCV4Android Demo,新建项目等操做省略,而后倒入OpenCV Samples中的face_detection项目,使用NDK编译检测的so库,倒入OpenCV SDK中的java Module到项目中,在app/src/main目录下新建jniLibs,复制sdk/native/libs/armeabi-v7a/libopencv_java3.so到jniLibs/armeabi-v7a中(可多选,arm,x86等,因为我只须要v7a便可,只倒入这个),复制sdk/native/jni/include到jniLibs下。可直接下载代码OpenCVJ。ide
到此搭建完工程,点击运行,发现只有横屏下才能正确检测到人脸,可是项目需求是在竖屏下检测人脸,怎么办,接着寻找答案,发现这篇博客OpenCV on Android 开发 (4)竖屏预览图像问题解决方法-续,在此多谢这位兄台的先驱行动,使用Core.rotate函数,最后一个参数填入Core.ROTATE_90_CLOCKWISE,旋转Gray Mat后可正确检测到人脸,返回MatOfRect,再把MatOfRect放入到mRgba中,再次经过Core.rotate函数,可是最后一个参数需填入Core.ROTATE_90_COUNTERCLOCKWISE,再返回到CameraBridgeViewBase中的deliverAndDrawFrame中,通过此操做后能够正确显示出人脸检测框。函数
使用上述方法就基本完成了。按照惯例,文章没有写完,确定会有可是的,没错,这里也有。post
可是:实际使用时,发现帧率只有10帧左右,彻底没法接受,整个页面都是卡顿的,怎么办,接着寻找方法。想到一个idea,既然是检测,我只须要OpenCV的检测功能,不须要OpenCV来本身画图,直接使用camera的预览效果,我只把人脸检测框画到预览图上面去就好,这样能够保证预览不卡顿,只是检测框可能要一点时间才能显示,这也是没法避免的了。优化
首先看xml文件: ` <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:opencv="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent">idea
<org.opencv.android.JavaCameraView android:id="@+id/fd_activity_surface_view" android:layout_width="match_parent" android:layout_height="match_parent" opencv:camera_id="back" /> <Button android:id="@+id/fd_switch_camera_view" android:layout_width="100dp" android:layout_height="100dp" android:layout_marginLeft="220dp" android:layout_marginRight="20dp" android:layout_marginTop="20dp"/> <ImageView android:id="@+id/fd_image_view" android:layout_width="match_parent" android:layout_height="match_parent"/> </RelativeLayout>
使用JavaCameraView开启Camera,id为fd_switch_camera_view为切换前置后置摄像头,id为fd_image_view用来显示人脸检测框。 再看FdActivity.java文件,只展现主要修改地方: ` @Override public Mat onCameraFrame(CvCameraViewFrame inputFrame) { mGray = inputFrame.gray(); Utils.bitmapToMat(mAlphaBitmap, mRgba); //使前置的图像也是正的 if (mOpenCvCameraView.getCameraIndex() == CameraBridgeViewBase.CAMERA_ID_FRONT) { Core.flip(mRgba, mRgba, 1); Core.flip(mGray, mGray, 1); } if (mAbsoluteFaceSize == 0) { int height = mGray.rows(); if (Math.round(height * mRelativeFaceSize) > 0) { mAbsoluteFaceSize = Math.round(height * mRelativeFaceSize); } if (mNativeDetector != null) { mNativeDetector.setMinFaceSize(mAbsoluteFaceSize); } } MatOfRect faces = new MatOfRect(); Core.rotate(mGray, gMatlin, Core.ROTATE_90_CLOCKWISE); Core.rotate(mRgba, Matlin, Core.ROTATE_90_CLOCKWISE); if (mNativeDetector != null) { mNativeDetector.detect(gMatlin, faces); } Rect[] faceArray = faces.toArray(); for (Rect rect : faceArray) { Imgproc.rectangle(Matlin, rect.tl(), rect.br(), new Scalar(0, 255, 0, 255), 2); } Core.rotate(Matlin, mRgba, Core.ROTATE_90_COUNTERCLOCKWISE); deliverAndDrawFrame(mRgba); return mRgba; } protected void deliverAndDrawFrame(Mat modified) { boolean bmpValid = true; if (modified != null) { try { Utils.matToBitmap(modified, mCacheBitmap, true); } catch (Exception e) { Log.e(TAG, "Mat type: " + modified); Log.e(TAG, "Bitmap type: " + mCacheBitmap.getWidth() + "*" + mCacheBitmap.getHeight()); Log.e(TAG, "Utils.matToBitmap() throws an exception: " + e.getMessage()); bmpValid = false; } } if (bmpValid && mCacheBitmap != null) { mHandler.post(new Runnable() { @Override public void run() { Matrix matrix = new Matrix(); // I rotate it with minimal process matrix.preTranslate((mViewWidth - mCacheBitmap.getWidth()) / 2, (mViewHeight - mCacheBitmap.getHeight()) / 2); matrix.postRotate(90f, (mViewWidth) / 2, (mViewHeight) / 2); float scale = (float) mViewWidth / (float) mCacheBitmap.getHeight(); matrix.postScale(scale, scale, mViewWidth / 2, mViewHeight / 2); // final Matrix matrix = new Matrix(); // I rotate it with minimal process // matrix.preTranslate((mViewWidth - mCacheBitmap.getWidth()) / 2, (mViewHeight - mCacheBitmap.getHeight()) / 2); // matrix.postRotate(90, mViewWidth / 2, mViewHeight / 2); // float scale = (float) mViewWidth / (float) mViewHeight; // matrix.postScale(scale, scale, mViewWidth / 2, mCacheBitmap.getHeight() / 2); Bitmap bitmap = Bitmap.createBitmap(mCacheBitmap, 0, 0, mCacheBitmap.getWidth(), mCacheBitmap.getHeight(), matrix, false); mImageView.setImageBitmap(bitmap); } }); } }
上面两部分代码都为FdActivity中,将MatOfRect贴到Mat中,再转换成Bitmap显示到ImageView中。code
JavaCameraView.java的修改:
initializeCamera函数增长
mCamera.setPreviewDisplay(getHolder());
mCamera.setDisplayOrientation(90);
CameraBridgeBaseView.java的修改 ` protected void deliverAndDrawFrame(CvCameraViewFrame frame) { Mat modified;
if (mListener != null) { modified = mListener.onCameraFrame(frame); } else { modified = frame.rgba(); }
}
只须要调用onCameraFrame的回调便可,不用再贴图了。 到此基本完成。 后续还须要优化,等优化完再更新。。。。 ----------更新更新----------- 回调onCameraFrame函数时,将Mat先缩小,而后再开始检测,能够大幅提高检测速度。 使用Imgproc.resize()函数便可,具体使用可网上查阅。