Rileva le pose con ML Kit su Android

ML Kit fornisce due SDK ottimizzati per il rilevamento della postura.

Nome SDKpose-detectionpose-detection-accurate
ImplementazioneIl codice e gli asset sono collegati staticamente alla tua app al momento della creazione.Il codice e gli asset sono collegati staticamente alla tua app al momento della creazione.
Impatto sulle dimensioni dell'app (inclusi codice e asset)~10,1 MB~13,3 MB
RendimentoPixel 3XL: ~30FPSPixel 3 XL: circa 23 FPS con CPU, circa 30 FPS con GPU

Prova

  • Prova l'app di esempio per vedere un esempio di utilizzo di questa API.

Prima di iniziare

  1. Nel file build.gradle a livello di progetto, assicurati di includere il repository Maven di Google nelle sezioni buildscript e allprojects.
  2. Aggiungi le dipendenze per le librerie Android ML Kit al file Gradle a livello di app del modulo, che di solito è 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. Crea un'istanza di PoseDetector

PoseDetector opzioni

Per rilevare una posa in un'immagine, crea prima un'istanza di PoseDetector e specifica facoltativamente le impostazioni del rilevatore.

Modalità di rilevamento

Il PoseDetector funziona in due modalità di rilevamento. Assicurati di scegliere quello che corrisponde al tuo caso d'uso.

STREAM_MODE (valore predefinito)
Il rilevatore di pose rileva prima la persona più prominente nell'immagine e poi esegue il rilevamento della posa. Nei frame successivi, il passaggio di rilevamento delle persone non verrà eseguito a meno che la persona non venga oscurata o non venga più rilevata con un alto livello di confidenza. Il rilevatore di pose tenterà di tracciare la persona più in evidenza e restituirà la sua posa in ogni inferenza. In questo modo si riduce la latenza e si semplifica il rilevamento. Utilizza questa modalità quando vuoi rilevare la postura in un flusso video.
SINGLE_IMAGE_MODE
Il rilevatore di postura rileva una persona e poi esegue il rilevamento della postura. Il passaggio di rilevamento delle persone verrà eseguito per ogni immagine, quindi la latenza sarà maggiore e non è previsto il tracciamento delle persone. Utilizza questa modalità quando utilizzi il rilevamento della postura su immagini statiche o quando non è necessario il monitoraggio.

Configurazione hardware

PoseDetector supporta più configurazioni hardware per ottimizzare le prestazioni:

  • CPU: esegui il rilevatore utilizzando solo la CPU
  • CPU_GPU: esegui il rilevatore utilizzando sia la CPU che la GPU

Quando crei le opzioni del rilevatore, puoi utilizzare l'API setPreferredHardwareConfigs per controllare la selezione dell'hardware. Per impostazione predefinita, tutte le configurazioni hardware sono impostate come preferite.

ML Kit prenderà in considerazione la disponibilità, la stabilità, la correttezza e la latenza di ogni configurazione e sceglierà la migliore tra le configurazioni preferite. Se nessuna delle configurazioni preferite è applicabile, la configurazione CPU verrà utilizzata automaticamente come fallback. ML Kit esegue questi controlli e la preparazione correlata in modo non bloccante prima di attivare qualsiasi accelerazione, quindi è molto probabile che la prima volta che l'utente esegue il rilevatore, questo utilizzi CPU. Al termine di tutta la preparazione, la configurazione migliore verrà utilizzata nelle esecuzioni successive.

Esempi di utilizzo di setPreferredHardwareConfigs:

  • Per consentire a ML Kit di scegliere la configurazione migliore, non chiamare questa API.
  • Se non vuoi attivare alcuna accelerazione, passa solo CPU.
  • Se vuoi utilizzare la GPU per scaricare la CPU anche se la GPU potrebbe essere più lenta, passa solo CPU_GPU.

Specifica le opzioni del rilevatore di 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();

Infine, crea un'istanza di PoseDetector. Trasmetti le opzioni che hai specificato:

Kotlin

val poseDetector = PoseDetection.getClient(options)

Java

PoseDetector poseDetector = PoseDetection.getClient(options);

2. Prepara l'immagine di input

Per rilevare le pose in un'immagine, crea un oggetto InputImage da un Bitmap, media.Image, ByteBuffer, array di byte o un file sul dispositivo. Poi, passa l'oggetto InputImage a PoseDetector.

Per il rilevamento della postura, devi utilizzare un'immagine con dimensioni di almeno 480x360 pixel. Se rilevi pose in tempo reale, l'acquisizione di frame a questa risoluzione minima può contribuire a ridurre la latenza.

Puoi creare un oggetto InputImage da diverse origini, ognuna delle quali è spiegata di seguito.

Utilizzo di un media.Image

Per creare un oggetto InputImage da un oggetto media.Image, ad esempio quando acquisisci un'immagine dalla fotocamera di un dispositivo, passa l'oggetto media.Image e la rotazione dell'immagine a InputImage.fromMediaImage().

Se utilizzi la libreria CameraX, le classi OnImageCapturedListener e ImageAnalysis.Analyzer calcolano il valore di rotazione per te.

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 non utilizzi una libreria di fotocamere che ti fornisce il grado di rotazione dell'immagine, puoi calcolarlo dal grado di rotazione del dispositivo e dall'orientamento del sensore della fotocamera nel dispositivo:

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

Poi, passa l'oggetto media.Image e il valore del grado di rotazione a InputImage.fromMediaImage():

Kotlin

val image = InputImage.fromMediaImage(mediaImage, rotation)

Java

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

Utilizzo di un URI file

Per creare un oggetto InputImage da un URI file, passa il contesto dell'app e l'URI file a InputImage.fromFilePath(). Questa funzionalità è utile quando utilizzi un intent ACTION_GET_CONTENT per chiedere all'utente di selezionare un'immagine dalla sua app galleria.

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

Utilizzo di ByteBuffer o ByteArray

Per creare un oggetto InputImage da un ByteBuffer o da un ByteArray, calcola prima il grado di rotazione dell'immagine come descritto in precedenza per l'input media.Image. Quindi, crea l'oggetto InputImage con il buffer o l'array, insieme all'altezza, alla larghezza, al formato di codifica del colore e al grado di rotazione dell'immagine:

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

Utilizzo di un Bitmap

Per creare un oggetto InputImage da un oggetto Bitmap, effettua la seguente dichiarazione:

Kotlin

val image = InputImage.fromBitmap(bitmap, 0)

Java

InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);

L'immagine è rappresentata da un oggetto Bitmap insieme ai gradi di rotazione.

3. Elabora l'immagine

Passa l'oggetto InputImage preparato al metodo process di 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. Ottenere informazioni sulla postura rilevata

Se viene rilevata una persona nell'immagine, l'API di rilevamento della postura restituisce un oggetto Pose con 33 PoseLandmark.

Se la persona non era completamente all'interno dell'immagine, il modello assegna le coordinate dei punti di riferimento mancanti al di fuori dell'inquadratura e assegna loro valori InFrameConfidence bassi.

Se non è stata rilevata alcuna persona nel frame, l'oggetto Pose non contiene PoseLandmark.

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

Suggerimenti per migliorare il rendimento

La qualità dei risultati dipende dalla qualità dell'immagine di input:

  • Affinché ML Kit rilevi la posa in modo accurato, la persona nell'immagine deve essere rappresentata da dati pixel sufficienti; per un rendimento ottimale, il soggetto deve essere di almeno 256 x 256 pixel.
  • Se rilevi la posa in un'applicazione in tempo reale, potresti anche prendere in considerazione le dimensioni complessive delle immagini di input. Le immagini più piccole possono essere elaborate più velocemente, quindi per ridurre la latenza, acquisisci immagini a risoluzioni inferiori, ma tieni presenti i requisiti di risoluzione sopra indicati e assicurati che il soggetto occupi la maggior parte dell'immagine possibile.
  • Anche una messa a fuoco scadente dell'immagine può influire sull'accuratezza. Se non ottieni risultati accettabili, chiedi all'utente di acquisire nuovamente l'immagine.

Se vuoi utilizzare il rilevamento della postura in un'applicazione in tempo reale, segui queste linee guida per ottenere i migliori frame rate:

  • Utilizza l'SDK per il rilevamento della postura di base e STREAM_MODE.
  • Valuta la possibilità di acquisire immagini a una risoluzione inferiore. Tuttavia, tieni presente anche i requisiti di dimensione delle immagini di questa API.
  • Se utilizzi l'API Camera o camera2, limita le chiamate al rilevatore. Se un nuovo frame video diventa disponibile durante l'esecuzione del rilevatore, rilascia il frame. Per un esempio, consulta la classe VisionProcessorBase nell'app di esempio della guida rapida.
  • Se utilizzi l'API CameraX, assicurati che la strategia di backpressure sia impostata sul valore predefinito ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST. In questo modo, per l'analisi verrà inviata una sola immagine alla volta. Se vengono prodotte più immagini quando l'analizzatore è occupato, queste verranno eliminate automaticamente e non verranno messe in coda per la consegna. Una volta chiusa l'immagine in fase di analisi chiamando ImageProxy.close(), verrà inviata l'ultima immagine più recente.
  • Se utilizzi l'output del rilevatore per sovrapporre elementi grafici all'immagine di input, prima ottieni il risultato da ML Kit, poi esegui il rendering dell'immagine e la sovrapposizione in un unico passaggio. Il rendering viene eseguito sulla superficie di visualizzazione solo una volta per ogni frame di input. Per un esempio, consulta le classi CameraSourcePreview e GraphicOverlay nell'app di esempio di avvio rapido.
  • Se utilizzi l'API Camera2, acquisisci immagini in formato ImageFormat.YUV_420_888. Se utilizzi la versione precedente dell'API Camera, acquisisci immagini in formato ImageFormat.NV21.

Passaggi successivi