Note:文章以Google Android API提供的人臉探測(cè)技術(shù)進(jìn)行講解砌左,并且使用的Camera class進(jìn)行開發(fā),而不是Google推薦的camera2竹挡,如果你開發(fā)中需要camera2可移步Detecting camera features with Camera2(需要梯子)
未引用OpenCV
人臉識(shí)別說明
人臉檢測(cè):檢測(cè)并定位圖片中的人臉液兽,返回人臉框坐標(biāo)庶弃。
人臉比對(duì):比對(duì)兩張臉為同一個(gè)人的可信度秦驯。
人臉關(guān)鍵點(diǎn):定位返回人臉關(guān)鍵部位和四官坐標(biāo)(眉尺碰、眼、鼻译隘、口)
人臉屬性:通過算法獲取人臉屬性亲桥,如:年齡、 性別固耘、微笑程度题篷、眼睛狀態(tài)、人種等
說這么多是不是很激動(dòng)厅目,但別忘了題目是人臉檢測(cè)番枚,我們要講的只有一個(gè)功能,人臉檢測(cè)
API
Google官方給出的有兩種方法:
- FaceDetector:通過傳遞Bitmap檢測(cè)圖中的人臉璧瞬,同時(shí)返回眼睛部位,識(shí)別人數(shù)渐夸,未詳細(xì)測(cè)試嗤锉;
- Camera.FaceDetectionListener:通過打開攝像頭(不是相機(jī)),實(shí)時(shí)獲取人臉定位墓塌,最大5人瘟忱。
講解
FaceDetector傳圖識(shí)臉
/**
* 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){}
翻譯:創(chuàng)建一個(gè)FaceDetector,配置要分析的圖像的大小以及可以檢測(cè)到的最大面孔數(shù)苫幢。一旦構(gòu)建對(duì)象访诱,這些參數(shù)就不能被更改。請(qǐng)注意韩肝,圖像的寬度必須均勻触菜。
用法:
public Bitmap detectionFace(Bitmap b) {
// 檢測(cè)前必須轉(zhuǎn)化為RGB_565格式。文末有詳述連接
Bitmap bitmap = b.copy(Bitmap.Config.RGB_565, true);
b.recycle();
// 設(shè)定最大可查的人臉數(shù)量
int MAX_FACES = 5;
FaceDetector faceDet = new FaceDetector(bitmap.getWidth(), bitmap.getHeight(), MAX_FACES);
// 將人臉數(shù)據(jù)存儲(chǔ)到faceArray 中
FaceDetector.Face[] faceArray = new FaceDetector.Face[MAX_FACES];
// 返回找到圖片中人臉的數(shù)量哀峻,同時(shí)把返回的臉部位置信息放到faceArray中涡相,過程耗時(shí)
int findFaceCount = faceDet.findFaces(bitmap, faceArray);
// 獲取傳回的臉部數(shù)組中的第一張臉的信息
FaceDetector.Face face1 = faceArray[0];
// 獲取雙眼的中心點(diǎn),用一個(gè)PointF來接收其x剩蟀、y坐標(biāo)
PointF point = new PointF();
face1.getMidPoint(point);
// 獲取該部位為人臉的可信度催蝗,0~1
float confidence = face1.confidence();
// 獲取雙眼間距
float eyesDistance = face1.eyesDistance();
// 獲取面部姿勢(shì)
// 傳入X則獲取到x方向上的角度,傳入Y則獲取到y(tǒng)方向上的角度育特,傳入Z則獲取到z方向上的角度
float angle = face1.pose(FaceDetector.Face.EULER_X);
// todo 在bitmap上繪制一個(gè)Rect框住臉丙号,因?yàn)榉祷氐氖茄劬ξ恢茫赃€要做一些處理
return bitmap;
}
以上就是通過傳遞一張Bitmap然后找出其中面部的具體方法
識(shí)別率:低
Camera.FaceDetectionListener實(shí)時(shí)檢測(cè)臉部
通過調(diào)用攝像頭,提供一個(gè)實(shí)時(shí)檢測(cè)臉部的方案
流程:打開攝像頭→給Camera類傳遞回調(diào)接口→從接口獲取臉部位置信息
/**
* 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);
}
這是一個(gè)回調(diào)接口犬缨,供Camera使用喳魏。返回的數(shù)據(jù)包含臉部集合和攝像頭對(duì)象。
用法:
- 在Activity的onCreate中提供一個(gè)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()中打開相機(jī)遍尺,設(shè)置監(jiān)聽
/**
* 把攝像頭的圖像顯示到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 {
// 設(shè)置臉部檢測(cè)監(jiān)聽
mCamera.setFaceDetectionListener(new FaceDetectorListener());
mCamera.setPreviewDisplay(holder);
// 開始臉部檢測(cè)
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;
}
});
}
/**
* 在攝像頭啟動(dòng)前設(shè)置參數(shù)
*
* @param camera
* @param width
* @param height
*/
private void setCameraParms(Camera camera, int width, int height) {
// 獲取攝像頭支持的pictureSize列表
Camera.Parameters parameters = camera.getParameters();
// /**/注釋的地方非必須截酷,參考來源 [Android 手把手帶你玩轉(zhuǎn)自定義相機(jī)](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();
}
// 根據(jù)選出的PictureSize重新設(shè)置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);
// 不對(duì)焦,拍攝電腦上的圖片都模糊
if (parameters.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
// 連續(xù)對(duì)焦
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
}
camera.cancelAutoFocus();
// 攝像頭圖像旋轉(zhuǎn)90度乾戏,此處不嚴(yán)謹(jǐn)
camera.setDisplayOrientation(90);// 可注釋該行代碼看看效果
camera.setParameters(parameters);
}
/**
* 啟動(dòng)臉部檢測(cè)迂苛,如果getMaxNumDetectedFaces()!=0說明不支持臉部檢測(cè)
*/
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】: " + "不支持");
}
}
- 臉部回調(diào)接口
/**
* 臉部檢測(cè)接口
*/
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 {
// 只會(huì)執(zhí)行一次
Log.e("tag", "【FaceDetectorListener】類的方法:【onFaceDetection】: " + "沒有臉部");
facesView.removeRect();
}
}
}
/**
* 因?yàn)閷?duì)攝像頭進(jìn)行了旋轉(zhuǎn),所以同時(shí)也旋轉(zhuǎn)畫板矩陣
* 詳細(xì)請(qǐng)查看{@link android.hardware.Camera.Face#rect}
* @return 旋轉(zhuǎn)后的矩陣
*/
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.
// 剛才我們?cè)O(shè)置了camera的旋轉(zhuǎn)參數(shù)鼓择,所以這里也要設(shè)置一下
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;
}
-
現(xiàn)在插播一條消息:
updateFaceRect()解釋:在臉部檢測(cè)時(shí)三幻,獲得的Rect并不是View的坐標(biāo),而是這樣的一個(gè)坐標(biāo)系呐能,中心為原點(diǎn)念搬,所以我們需要轉(zhuǎn)換一下
繪制臉部方框的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);
// 因?yàn)樾D(zhuǎn)了畫布矩陣,所以字體也跟著旋轉(zhuǎn)
// 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 旋轉(zhuǎn)畫布的矩陣
* @param faces 臉部信息數(shù)組
*/
public void updateFaces(Matrix matrix, Camera.Face[] faces) {
this.matrix = matrix;
this.faces = faces;
invalidate();
}
/**
* 清除已經(jīng)畫上去的框
*/
public void removeRect() {
isClear = true;
invalidate();
}
}
權(quán)限
<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識(shí)別
- 攝像頭實(shí)時(shí)檢測(cè)
參考
Google Android Camera API
Android 手把手帶你玩轉(zhuǎn)自定義相機(jī)