美文网首页
OpenCV4人脸检测功能 Android

OpenCV4人脸检测功能 Android

作者: 如果天空不蓝 | 来源:发表于2019-04-04 15:38 被阅读0次

    最近公司需要做一个人脸检测的新功能,在网上找了找,有很多开源的第三方库都可以用,例如OpenCV,虹软,Face++,百度,阿里等等。

    由于在APP的需求,只能本地检测,所以Face++,百度,阿里这些需要用HTTP进行网络请求返回结果的,只能舍弃了。集中研究OpenCV以及虹软。

    首先介绍一下虹软,这家公司开源了so库以及jar包,可直接下载集成到项目中,简单配置之后就可检测人脸,而且识别率还是不错的。可借鉴此博客点击打开链接。详细教你在Android Studio中使用虹软检测以及识别人脸。

    接下来讲一下使用OpenCV开源库检测人脸。其实OpenCV非常强大,有兴趣的同学可以去查阅一下。目前只讲一下使用OpenCV通过Camera动态检测人脸。OpenCV搭建流程可百度,内容很多,这里仅做简单说明。

    首先下载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。

    到此搭建完工程,点击运行,发现只有横屏下才能正确检测到人脸,但是项目需求是在竖屏下检测人脸,怎么办,接着寻找答案,发现这篇博客OpenCV on Android 开发 (4)竖屏预览图像问题解决方法-续,在此多谢这位兄台的先驱行动,使用Core.rotate函数,最后一个参数填入Core.ROTATE_90_CLOCKWISE,旋转Gray Mat后可正确检测到人脸,返回MatOfRect,再把MatOfRect放入到mRgba中,再次通过Core.rotate函数,但是最后一个参数需填入Core.ROTATE_90_COUNTERCLOCKWISE,再返回到CameraBridgeViewBase中的deliverAndDrawFrame中,经过此操作后可以正确显示出人脸检测框。

    使用上述方法就基本完成了。按照惯例,文章没有写完,肯定会有但是的,没错,这里也有。

    但是:实际使用时,发现帧率只有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">
     
        <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中。

    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()函数即可,具体使用可网上查阅。

    相关文章

      网友评论

          本文标题:OpenCV4人脸检测功能 Android

          本文链接:https://www.haomeiwen.com/subject/fafqiqtx.html