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不仅仅可以检测人脸,也可以检测眼睛,身体,嘴巴等.通过加载一个想要检测的.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
加入我的个人技术公众号,一起学习Android知识!