ML Kit는 자세 감지에 최적화된 두 가지 SDK를 제공합니다.
<ph type="x-smartling-placeholder">SDK 이름 | 자세 감지 | 정확한 자세-감지 |
---|---|---|
구현 | 빌드 시 코드와 애셋이 앱에 정적으로 연결됩니다. | 빌드 시 코드와 애셋이 앱에 정적으로 연결됩니다. |
앱 크기 영향 (코드 및 애셋 포함) | 약 10.1MB | 약 13.3MB |
성능 | Pixel 3XL: 최대 30FPS | Pixel 3XL: CPU 사용 시 최대 23FPS, GPU 사용 시 최대 30FPS |
사용해 보기
- 샘플 앱을 사용하여 이 API의 사용 예를 참조하세요.
시작하기 전에
<ph type="x-smartling-placeholder">- 프로젝트 수준
build.gradle
파일의buildscript
및allprojects
섹션에 Google의 Maven 저장소가 포함되어야 합니다. 모듈의 앱 수준 Gradle 파일(일반적으로
app/build.gradle
)에 ML Kit Android 라이브러리의 종속 항목을 추가합니다.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만 사용하여 감지기를 실행합니다.CPU_GPU
: CPU와 GPU를 모두 사용하여 감지기를 실행합니다.
감지기 옵션을 빌드할 때 API를 사용할 수 있습니다.
setPreferredHardwareConfigs
: 하드웨어 선택을 제어합니다. 기본적으로
모든 하드웨어 구성이 선호로 설정됩니다.
ML Kit에서 각 구성의 가용성, 안정성, 정확성, 지연 시간을 고려합니다.
이를 고려하여 선호하는 구성 중에서 가장 적합한 구성을 선택하세요. 다음 중 아무 것도 없는 경우
기본 구성을 적용할 수 있는 경우 CPU
구성이 자동으로 사용됩니다.
사용할 수 있습니다 ML Kit는 이러한 확인 및 관련 준비를
비 블로킹 방식을 사용하므로
사용자가 감지기를 처음 실행하면 CPU
가 사용됩니다. 모든
준비가 완료되면 다음 실행에서 가장 적합한 구성이 사용됩니다.
setPreferredHardwareConfigs
사용 예:
- ML Kit가 최적의 구성을 선택하도록 하려면 이 API를 호출하지 마세요.
- 가속을 사용 설정하지 않으려면
CPU
만 전달합니다. - GPU가 느려도 CPU를 오프로드하기 위해 GPU를 사용하려면 다음을 전달합니다.
CPU_GPU
에만 적용
포즈 감지기 옵션을 지정합니다.
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()
자바
// 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
의 인스턴스를 만듭니다. 지정한 옵션을 전달합니다.
Kotlin
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()
로 회전
<ph type="x-smartling-placeholder"></ph>
CameraX 라이브러리, OnImageCapturedListener
및
회전 값을 계산하는 ImageAnalysis.Analyzer
클래스
있습니다.
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 // ... } } }
자바
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 // ... } } }
이미지의 회전 각도를 제공하는 카메라 라이브러리를 사용하지 않는 경우 기기의 회전 각도와 카메라의 방향에서 센서에 있어야 합니다.
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 }
자바
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()
로:
Kotlin
val image = InputImage.fromMediaImage(mediaImage, rotation)
Java
InputImage image = InputImage.fromMediaImage(mediaImage, rotation);
파일 URI 사용
InputImage
를 만들려면 다음 안내를 따르세요.
객체를 만들고, 앱 컨텍스트와 파일 URI를
InputImage.fromFilePath()
입니다. 이 기능은
ACTION_GET_CONTENT
인텐트를 사용하여 사용자에게 선택하라는 메시지를 표시합니다.
만들 수 있습니다
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(); }
ByteBuffer
또는 ByteArray
사용
InputImage
를 만들려면 다음 안내를 따르세요.
ByteBuffer
또는 ByteArray
이전에 media.Image
입력에 대해 설명한 회전 각도입니다.
그런 다음 버퍼 또는 배열과 이미지의 InputImage
객체를
높이, 너비, 색상 인코딩 형식 및 회전 각도:
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 )
자바
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
객체에서 삭제하려면 다음과 같이 선언합니다.
Kotlin
val image = InputImage.fromBitmap(bitmap, 0)
Java
InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);
이미지는 회전 각도와 함께 Bitmap
객체로 표현됩니다.
3. 이미지 처리
준비된 InputImage
객체를 PoseDetector
의 process
메서드에 전달합니다.
Kotlin
Task<Pose> result = poseDetector.process(image) .addOnSuccessListener { results -> // Task completed successfully // ... } .addOnFailureListener { e -> // Task failed with an exception // ... }
자바
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
를 반환합니다.
PoseLandmark
가 33개인 객체를 반환합니다.
사람이 이미지 안에 완전히 포함되지 않았다면 모델은 누락된 랜드마크의 좌표를 계산하여 InFrameConfidence 값
프레임에서 사람이 감지되지 않으면 Pose
객체에 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)
자바
// 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의 이미지 크기 요구사항도 유의해야 합니다.
-
Camera
또는camera2
API 감지기 호출을 제한합니다. 새 동영상 감지기가 실행되는 동안 frame 사용할 수 있게 되면 프레임을 삭제합니다. 자세한 내용은 <ph type="x-smartling-placeholder"></ph>VisionProcessorBase
클래스를 참조하세요. CameraX
API를 사용하는 경우 백프레셔 전략이 기본값으로 설정되어 있는지 확인ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST
이렇게 하면 분석을 위해 한 번에 하나의 이미지만 전송됩니다. 더 많은 이미지가 분석기가 사용 중일 때 생성되지 않으면 자동으로 삭제되어 배달. 분석 중인 이미지가 ImageProxy.close()를 호출하면 다음 최신 이미지가 게재됩니다.- 감지기 출력을 사용하여 그래픽 이미지를
먼저 ML Kit에서 결과를 가져온 후 이미지를
하나의 단계로 오버레이할 수 있습니다. 이는 디스플레이 표면에 렌더링됩니다.
각 입력 프레임에 대해 한 번만 허용됩니다. 자세한 내용은
<ph type="x-smartling-placeholder"></ph>
CameraSourcePreview
및GraphicOverlay
클래스를 참조하세요. - Camera2 API를 사용하는 경우
ImageFormat.YUV_420_888
형식으로 이미지를 캡처합니다. 이전 Camera API를 사용하는 경우ImageFormat.NV21
형식으로 이미지를 캡처합니다.
다음 단계
- 랜드마크를 사용하여 포즈를 분류하는 방법을 알아보려면 포즈 분류 도움말을 참고하세요.