Android面部动态识别(眼睛+嘴巴+鼻子轮廓标记)

48 篇文章 2 订阅

先上效果图(整个项目源码在gitee,如果有需要的同学可以私信我,仅限于学习)

准备工作

  1. 将 Firebase 添加到您的 Android 项目(如果尚未添加)。
  2. 将 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'
    }
  3. 非强制但建议执行的操作:对您的应用进行配置,使之在从 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 像素。

如果您是在实时应用中检测人脸,则可能还需要考虑输入图片的整体尺寸。较小图片的处理速度相对较快,因此,为了减少延迟时间,请以较低的分辨率捕获图片(牢记上述准确性要求),并确保主体的面部在图片中占尽可能大的部分。另请参阅提高实时性能的相关提示

图片聚焦不良会影响准确性。如果您获得的结果不可接受,请尝试让用户重新捕获图片。

1. 配置人脸检测器

在对图片应用人脸检测之前,如果要更改人脸检测器的任何默认设置,请使用 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 对象:Bitmapmedia.ImageByteBuffer、字节数组或文件。然后,将 FirebaseVisionImage 对象传递给 FirebaseVisionFaceDetector 的 detectInImage 方法。

对于人脸识别,您使用的图片尺寸应至少为 480x360 像素。如果您要实时识别人脸,以此最低分辨率捕获帧有助于减少延迟时间。

  1. 基于图片创建 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 对象表示的图片必须保持竖直,不需要额外的旋转。
  2. 获取 FirebaseVisionFaceDetector 的一个实例:

    FirebaseVisionFaceDetector detector = FirebaseVision.getInstance()
            .getVisionFaceDetector(options);

注意:检查控制台,看看是否存在构造函数生成的错误。

  1. 最后,将图片传递给 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
                                    // ...
                                }
                            });

    注意:检查控制台,看看是否存在检测器生成的错误。

3. 获取检测到的人脸的相关信息

如果人脸识别操作成功,系统会向成功侦听器传递一组 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/

  • 13
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不会写代码的猴子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值