Android for OpenCV 调用 CameraAPI 1 实现人脸检测

对于人脸检测,我们第一想到的肯定是利用相机功能,但是android我们都很了解,android从5.0之后 camera 有两套API分别是 CameraAPI 1 和 CameraAPI 2,希望大家还是先去熟悉以下Camera 1/2 的用法然后再来看本章,会更好理解Open cv 调用 Camera的流程。在OpenCV中已经为我们封装了两个类 JavaCameraView 和 JavaCamera2View 顾名思义,JavaCameraView 封装的就是我们的 Camera 1,另外一个就是2。今天我们主要讲讲 OpenCV 调用 Camera 1 的流程,下一篇记录OpenCV 调用Camera 2 的流程。

  1. 级联分类器
    (百度百科)首先我们要理解一个名词‘级联分类器’(Cascade Classifier),OpenCV 中人脸检测是基于Harr的级联分类和LBP的级联分类。
    Harr是在2001年,由Viola和Jones等人提出的,它的脸部检测的基本思想是:对于面部正面的大部分区域而言,会有眼睛所在的区域比前额和脸颊更暗,嘴巴应该比脸颊更暗等情况。和这样类似的比较大约有20个,通过这样的比较决定该区域是否为人脸。 
    LBP是在2006年由Ahonen等人提出的,相比于Harr,LBP有更快的速度。通过比较想读亮度直方图来确定是否为人脸。但是对于稳定性,LBP要弱于前者。
    OpenCV中提供了 Harr 和 LBP 两种分类器,我们主要使用的是LBP,获取LBP 的 xml 文件:

  2. 用LBP级联分类器实现人脸检测
    先把代码具体实现流程梳理一遍,然后再针对代码流程去了解 OpenCV 和 我们的 Camera 是如何联系的。
    1.首先我们把上面提到的 lbpcascade_frontalface.xml 文件拷贝到我们 raw 下。


    2.创建我们级联分类器和Camera对象,级联分类器英文:Cascade Classifier 所以OpenCV 给我们的类名是CascadeClassifier
        <org.opencv.android.JavaCameraView
            android:id="@+id/camera"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    

    3.利用流的形式把 raw 下的文件拷贝到我们的程序中,用CascadeClassifier对象加载
        private void initClassifier() {
            InputStream is = getResources().openRawResource(R.raw.lbpcascade_frontalface);
            File cascadeDir = getDir("cascade", Context.MODE_PRIVATE);
            File mCascadeFile = new File(cascadeDir, "lbpcascade_frontalface.xml");
            FileOutputStream os = null;
            try {
                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();
                cascadeClassifier = new CascadeClassifier(mCascadeFile.getAbsolutePath());
            } catch (Exception e) {
                e.printStackTrace();
            }
            mCameraView.enableView();
        }

    4.利用OpenCV提供的camera 对象获取frame帧数,显示在screen上,具体显示流程接下来会把流程图画出
     
     mCameraView = findViewById(R.id.camera);
            mCameraView.setCvCameraViewListener(new CameraBridgeViewBase.CvCameraViewListener() {
                @Override
                public void onCameraViewStarted(int width, int height) {
                    grayscaleImage = new Mat(height, width, CvType.CV_8UC4);
                    absoluteFaceSize = (int) (height * 0.2);
                }
    
                @Override
                public void onCameraViewStopped() {
    
                }
    
                @Override
                public Mat onCameraFrame(Mat aInputFrame) {
                    Imgproc.cvtColor(aInputFrame, grayscaleImage, Imgproc.COLOR_RGBA2RGB);
                    MatOfRect faces = new MatOfRect();
                    if (cascadeClassifier != null) {
                        cascadeClassifier.detectMultiScale(grayscaleImage, faces, 1.1, 2, 2,
                                new Size(absoluteFaceSize, absoluteFaceSize), new Size());
                    }
                    Rect[] facesArray = faces.toArray();
                    for (int i = 0; i <facesArray.length; i++)
                        Imgproc.rectangle(aInputFrame, facesArray[i].tl(), facesArray[i].br(), new Scalar(0, 255, 0, 255), 3);
                    return aInputFrame;
                }
            });
    到此代码就这么多,详细代码地址:https://github.com/WangRain1/OpencvDemo
  3. OpenCV 中 JavaCameraView 如何调用 Camera API 1 流程分析
     
    首先重要类的集成关系: 

    可以看到 OpenCV 给我们提供的 JavaCameraView 和 JavaCamera2View 最终都是继承 SurfaceView 的
    CameraBridgeViewBase 就相当于一个桥梁,具体起到什么作用
    根据上述代码,我们流程从 setCvCameraViewListener() 接口开启:
    首先通过这个接口会会创建一个 CvCameraViewListenerAdapter 对象
      public void setCvCameraViewListener(CvCameraViewListener listener) {
            CvCameraViewListenerAdapter adapter = new CvCameraViewListenerAdapter(listener);
            adapter.setFrameFormat(mPreviewFormat);
            mListener = adapter;
        }
    然后 CvCameraViewListenerAdapter 的代码:
    
        protected class CvCameraViewListenerAdapter implements CvCameraViewListener2  {
            public CvCameraViewListenerAdapter(CvCameraViewListener oldStypeListener) {
                mOldStyleListener = oldStypeListener;
            }
            public void onCameraViewStarted(int width, int height) {
                mOldStyleListener.onCameraViewStarted(width, height);
            }
            public void onCameraViewStopped() {
                mOldStyleListener.onCameraViewStopped();
            }
            public Mat onCameraFrame(CvCameraViewFrame inputFrame) {
                 Mat result = null;
                 switch (mPreviewFormat) {
                    case RGBA:
                        result = mOldStyleListener.onCameraFrame(inputFrame.rgba());
                        break;
                    case GRAY:
                        result = mOldStyleListener.onCameraFrame(inputFrame.gray());
                        break;
                    default:
                        Log.e(TAG, "Invalid frame format! Only RGBA and Gray Scale are supported!");
                };
    
                return result;
            }
            public void setFrameFormat(int format) {
                mPreviewFormat = format;
            }
            private int mPreviewFormat = RGBA;
            private CvCameraViewListener mOldStyleListener;
        };
    可以看到只是 CvCameraViewListener2 的接口实现类,其中构造方法中传入了 CvCameraViewListener 然后调用
    CvCameraViewListener 的方法,我们明白了,就是为了用 CvCameraViewListener2 转化以下让我们在CameraBridgeViewBase 中只需写一份代码。就是 Camera 1 和 Camera 2 通用。
    
    

     
  4. 总结
    看代码的时候我们可以结合流程图看,方便理解。
    gitHub:
    https://github.com/WangRain1/OpencvDemo opencv的所有代码都在着一个demo里
    下一章学习 opencv 和 camera2 配合。
     
发布了119 篇原创文章 · 获赞 140 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/WangRain1/article/details/89670831
今日推荐