对于人脸检测,我们第一想到的肯定是利用相机功能,但是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 的流程。
-
级联分类器
(百度百科)首先我们要理解一个名词‘级联分类器’(Cascade Classifier),OpenCV 中人脸检测是基于Harr的级联分类和LBP的级联分类。
Harr是在2001年,由Viola和Jones等人提出的,它的脸部检测的基本思想是:对于面部正面的大部分区域而言,会有眼睛所在的区域比前额和脸颊更暗,嘴巴应该比脸颊更暗等情况。和这样类似的比较大约有20个,通过这样的比较决定该区域是否为人脸。
LBP是在2006年由Ahonen等人提出的,相比于Harr,LBP有更快的速度。通过比较想读亮度直方图来确定是否为人脸。但是对于稳定性,LBP要弱于前者。
OpenCV中提供了 Harr 和 LBP 两种分类器,我们主要使用的是LBP,获取LBP 的 xml 文件: - 用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上,具体显示流程接下来会把流程图画出
到此代码就这么多,详细代码地址:https://github.com/WangRain1/OpencvDemomCameraView = 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; } });
- 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 的代码:
可以看到只是 CvCameraViewListener2 的接口实现类,其中构造方法中传入了 CvCameraViewListener 然后调用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; };
CvCameraViewListener 的方法,我们明白了,就是为了用 CvCameraViewListener2 转化以下让我们在CameraBridgeViewBase 中只需写一份代码。就是 Camera 1 和 Camera 2 通用。
- 总结
看代码的时候我们可以结合流程图看,方便理解。
gitHub:https://github.com/WangRain1/OpencvDemo opencv的所有代码都在着一个demo里
下一章学习 opencv 和 camera2 配合。