Rilevare le posizioni con il kit ML su Android

Mantieni tutto organizzato con le raccolte Salva e classifica i contenuti in base alle tue preferenze.

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

Nome SDKrilevamento posizionerilevamento-posa-preciso
ImplementazioneCodice e asset collegati in modo statico alla tua app al momento della build.Codice e asset collegati in modo statico alla tua app al momento della build.
Impatto sulle dimensioni dell'app (inclusi codice e asset)~11MB~14MB
PrestazioniPixel 3 XL: circa 30 f/sPixel 3XL: ~ 23 f/s con CPU, ~ 30 f/s con GPU

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 di ML Kit al file gradle a livello di app del modulo, che in genere è app/build.gradle:

    dependencies {
      // If you want to use the base sdk
      implementation 'com.google.mlkit:pose-detection:18.0.0-beta3'
      // If you want to use the accurate sdk
      implementation 'com.google.mlkit:pose-detection-accurate:18.0.0-beta3'
    }
    

1. Crea un'istanza di PoseDetector

Opzioni PoseDetector

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

Modalità di rilevamento

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

STREAM_MODE (valore predefinito)
Il rilevatore di posa rileva innanzitutto la persona più in evidenza nell'immagine, quindi esegue il rilevamento. Nei frame successivi, il passaggio di rilevamento di una persona non verrà eseguito a meno che la persona non venga oscurata o non venga più rilevata con un'elevata affidabilità. Il rilevatore di posizioni proverà a tenere traccia della persona più in evidenza e a restituire la sua posa a ogni inferenza. Questo riduce la latenza e agevola il rilevamento. Utilizza questa modalità se vuoi rilevare la posa in un video stream.
SINGLE_IMAGE_MODE
Il rilevatore di posa rileva la persona e poi esegue il rilevamento della posa. Il passaggio di rilevamento persone viene eseguito per ogni immagine, quindi la latenza è superiore e non è previsto il monitoraggio delle persone. Usa questa modalità quando utilizzi il rilevamento della posa su immagini statiche o dove non vuoi il monitoraggio.

Configurazione hardware

PoseDetector supporta più configurazioni hardware per l'ottimizzazione delle prestazioni:

  • CPU: esegui il rilevatore utilizzando solo la CPU
  • CPU_GPU: esegui il rilevatore utilizzando CPU e 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 disponibilità, stabilità, correttezza e latenza di ogni configurazione e sceglierà la migliore dalle configurazioni preferite. Se nessuna delle configurazioni preferite è applicabile, la configurazione CPU verrà utilizzata automaticamente come riserva. ML Kit eseguirà questi controlli e la relativa preparazione in modo non bloccante prima di abilitare qualsiasi accelerazione, quindi è molto probabile che la prima volta che l'utente eseguirà il rilevatore, utilizzerà CPU. Al termine della preparazione, la configurazione migliore verrà utilizzata nelle esecuzioni successive.

Esempi di utilizzo di setPreferredHardwareConfigs:

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

Specifica le opzioni del rilevatore di posizioni:

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. Passa le opzioni specificate:

Kotlin

val poseDetector = PoseDetection.getClient(options)

Java

PoseDetector poseDetector = PoseDetection.getClient(options);

2. Prepara l'immagine di input

Per rilevare le posizioni in un'immagine, crea un oggetto InputImage da un elemento Bitmap, media.Image, ByteBuffer, da un array di byte o da un file sul dispositivo. Passa quindi l'oggetto InputImage a PoseDetector.

Per il rilevamento della posa, devi utilizzare un'immagine con dimensioni di almeno 480 x 360 pixel. Se rilevi le posizioni in tempo reale, l'acquisizione di frame con questa risoluzione minima può aiutarti a ridurre la latenza.

Puoi creare un oggetto InputImage da origini diverse, ognuno dei quali è illustrato di seguito.

Con 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 automaticamente.

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 raccolta di fotocamere che ti dà il grado di rotazione dell'immagine, puoi calcolarla in base al grado di rotazione e all'orientamento del sensore della fotocamera del 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;
}

Quindi, trasmetti 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);

usando un URI di file.

Per creare un oggetto InputImage da un URI del file, passa il contesto dell'app e l'URI del file a InputImage.fromFilePath(). Ciò è utile quando utilizzi un intent ACTION_GET_CONTENT per richiedere 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();
}

Con ByteBuffer o ByteArray

Per creare un oggetto InputImage da un elemento ByteBuffer o ByteArray, innanzitutto calcola il grado di rotazione delle immagini 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 dei colori 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
);

Con 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 a 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. Ricevere informazioni sulla posa rilevata

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

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

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 possa rilevare con precisione la posizione, la persona nell'immagine deve essere rappresentata da dati pixel sufficienti; per prestazioni ottimali, il soggetto deve essere di almeno 256 x 256 pixel.
  • Se rilevi la posa in un'applicazione in tempo reale, puoi anche valutare 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 più basse, ma tieni presenti i requisiti di risoluzione precedenti e assicurati che il soggetto occupi il maggior numero possibile di immagini.
  • Una scarsa messa a fuoco dell'immagine può anche influire sulla precisione. Se non ottieni risultati accettabili, chiedi all'utente di riottenere l'immagine.

Se vuoi utilizzare il rilevamento della posa in un'applicazione in tempo reale, segui queste linee guida per ottenere le migliori frequenze fotogrammi:

  • Usa l'SDK per il rilevamento della posizione di base e STREAM_MODE.
  • Potresti acquisire immagini a una risoluzione inferiore. Tuttavia, tieni presente anche i requisiti relativi alle dimensioni delle immagini di questa API.
  • Se utilizzi l'API Camera o camera2, limita le chiamate al rilevatore. Se è disponibile un nuovo frame video mentre il rilevatore è in esecuzione, trascina il frame. Consulta la classe VisionProcessorBase nell'app di esempio della guida rapida.
  • Se utilizzi l'API CameraX, assicurati che la strategia di contropressione sia impostata sul suo valore predefinito ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST. Garantisce che venga pubblicata una sola immagine alla volta. Se vengono generate più immagini quando lo strumento di analisi è occupato, le immagini vengono eliminate automaticamente e non vengono messe in coda per la pubblicazione. Una volta che l'immagine da analizzare viene chiusa chiamando ImageProxy.close(), verrà pubblicata l'ultima immagine successiva.
  • Se utilizzi l'output del rilevatore per sovrapporre la grafica sull'immagine di input, ottieni prima il risultato dal ML Kit, quindi visualizza l'immagine e l'overlay in un solo passaggio. Viene visualizzato sulla superficie di visualizzazione una sola volta per ogni frame di input. Per un esempio, consulta le classi CameraSourcePreview e GraphicOverlay nell'app di esempio della guida rapida.
  • Se utilizzi l'API Camera2, acquisisci le immagini nel formato ImageFormat.YUV_420_888. Se utilizzi la precedente API Camera, acquisisci le immagini nel formato ImageFormat.NV21.

Passaggi successivi