Ir para o console

Detectar faces com o Kit de ML no Android

Você pode usar o Kit de ML para detectar rostos em imagens e vídeos.

Consulte o guia de início rápido do Kit de ML (em inglês) no GitHub para ver um exemplo desta API em uso.

Antes de começar

  1. Adicione o Firebase ao seu projeto para Android, caso ainda não tenha feito isso.
  2. No arquivo build.gradle no nível do projeto, inclua o repositório Maven do Google nas seções buildscript e allprojects.
  3. Adicione as dependências das bibliotecas Android do Kit de ML ao arquivo Gradle do módulo (nível do aplicativo) que geralmente é app/build.gradle:
    dependencies {
      // ...
    
      implementation 'com.google.firebase:firebase-ml-vision:24.0.0'
      // 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:19.0.0'
    }
    apply plugin: 'com.google.gms.google-services'
    
  4. Opcional, mas recomendado: configure seu aplicativo para fazer o download automático do modelo de ML no dispositivo após a instalação do aplicativo a partir da Play Store.

    Para fazer isso, adicione a seguinte declaração ao arquivo AndroidManifest.xml do seu aplicativo:

    <application ...>
      ...
      <meta-data
          android:name="com.google.firebase.ml.vision.DEPENDENCIES"
          android:value="face" />
      <!-- To use multiple models: android:value="face,model2,model3" -->
    </application>
    
    Se você não ativar os downloads do modelo de tempo de instalação, ele será transferido na primeira vez em que você executar o detector. As solicitações feitas antes da conclusão do download não produzirão resultados.

Diretrizes de imagem de entrada

Para que o Kit de ML detecte rostos com precisão, as imagens de entrada devem conter rostos representados por dados de pixel suficientes. Em geral, cada rosto que você quiser detectar em uma imagem deverá ter pelo menos 100x100 pixels. Se você quiser detectar os contornos dos rostos, o Kit de ML precisará de uma entrada de resolução mais alta: cada rosto deverá ter pelo menos 200x200 pixels.

Se você estiver fazendo a detecção facial em um aplicativo em tempo real, considere as dimensões gerais das imagens de entrada. Já que imagens menores podem ser processadas mais rapidamente, para reduzir a latência, capture imagens em resoluções mais baixas (tendo em mente os requisitos de precisão acima) e certifique-se de que o rosto da pessoa ocupe o máximo possível da imagem. Consulte também Dicas para melhorar o desempenho em tempo real.

Uma imagem com foco inadequado pode prejudicar a precisão. Se você não estiver conseguindo resultados aceitáveis, peça para o usuário recapturar a imagem.

A orientação de um rosto em relação à câmera também pode afetar os atributos faciais que o Kit de ML detecta. Veja Conceitos de detecção facial.

1. Configurar o detector facial

Antes de aplicar a detecção facial a uma imagem, avalie se você quer alterar as configurações padrão do detector. Em caso positivo, use um objeto FirebaseVisionFaceDetectorOptions para fazer isso. É possível alterar as seguintes configurações:

Configurações
Modo de desempenho FAST (padrão) | ACCURATE

Favoreça a velocidade ou a precisão durante a detecção facial.

Detectar pontos de referência NO_LANDMARKS (padrão) | ALL_LANDMARKS

Para tentar identificar "pontos de referência" faciais: olhos, orelhas, nariz, bochechas, boca e assim por diante.

Detectar contornos NO_CONTOURS (padrão) | ALL_CONTOURS

Para detectar os contornos dos atributos faciais. São detectados apenas os contornos do rosto mais proeminente de uma imagem.

Classificar rostos NO_CLASSIFICATIONS (padrão) | ALL_CLASSIFICATIONS

Se é necessário classificar rostos em categorias como "sorrindo" e "olhos abertos".

Tamanho mínimo da face float (padrão: 0.1f)

O tamanho mínimo, relativo à imagem, de rostos a serem detectados.

Ativar rastreamento facial false (padrão) | true

Se é necessário atribuir um identificador às faces, que pode ser usado para rastrear rostos em imagens.

Quando a detecção de contorno está ativada, apenas um rosto é detectado, portanto, o rastreamento facial não produz resultados úteis. Por esse motivo, e para melhorar a velocidade de detecção, não ative a detecção de contorno simultaneamente com o rastreamento facial.

Exemplo:

Java

// High-accuracy landmark detection and face classification
FirebaseVisionFaceDetectorOptions highAccuracyOpts =
        new FirebaseVisionFaceDetectorOptions.Builder()
                .setPerformanceMode(FirebaseVisionFaceDetectorOptions.ACCURATE)
                .setLandmarkMode(FirebaseVisionFaceDetectorOptions.ALL_LANDMARKS)
                .setClassificationMode(FirebaseVisionFaceDetectorOptions.ALL_CLASSIFICATIONS)
                .build();

// Real-time contour detection of multiple faces
FirebaseVisionFaceDetectorOptions realTimeOpts =
        new FirebaseVisionFaceDetectorOptions.Builder()
                .setContourMode(FirebaseVisionFaceDetectorOptions.ALL_CONTOURS)
                .build();

Kotlin

// High-accuracy landmark detection and face classification
val highAccuracyOpts = FirebaseVisionFaceDetectorOptions.Builder()
        .setPerformanceMode(FirebaseVisionFaceDetectorOptions.ACCURATE)
        .setLandmarkMode(FirebaseVisionFaceDetectorOptions.ALL_LANDMARKS)
        .setClassificationMode(FirebaseVisionFaceDetectorOptions.ALL_CLASSIFICATIONS)
        .build()

// Real-time contour detection of multiple faces
val realTimeOpts = FirebaseVisionFaceDetectorOptions.Builder()
        .setContourMode(FirebaseVisionFaceDetectorOptions.ALL_CONTOURS)
        .build()

2. Executar o detector facial

Para detectar rostos em uma imagem, crie um objeto FirebaseVisionImage usando um Bitmap, media.Image, ByteBuffer, uma matriz de bytes ou um arquivo no dispositivo. Em seguida, transfira o objeto FirebaseVisionImage ao método detectInImage de FirebaseVisionFaceDetector.

Para reconhecimento facial, você deve usar uma imagem com dimensões de pelo menos 480x360 pixels. Se você estiver fazendo reconhecimento facial em tempo real, a captura de frames com essa resolução mínima poderá ajudar a reduzir a latência.

  1. Crie um objeto FirebaseVisionImage usando sua imagem.

    • Para criar um objeto FirebaseVisionImage usando um objeto media.Image, como na captura de uma imagem da câmera de um dispositivo, transmita o objeto media.Image e a rotação da imagem para FirebaseVisionImage.fromMediaImage().

      Se você usar a biblioteca CameraX, as classes OnImageCapturedListener e ImageAnalysis.Analyzer calcularão o valor de rotação e será necessário apenas converter a rotação em uma das constantes ROTATION_ do Kit de ML antes de chamar FirebaseVisionImage.fromMediaImage():

      Java

      private class YourAnalyzer implements ImageAnalysis.Analyzer {
      
          private int degreesToFirebaseRotation(int degrees) {
              switch (degrees) {
                  case 0:
                      return FirebaseVisionImageMetadata.ROTATION_0;
                  case 90:
                      return FirebaseVisionImageMetadata.ROTATION_90;
                  case 180:
                      return FirebaseVisionImageMetadata.ROTATION_180;
                  case 270:
                      return FirebaseVisionImageMetadata.ROTATION_270;
                  default:
                      throw new IllegalArgumentException(
                              "Rotation must be 0, 90, 180, or 270.");
              }
          }
      
          @Override
          public void analyze(ImageProxy imageProxy, int degrees) {
              if (imageProxy == null || imageProxy.getImage() == null) {
                  return;
              }
              Image mediaImage = imageProxy.getImage();
              int rotation = degreesToFirebaseRotation(degrees);
              FirebaseVisionImage image =
                      FirebaseVisionImage.fromMediaImage(mediaImage, rotation);
              // Pass image to an ML Kit Vision API
              // ...
          }
      }
      

      Kotlin

      private class YourImageAnalyzer : ImageAnalysis.Analyzer {
          private fun degreesToFirebaseRotation(degrees: Int): Int = when(degrees) {
              0 -> FirebaseVisionImageMetadata.ROTATION_0
              90 -> FirebaseVisionImageMetadata.ROTATION_90
              180 -> FirebaseVisionImageMetadata.ROTATION_180
              270 -> FirebaseVisionImageMetadata.ROTATION_270
              else -> throw Exception("Rotation must be 0, 90, 180, or 270.")
          }
      
          override fun analyze(imageProxy: ImageProxy?, degrees: Int) {
              val mediaImage = imageProxy?.image
              val imageRotation = degreesToFirebaseRotation(degrees)
              if (mediaImage != null) {
                  val image = FirebaseVisionImage.fromMediaImage(mediaImage, imageRotation)
                  // Pass image to an ML Kit Vision API
                  // ...
              }
          }
      }
      

      Se você não usar uma biblioteca de câmera que ofereça a rotação da imagem, será possível calculá-la a partir da rotação do dispositivo e da orientação do sensor da câmera:

      Java

      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;
      }

      Kotlin

      private val ORIENTATIONS = SparseIntArray()
      
      init {
          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)
      @Throws(CameraAccessException::class)
      private fun getRotationCompensation(cameraId: String, activity: Activity, context: Context): Int {
          // 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.
          val deviceRotation = activity.windowManager.defaultDisplay.rotation
          var 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.
          val cameraManager = context.getSystemService(CAMERA_SERVICE) as CameraManager
          val sensorOrientation = cameraManager
                  .getCameraCharacteristics(cameraId)
                  .get(CameraCharacteristics.SENSOR_ORIENTATION)!!
          rotationCompensation = (rotationCompensation + sensorOrientation + 270) % 360
      
          // Return the corresponding FirebaseVisionImageMetadata rotation value.
          val result: Int
          when (rotationCompensation) {
              0 -> result = FirebaseVisionImageMetadata.ROTATION_0
              90 -> result = FirebaseVisionImageMetadata.ROTATION_90
              180 -> result = FirebaseVisionImageMetadata.ROTATION_180
              270 -> result = FirebaseVisionImageMetadata.ROTATION_270
              else -> {
                  result = FirebaseVisionImageMetadata.ROTATION_0
                  Log.e(TAG, "Bad rotation value: $rotationCompensation")
              }
          }
          return result
      }

      Depois, transmita o objeto media.Image e o valor de rotação para FirebaseVisionImage.fromMediaImage():

      Java

      FirebaseVisionImage image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation);

      Kotlin

      val image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation)
    • Para criar um objeto FirebaseVisionImage a partir do URI de um arquivo, transmita o contexto do aplicativo e o URI do arquivo para FirebaseVisionImage.fromFilePath(). Isso é útil ao usar um intent ACTION_GET_CONTENT para solicitar que o usuário selecione uma imagem do aplicativo de galeria dele.

      Java

      FirebaseVisionImage image;
      try {
          image = FirebaseVisionImage.fromFilePath(context, uri);
      } catch (IOException e) {
          e.printStackTrace();
      }

      Kotlin

      val image: FirebaseVisionImage
      try {
          image = FirebaseVisionImage.fromFilePath(context, uri)
      } catch (e: IOException) {
          e.printStackTrace()
      }
    • Para criar um objeto FirebaseVisionImage usando um ByteBuffer ou uma matriz de bytes, primeiro calcule a rotação da imagem conforme descrito acima para a entrada media.Image.

      Em seguida, crie um objeto FirebaseVisionImageMetadata que contenha a altura, a largura, o formato de codificação de cor e a rotação da imagem:

      Java

      FirebaseVisionImageMetadata metadata = new FirebaseVisionImageMetadata.Builder()
              .setWidth(480)   // 480x360 is typically sufficient for
              .setHeight(360)  // image recognition
              .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21)
              .setRotation(rotation)
              .build();

      Kotlin

      val metadata = FirebaseVisionImageMetadata.Builder()
              .setWidth(480) // 480x360 is typically sufficient for
              .setHeight(360) // image recognition
              .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21)
              .setRotation(rotation)
              .build()

      Use o buffer ou a matriz junto do objeto de metadados para criar um objeto FirebaseVisionImage:

      Java

      FirebaseVisionImage image = FirebaseVisionImage.fromByteBuffer(buffer, metadata);// Or: FirebaseVisionImage image = FirebaseVisionImage.fromByteArray(byteArray, metadata);

      Kotlin

      val image = FirebaseVisionImage.fromByteBuffer(buffer, metadata)// Or: val image = FirebaseVisionImage.fromByteArray(byteArray, metadata)
    • Para criar um objeto FirebaseVisionImage a partir de um objeto Bitmap, use os snippets a seguir:

      Java

      FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);

      Kotlin

      val image = FirebaseVisionImage.fromBitmap(bitmap)
      A imagem representada pelo objeto Bitmap precisa estar na posição vertical, sem necessidade de girá-la novamente.
  2. Crie uma instância de FirebaseVisionFaceDetector:

    Java

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

    Kotlin

    val detector = FirebaseVision.getInstance()
            .getVisionFaceDetector(options)
  3. Finalmente, passe a imagem para o método detectInImage:

    Java

    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
                                    // ...
                                }
                            });

    Kotlin

    val result = detector.detectInImage(image)
            .addOnSuccessListener { faces ->
                // Task completed successfully
                // ...
            }
            .addOnFailureListener { e ->
                // Task failed with an exception
                // ...
            }

3. Acessar informações sobre rostos detectados

Se a operação de reconhecimento facial for bem-sucedida, uma lista de objetos de FirebaseVisionFace será transmitida para o listener de êxito. Cada objeto FirebaseVisionFace representa um rosto detectado na imagem. Para cada rosto, é possível receber as coordenadas delimitadoras na imagem de entrada, além de qualquer outra informação que você tenha configurado para detecção. Exemplo:

Java

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();
    }
}

Kotlin

for (face in faces) {
    val bounds = face.boundingBox
    val rotY = face.headEulerAngleY // Head is rotated to the right rotY degrees
    val rotZ = face.headEulerAngleZ // Head is tilted sideways rotZ degrees

    // If landmark detection was enabled (mouth, ears, eyes, cheeks, and
    // nose available):
    val leftEar = face.getLandmark(FirebaseVisionFaceLandmark.LEFT_EAR)
    leftEar?.let {
        val leftEarPos = leftEar.position
    }

    // If contour detection was enabled:
    val leftEyeContour = face.getContour(FirebaseVisionFaceContour.LEFT_EYE).points
    val upperLipBottomContour = face.getContour(FirebaseVisionFaceContour.UPPER_LIP_BOTTOM).points

    // If classification was enabled:
    if (face.smilingProbability != FirebaseVisionFace.UNCOMPUTED_PROBABILITY) {
        val smileProb = face.smilingProbability
    }
    if (face.rightEyeOpenProbability != FirebaseVisionFace.UNCOMPUTED_PROBABILITY) {
        val rightEyeOpenProb = face.rightEyeOpenProbability
    }

    // If face tracking was enabled:
    if (face.trackingId != FirebaseVisionFace.INVALID_ID) {
        val id = face.trackingId
    }
}

Exemplo de contornos faciais

Quando a detecção de contorno facial está ativada, você recebe uma lista de pontos para cada atributo facial que foi detectado. Esses pontos representam a forma do elemento. Consulte a Visão geral dos conceitos de detecção facial para ver detalhes sobre como os contornos são representados.

A imagem a seguir ilustra como esses pontos mapeiam um rosto. Clique para ampliá-la:

Detecção facial em tempo real

Se você quiser usar a detecção facial em um aplicativo em tempo real, siga estas diretrizes para conseguir as melhores taxas de frames.

  • Configure o detector facial para usar a detecção de contorno facial ou a classificação e a detecção de pontos de referência, mas não os dois:

    Detecção de contorno
    Detecção de pontos de referência
    Classificação
    Detecção e classificação de pontos de referência
    Detecção de contorno e detecção de pontos de referência
    Detecção e classificação de contornos
    Detecção de contorno, detecção e classificação de pontos de referência

  • Ative o modo FAST (ativado por padrão).

  • Capture imagens em uma resolução menor. No entanto, lembre-se também dos requisitos de dimensão de imagem da API.

  • Limite as chamadas ao detector. Se um novo frame de vídeo for disponibilizado enquanto o detector estiver em execução, descarte esse frame. Consulte a classe VisionProcessorBase no app de amostra do guia de início rápido para ver um exemplo.
  • Se você estiver usando a saída do detector para sobrepor elementos gráficos na imagem de entrada, primeiro acesse o resultado do Kit de ML e, em seguida, renderize a imagem e a sobreposição em uma única etapa. Ao fazer isso, você renderiza a superfície de exibição apenas uma vez para cada frame de entrada. Consulte as classes CameraSourcePreview e GraphicOverlay no app de amostra do guia de início rápido para ver um exemplo.
  • Se você usar a API Camera2, capture imagens no formato ImageFormat.YUV_420_888.

    Se você usar a API Camera mais antiga, capture imagens no formato ImageFormat.NV21.

Próximas etapas

Consulte o guia de início rápido do Kit de ML (em inglês) no GitHub para ver um exemplo desta API em uso.