ML Kit의 On-device ML은 사전에 학습된 모델을 디바이스에 내장하여, 네트워크 의존 없이 로컬에서 추론을 수행하는 구조다.
이 방식은 서버 호출 대비 지연 시간을 줄이고, 오프라인 환경에서도 동작 가능하며, 사용자 데이터를 외부로 전송하지 않아 개인정보 보호 측면에서도 유리하다.
특히 ML Kit는 모델 처리 과정을 추상화하여, 모바일 개발자가 비교적 적은 비용으로 머신러닝 기반 기능을 앱에 통합할 수 있게 한다.
ML Kit’s on-device ML runs pre-trained models directly on the device, enabling local inference without relying on network connectivity.
This approach reduces latency compared to server-based processing, works in offline environments, and enhances privacy by keeping user data on the device.
In addition, ML Kit abstracts much of the model handling, allowing mobile developers to integrate machine learning features into their apps with relatively low overhead.
해보고 나니 onDevice인데도 불구하고 인식이 매우 잘되더라고요.


전체 데이터 흐름
[사용자] 이미지 선택 버튼 탭
↓
[시스템] ActivityResultContracts.GetContent()로 파일 선택기 실행
↓
[사용자] 갤러리에서 사진 선택 → content:// URI 반환
↓
[FaceDetectionScreen] URI를 ViewModel에 전달
↓
[FaceDetectionViewModel]
1. UI 상태를 isLoading=true로 전환
2. InputImage.fromFilePath(context, uri)로 이미지 로드 (IO 스레드)
3. detector.process(inputImage)로 ML Kit 얼굴 검출 실행
4. 성공 시 → faceCount, faceRects, imageWidth/Height를 상태에 반영
실패 시 → errorMessage를 상태에 반영
↓
[FaceDetectionScreen] StateFlow 변경 감지 → UI 자동 갱신
↓
[ImageWithFaceOverlay] 이미지 표시 + Canvas로 bounding box 오버레이
Detector 설정:
private val detector = FaceDetection.getClient(
FaceDetectorOptions.Builder()
.setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_ACCURATE)
.setLandmarkMode(FaceDetectorOptions.LANDMARK_MODE_NONE)
.setClassificationMode(FaceDetectorOptions.CLASSIFICATION_MODE_NONE)
.setMinFaceSize(0.1f)
.build()
)
- PERFORMANCE_MODE_ACCURATE: 정적 이미지니까 정확도 우선. 실시간 카메라라면 FAST를 씁니다
- LANDMARK_MODE_NONE: 눈/코/입 좌표는 지금 안 쓰므로 끔 (성능 이점)
- CLASSIFICATION_MODE_NONE: 웃음/눈 뜸 확률도 미사용
- setMinFaceSize(0.1f): 이미지 너비의 10% 이상인 얼굴만 검출. 너무 작은 오탐을 방지합니다
검출 흐름 (detectFaces):
// 1단계: IO 스레드에서 이미지 로드
val inputImage = withContext(Dispatchers.IO) {
InputImage.fromFilePath(context, uri)
}
fromFilePath()는 내부적으로 디스크에서 이미지를 읽고 EXIF 메타데이터(회전 정보)까지 처리합니다. 디스크 I/O이므로 Dispatchers.IO로 스레드를 전환합니다.
// 2단계: ML Kit에 분석 요청 (비동기 Task)
detector.process(inputImage)
.addOnSuccessListener { faces -> ... }
.addOnFailureListener { e -> ... }
override fun onCleared() {
super.onCleared()
detector.close()
}
FaceOverlay.kt — Bounding Box 오버레이
이 파일이 이 프로젝트에서 가장 까다로운 부분입니다.
핵심 문제: 좌표계 불일치
ML Kit은 bounding box를 원본 이미지의 픽셀 좌표로 반환합니다. 예를 들어 4000x3000 사진에서 Rect(1200, 800, 1600, 1300) 이런 식입니다.
하지만 화면에는 이 이미지가 ContentScale.Fit으로 축소되어 표시됩니다. 만약 좌표를 그대로 쓰면 bounding box가 화면 밖으로 벗어나거나, 완전히 엉뚱한 위치에 그려집니다.
좌표 변환 공식:
// 1. 축소 비율 계산
val scaleX = displayWidth / imageWidth // 가로 비율
val scaleY = displayHeight / imageHeight // 세로 비율
val scale = min(scaleX, scaleY) // Fit이므로 작은 쪽 기준
// 2. 중앙 정렬 여백 계산
val offsetX = (displayWidth - imageWidth * scale) / 2
val offsetY = (displayHeight - imageHeight * scale) / 2
// 3. 변환 적용
화면좌표 = 원본좌표 × scale + offset
구현:
Box {
AsyncImage(...) // 1층: Coil로 이미지 표시
Canvas(Modifier.fillMaxSize()) { // 2층: 같은 크기의 Canvas를 위에 겹침
// 좌표 변환 후 drawRect()
}
}
Box 안에 AsyncImage와 Canvas를 같은 크기(fillMaxSize)로 겹쳐서, Canvas의 좌표계와 이미지의 표시 영역이 정확히 일치하도록 합니다.
의존성
얼굴 검출용 엔진
http://m.google.mlkit:face-detection
+ 재미로 디자인이 너무 밤티나서 claude agent로 안드로이드 디자이너를 만들어서 해보겠습니다.



좌측이 Before / 우측이 After
좀 더 테스트앱 느낌에서 벗어나긴했네요!
재밌는 경험!! ㅎㅎ
'안드로이드' 카테고리의 다른 글
| Play Feature Delivery - 앱 크기 최적화의 열쇠 (0) | 2025.05.24 |
|---|---|
| Compose 성능 최적화 (Donut hole, 도넛홀) (1) | 2023.10.04 |
| Android의 Touch Event 전달 (0) | 2023.01.25 |
| Android 인터뷰 20선 - 2 (0) | 2023.01.23 |
| 안드로이드 인터뷰 면접 질문 20선! - 1 (0) | 2022.09.18 |