先上效果图(整个项目源码在gitee,如果有需要的同学可以私信我,仅限于学习)
准备工作
- 将 Firebase 添加到您的 Android 项目(如果尚未添加)。
- 将 Android 版机器学习套件库的依赖项添加到您的模块(应用级层)Gradle 文件(通常为?
app/build.gradle ): apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services'
dependencies {
? // ...
? implementation 'com.google.firebase:firebase-ml-vision:24.0.3'
? // If you want to detect face contours (landmark detection and classification
? // don't require this additional model):
? implementation 'com.google.firebase:firebase-ml-vision-face-model:20.0.1'
} - 非强制但建议执行的操作:对您的应用进行配置,使之在从 Play 商店安装后自动将机器学习模式下载到设备上。
为此,请将以下声明添加到您应用的?AndroidManifest.xml ?文件: <application ...>
? ...
? <meta-data
? ? ? android:name="com.google.firebase.ml.vision.DEPENDENCIES"
? ? ? android:value="face" />
? <!-- To use multiple models: android:value="face,model2,model3" -->
</application> 如果您未启用在安装时下载模型的选项,模型将在您首次运行检测器时下载。您在下载完毕之前提出的请求不会产生任何结果。
为了使机器学习套件准确检测人脸,输入图片必须包含由足够像素数据表示的人脸。通常,要在图片中检测的每个人脸应至少为 100x100 像素。如果要检测人脸轮廓,机器学习套件需要更高的分辨率输入:每个人脸应至少为 200x200 像素。
如果您是在实时应用中检测人脸,则可能还需要考虑输入图片的整体尺寸。较小图片的处理速度相对较快,因此,为了减少延迟时间,请以较低的分辨率捕获图片(牢记上述准确性要求),并确保主体的面部在图片中占尽可能大的部分。另请参阅提高实时性能的相关提示。
图片聚焦不良会影响准确性。如果您获得的结果不可接受,请尝试让用户重新捕获图片。
在对图片应用人脸检测之前,如果要更改人脸检测器的任何默认设置,请使用?FirebaseVisionFaceDetectorOptions?对象指定这些设置。您可以更改以下设置:
设置 |
---|
性能模式 | FAST (默认)|?ACCURATE 在检测人脸时更注重速度还是准确性。 | 检测特征点 | NO_LANDMARKS (默认)|?ALL_LANDMARKS 是否尝试识别面部“特征点”:眼睛、耳朵、鼻子、脸颊、嘴巴。 | 检测轮廓 | NO_CONTOURS (默认)|?ALL_CONTOURS 是否检测面部特征的轮廓。仅检测图片中最突出的人脸的轮廓。 | 对人脸进行分类 | NO_CLASSIFICATIONS (默认)|?ALL_CLASSIFICATIONS 是否将人脸分为不同类别(例如“微笑”和“睁眼”)。 | 人脸大小下限 | float (默认:0.1f ) 需要检测的人脸的大小下限(相对于图片)。 | 启用面部跟踪 | false (默认)|?true 是否为人脸分配 ID,以用于跨图片跟踪人脸。 请注意,启用轮廓检测后,仅检测一个人脸,因此人脸跟踪不会产生有用的结果。为此,若要加快检测速度,请勿同时启用轮廓检测和人脸跟踪。 |
public void initFaceSource() {
FirebaseApp.initializeApp(activity);
// High-accuracy landmark detection and face classification
/*FirebaseVisionFaceDetectorOptions highAccuracyOpts =
new FirebaseVisionFaceDetectorOptions.Builder()
.setPerformanceMode(FirebaseVisionFaceDetectorOptions.ACCURATE) //性能模式
.setLandmarkMode(FirebaseVisionFaceDetectorOptions.ALL_LANDMARKS) //检测特征点
.setClassificationMode(FirebaseVisionFaceDetectorOptions.ALL_CLASSIFICATION
.build();*/
// Real-time contour detection of multiple faces
FirebaseVisionFaceDetectorOptions realTimeOpts =
new FirebaseVisionFaceDetectorOptions.Builder()
.setPerformanceMode(FirebaseVisionFaceDetectorOptions.FAST)
.setContourMode(FirebaseVisionFaceDetectorOptions.ALL_CONTOURS) //检测轮廓
.build();
detector = FirebaseVision.getInstance().getVisionFaceDetector(realTimeOpts);
}
2.运行人脸检测器
如需识别图片中的文本,请从设备上的以下资源创建一个?FirebaseVisionImage ?对象:Bitmap 、media.Image 、ByteBuffer 、字节数组或文件。然后,将?FirebaseVisionImage ?对象传递给?FirebaseVisionFaceDetector ?的?detectInImage ?方法。
对于人脸识别,您使用的图片尺寸应至少为?480x360?像素。如果您要实时识别人脸,以此最低分辨率捕获帧有助于减少延迟时间。
-
基于图片创建?FirebaseVisionImage?对象。
-
如需基于?media.Image ?对象创建?FirebaseVisionImage ?对象(例如从设备的相机捕获图片时),请将?media.Image ?对象和图片的旋转角度传递给?FirebaseVisionImage.fromMediaImage() 。 如果您使用了?CameraX?库,OnImageCapturedListener ?和?ImageAnalysis.Analyzer ?类会为您计算旋转角度值,因此您只需在调用?FirebaseVisionImage.fromMediaImage() ?之前将旋转角度转换为机器学习套件的?ROTATION_ ?常量之一(一般情况下前置摄像头需要旋转270,后置摄像头需要旋转90): imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(activity), imageProxy -> {
if (task != null && !task.isComplete()) {
imageProxy.close();
return;
}
@SuppressLint("UnsafeExperimentalUsageError")
//Bitmap bitmap = BitmapUtils.getBitmap(imageProxy);
Image image = imageProxy.getImage();
if (image == null) {
imageProxy.close();
return;
}
FirebaseVisionImage visionImage = FirebaseVisionImage.fromMediaImage(image, FirebaseVisionImageMetadata.ROTATION_90);
//FirebaseVisionImage visionImage = FirebaseVisionImage.fromBitmap(bitmap);
detect(visionImage);
//detect(visionImage, bitmap);
imageProxy.close();
}); 如果您没有使用可提供图片旋转角度的相机库,可以根据设备的旋转角度和设备中相机传感器的朝向来计算旋转角度: private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
static {
? ? ORIENTATIONS.append(Surface.ROTATION_0, 90);
? ? ORIENTATIONS.append(Surface.ROTATION_90, 0);
? ? ORIENTATIONS.append(Surface.ROTATION_180, 270);
? ? ORIENTATIONS.append(Surface.ROTATION_270, 180);
}
/**
?* Get the angle by which an image must be rotated given the device's current
?* orientation.
?*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private int getRotationCompensation(String cameraId, Activity activity, Context context)
? ? ? ? throws CameraAccessException {
? ? // Get the device's current rotation relative to its "native" orientation.
? ? // Then, from the ORIENTATIONS table, look up the angle the image must be
? ? // rotated to compensate for the device's rotation.
? ? int deviceRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
? ? int rotationCompensation = ORIENTATIONS.get(deviceRotation);
? ? // On most devices, the sensor orientation is 90 degrees, but for some
? ? // devices it is 270 degrees. For devices with a sensor orientation of
? ? // 270, rotate the image an additional 180 ((270 + 270) % 360) degrees.
? ? CameraManager cameraManager = (CameraManager) context.getSystemService(CAMERA_SERVICE);
? ? int sensorOrientation = cameraManager
? ? ? ? ? ? .getCameraCharacteristics(cameraId)
? ? ? ? ? ? .get(CameraCharacteristics.SENSOR_ORIENTATION);
? ? rotationCompensation = (rotationCompensation + sensorOrientation + 270) % 360;
? ? // Return the corresponding FirebaseVisionImageMetadata rotation value.
? ? int result;
? ? switch (rotationCompensation) {
? ? ? ? case 0:
? ? ? ? ? ? result = FirebaseVisionImageMetadata.ROTATION_0;
? ? ? ? ? ? break;
? ? ? ? case 90:
? ? ? ? ? ? result = FirebaseVisionImageMetadata.ROTATION_90;
? ? ? ? ? ? break;
? ? ? ? case 180:
? ? ? ? ? ? result = FirebaseVisionImageMetadata.ROTATION_180;
? ? ? ? ? ? break;
? ? ? ? case 270:
? ? ? ? ? ? result = FirebaseVisionImageMetadata.ROTATION_270;
? ? ? ? ? ? break;
? ? ? ? default:
? ? ? ? ? ? result = FirebaseVisionImageMetadata.ROTATION_0;
? ? ? ? ? ? Log.e(TAG, "Bad rotation value: " + rotationCompensation);
? ? }
? ? return result;
} 然后,将?media.Image ?对象及旋转角度值传递给?FirebaseVisionImage.fromMediaImage() : FirebaseVisionImage image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation); - 如需基于文件 URI 创建?
FirebaseVisionImage ?对象,请将应用上下文和文件 URI 传递给?FirebaseVisionImage.fromFilePath() 。如果您使用?ACTION_GET_CONTENT ?Intent 提示用户从图库应用中选择图片,则这一操作非常有用。 FirebaseVisionImageMetadata metadata = new FirebaseVisionImageMetadata.Builder()
? ? ? ? .setWidth(480) ? // 480x360 is typically sufficient for
? ? ? ? .setHeight(360) ?// image recognition
? ? ? ? .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21)
? ? ? ? .setRotation(rotation)
? ? ? ? .build(); - 如需基于?
ByteBuffer ?或字节数组创建?FirebaseVisionImage ?对象,请先按上述?media.Image ?输入的说明计算图片旋转角度。 然后,创建一个包含图片的高度、宽度、颜色编码格式和旋转角度的?FirebaseVisionImageMetadata ?对象: FirebaseVisionImageMetadata metadata = new FirebaseVisionImageMetadata.Builder()
? ? ? ? .setWidth(480) ? // 480x360 is typically sufficient for
? ? ? ? .setHeight(360) ?// image recognition
? ? ? ? .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21)
? ? ? ? .setRotation(rotation)
? ? ? ? .build(); 使用缓冲区或数组以及元数据对象来创建?FirebaseVisionImage ?对象: FirebaseVisionImage image = FirebaseVisionImage.fromByteBuffer(buffer, metadata);
// Or: FirebaseVisionImage image = FirebaseVisionImage.fromByteArray(byteArray, metadata); - 如需基于?
Bitmap ?对象创建?FirebaseVisionImage ?对象,请执行以下操作 FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap); 以?Bitmap ?对象表示的图片必须保持竖直,不需要额外的旋转。
-
获取?FirebaseVisionFaceDetector?的一个实例: FirebaseVisionFaceDetector detector = FirebaseVision.getInstance()
? ? ? ? .getVisionFaceDetector(options);
注意:检查控制台,看看是否存在构造函数生成的错误。
-
最后,将图片传递给?detectInImage ?方法: Task<List<FirebaseVisionFace>> result =
? ? ? ? detector.detectInImage(image)
? ? ? ? ? ? ? ? .addOnSuccessListener(
? ? ? ? ? ? ? ? ? ? ? ? new OnSuccessListener<List<FirebaseVisionFace>>() {
? ? ? ? ? ? ? ? ? ? ? ? ? ? @Override
? ? ? ? ? ? ? ? ? ? ? ? ? ? public void onSuccess(List<FirebaseVisionFace> faces) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // Task completed successfully
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // ...
? ? ? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? })
? ? ? ? ? ? ? ? .addOnFailureListener(
? ? ? ? ? ? ? ? ? ? ? ? new OnFailureListener() {
? ? ? ? ? ? ? ? ? ? ? ? ? ? @Override
? ? ? ? ? ? ? ? ? ? ? ? ? ? public void onFailure(@NonNull Exception e) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // Task failed with an exception
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // ...
? ? ? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? }); 注意:检查控制台,看看是否存在检测器生成的错误。
如果人脸识别操作成功,系统会向成功侦听器传递一组?FirebaseVisionFace?对象。每个?FirebaseVisionFace ?对象都代表一张在图片中检测到的面孔。对于每张面孔,您可以获取它在输入图片中的边界坐标,以及您已配置面部检测器查找的任何其他信息。例如:
for (FirebaseVisionFace face : faces) {
Rect bounds = face.getBoundingBox();
float rotY = face.getHeadEulerAngleY(); // Head is rotated to the right rotY degrees
float rotZ = face.getHeadEulerAngleZ(); // Head is tilted sideways rotZ degrees
// If landmark detection was enabled (mouth, ears, eyes, cheeks, and
// nose available):
FirebaseVisionFaceLandmark leftEar = face.getLandmark(FirebaseVisionFaceLandmark.LEFT_EAR);
if (leftEar != null) {
FirebaseVisionPoint leftEarPos = leftEar.getPosition();
}
// If contour detection was enabled:
List<FirebaseVisionPoint> leftEyeContour =
face.getContour(FirebaseVisionFaceContour.LEFT_EYE).getPoints();
List<FirebaseVisionPoint> upperLipBottomContour =
face.getContour(FirebaseVisionFaceContour.UPPER_LIP_BOTTOM).getPoints();
// If classification was enabled:
if (face.getSmilingProbability() != FirebaseVisionFace.UNCOMPUTED_PROBABILITY) {
float smileProb = face.getSmilingProbability();
}
if (face.getRightEyeOpenProbability() != FirebaseVisionFace.UNCOMPUTED_PROBABILITY) {
float rightEyeOpenProb = face.getRightEyeOpenProbability();
}
// If face tracking was enabled:
if (face.getTrackingId() != FirebaseVisionFace.INVALID_ID) {
int id = face.getTrackingId();
}
}
4.实时人脸检测
相机获取到的图片对象imageProxy需要转换成
FirebaseVisionImage visionImage = FirebaseVisionImage.fromMediaImage(image, FirebaseVisionImageMetadata.ROTATION_90);
然后调用接口
detector.detectInImage(image)
添加
addOnSuccessListener
addOnFailureListener
识别成功和失败的接口(注意:如果图片角度不对,则识别失败也不会回调,相当于空跑,只有图片角度对了才可以)
识别成功回调到人脸信息List<FirebaseVisionFace>faces
遍历这个列表可以拿到人脸信息
private final List<Float> points = new ArrayList<>();
for (FirebaseVisionFace face : faces) {
//Rect bounds = face.getBoundingBox();
//float rotY = face.getHeadEulerAngleY(); // Head is rotated to the right rotY degrees
//float rotZ = face.getHeadEulerAngleZ(); // Head is tilted sideways rotZ degrees
//Log.e("Point", "top=" + bounds.top + " bottom=" + bounds.bottom + " left=" + bounds.left + " right=" + bounds.right);
// If landmark detection was enabled (mouth, ears, eyes, cheeks, and nose available):
//左眼
for (FirebaseVisionPoint point : face.getContour(FirebaseVisionFaceContour.LEFT_EYE).getPoints()) {
points.add(point.getX());
points.add(point.getY());
}
//右眼
for (FirebaseVisionPoint point : face.getContour(FirebaseVisionFaceContour.RIGHT_EYE).getPoints()) {
points.add(point.getX());
points.add(point.getY());
}
//左眉上
for (FirebaseVisionPoint point : face.getContour(FirebaseVisionFaceContour.LEFT_EYEBROW_TOP).getPoints()) {
points.add(point.getX());
points.add(point.getY());
}
//左眉下
for (FirebaseVisionPoint point : face.getContour(FirebaseVisionFaceContour.LEFT_EYEBROW_BOTTOM).getPoints()) {
points.add(point.getX());
points.add(point.getY());
}
//右眉上
for (FirebaseVisionPoint point : face.getContour(FirebaseVisionFaceContour.RIGHT_EYEBROW_TOP).getPoints()) {
points.add(point.getX());
points.add(point.getY());
}
//右眉下
for (FirebaseVisionPoint point : face.getContour(FirebaseVisionFaceContour.RIGHT_EYEBROW_BOTTOM).getPoints()) {
points.add(point.getX());
points.add(point.getY());
}
//鼻梁
for (FirebaseVisionPoint point : face.getContour(FirebaseVisionFaceContour.NOSE_BRIDGE).getPoints()) {
points.add(point.getX());
points.add(point.getY());
}
//鼻孔
for (FirebaseVisionPoint point : face.getContour(FirebaseVisionFaceContour.NOSE_BOTTOM).getPoints()) {
points.add(point.getX());
points.add(point.getY());
}
//上唇顶部
for (FirebaseVisionPoint point : face.getContour(FirebaseVisionFaceContour.UPPER_LIP_TOP).getPoints()) {
points.add(point.getX());
points.add(point.getY());
}
//上唇底部
for (FirebaseVisionPoint point : face.getContour(FirebaseVisionFaceContour.UPPER_LIP_BOTTOM).getPoints()) {
points.add(point.getX());
points.add(point.getY());
}
//下唇顶部
for (FirebaseVisionPoint point : face.getContour(FirebaseVisionFaceContour.LOWER_LIP_TOP).getPoints()) {
points.add(point.getX());
points.add(point.getY());
}
//下唇底部
for (FirebaseVisionPoint point : face.getContour(FirebaseVisionFaceContour.LOWER_LIP_BOTTOM).getPoints()) {
points.add(point.getX());
points.add(point.getY());
}
}
识别出来的所有特征坐标,是依赖于
FirebaseVisionImage getBitmap()
所以在实时更新识别后的画面时,可以在原bitmap的基础上绘制轮廓坐标点
Canvas canvas = new Canvas(bitmap);
float[] points_f = new float[points.size()];
for (int i = 0; i < points.size(); i++) {
points_f[i] = points.get(i);
}
Paint paint = new Paint();
paint.setColor(Color.BLUE);
paint.setStrokeWidth(5);
canvas.drawPoints(points_f, paint);
imageView.setImageBitmap(bitmap);//imageView是个图片控件
5.获取google服务
在没有添加google-services.json的时候,会报错提示没有initializeApp
FirebaseApp.initializeApp(activity);
官网
https://console.firebase.google.com/https://console.firebase.google.com/
|