资讯详情

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

先上效果图(整个项目源代码在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对象传递给FirebaseVisionFaceDetectordetectInImage方法。

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

  1. 基于图片创建FirebaseVisionImage对象。

    • 如需基于media.Image对象创建FirebaseVisionImage对象(例如从设备的相机捕获图片时),请将media.Image对象和图片的旋转角度传递给FirebaseVisionImage.fromMediaImage()

      假如你用了CameraX库,OnImageCapturedListenerImagAnalysis.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/

标签: android传感器类别

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台