How to Implement Real-Time Face Detection with CameraX and ML Kit
Step-by-step guide to add real-time face detection to Android apps using CameraX ImageAnalysis and ML Kit FaceDetector.
Real-time face detection powers selfie filters, attendance systems, and identity checks. Google ML Kit provides an on-device FaceDetector that works with CameraX frames. This guide shows the complete setup from dependencies to overlay drawing.
You need Android Studio, a project targeting API 21 or higher, and basic Kotlin knowledge. The official ML Kit face detection documentation confirms the minimum SDK and image size requirements.
Add the required dependencies
Open your module-level build.gradle (or build.gradle.kts). Add CameraX and the ML Kit face detection library.
For the bundled model:
implementation("androidx.camera:camera-core:1.4.0")
implementation("androidx.camera:camera-camera2:1.4.0")
implementation("androidx.camera:camera-lifecycle:1.4.0")
implementation("androidx.camera:camera-view:1.4.0")
implementation("com.google.mlkit:face-detection:16.1.7")
Use version 1.4.0 or the latest stable CameraX release. The ML Kit version matches the current Google documentation. Sync the project after adding these lines.
If you prefer the unbundled model that downloads via Google Play Services, replace the last line with:
implementation("com.google.android.gms:play-services-mlkit-face-detection:17.1.0")
The unbundled option keeps the APK smaller but requires a network connection on first run. Google recommends declaring the model in the manifest for install-time download when using the Play Services version.
Configure the FaceDetector options
Create a FaceDetectorOptions object before you start the camera. Performance mode, landmarks, and classification settings control speed versus detail.
val options = FaceDetectorOptions.Builder()
.setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_FAST)
.setLandmarkMode(FaceDetectorOptions.LANDMARK_MODE_NONE)
.setClassificationMode(FaceDetectorOptions.CLASSIFICATION_MODE_NONE)
.setMinFaceSize(0.15f)
.enableTracking()
.build()
val faceDetector = FaceDetection.getClient(options)
Fast mode reduces latency for live camera frames. Disable landmarks and classification when you only need bounding boxes. Enable tracking so each face receives a stable ID across frames. The official docs note that contour mode and tracking should not run together because contour detection returns only the largest face.
For high-resolution still captures instead of live analysis, see our guide on high-res selfies for ID verification with CameraX.
Bind CameraX Preview and ImageAnalysis
Request the CAMERA permission at runtime. Then obtain a ProcessCameraProvider and bind the use cases to the lifecycle.
val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
cameraProviderFuture.addListener({
val cameraProvider = cameraProviderFuture.get()
val preview = Preview.Builder().build()
preview.setSurfaceProvider(previewView.surfaceProvider)
val imageAnalysis = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.setTargetResolution(Size(640, 480))
.build()
imageAnalysis.setAnalyzer(cameraExecutor, FaceAnalyzer(faceDetector))
val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
lifecycleOwner,
cameraSelector,
preview,
imageAnalysis
)
}, ContextCompat.getMainExecutor(context))
STRATEGY_KEEP_ONLY_LATEST drops older frames so the analyzer never falls behind. A 640×480 target resolution balances speed and the 480×360 minimum size recommended by ML Kit. Front-camera selection works well for selfie-style detection.
Process each frame in the Analyzer
Implement ImageAnalysis.Analyzer. Convert the ImageProxy to an ML Kit InputImage and call process.
class FaceAnalyzer(
private val faceDetector: FaceDetector,
private val overlay: FaceOverlay
) : ImageAnalysis.Analyzer {
override fun analyze(imageProxy: ImageProxy) {
val mediaImage = imageProxy.image
if (mediaImage == null) {
imageProxy.close()
return
}
val rotation = imageProxy.imageInfo.rotationDegrees
val inputImage = InputImage.fromMediaImage(mediaImage, rotation)
faceDetector.process(inputImage)
.addOnSuccessListener { faces ->
overlay.setFaces(faces, imageProxy.width, imageProxy.height)
}
.addOnFailureListener { e ->
Log.e("FaceAnalyzer", "Detection failed", e)
}
.addOnCompleteListener {
imageProxy.close()
}
}
}
Always close the ImageProxy inside the complete listener. Failure to close it freezes the camera pipeline. The overlay view receives the list of Face objects and draws rectangles or landmarks on the preview surface.
Draw bounding boxes on a custom overlay
Create a custom View that sits above the PreviewView. Override onDraw to paint rectangles that match the detected faces after accounting for rotation and scaling.
class FaceOverlay(context: Context) : View(context) {
private var faces: List<Face> = emptyList()
private var imageWidth = 0
private var imageHeight = 0
private val paint = Paint().apply {
color = Color.GREEN
style = Paint.Style.STROKE
strokeWidth = 4f
}
fun setFaces(detected: List<Face>, width: Int, height: Int) {
faces = detected
imageWidth = width
imageHeight = height
invalidate()
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
if (faces.isEmpty() || imageWidth == 0) return
val scaleX = width.toFloat() / imageWidth
val scaleY = height.toFloat() / imageHeight
for (face in faces) {
val box = face.boundingBox
val left = box.left * scaleX
val top = box.top * scaleY
val right = box.right * scaleX
val bottom = box.bottom * scaleY
canvas.drawRect(left, top, right, bottom, paint)
}
}
}
Adjust the scale factors if the preview aspect ratio differs from the analysis resolution. For front-camera mirrors, flip the x-coordinates before drawing.
Performance tips for live detection
ML Kit processes frames on a background thread. Keep the analyzer light so it returns before the next frame arrives.
- Prefer
PERFORMANCE_MODE_FASTfor continuous preview. - Lower the analysis resolution when the device struggles.
- Avoid enabling contour mode and tracking at the same time.
- Close every
ImageProxypromptly. - Test on mid-range devices; high-end phones hide latency problems.
The Google documentation states that faces should occupy at least 100×100 pixels for reliable detection. Guide the user to fill more of the frame when accuracy drops.
Next steps and production considerations
Once bounding boxes appear, you can read smile probability, eye-open probability, or head Euler angles from each Face object. Store tracking IDs to follow the same person across frames.
For identity verification flows that need sharp still images, combine this live detector with the high-resolution capture path described in the CameraX selfie guide. Always request the camera permission with a clear rationale and handle denial gracefully.
Test the pipeline under different lighting conditions and with multiple faces. The detector returns an empty list when no faces meet the size and confidence thresholds. Log the number of faces and average processing time during development.
Sources
Share this story