Note:文章以Google Android API提供的人脸探测技术进行讲解,并且使用的Camera class进行开发,而不是Google推荐的camera2,如果你开发中需要camera2可移步Detecting camera features with Camera2(需要梯子)
未引用OpenCV
人脸识别说明
人脸检测:检测并定位图片中的人脸,返回人脸框坐标。
人脸比对:比对两张脸为同一个人的可信度。
人脸关键点:定位返回人脸关键部位和四官坐标(眉、眼、鼻、口)
人脸属性:通过算法获取人脸属性,如:年龄、 性别、微笑程度、眼睛状态、人种等
说这么多是不是很激动,但别忘了题目是人脸检测,我们要讲的只有一个功能,人脸检测
API
Google官方给出的有两种方法:
- FaceDetector:通过传递Bitmap检测图中的人脸,同时返回眼睛部位,识别人数,未详细测试;
- Camera.FaceDetectionListener:通过打开摄像头(不是相机),实时获取人脸定位,最大5人。
讲解
FaceDetector传图识脸
/**
* Creates a FaceDetector, configured with the size of the images to
* be analysed and the maximum number of faces that can be detected.
* These parameters cannot be changed once the object is constructed.
* Note that the width of the image must be even.
*
* @param width the width of the image
* @param height the height of the image
* @param maxFaces the maximum number of faces to identify
*
*/
public FaceDetector(int width, int height, int maxFaces){}
翻译:创建一个FaceDetector,配置要分析的图像的大小以及可以检测到的最大面孔数。一旦构建对象,这些参数就不能被更改。请注意,图像的宽度必须均匀。
用法:
public Bitmap detectionFace(Bitmap b) {
// 检测前必须转化为RGB_565格式。文末有详述连接
Bitmap bitmap = b.copy(Bitmap.Config.RGB_565, true);
b.recycle();
// 设定最大可查的人脸数量
int MAX_FACES = 5;
FaceDetector faceDet = new FaceDetector(bitmap.getWidth(), bitmap.getHeight(), MAX_FACES);
// 将人脸数据存储到faceArray 中
FaceDetector.Face[] faceArray = new FaceDetector.Face[MAX_FACES];
// 返回找到图片中人脸的数量,同时把返回的脸部位置信息放到faceArray中,过程耗时
int findFaceCount = faceDet.findFaces(bitmap, faceArray);
// 获取传回的脸部数组中的第一张脸的信息
FaceDetector.Face face1 = faceArray[0];
// 获取双眼的中心点,用一个PointF来接收其x、y坐标
PointF point = new PointF();
face1.getMidPoint(point);
// 获取该部位为人脸的可信度,0~1
float confidence = face1.confidence();
// 获取双眼间距
float eyesDistance = face1.eyesDistance();
// 获取面部姿势
// 传入X则获取到x方向上的角度,传入Y则获取到y方向上的角度,传入Z则获取到z方向上的角度
float angle = face1.pose(FaceDetector.Face.EULER_X);
// todo 在bitmap上绘制一个Rect框住脸,因为返回的是眼睛位置,所以还要做一些处理
return bitmap;
}
以上就是通过传递一张Bitmap然后找出其中面部的具体方法
识别率:低
Camera.FaceDetectionListener实时检测脸部
通过调用摄像头,提供一个实时检测脸部的方案
流程:打开摄像头→给Camera类传递回调接口→从接口获取脸部位置信息
/**
* Callback interface for face detected in the preview frame.
*
* @deprecated We recommend using the new {@link android.hardware.camera2} API for new
* applications.
*/
@Deprecated
public interface FaceDetectionListener
{
/**
* Notify the listener of the detected faces in the preview frame.
*
* @param faces The detected faces in a list
* @param camera The {@link Camera} service object
*/
void onFaceDetection(Face[] faces, Camera camera);
}
这是一个回调接口,供Camera使用。返回的数据包含脸部集合和摄像头对象。
用法:
- 在Activity的onCreate中提供一个SurfaceView给Camera显示图像
private void initViews() {
surfaceView = new SurfaceView(this);
rectView = new DrawFacesView(this);
addContentView(surfaceView, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
addContentView(rectView, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
}
这是Activity布局
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" />
- 在onCreate()中打开相机,设置监听
/**
* 把摄像头的图像显示到SurfaceView
*/
private void openSurfaceView() {
mHolder = surfaceView.getHolder();
mHolder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
if (mCamera == null) {
mCamera = Camera.open();
try {
// 设置脸部检测监听
mCamera.setFaceDetectionListener(new FaceDetectorListener());
mCamera.setPreviewDisplay(holder);
// 开始脸部检测
startFaceDetection();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
if (mHolder.getSurface() == null) {
// preview surface does not exist
Log.e(TAG, "mHolder.getSurface() == null");
return;
}
try {
mCamera.stopPreview();
} catch (Exception e) {
// ignore: tried to stop a non-existent preview
Log.e(TAG, "Error stopping camera preview: " + e.getMessage());
}
try {
mCamera.setPreviewDisplay(mHolder);
int measuredWidth = surfaceView.getMeasuredWidth();
int measuredHeight = surfaceView.getMeasuredHeight();
setCameraParms(mCamera, measuredWidth, measuredHeight);
mCamera.startPreview();
startFaceDetection(); // re-start face detection feature
} catch (Exception e) {
// ignore: tried to stop a non-existent preview
Log.d(TAG, "Error starting camera preview: " + e.getMessage());
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mCamera.stopPreview();
mCamera.release();
mCamera = null;
holder = null;
}
});
}
/**
* 在摄像头启动前设置参数
*
* @param camera
* @param width
* @param height
*/
private void setCameraParms(Camera camera, int width, int height) {
// 获取摄像头支持的pictureSize列表
Camera.Parameters parameters = camera.getParameters();
// /**/注释的地方非必须,参考来源 [Android 手把手带你玩转自定义相机](http://blog.csdn.net/qq_17250009/article/details/52795530)
/*List<Camera.Size> pictureSizeList = parameters.getSupportedPictureSizes();
// 从列表中选择合适的分辨率
Camera.Size pictureSize = getProperSize(pictureSizeList, (float) height / width);
if (null == pictureSize) {
pictureSize = parameters.getPictureSize();
}
// 根据选出的PictureSize重新设置SurfaceView大小
float w = pictureSize.width;
float h = pictureSize.height;
parameters.setPictureSize(pictureSize.width, pictureSize.height);
surfaceView.setLayoutParams(new FrameLayout.LayoutParams((int) (height * (h / w)), height));
// 获取摄像头支持的PreviewSize列表
List<Camera.Size> previewSizeList = parameters.getSupportedPreviewSizes();
Camera.Size preSize = getProperSize(previewSizeList, (float) height / width);
if (null != preSize) {
parameters.setPreviewSize(preSize.width, preSize.height);
}
*/
parameters.setJpegQuality(100);
// 不对焦,拍摄电脑上的图片都模糊
if (parameters.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
// 连续对焦
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
}
camera.cancelAutoFocus();
// 摄像头图像旋转90度,此处不严谨
camera.setDisplayOrientation(90);// 可注释该行代码看看效果
camera.setParameters(parameters);
}
/**
* 启动脸部检测,如果getMaxNumDetectedFaces()!=0说明不支持脸部检测
*/
public void startFaceDetection() {
// Try starting Face Detection
Camera.Parameters params = mCamera.getParameters();
// start face detection only *after* preview has started
if (params.getMaxNumDetectedFaces() > 0) {
// mCamera supports face detection, so can start it:
mCamera.startFaceDetection();
} else {
Log.e("tag", "【FaceDetectorActivity】类的方法:【startFaceDetection】: " + "不支持");
}
}
- 脸部回调接口
/**
* 脸部检测接口
*/
private class FaceDetectorListener implements Camera.FaceDetectionListener {
@Override
public void onFaceDetection(Camera.Face[] faces, Camera camera) {
if (faces.length > 0) {
Camera.Face face = faces[0];
Rect rect = face.rect;
Log.d("FaceDetection", "可信度:" + face.score + "face detected: " + faces.length +
" Face 1 Location X: " + rect.centerX() +
"Y: " + rect.centerY() + " " + rect.left + " " + rect.top + " " + rect.right + " " + rect.bottom);
Log.e("tag", "【FaceDetectorListener】类的方法:【onFaceDetection】: ");
Matrix matrix = updateFaceRect();
facesView.updateFaces(matrix, faces);
} else {
// 只会执行一次
Log.e("tag", "【FaceDetectorListener】类的方法:【onFaceDetection】: " + "没有脸部");
facesView.removeRect();
}
}
}
/**
* 因为对摄像头进行了旋转,所以同时也旋转画板矩阵
* 详细请查看{@link android.hardware.Camera.Face#rect}
* @return 旋转后的矩阵
*/
private Matrix updateFaceRect() {
Matrix matrix = new Matrix();
Camera.CameraInfo info = new android.hardware.Camera.CameraInfo();
// Need mirror for front camera.
boolean mirror = (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT);
matrix.setScale(mirror ? -1 : 1, 1);
// This is the value for android.hardware.Camera.setDisplayOrientation.
// 刚才我们设置了camera的旋转参数,所以这里也要设置一下
matrix.postRotate(90);
// Camera driver coordinates range from (-1000, -1000) to (1000, 1000).
// UI coordinates range from (0, 0) to (width, height).
matrix.postScale(surfaceView.getWidth() / 2000f, surfaceView.getHeight() / 2000f);
matrix.postTranslate(surfaceView.getWidth() / 2f, surfaceView.getHeight() / 2f);
return matrix;
}
-
现在插播一条消息:
updateFaceRect()解释:在脸部检测时,获得的Rect并不是View的坐标,而是这样的一个坐标系,中心为原点,所以我们需要转换一下
绘制脸部方框的View
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.hardware.Camera;
import android.util.AttributeSet;
import android.view.View;
public class DrawFacesView extends View {
private Matrix matrix;
private Paint paint;
private Camera.Face[] faces;
private boolean isClear;
public DrawFacesView(Context context) {
this(context, null);
}
public DrawFacesView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DrawFacesView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
paint = new Paint();
paint.setColor(Color.GREEN);
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
faces = new Camera.Face[]{};
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.setMatrix(matrix);
// canvas.drawRect(rect, paint);
for (Camera.Face face : faces) {
if (face == null) break;
canvas.drawRect(face.rect, paint);
if (face.leftEye != null)
canvas.drawPoint(face.leftEye.x, face.leftEye.y, paint);
if (face.rightEye != null)
canvas.drawPoint(face.rightEye.x, face.rightEye.y, paint);
if (face.mouth != null)
canvas.drawPoint(face.mouth.x, face.mouth.y, paint);
// 因为旋转了画布矩阵,所以字体也跟着旋转
// canvas.drawText(String.valueOf("id:" + face.id + "\n置信度:" + face.score), face.rect.left, face.rect.bottom + 10, paint);
}
if (isClear) {
canvas.drawColor(Color.WHITE, PorterDuff.Mode.CLEAR);
isClear = false;
}
}
/**
* 绘制脸部方框
*
* @param matrix 旋转画布的矩阵
* @param faces 脸部信息数组
*/
public void updateFaces(Matrix matrix, Camera.Face[] faces) {
this.matrix = matrix;
this.faces = faces;
invalidate();
}
/**
* 清除已经画上去的框
*/
public void removeRect() {
isClear = true;
invalidate();
}
}
权限
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera.autofocus" />
效果:
- 传入Bitmap识别
- 摄像头实时检测
参考
Google Android Camera API
Android 手把手带你玩转自定义相机