ML Kit предоставляет два оптимизированных SDK для определения позы.
| Название SDK | определение позы | точное определение позы |
|---|---|---|
| Выполнение | Код и ресурсы статически связываются с вашим приложением во время сборки. | Код и ресурсы статически связываются с вашим приложением во время сборки. |
| Влияние на размер приложения (включая код и ресурсы) | ~10,1 МБ | ~13,3 МБ |
| Производительность | Pixel 3XL: ~30 кадров в секунду | Pixel 3XL: ~23 FPS с процессором, ~30 FPS с видеокартой. |
Попробуйте!
- Поэкспериментируйте с примером приложения , чтобы увидеть, как используется этот API.
Прежде чем начать
- В файле
build.gradleна уровне проекта обязательно укажите репозиторий Maven от Google в разделахbuildscriptиallprojects. Добавьте зависимости для библиотек ML Kit Android в файл gradle вашего модуля, обычно это
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. Создайте экземпляр PoseDetector
Параметры PoseDetector
Для определения позы на изображении сначала создайте экземпляр PoseDetector и, при необходимости, укажите параметры детектора.
Режим обнаружения
Устройство PoseDetector работает в двух режимах обнаружения. Убедитесь, что вы выбрали тот, который соответствует вашим задачам.
-
STREAM_MODE(по умолчанию) - Детектор позы сначала определит наиболее заметного человека на изображении, а затем выполнит определение позы. В последующих кадрах этап определения человека не будет выполняться, если только человек не будет скрыт или перестанет определяться с высокой степенью достоверности. Детектор позы будет пытаться отслеживать наиболее заметного человека и возвращать его позу при каждом определении. Это уменьшает задержку и сглаживает процесс обнаружения. Используйте этот режим, если вам нужно определить позу в видеопотоке.
-
SINGLE_IMAGE_MODE - Детектор поз сначала обнаружит человека, а затем выполнит определение позы. Этап обнаружения человека будет выполняться для каждого изображения, поэтому задержка будет выше, и отслеживание человека не будет выполняться. Используйте этот режим, если определение позы применяется к статическим изображениям или если отслеживание не требуется.
Конфигурация оборудования
PoseDetector поддерживает различные аппаратные конфигурации для оптимизации производительности:
-
CPU: запустить детектор, используя только процессор. -
CPU_GPU: запустить детектор, используя одновременно CPU и GPU.
При настройке параметров детектора можно использовать API setPreferredHardwareConfigs для управления выбором оборудования. По умолчанию все конфигурации оборудования устанавливаются как предпочтительные.
ML Kit учтет доступность, стабильность, корректность и задержку каждой конфигурации и выберет лучшую из предпочтительных. Если ни одна из предпочтительных конфигураций не подходит, в качестве резервной будет автоматически использована конфигурация CPU . ML Kit выполнит эти проверки и соответствующую подготовку в неблокирующем режиме перед включением любого ускорения, поэтому, скорее всего, при первом запуске детектора пользователем будет использоваться CPU . После завершения всей подготовки в последующих запусках будет использоваться лучшая конфигурация.
Примеры использования функции setPreferredHardwareConfigs :
- Чтобы ML Kit мог выбрать оптимальную конфигурацию, не вызывайте этот API.
- Если вы не хотите включать ускорение, передайте только
CPU. - Если вы хотите использовать графический процессор для разгрузки центрального процессора, даже если графический процессор может быть медленнее, передавайте только
CPU_GPU.
Укажите параметры детектора позы:
Котлин
// 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();
Наконец, создайте экземпляр PoseDetector . Передайте указанные вами параметры:
Котлин
val poseDetector = PoseDetection.getClient(options)
Java
PoseDetector poseDetector = PoseDetection.getClient(options);
2. Подготовьте входное изображение.
Для определения поз на изображении создайте объект InputImage из объекта Bitmap , media.Image , ByteBuffer , массива байтов или файла на устройстве. Затем передайте объект InputImage в PoseDetector .
Для определения позы следует использовать изображение с размерами не менее 480x360 пикселей. Если определение позы происходит в реальном времени, захват кадров с таким минимальным разрешением может помочь уменьшить задержку.
Вы можете создать объект InputImage из различных источников, каждый из которых описан ниже.
Использование media.Image
Чтобы создать объект InputImage из объекта media.Image , например, при захвате изображения с камеры устройства, передайте объект media.Image и угол поворота изображения в метод InputImage.fromMediaImage() .
Если вы используете библиотеку CameraX , классы OnImageCapturedListener и ImageAnalysis.Analyzer автоматически вычисляют значение поворота.
Котлин
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 // ... } } }
Если вы не используете библиотеку для работы с камерой, которая предоставляет угол поворота изображения, вы можете рассчитать его, исходя из угла поворота устройства и ориентации датчика камеры в устройстве:
Котлин
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; }
Затем передайте объект media.Image и значение угла поворота в InputImage.fromMediaImage() :
Котлин
val image = InputImage.fromMediaImage(mediaImage, rotation)
Java
InputImage image = InputImage.fromMediaImage(mediaImage, rotation);
Использование URI файла
Чтобы создать объект InputImage из URI файла, передайте контекст приложения и URI файла в метод InputImage.fromFilePath() . Это полезно, когда вы используете интент ACTION_GET_CONTENT чтобы предложить пользователю выбрать изображение из галереи приложения.
Котлин
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(); }
Использование ByteBuffer или ByteArray
Чтобы создать объект InputImage из ByteBuffer или ByteArray , сначала вычислите угол поворота изображения, как описано ранее для входного объекта media.Image . Затем создайте объект InputImage , используя буфер или массив, а также высоту, ширину изображения, формат кодирования цвета и угол поворота:
Котлин
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 );
Использование Bitmap
Для создания объекта InputImage из объекта Bitmap необходимо сделать следующее объявление:
Котлин
val image = InputImage.fromBitmap(bitmap, 0)
Java
InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);
Изображение представлено объектом Bitmap вместе с градусами поворота.
3. Обработка изображения
Передайте подготовленный объект InputImage в метод process объекта PoseDetector .
Котлин
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. Получите информацию об обнаруженной позе.
Если на изображении обнаружен человек, API определения позы возвращает объект Pose , содержащий 33 объекта PoseLandmark .
Если человек не полностью находится внутри изображения, модель присваивает отсутствующим ориентирам координаты за пределами кадра и дает им низкие значения InFrameConfidence.
Если в кадре не обнаружено ни одного человека, объект Pose не содержит объектов PoseLandmark .
Котлин
// 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);
Советы по повышению производительности
Качество ваших результатов зависит от качества исходного изображения:
- Для точного определения позы с помощью ML Kit, человек на изображении должен быть представлен достаточным количеством пикселей; для достижения наилучшей производительности размер объекта должен составлять не менее 256x256 пикселей.
- Если вы определяете позу в приложении реального времени, вам также следует учитывать общие размеры входных изображений. Изображения меньшего размера обрабатываются быстрее, поэтому для уменьшения задержки захватывайте изображения с более низким разрешением, но помните о вышеуказанных требованиях к разрешению и убедитесь, что объект занимает как можно большую часть изображения.
- Плохая фокусировка изображения также может повлиять на точность. Если вы не получаете приемлемых результатов, попросите пользователя сделать повторный снимок.
Если вы хотите использовать определение позы в приложении реального времени, следуйте этим рекомендациям для достижения наилучшей частоты кадров:
- Используйте базовый SDK для определения позы и
STREAM_MODE. - Рекомендуется делать снимки с более низким разрешением. Однако также следует учитывать требования к размерам изображений, предъявляемые этим API.
- При использовании API
Cameraилиcamera2, ограничьте количество вызовов детектора. Если во время работы детектора появляется новый видеокадр, отбросьте его. Пример можно увидеть в классеVisionProcessorBaseв примере быстрого запуска приложения. - Если вы используете API
CameraX, убедитесь, что стратегия обратного давления установлена на значение по умолчаниюImageAnalysis.STRATEGY_KEEP_ONLY_LATEST. Это гарантирует, что для анализа будет доставлено только одно изображение за раз. Если при загруженности анализатора будет создано больше изображений, они будут автоматически отброшены и не будут поставлены в очередь на доставку. После закрытия анализируемого изображения путем вызова ImageProxy.close() будет доставлено следующее самое позднее изображение. - Если вы используете выходные данные детектора для наложения графики на входное изображение, сначала получите результат из ML Kit, а затем отрендерите изображение и наложение за один шаг. При этом рендеринг на поверхность дисплея выполняется только один раз для каждого входного кадра. Пример можно увидеть в классах
CameraSourcePreviewиGraphicOverlayв примере быстрого запуска приложения. - При использовании API Camera2, захватывайте изображения в формате
ImageFormat.YUV_420_888. При использовании более старого API Camera, захватывайте изображения в форматеImageFormat.NV21.
Следующие шаги
- Чтобы узнать, как использовать ориентиры позы для классификации поз, см. раздел «Советы по классификации поз» .