基于opencv实现人脸检测

原文地址:

https://blog.csdn.net/qq_34902522/article/details/82464516


基于opencv实现人脸检测

opencv简述

opencv是一个开源的计算机视觉库,它有着C++,Python,Java等接口,支持Windows,Linux,Mac OS,IOS 和 Android平台.Opencv 是使用C/C++所写的,可以利用多核处理.通过OpenCL启用,它可以利用底层异构计算平台的硬件加速。关于Opencv的详细介绍可以去其官网查看.Opencv 官网

注意

1.如果对opencv还没有接触过的,可以先参考参考这篇文章,了解如何在Android项目接入opencv.android 接入opencv的3种方式 .建议结合Opencv 的Tutorials看,效果更加.
2.这边接入使用的opencv library是利用github上面opencv和opencv-contrib库里的源码来编译的适用于Android平台的库.这个库的地址在opencv+opencv-contrib-lib4Android 编的这个库是3.4.2版本,Android的各个平台都有.还是很全的.
如果想自己编译的话,可以参考我之前的文章编译opencv+opencv-contrib 遇到的坑 .
当然你也可以直接从opencv的官网下载人家已经编译好的库.但是从其官网下载的库内容不全,比如Tracker这一块的内容,就没有.(PS:后面会更一篇利用OpenCV实现物体追踪功能的文章。就是需要tracker)这是在opencv-contrib库里的.
那什么是opencv-contrib库?opencv-contrib库是一个额外库,里面包含的是一些较新,高级些的功能模块.

目标

利用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.我们进去可以看到:


这里写图片描述

我们发现,只要是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.

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

这里写图片描述

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

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

这里写图片描述

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

效果图

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

结语

关于使用OpenCV来进行人脸检测,就说到这,后期想到什么要补充的会再补充。人脸检测的实现,也写了一篇利用Firebase Vision ML Kit库来实现的文章,建议大家去看看,做做对比,根据需要选择合适的技术手段来实现。利用Google vision来实现人脸检测
演示demo地址如下。
OpencvFaceDetect

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容

  • 转载请注明出处(https://www.jianshu.com/p/5f538820e370),您的打赏是小编继续...
    福later阅读 26,704评论 8 70
  • 1 实验目的 目前计算机视觉技术已经比较成熟,相关的开源项目与算法很多,可以将这些开源算法进行整合,进而做成一个小...
    YOUNG_FAN阅读 6,670评论 0 50
  • 有一天晚上我梦见了雨 它落在了屋檐上耗尽之前所有的喧嚣像是一位匆匆过客却余音绕梁 它落在了田野上成为广袤中的无数鳞...
    不安分大叔阅读 189评论 6 3
  • 在昨晚她进屋洗澡,反锁门的时候,我就意识到。她已经把我拒之门外了。所有一切都证明,我还是在她心门之外。 我觉得再多...
    d3993c4b32f3阅读 156评论 0 0
  • 一只鸟, 落在输电线上, 四处张望, 塔杆下的我, 不知所措。
    野_方阅读 120评论 1 0