CameraX Image Analysis — Complete Guide
In this tutorial, you'll learn about CameraX Image Analysis. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
The Problem
Your CameraX image analyzer lags behind the camera feed, or onAnalyze is called faster than you can Process frames.
Wrong Approach ❌
// No cooldown — analyzer called for every frame
class BarcodeAnalyzer : ImageAnalysis.Analyzer {
override fun analyze(image: ImageProxy) {
// Heavy ML processing on every frame — can't keep up!
val barcodes = detectBarcodes(image)
image.close()
}
}
Output: Frames queued up, memory grows, analysis runs on stale frames.
Right Approach ✅
class EfficientAnalyzer : ImageAnalysis.Analyzer {
private var isProcessing = false
private val fpsController = FpsController()
override fun analyze(image: ImageProxy) {
// Skip frames if still processing
if (isProcessing) {
image.close()
return
}
isProcessing = true
// Convert to Bitmap for processing
val bitmap = imageProxyToBitmap(image)
// Process on background thread
Thread {
try {
val result = processFrame(bitmap)
// Post result back to UI
Handler(Looper.getMainLooper()).post {
onResult(result)
}
} finally {
image.close() // Always close!
isProcessing = false
}
}.start()
}
// Alternative: use executor for background processing
fun setupImageAnalysis(imageAnalysis: ImageAnalysis) {
imageAnalysis.setAnalyzer(Executors.newSingleThreadExecutor()) { image ->
processOnBackground(image)
image.close()
}
}
// Set target frame rate
fun setupWithBackpressure(imageAnalysis: ImageAnalysis) {
// STRATEGY_KEEP_ONLY_LATEST: drops frames if analyzer is busy
ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.setTargetFrameRate(15) // Process 15 fps max
.build()
}
private var lastAnalyzedTime = 0L
private fun shouldAnalyze(): Boolean {
val now = System.currentTimeMillis()
return if (now - lastAnalyzedTime > 100) { // 10 fps max
lastAnalyzedTime = now
true
} else false
}
private class FpsController {
private val frameWindow = ArrayDeque<Long>(30)
fun canProcess(currentFps: Int = 15): Boolean {
val now = System.currentTimeMillis()
frameWindow.addLast(now)
if (frameWindow.size > 30) frameWindow.removeFirst()
val elapsed = now - (frameWindow.firstOrNull() ?: now)
return elapsed > 0 && frameWindow.size * 1000 / elapsed < currentFps
}
}
}
Output: Frame analysis at sustainable rate without memory growth.
Prevention
- Use
<a href="/design-patterns/strategy/">Strategy</a>_KEEP_ONLY_LATESTfor backpressure. - Run analysis on a background executor, not the main thread.
- Always close
ImageProxyin afinallyblock. - Implement frame skipping for heavy processing.
- Set a reasonable target frame rate.
Common Mistakes with camera x analyze
- Misunderstanding that
Stringis[Char]with poor performance for large text operations - Using
foldlinstead offoldl'causing stack overflow on large lists - Forgetting
deriving (Show, Eq)on custom data types needed for debugging
These mistakes appear frequently in real-world Android code. DodaTech's contributors have identified these patterns through analysis of open-source projects and production systems.
Practice Exercise
Write a pure function that safely divides two integers using Maybe, then test it with edge cases like division by zero and negative numbers.
This exercise reinforces the concepts covered in this guide. Try implementing it before checking online solutions.
FAQ
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro