为了账号安全,请及时绑定邮箱和手机立即绑定

基于opencv实现人脸检测

标签:
Java

目标

利用Opencv的分类器CascadeClassifier,对从Camera读取的yuv数据进行实时检测,并且把结果显示在屏幕上.

CascadeClassifier介绍#####

CascadeClassifier不仅仅可以检测人脸,也可以检测眼睛,身体,嘴巴等.通过加载一个想要检测的.xml的分类器文件就可以.

开撸####

要实现在相机预览画面的实时人脸检测,那么关于Android Camera的部分肯定要先弄起来.这边为了方便演示,简单封装了一个CameraModule库.来实现相机预览,Camera 数据回调.

            CameraApi.getInstance().setCameraId(CameraApi.CAMERA_INDEX_BACK);            CameraApi.getInstance().initCamera(this, this);            CameraApi.getInstance().setPreviewSize(new Size(previewWidth, previewHeight));            CameraApi.getInstance().setFps(30).configCamera();            CameraApi.getInstance().startPreview(holder);

在成功获取Camera 的preview数据之后,我们开始处理opencv部分的逻辑.
1.首先我们需要创建CascadeClassifier.
在Activity的onResume回调中,先加载Opencv的library.

@Override
    protected void onResume() {        super.onResume();        if (!OpenCVLoader.initDebug()) {
            Log.d(TAG, "Internal OpenCV library not found. Using OpenCV Manager for initialization");
            OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_4_0, this, mLoaderCallback);
        } else {
            Log.d(TAG, "OpenCV library found inside package. Using it!");
            mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
        }
    }

在加载成功的回调中,创建需要的属于opencv的对象.代码如下:

private LoaderCallbackInterface mLoaderCallback = new LoaderCallbackInterface() {        @Override
        public void onManagerConnected(int status) {            if (status == LoaderCallbackInterface.SUCCESS) {
                init();
                isLoadSuccess = true;                try {                    // load cascade file from application resources
                    InputStream is = getResources().openRawResource(R.raw.lbpcascade_frontalface);
                    File cascadeDir = getDir("cascade", Context.MODE_PRIVATE);
                    mCascadeFile = new File(cascadeDir, "lbpcascade_frontalface.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();

                    mFaceCascade = new CascadeClassifier(mCascadeFile.getAbsolutePath());                    if (mFaceCascade.empty()) {
                        Log.e(TAG, "Failed to load cascade classifier");
                        mFaceCascade = null;
                    } else {
                        Log.e(TAG, "Loaded cascade classifier from " + mCascadeFile.getAbsolutePath());
                    }


                    cascadeDir.delete();

                } catch (IOException e) {
                    e.printStackTrace();
                    Log.e(TAG, "Failed to load cascade. Exception thrown: " + e);
                }


            }
        }        @Override
        public void onPackageInstall(int operation, InstallCallbackInterface callback) {

        }
    };
private void init() {
        mSrcMat = new Mat(previewHeight, previewWidth, CvType.CV_8UC1);
        mDesMat = new Mat(previewHeight, previewWidth, CvType.CV_8UC1);
        matOfRect = new MatOfRect();
        initQueue();
    }

我们分析一下上面的代码,首先我们创建了一些必须的对象,比如Mat对象.
Mat - The Basic Image Container 在opencv里,对图像的处理,大都是先把图像数据转化成Mat对象,Mat对象就像是一个容器,对图像的处理就是对Mat的处理.
然后,我们从raw文件夹里读取了opencv训练好的,用于检测人脸的分类器文件lbpcascade_frontalface.xml.xml分类器文件,可以从opencv下载的包里面找到.你会发现,里面有两种类型的分类器文件,一种是haar的,一种是lbp的.关于这两种的不同.可以参考haar-vs-lbp .
这里我们选择的是lbp的.分类器文件获取到后,我们通过分类器文件,创建了我们想要的CascadeClassifier.

2.对相机预览数据的处理
这边设置的预览的FPS是30,因为人脸检测是耗时操作,为了不影响预览的画面流畅度.这边采用了,子线程+队列的处理方式.通过创建两个队列,来保证对相机数据的管理.

@Override
    public void onPreviewFrameCallback(byte[] data, Camera camera) {
        mCamera.addCallbackBuffer(data);        if (isStart) {
            CameraRawData rawData = mFreeQueue.poll();            if (rawData != null) {
                rawData.setRawData(data);
                rawData.setTimestamp(System.currentTimeMillis());
                mFrameQueue.offer(rawData);
            }
        }

    }

3.从队列获取数据,进行检测

/**
     * face detect thread
     */
    private class DetectThread extends Thread {
        DetectThread(String name) {            super(name);
        }        @Override
        public void run() {            super.run();            while (isStart && isLoadSuccess) {                synchronized (mLock) {                    try {
                        mCameraRawData = mFrameQueue.poll(20, TimeUnit.MILLISECONDS);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }                    if (mCameraRawData == null) {                        continue;
                    }
                    frameDatas = mCameraRawData.getRawData();
                    mSrcMat.put(0, 0, frameDatas);
                    Imgproc.cvtColor(mSrcMat, mDesMat, Imgproc.COLOR_YUV2GRAY_420);
                    mFaceCascade.detectMultiScale(mDesMat, matOfRect, 1.1, 5
                            , 2, mMinSize, mMaxSize);                    if (matOfRect.toArray().length != 0) {
                        Rect rect = getBiggestFace(matOfRect.toArray());
                        mResultView.showFace(rect);
                    } else {
                        mResultView.clear();
                    }
                    mFreeQueue.offer(mCameraRawData);
                    mCamera.addCallbackBuffer(frameDatas);
                }

            }
        }
    }

如上面的代码所示,我们通过创建子线程,在里面进行face detect处理.
首先我们从队列中获取Camera data.接着为mSrcMat对象赋值.接着利用Opencv 的convert方法,对源数据进行灰度化处理.

 Imgproc.cvtColor(mSrcMat, mDesMat, Imgproc.COLOR_YUV2GRAY_420);

这里主要看下第三个参数.因为我这边设置的image format 是NV21 ,所用了这个YUV420 to Gray的flag.我们进去可以看到:


webp

这里写图片描述


我们发现,只要是YUV420的无论是P还是SP都是用的同一个Flag.还挺省事,省的格式转化了O(∩_∩)O.

我们接着看这行代码:

mFaceCascade.detectMultiScale(mDesMat, matOfRect, 1.1, 5
                            , 2, mMinSize, mMaxSize);

这就是检测的核心代码,这里说一下各个参数所代表的含义.
Parameters
image   Matrix of the type CV_8U containing an image where objects are detected.
objects Vector of rectangles where each rectangle contains the detected object, the rectangles may be partially outside the original image.
scaleFactor Parameter specifying how much the image size is reduced at each image scale.
minNeighbors    Parameter specifying how many neighbors each candidate rectangle should have to retain it.
flags   Parameter with the same meaning for an old cascade as in the function cvHaarDetectObjects. It is not used for a new cascade.
minSize Minimum possible object size. Objects smaller than that are ignored.
maxSize Maximum possible object size. Objects larger than that are ignored. If maxSize == minSize model is evaluated on single scale.

参数的含义,来自官网,懒得翻译。

webp

这里写图片描述


这里主要说下minNeighbors,minSize,maxSize.这三个参数.
通过这三个参数可以控制检测的精确度.
minNeighbors 值越大,检测的准确度越高,不过耗时也越久.酌情调整.
minSize 可以根据Screen 尺寸的一定比例来设置,别设置太小,不然会有一些错误干扰结果.
maxSize 最大可检测尺寸,酌情调整.


接着往下看,检测结果出来后,是一个rect集合,检测到的每一张脸是一个矩形....O__O "…
这边做的处理选择了一张脸最大的来显示.

webp

这里写图片描述


现在我们把代码跑起来,看一下效果:

webp

效果图

通过效果gif演示,我们可以很明显的看到,成功的检测到了人脸。这里要说个缺点,就是使用OpenCV的CascadeClassifier进行人脸检测,检测的结果是返回一个MatOfRect对象,可理解为rectangle集合,意思是只记录着检测到脸的位置。并没有其他的信息了,并不能记住识别人脸。注意人脸识别人脸检测的区别。关于OpenCV的人脸识别(FaceRecognizer)需要对目标先进行数据训练,训练好才能对目标成功识别。关于OpenCV人脸识别的内容,这边先不说了,之后的文章再讨论。



作者:MrYangY
链接:https://www.jianshu.com/p/ec18cae7454d


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消