如何在 CameraX 中集成实时物体识别功能?
解读
国内面试问这道题,不是让你背“官方 demo 三步走”,而是考察:
- 对 CameraX 流水线(UseCase → Analyzer → ImageProxy)是否真正撸过代码;
- 是否能把 AI 推理模块(TFLite/MNN/NCNN)无缝嫁接到这条流水线,并解决“帧率 vs 功耗 vs 准确率”三角矛盾;
- 是否了解国产机碎片化的坑:华为无 GMS、小米杀后台、OPPO 限 30 ms Analyzer、折叠屏分辨率突变;
- 是否具备“端到端”思维:从 Preview 上画框、到线程模型、再到内存零拷贝、最后到隐私合规(工信部 164 号文要求本地推理不上云)。
一句话:让相机 30 ms 内把一帧 YUV_420_888 送进模型,再把结果回主线程画框,全程不卡 16 ms 渲染、不 OOM、不泄露。
知识点
-
CameraX 架构
- UseCase 组合:Preview + ImageAnalysis + VideoCapture(可选)
- ImageAnalysis.setBackpressureStrategy(STRATEGY_KEEP_ONLY_LATEST) 解决背压
- setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888) 保证 TFLite 输入零拷贝
- Analyzer 回调运行在 CameraX 提供的后台 HandlerThread,默认 30 ms 超时(国内部分 ROM 强制)
-
图像转换
- ImageProxy 转 TFLite 的 TensorImage:避免 Java 层 RGB 创建,直接用 RenderScript/RS 或 YuvToRgbConverter(androidx.camera:camera-core 自带)在 GPU 或 C++ 层做 90°/270° 旋转 + 裁剪
- 旋转角度:通过 CameraInfo.getSensorRotationDegrees() 与 Display.getRotation() 计算,折叠屏需监听 androidx.window:window 库回调
-
模型与推理
- TFLite Task-Vision ObjectDetector.createFromFileAndOptions(),量化 UInt8,输入 300×300,输出 List<Detection>
- 国内替代:MNN、NCNN、Paddle-Lite,均提供 Java API,可开启 NEON/OpenCL/Hexagon HTP
- 线程:Analyzer 回调直接推理会阻塞,应把 ImageProxy 丢到单线程 Executor(或 Kotlin Coroutine + Dispatchers.Default),推理完通过 Handler 把结果 post 到主线程
-
内存与帧率
- ImageProxy.close() 必须在 Analyzer 返回前调用,否则相机 HAL 停止出帧
- 使用 SharedMemory/buffer 池复用 ByteBuffer,防止每帧 new
- 降低分析帧率:ImageAnalysis.setTargetResolution(640×480) + setTargetFps(10) 足够人眼跟踪
-
绘制结果
- PreviewView 继承自 TextureView/SurfaceView,用 Overlay 方式在顶层自定义 View 画框
- 坐标映射:Detection.boundingBox 是模型坐标系,需乘以 scaleX=previewWidth/modelWidth,再叠加 sensor 旋转矩阵
- 折叠屏分屏时监听 WindowLayoutInfo 重新计算 scale
-
权限与合规
- 动态申请 CAMERA + 存储(Android 13 起无必要)
- 工信部 164 号文:若使用相机,隐私政策必须写明“本地处理、不上传”,并在首次弹窗告知
- 华为无 GMS 手机:TFLite 依赖的 Google NNAPI 缺失,需 fallback 到 CPU 或自带 HIAI 引擎
答案
步骤级回答,面试官可打断任何一步深挖:
- 引入依赖(gradle)
implementation "androidx.camera:camera-camera2:1.3.0"
implementation "androidx.camera:camera-lifecycle:1.3.0"
implementation "androidx.camera:camera-view:1.3.0"
implementation "org.tensorflow:tensorflow-lite-task-vision:0.4.4"
- 配置 ImageAnalysis
val analysis = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888)
.setTargetResolution(Size(640, 480))
.build()
- 实现 Analyzer
class TfliteAnalyzer(
private val detector: ObjectDetector,
private val onResult: (List<Detection>) -> Unit
) : ImageAnalysis.Analyzer {
private val yuvConverter = YuvToRgbConverter(context)
private val bitmapBuffer = Bitmap.createBitmap(640, 480, Bitmap.Config.ARGB_8888)
override fun analyze(image: ImageProxy) {
// 1. 转 RGB
yuvConverter.yuvToRgb(image.image, bitmapBuffer)
// 2. 旋转
val rotated = Bitmap.createBitmap(
bitmapBuffer, 0, 0, bitmapBuffer.width, bitmapBuffer.height,
matrix.apply { postRotate(image.imageInfo.rotationDegrees.toFloat()) }, true
)
// 3. 推理
val tensorImage = TensorImage.fromBitmap(rotated)
val results = detector.detect(tensorImage)
// 4. 回调主线程
onResult(results)
// 5. 必须 close
image.close()
}
}
- 绑定生命周期
val cameraProvider = ProcessCameraProvider.getInstance(this).get()
cameraProvider.bindToLifecycle(
this, CameraSelector.DEFAULT_BACK_CAMERA,
preview, analysis
)
- 画框
viewModel.detectionList.observe(this) { list ->
overlayView.setPreviewSize(previewView.width, previewView.height)
overlayView.setDetectionList(list)
overlayView.invalidate()
}
- 性能兜底
- 若 Analyzer 耗时 > 30 ms,OPPO 会抛 CameraAccessException,应在 release 包打开日志监控,动态降级跳帧
- 低端机(骁龙 450)关闭 GPU Delegate,纯 CPU 四线程,帧率降到 5 fps,保证不卡 UI
拓展思考
-
多路流水线:如果同时做人脸 + 物体 + 二维码,需要把 ImageAnalysis 的帧复制三份,可用 ImageReader.OnImageAvailableListener 实现零拷贝共享 buffer,但需自己管理引用计数,CameraX 1.4 将官方支持 MultiAnalyzer。
-
端侧训练:国内厂商要求“千人千面”识别,可用 TFLite Model Personalization,在后台 WorkManager 里收集用户本地数据,夜间充电时做 5 轮微调,第二天下发新模型,全程加密存储在 Keystore + TEE。
-
功耗模型:5G 直播场景下,相机 + AI + 网络上传同时工作,电池衰减曲线非线性。可结合 Battery Historian 数据,训练一个线性回归模型,动态关闭 AI(检测不到物体 3 s 后自动停 Analyzer),实测可省 18 % 电量。
-
安全红线:工信部 164 号文后,若偷偷把原始帧上传做云端识别,应用商店会直接下架;正确做法是本地推理,结果脱敏(只传“是否检测到猫”布尔值),且提供用户“一键关闭 AI”开关,否则隐私检测工具会扫描到 tensorflow-lite-gpu.delegates.so 调用,触发通报。
-
折叠屏适配:展开时分辨率为 2480×2200,模型输入 300×300 导致 scale 过大,检测框漂移。可在 WindowLayoutInfo 变化时,重新计算 cropRect,把 16:9 区域居中裁剪后再推理,保证坐标一致。