O kit de ML oferece dois SDKs otimizados para detecção de postura.
| Nome do SDK | pose-detection | pose-detection-accurate |
|---|---|---|
| Implementação | O código e os recursos são vinculados estaticamente ao app no momento da compilação. | O código e os recursos são vinculados estaticamente ao app no momento da compilação. |
| Impacto do tamanho do app (incluindo código e recursos) | ~10,1 MB | ~13,3 MB |
| Desempenho | Pixel 3XL: ~30FPS | Pixel 3XL: ~23 QPS com CPU, ~30 QPS com GPU |
Faça um teste
- Teste o app de exemplo para ver um exemplo de uso dessa API.
Antes de começar
- No arquivo
build.gradleno nível do projeto, inclua o repositório Maven do Google nas seçõesbuildscripteallprojects. Adicione as dependências das bibliotecas do Android do Kit de ML ao arquivo Gradle no nível do app do módulo, que geralmente é
app/build.gradle:dependencies { // If you want to use the base sdk implementation 'com.google.mlkit:pose-detection:18.0.0-beta5' // If you want to use the accurate sdk implementation 'com.google.mlkit:pose-detection-accurate:18.0.0-beta5' }
1. Criar uma instância de PoseDetector
PoseDetector opções
Para detectar uma pose em uma imagem, primeiro crie uma instância de PoseDetector e especifique as configurações do detector, se quiser.
Modo de detecção
O PoseDetector opera em dois modos de detecção. Escolha a opção que corresponde ao seu caso de uso.
STREAM_MODE(padrão)- O detector de postura primeiro detecta a pessoa mais proeminente na imagem e depois executa a detecção de postura. Nos frames subsequentes, a etapa de detecção de pessoas não será realizada, a menos que a pessoa fique obscurecida ou não seja mais detectada com alta confiança. O detector de postura tentará rastrear a pessoa mais proeminente e retornar a postura dela em cada inferência. Isso reduz a latência e facilita a detecção. Use esse modo quando quiser detectar poses em um stream de vídeo.
SINGLE_IMAGE_MODE- O detector de postura detecta uma pessoa e executa a detecção de postura. A etapa de detecção de pessoas será executada para todas as imagens, então a latência será maior, e não haverá rastreamento de pessoas. Use esse modo quando usar a detecção de postura em imagens estáticas ou quando não quiser fazer o rastreamento.
Configuração do hardware
O PoseDetector é compatível com várias configurações de hardware para otimizar o desempenho:
CPU: execute o detector usando apenas a CPU.CPU_GPU: execute o detector usando CPU e GPU.
Ao criar as opções de detector, use a API
setPreferredHardwareConfigs para controlar a seleção de hardware. Por padrão, todas as configurações de hardware são definidas como preferenciais.
O ML Kit considera a disponibilidade, a estabilidade, a correção e a latência de cada configuração
e escolhe a melhor entre as configurações preferidas. Se nenhuma das configurações preferenciais for aplicável, a configuração CPU será usada automaticamente como substituta. O ML Kit faz essas verificações e o preparo relacionado de maneira
não bloqueadora antes de ativar qualquer aceleração. Portanto, é muito provável que a
primeira vez que o usuário executar o detector, ele usará CPU. Depois que todo o
preparo terminar, a melhor configuração será usada nas execuções a seguir.
Exemplos de uso do setPreferredHardwareConfigs:
- Para permitir que o ML Kit escolha a melhor configuração, não chame essa API.
- Se você não quiser ativar nenhuma aceleração, transmita apenas
CPU. - Se você quiser usar a GPU para descarregar a CPU, mesmo que a GPU possa ser mais lenta, transmita apenas
CPU_GPU.
Especifique as opções do detector de postura:
Kotlin
// Base pose detector with streaming frames, when depending on the pose-detection sdk val options = PoseDetectorOptions.Builder() .setDetectorMode(PoseDetectorOptions.STREAM_MODE) .build() // Accurate pose detector on static images, when depending on the pose-detection-accurate sdk val options = AccuratePoseDetectorOptions.Builder() .setDetectorMode(AccuratePoseDetectorOptions.SINGLE_IMAGE_MODE) .build()
Java
// Base pose detector with streaming frames, when depending on the pose-detection sdk PoseDetectorOptions options = new PoseDetectorOptions.Builder() .setDetectorMode(PoseDetectorOptions.STREAM_MODE) .build(); // Accurate pose detector on static images, when depending on the pose-detection-accurate sdk AccuratePoseDetectorOptions options = new AccuratePoseDetectorOptions.Builder() .setDetectorMode(AccuratePoseDetectorOptions.SINGLE_IMAGE_MODE) .build();
Por fim, crie uma instância de PoseDetector. Transmita as opções especificadas:
Kotlin
val poseDetector = PoseDetection.getClient(options)
Java
PoseDetector poseDetector = PoseDetection.getClient(options);
2. Preparar a imagem de entrada
Para detectar poses em uma imagem, crie um objeto InputImage
usando Bitmap, media.Image, ByteBuffer, matriz de bytes ou um arquivo
no dispositivo. Em seguida, transmita o objeto InputImage para o
PoseDetector.
Para detecção de postura, use uma imagem com dimensões de pelo menos 480x360 pixels. Se você estiver fazendo detecção de poses em tempo real, a captura de frames com essa resolução mínima poderá ajudar a reduzir a latência.
É possível criar um objeto InputImage
de diferentes fontes, cada uma explicada abaixo.
Como usar um media.Image
Para criar um objeto InputImage
usando um objeto media.Image, como ao capturar uma imagem da
câmera de um dispositivo, transmita o objeto media.Image e a
rotação da imagem para InputImage.fromMediaImage().
Se você usar a
biblioteca
CameraX, as classes OnImageCapturedListener e
ImageAnalysis.Analyzer vão calcular o valor de rotação
para você.
Kotlin
private class YourImageAnalyzer : ImageAnalysis.Analyzer { override fun analyze(imageProxy: ImageProxy) { val mediaImage = imageProxy.image if (mediaImage != null) { val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees) // Pass image to an ML Kit Vision API // ... } } }
Java
private class YourAnalyzer implements ImageAnalysis.Analyzer { @Override public void analyze(ImageProxy imageProxy) { Image mediaImage = imageProxy.getImage(); if (mediaImage != null) { InputImage image = InputImage.fromMediaImage(mediaImage, imageProxy.getImageInfo().getRotationDegrees()); // Pass image to an ML Kit Vision API // ... } } }
Se você não usar uma biblioteca de câmera que ofereça o grau de rotação da imagem, será possível calcular usando o grau de rotação do dispositivo e a orientação do sensor da câmera:
Kotlin
private val ORIENTATIONS = SparseIntArray() init { ORIENTATIONS.append(Surface.ROTATION_0, 0) ORIENTATIONS.append(Surface.ROTATION_90, 90) ORIENTATIONS.append(Surface.ROTATION_180, 180) ORIENTATIONS.append(Surface.ROTATION_270, 270) } /** * 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, isFrontFacing: Boolean): 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) // Get the device's sensor orientation. val cameraManager = activity.getSystemService(CAMERA_SERVICE) as CameraManager val sensorOrientation = cameraManager .getCameraCharacteristics(cameraId) .get(CameraCharacteristics.SENSOR_ORIENTATION)!! if (isFrontFacing) { rotationCompensation = (sensorOrientation + rotationCompensation) % 360 } else { // back-facing rotationCompensation = (sensorOrientation - rotationCompensation + 360) % 360 } return rotationCompensation }
Java
private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); static { ORIENTATIONS.append(Surface.ROTATION_0, 0); ORIENTATIONS.append(Surface.ROTATION_90, 90); ORIENTATIONS.append(Surface.ROTATION_180, 180); ORIENTATIONS.append(Surface.ROTATION_270, 270); } /** * 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, boolean isFrontFacing) 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); // Get the device's sensor orientation. CameraManager cameraManager = (CameraManager) activity.getSystemService(CAMERA_SERVICE); int sensorOrientation = cameraManager .getCameraCharacteristics(cameraId) .get(CameraCharacteristics.SENSOR_ORIENTATION); if (isFrontFacing) { rotationCompensation = (sensorOrientation + rotationCompensation) % 360; } else { // back-facing rotationCompensation = (sensorOrientation - rotationCompensation + 360) % 360; } return rotationCompensation; }
Em seguida, transmita o objeto media.Image e o
valor do grau de rotação para InputImage.fromMediaImage():
Kotlin
val image = InputImage.fromMediaImage(mediaImage, rotation)
Java
InputImage image = InputImage.fromMediaImage(mediaImage, rotation);
Como usar um URI de arquivo
Para criar um objeto InputImage
usando o URI de um arquivo, transmita o contexto do app e o URI do arquivo para
InputImage.fromFilePath(). Isso é útil ao usar
uma intent ACTION_GET_CONTENT para solicitar que o usuário selecione
uma imagem no app de galeria dele.
Kotlin
val image: InputImage try { image = InputImage.fromFilePath(context, uri) } catch (e: IOException) { e.printStackTrace() }
Java
InputImage image; try { image = InputImage.fromFilePath(context, uri); } catch (IOException e) { e.printStackTrace(); }
Como usar ByteBuffer ou ByteArray
Para criar um objeto InputImage
usando um ByteBuffer ou um ByteArray, primeiro calcule o grau de rotação da imagem
conforme descrito anteriormente para a entrada de media.Image.
Em seguida, crie o objeto InputImage com o buffer ou a matriz, com a altura,
a largura, o formato de codificação de cores e o grau de rotação da imagem:
Kotlin
val image = InputImage.fromByteBuffer( byteBuffer, /* image width */ 480, /* image height */ 360, rotationDegrees, InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12 ) // Or: val image = InputImage.fromByteArray( byteArray, /* image width */ 480, /* image height */ 360, rotationDegrees, InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12 )
Java
InputImage image = InputImage.fromByteBuffer(byteBuffer, /* image width */ 480, /* image height */ 360, rotationDegrees, InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12 ); // Or: InputImage image = InputImage.fromByteArray( byteArray, /* image width */480, /* image height */360, rotation, InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12 );
Como usar um Bitmap
Para criar um objeto InputImage
com base em um objeto Bitmap, faça a seguinte declaração:
Kotlin
val image = InputImage.fromBitmap(bitmap, 0)
Java
InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);
A imagem é representada por um objeto Bitmap com os graus de rotação.
3. Processar a imagem
Transmita o objeto InputImage preparado para o método process do PoseDetector.
Kotlin
Task<Pose> result = poseDetector.process(image)
.addOnSuccessListener { results ->
// Task completed successfully
// ...
}
.addOnFailureListener { e ->
// Task failed with an exception
// ...
}Java
Task<Pose> result = poseDetector.process(image) .addOnSuccessListener( new OnSuccessListener<Pose>() { @Override public void onSuccess(Pose pose) { // Task completed successfully // ... } }) .addOnFailureListener( new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { // Task failed with an exception // ... } });
4. Receber informações sobre a postura detectada
Se uma pessoa for detectada na imagem, a API de detecção de postura vai retornar um objeto Pose com 33 PoseLandmarks.
Se a pessoa não estiver completamente dentro da imagem, o modelo vai atribuir as coordenadas dos pontos de referência ausentes fora do frame e dar a elas valores baixos de InFrameConfidence.
Se nenhuma pessoa for detectada no frame, o objeto Pose não vai conter PoseLandmarks.
Kotlin
// Get all PoseLandmarks. If no person was detected, the list will be empty val allPoseLandmarks = pose.getAllPoseLandmarks() // Or get specific PoseLandmarks individually. These will all be null if no person // was detected val leftShoulder = pose.getPoseLandmark(PoseLandmark.LEFT_SHOULDER) val rightShoulder = pose.getPoseLandmark(PoseLandmark.RIGHT_SHOULDER) val leftElbow = pose.getPoseLandmark(PoseLandmark.LEFT_ELBOW) val rightElbow = pose.getPoseLandmark(PoseLandmark.RIGHT_ELBOW) val leftWrist = pose.getPoseLandmark(PoseLandmark.LEFT_WRIST) val rightWrist = pose.getPoseLandmark(PoseLandmark.RIGHT_WRIST) val leftHip = pose.getPoseLandmark(PoseLandmark.LEFT_HIP) val rightHip = pose.getPoseLandmark(PoseLandmark.RIGHT_HIP) val leftKnee = pose.getPoseLandmark(PoseLandmark.LEFT_KNEE) val rightKnee = pose.getPoseLandmark(PoseLandmark.RIGHT_KNEE) val leftAnkle = pose.getPoseLandmark(PoseLandmark.LEFT_ANKLE) val rightAnkle = pose.getPoseLandmark(PoseLandmark.RIGHT_ANKLE) val leftPinky = pose.getPoseLandmark(PoseLandmark.LEFT_PINKY) val rightPinky = pose.getPoseLandmark(PoseLandmark.RIGHT_PINKY) val leftIndex = pose.getPoseLandmark(PoseLandmark.LEFT_INDEX) val rightIndex = pose.getPoseLandmark(PoseLandmark.RIGHT_INDEX) val leftThumb = pose.getPoseLandmark(PoseLandmark.LEFT_THUMB) val rightThumb = pose.getPoseLandmark(PoseLandmark.RIGHT_THUMB) val leftHeel = pose.getPoseLandmark(PoseLandmark.LEFT_HEEL) val rightHeel = pose.getPoseLandmark(PoseLandmark.RIGHT_HEEL) val leftFootIndex = pose.getPoseLandmark(PoseLandmark.LEFT_FOOT_INDEX) val rightFootIndex = pose.getPoseLandmark(PoseLandmark.RIGHT_FOOT_INDEX) val nose = pose.getPoseLandmark(PoseLandmark.NOSE) val leftEyeInner = pose.getPoseLandmark(PoseLandmark.LEFT_EYE_INNER) val leftEye = pose.getPoseLandmark(PoseLandmark.LEFT_EYE) val leftEyeOuter = pose.getPoseLandmark(PoseLandmark.LEFT_EYE_OUTER) val rightEyeInner = pose.getPoseLandmark(PoseLandmark.RIGHT_EYE_INNER) val rightEye = pose.getPoseLandmark(PoseLandmark.RIGHT_EYE) val rightEyeOuter = pose.getPoseLandmark(PoseLandmark.RIGHT_EYE_OUTER) val leftEar = pose.getPoseLandmark(PoseLandmark.LEFT_EAR) val rightEar = pose.getPoseLandmark(PoseLandmark.RIGHT_EAR) val leftMouth = pose.getPoseLandmark(PoseLandmark.LEFT_MOUTH) val rightMouth = pose.getPoseLandmark(PoseLandmark.RIGHT_MOUTH)
Java
// Get all PoseLandmarks. If no person was detected, the list will be empty List<PoseLandmark> allPoseLandmarks = pose.getAllPoseLandmarks(); // Or get specific PoseLandmarks individually. These will all be null if no person // was detected PoseLandmark leftShoulder = pose.getPoseLandmark(PoseLandmark.LEFT_SHOULDER); PoseLandmark rightShoulder = pose.getPoseLandmark(PoseLandmark.RIGHT_SHOULDER); PoseLandmark leftElbow = pose.getPoseLandmark(PoseLandmark.LEFT_ELBOW); PoseLandmark rightElbow = pose.getPoseLandmark(PoseLandmark.RIGHT_ELBOW); PoseLandmark leftWrist = pose.getPoseLandmark(PoseLandmark.LEFT_WRIST); PoseLandmark rightWrist = pose.getPoseLandmark(PoseLandmark.RIGHT_WRIST); PoseLandmark leftHip = pose.getPoseLandmark(PoseLandmark.LEFT_HIP); PoseLandmark rightHip = pose.getPoseLandmark(PoseLandmark.RIGHT_HIP); PoseLandmark leftKnee = pose.getPoseLandmark(PoseLandmark.LEFT_KNEE); PoseLandmark rightKnee = pose.getPoseLandmark(PoseLandmark.RIGHT_KNEE); PoseLandmark leftAnkle = pose.getPoseLandmark(PoseLandmark.LEFT_ANKLE); PoseLandmark rightAnkle = pose.getPoseLandmark(PoseLandmark.RIGHT_ANKLE); PoseLandmark leftPinky = pose.getPoseLandmark(PoseLandmark.LEFT_PINKY); PoseLandmark rightPinky = pose.getPoseLandmark(PoseLandmark.RIGHT_PINKY); PoseLandmark leftIndex = pose.getPoseLandmark(PoseLandmark.LEFT_INDEX); PoseLandmark rightIndex = pose.getPoseLandmark(PoseLandmark.RIGHT_INDEX); PoseLandmark leftThumb = pose.getPoseLandmark(PoseLandmark.LEFT_THUMB); PoseLandmark rightThumb = pose.getPoseLandmark(PoseLandmark.RIGHT_THUMB); PoseLandmark leftHeel = pose.getPoseLandmark(PoseLandmark.LEFT_HEEL); PoseLandmark rightHeel = pose.getPoseLandmark(PoseLandmark.RIGHT_HEEL); PoseLandmark leftFootIndex = pose.getPoseLandmark(PoseLandmark.LEFT_FOOT_INDEX); PoseLandmark rightFootIndex = pose.getPoseLandmark(PoseLandmark.RIGHT_FOOT_INDEX); PoseLandmark nose = pose.getPoseLandmark(PoseLandmark.NOSE); PoseLandmark leftEyeInner = pose.getPoseLandmark(PoseLandmark.LEFT_EYE_INNER); PoseLandmark leftEye = pose.getPoseLandmark(PoseLandmark.LEFT_EYE); PoseLandmark leftEyeOuter = pose.getPoseLandmark(PoseLandmark.LEFT_EYE_OUTER); PoseLandmark rightEyeInner = pose.getPoseLandmark(PoseLandmark.RIGHT_EYE_INNER); PoseLandmark rightEye = pose.getPoseLandmark(PoseLandmark.RIGHT_EYE); PoseLandmark rightEyeOuter = pose.getPoseLandmark(PoseLandmark.RIGHT_EYE_OUTER); PoseLandmark leftEar = pose.getPoseLandmark(PoseLandmark.LEFT_EAR); PoseLandmark rightEar = pose.getPoseLandmark(PoseLandmark.RIGHT_EAR); PoseLandmark leftMouth = pose.getPoseLandmark(PoseLandmark.LEFT_MOUTH); PoseLandmark rightMouth = pose.getPoseLandmark(PoseLandmark.RIGHT_MOUTH);
Dicas para melhorar a performance
A qualidade dos resultados depende da qualidade da imagem de entrada:
- Para que o Kit de ML detecte a postura com precisão, a pessoa na imagem precisa ser representada por dados de pixel suficientes. Para ter o melhor desempenho, o assunto precisa ter pelo menos 256 x 256 pixels.
- Se você estiver detectando poses em um aplicativo em tempo real, considere as dimensões gerais das imagens de entrada. Imagens menores podem ser processadas mais rapidamente. Para reduzir a latência, capture imagens em resoluções mais baixas, mas tenha em mente os requisitos de resolução acima e faça o assunto ocupar o máximo possível da imagem.
- Uma imagem com foco inadequado também pode prejudicar a precisão. Se os resultados não forem aceitáveis, peça para o usuário recapturar a imagem.
Se você quiser usar a detecção de postura em um aplicativo em tempo real, siga estas diretrizes para conseguir as melhores taxas de frames:
- Use o SDK de detecção de postura básica e
STREAM_MODE. - Capture imagens em uma resolução menor. No entanto, lembre-se também dos requisitos de dimensão de imagem da API.
- Se você usar a API
Cameraoucamera2, limite as chamadas ao detector. Se um novo frame de vídeo ficar disponível durante a execução do detector, descarte esse frame. Consulte a classeVisionProcessorBaseno app de amostra do guia de início rápido para conferir um exemplo. - Se você usar a API
CameraX, verifique se a estratégia de contrapressão está definida como o valor padrãoImageAnalysis.STRATEGY_KEEP_ONLY_LATEST. Isso garante que apenas uma imagem seja entregue para análise por vez. Se mais imagens forem produzidas quando o analisador estiver ocupado, elas serão descartadas automaticamente e não serão enfileiradas para entrega. Quando a imagem analisada é fechada chamando ImageProxy.close(), a próxima imagem mais recente é entregue. - Se você usar a saída do detector para sobrepor elementos gráficos na
imagem de entrada, primeiro acesse o resultado do Kit de ML. Em seguida, renderize a imagem
e faça a sobreposição de uma só vez. Isso renderiza a superfície de exibição apenas uma vez para cada frame de entrada. Consulte as classes
CameraSourcePrevieweGraphicOverlayno app de amostra do guia de início rápido para conferir 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 formatoImageFormat.NV21.
Próximas etapas
- Para saber como usar pontos de referência de postura para classificar poses, consulte Dicas de classificação de postura.