Android人脸检测
随着Google Play服务7.8的发布,Google引入了Mobile Vision API,可让您执行人脸检测,条形码检测和文本检测。
在本教程中,我们将开发一个Android人脸检测应用程序,让您可以检测图像中的人脸。
Android人脸检测
Android人脸检测API使用眼睛,鼻子,耳朵,脸颊和嘴巴等地标来跟踪照片,视频中的人脸。
API不会立即检测到个人特征,而是立即检测到人脸,然后在定义后检测界标和分类。
此外,API还可以检测各种角度的面部。
Android人脸检测–地标
地标是人脸内的兴趣点。
左眼,右眼和鼻根都是地标的例子。
以下是该API当前可能找到的地标:
- 左右眼
- 左右耳
- 左右耳尖
- 鼻子的根部
- 左右脸颊
- 嘴的左右角
- 嘴巴
当使用"左"和"右"时,它们相对于主题。
例如,LEFT_EYE地标是对象的左眼,而不是查看图像时在左眼。
分类
分类确定是否存在某种面部特征。
Android Face API当前支持两种分类:
睁开眼睛:使用了getIsLeftEyeOpenProbability()和getIsRightEyeOpenProbability()方法。
微笑:使用
getIsSmilingProbability()方法。
脸部定位
使用欧拉角确定脸部的方向。
这些是指面围绕X,Y和Z轴的旋转角度。
欧拉Y告诉我们该脸是向左看还是向右看。
欧拉Z告诉我们面部是否旋转/定格
欧拉X告诉我们脸部是向上还是向下(当前不支持)
注意:如果无法计算概率,则将其设置为-1。
让我们跳到本教程的业务结束。
我们的应用程序应包含一些示例图像以及捕获您自己的图像的功能。
注意:API仅支持人脸检测。
当前的Mobile Vision API不支持面部识别。
Android人脸检测代码
在您的应用程序的build.gradle文件中添加以下依赖项。
compile 'com.google.android.gms:play-services-vision:11.0.4'
如下所示,在AndroidManifest.xml文件的application标签内添加以下meta-deta。
<meta-data
android:name="com.google.android.gms.vision.DEPENDENCIES"
android:value="face"
这使Vision库知道您计划检测应用程序中的面部。
在AndroidManifest.xml的列表标记中添加以下权限以获取相机权限。
<uses-feature
android:name="android.hardware.camera"
android:required="true"
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
下面给出了" activity_main.xml"布局文件的代码。
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:app="https://schemas.android.com/apk/res-auto"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.constraint.ConstraintLayout xmlns:app="https://schemas.android.com/apk/res-auto"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:context="com.theitroad.facedetectionapi.MainActivity">
<ImageView
android:id="@+id/imageView"
android:layout_width="300dp"
android:layout_height="300dp"
android:layout_marginTop="8dp"
android:src="@drawable/sample_1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
<Button
android:id="@+id/btnProcessNext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="PROCESS NEXT"
app:layout_constraintHorizontal_bias="0.501"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView"
<ImageView
android:id="@+id/imgTakePic"
android:layout_width="250dp"
android:layout_height="250dp"
android:layout_marginTop="8dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/txtSampleDescription"
app:srcCompat="@android:drawable/ic_menu_camera"
<Button
android:id="@+id/btnTakePicture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="TAKE PICTURE"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imgTakePic"
<TextView
android:id="@+id/txtSampleDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
android:gravity="center"
app:layout_constraintBottom_toTopOf="@+id/txtTakePicture"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btnProcessNext"
app:layout_constraintVertical_bias="0.0"
<TextView
android:id="@+id/txtTakePicture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btnTakePicture"
</android.support.constraint.ConstraintLayout>
</ScrollView>
我们定义了两个ImageView,TextView和Button。
可以遍历示例图像并显示结果的一种。
另一个用于从相机捕获图像。
下面给出了MainActivity.java文件的代码。
package com.theitroad.facedetectionapi;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.SparseArray;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.gms.vision.Frame;
import com.google.android.gms.vision.face.Face;
import com.google.android.gms.vision.face.FaceDetector;
import com.google.android.gms.vision.face.Landmark;
import java.io.File;
import java.io.FileNotFoundException;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
ImageView imageView, imgTakePicture;
Button btnProcessNext, btnTakePicture;
TextView txtSampleDesc, txtTakenPicDesc;
private FaceDetector detector;
Bitmap editedBitmap;
int currentIndex = 0;
int[] imageArray;
private Uri imageUri;
private static final int REQUEST_WRITE_PERMISSION = 200;
private static final int CAMERA_REQUEST = 101;
private static final String SAVED_INSTANCE_URI = "uri";
private static final String SAVED_INSTANCE_BITMAP = "bitmap";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageArray = new int[]{R.drawable.sample_1, R.drawable.sample_2, R.drawable.sample_3};
detector = new FaceDetector.Builder(getApplicationContext())
.setTrackingEnabled(false)
.setLandmarkType(FaceDetector.ALL_CLASSIFICATIONS)
.setClassificationType(FaceDetector.ALL_CLASSIFICATIONS)
.build();
initViews();
}
private void initViews() {
imageView = (ImageView) findViewById(R.id.imageView);
imgTakePicture = (ImageView) findViewById(R.id.imgTakePic);
btnProcessNext = (Button) findViewById(R.id.btnProcessNext);
btnTakePicture = (Button) findViewById(R.id.btnTakePicture);
txtSampleDesc = (TextView) findViewById(R.id.txtSampleDescription);
txtTakenPicDesc = (TextView) findViewById(R.id.txtTakePicture);
processImage(imageArray[currentIndex]);
currentIndex++;
btnProcessNext.setOnClickListener(this);
btnTakePicture.setOnClickListener(this);
imgTakePicture.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btnProcessNext:
imageView.setImageResource(imageArray[currentIndex]);
processImage(imageArray[currentIndex]);
if (currentIndex == imageArray.length - 1)
currentIndex = 0;
else
currentIndex++;
break;
case R.id.btnTakePicture:
ActivityCompat.requestPermissions(MainActivity.this, new
String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_WRITE_PERMISSION);
break;
case R.id.imgTakePic:
ActivityCompat.requestPermissions(MainActivity.this, new
String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_WRITE_PERMISSION);
break;
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case REQUEST_WRITE_PERMISSION:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startCamera();
} else {
Toast.makeText(getApplicationContext(), "Permission Denied!", Toast.LENGTH_SHORT).show();
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == CAMERA_REQUEST && resultCode == RESULT_OK) {
launchMediaScanIntent();
try {
processCameraPicture();
} catch (Exception e) {
Toast.makeText(getApplicationContext(), "Failed to load Image", Toast.LENGTH_SHORT).show();
}
}
}
private void launchMediaScanIntent() {
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
mediaScanIntent.setData(imageUri);
this.sendBroadcast(mediaScanIntent);
}
private void startCamera() {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
File photo = new File(Environment.getExternalStorageDirectory(), "photo.jpg");
imageUri = Uri.fromFile(photo);
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent, CAMERA_REQUEST);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
if (imageUri != null) {
outState.putParcelable(SAVED_INSTANCE_BITMAP, editedBitmap);
outState.putString(SAVED_INSTANCE_URI, imageUri.toString());
}
super.onSaveInstanceState(outState);
}
private void processImage(int image) {
Bitmap bitmap = decodeBitmapImage(image);
if (detector.isOperational() && bitmap != null) {
editedBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap
.getHeight(), bitmap.getConfig());
float scale = getResources().getDisplayMetrics().density;
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.GREEN);
paint.setTextSize((int) (16 * scale));
paint.setShadowLayer(1f, 0f, 1f, Color.WHITE);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(6f);
Canvas canvas = new Canvas(editedBitmap);
canvas.drawBitmap(bitmap, 0, 0, paint);
Frame frame = new Frame.Builder().setBitmap(editedBitmap).build();
SparseArray<Face> faces = detector.detect(frame);
txtSampleDesc.setText(null);
for (int index = 0; index < faces.size(); ++index) {
Face face = faces.valueAt(index);
canvas.drawRect(
face.getPosition().x,
face.getPosition().y,
face.getPosition().x + face.getWidth(),
face.getPosition().y + face.getHeight(), paint);
canvas.drawText("Face " + (index + 1), face.getPosition().x + face.getWidth(), face.getPosition().y + face.getHeight(), paint);
txtSampleDesc.setText(txtSampleDesc.getText() + "FACE " + (index + 1) + "\n");
txtSampleDesc.setText(txtSampleDesc.getText() + "Smile probability:" + " " + face.getIsSmilingProbability() + "\n");
txtSampleDesc.setText(txtSampleDesc.getText() + "Left Eye Is Open Probability: " + " " + face.getIsLeftEyeOpenProbability() + "\n");
txtSampleDesc.setText(txtSampleDesc.getText() + "Right Eye Is Open Probability: " + " " + face.getIsRightEyeOpenProbability() + "\n\n");
for (Landmark landmark : face.getLandmarks()) {
int cx = (int) (landmark.getPosition().x);
int cy = (int) (landmark.getPosition().y);
canvas.drawCircle(cx, cy, 8, paint);
}
}
if (faces.size() == 0) {
txtSampleDesc.setText("Scan Failed: Found nothing to scan");
} else {
imageView.setImageBitmap(editedBitmap);
txtSampleDesc.setText(txtSampleDesc.getText() + "No of Faces Detected: " + " " + String.valueOf(faces.size()));
}
} else {
txtSampleDesc.setText("Could not set up the detector!");
}
}
private Bitmap decodeBitmapImage(int image) {
int targetW = 300;
int targetH = 300;
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
bmOptions.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), image,
bmOptions);
int photoW = bmOptions.outWidth;
int photoH = bmOptions.outHeight;
int scaleFactor = Math.min(photoW/targetW, photoH/targetH);
bmOptions.inJustDecodeBounds = false;
bmOptions.inSampleSize = scaleFactor;
return BitmapFactory.decodeResource(getResources(), image,
bmOptions);
}
private void processCameraPicture() throws Exception {
Bitmap bitmap = decodeBitmapUri(this, imageUri);
if (detector.isOperational() && bitmap != null) {
editedBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap
.getHeight(), bitmap.getConfig());
float scale = getResources().getDisplayMetrics().density;
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.GREEN);
paint.setTextSize((int) (16 * scale));
paint.setShadowLayer(1f, 0f, 1f, Color.WHITE);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(6f);
Canvas canvas = new Canvas(editedBitmap);
canvas.drawBitmap(bitmap, 0, 0, paint);
Frame frame = new Frame.Builder().setBitmap(editedBitmap).build();
SparseArray<Face> faces = detector.detect(frame);
txtTakenPicDesc.setText(null);
for (int index = 0; index < faces.size(); ++index) {
Face face = faces.valueAt(index);
canvas.drawRect(
face.getPosition().x,
face.getPosition().y,
face.getPosition().x + face.getWidth(),
face.getPosition().y + face.getHeight(), paint);
canvas.drawText("Face " + (index + 1), face.getPosition().x + face.getWidth(), face.getPosition().y + face.getHeight(), paint);
txtTakenPicDesc.setText("FACE " + (index + 1) + "\n");
txtTakenPicDesc.setText(txtTakenPicDesc.getText() + "Smile probability:" + " " + face.getIsSmilingProbability() + "\n");
txtTakenPicDesc.setText(txtTakenPicDesc.getText() + "Left Eye Is Open Probability: " + " " + face.getIsLeftEyeOpenProbability() + "\n");
txtTakenPicDesc.setText(txtTakenPicDesc.getText() + "Right Eye Is Open Probability: " + " " + face.getIsRightEyeOpenProbability() + "\n\n");
for (Landmark landmark : face.getLandmarks()) {
int cx = (int) (landmark.getPosition().x);
int cy = (int) (landmark.getPosition().y);
canvas.drawCircle(cx, cy, 8, paint);
}
}
if (faces.size() == 0) {
txtTakenPicDesc.setText("Scan Failed: Found nothing to scan");
} else {
imgTakePicture.setImageBitmap(editedBitmap);
txtTakenPicDesc.setText(txtTakenPicDesc.getText() + "No of Faces Detected: " + " " + String.valueOf(faces.size()));
}
} else {
txtTakenPicDesc.setText("Could not set up the detector!");
}
}
private Bitmap decodeBitmapUri(Context ctx, Uri uri) throws FileNotFoundException {
int targetW = 300;
int targetH = 300;
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
bmOptions.inJustDecodeBounds = true;
BitmapFactory.decodeStream(ctx.getContentResolver().openInputStream(uri), null, bmOptions);
int photoW = bmOptions.outWidth;
int photoH = bmOptions.outHeight;
int scaleFactor = Math.min(photoW/targetW, photoH/targetH);
bmOptions.inJustDecodeBounds = false;
bmOptions.inSampleSize = scaleFactor;
return BitmapFactory.decodeStream(ctx.getContentResolver()
.openInputStream(uri), null, bmOptions);
}
@Override
protected void onDestroy() {
super.onDestroy();
detector.release();
}
}
从上面的代码得出的推论很少是:
当点击"下一个处理"按钮时," imageArray"保存将要扫描的示例图像。
检测器使用以下代码段实例化:
地标会增加计算时间,因此需要明确设置。
根据我们的要求,人脸检测器可以设置为" FAST_MODE"或者" ACCURATE_MODE"。
由于我们要处理静止图像,因此在上面的代码中将跟踪设置为false。
可以将其设置为true以检测视频中的面部。
processImage()和processCameraPicture()方法包含代码,在这些代码中我们实际检测到面孔并在其上绘制一个矩形
" detector.isOperational()"用于检查手机中当前的Google Play服务库是否支持视觉API(如果不支持,则Google Play会下载所需的本机库以提供支持)。
实际进行人脸检测的代码段为:
一旦被检测到,我们就会循环通过" faces"数组来查找每个面孔的位置和属性。
每个面孔的属性都附加在按钮下方的TextView中。
当我们用相机捕获图像时,这是相同的,除了我们需要在运行时要求相机许可并保存uri,由相机应用程序返回的位图。
尝试捕获狗的照片,您会发现Vision API未检测到它的脸(该API仅检测人脸)。

