核心内容摘要
软著等了1年了,我打算上架google play
关键词Android, Jetpack Compose, CameraX, TFLite, Object Detection大家好我是飞哥拒绝云端依赖3MB 模型跑在手机上TFLite 转换保姆级教程我们成功把 YOLOv8 模型“压缩”成了tflite格式。
今天我们要把它装进 Android 手机开发一个酷炫的实时物体检测 App想象一下打开摄像头对着家里的猫、杯子、电脑手机屏幕上立马画出框框并显示“Cat 99%”——是不是很有科技感
为什么要用 Jetpack Compose CameraX(Why)锚定已知 ⚓️以前做 Android 相机预览要写 SurfaceView, TextureView还要处理各种旋转、拉伸问题代码又臭又长。
就像用古老的胶卷相机拍张照片要调半天光圈快门。
生动类比 Jetpack Compose CameraX 就是 Android 界的“傻瓜数码相机”Compose就像 Vue/React你告诉它“我要一个相机预览窗口”它就给你画出来。
不用管底层的 XML 布局。
CameraXGoogle 帮你把底层的脏活累活设备兼容性、生命周期全干了。
你只需要关注两件事“给我画面” (Preview)和“给我数据” (ImageAnalysis)。
提炼骨架 核心数据流如下摄像头 (CameraX) ➡️ 每一帧图片 (ImageAnalysis) ➡️ AI 大脑 (TFLite) ➡️ 结果坐标 ➡️ 绘制 (Compose Canvas)
核心代码拆解 (How)第一步添加依赖与权限 (build.gradle.kts Manifest) 在app/build.gradle.kts(Kotlin DSL) 里加上这些dependencies{//
CameraX (相机库)valcamerax_version
1.
0implementation(androidx.camera:camera-core:$camerax_version)implementation(androidx.camera:camera-camera2:$camerax_version)implementation(androidx.camera:camera-lifecycle:$camerax_version)implementation(androidx.camera:camera-view:$camerax_version)//
TensorFlow Lite (AI 推理库)implementation(org.tensorflow:tensorflow-lite:
2.
14.
implementation(org.tensorflow:tensorflow-lite-support:
0.
4.
}在AndroidManifest.xml中申请相机权限uses-featureandroid:nameandroid.hardware.camera.any/uses-permissionandroid:nameandroid.permission.CAMERA/⚠️ 注意Android
0 以上需要动态申请权限在 Compose 中可以使用Accompanist库或者Activity的requestPermissions来搞定。
如果不申请App 会直接闪退。
第二步核心分析器 (Analyzer) 这是最关键的一步。
我们需要自定义一个ImageAnalysis.Analyzer它会不断收到摄像头的图片帧。
为了简化繁琐的图片处理YUV转RGB、缩放、归一化强烈推荐使用TFLite Support Library。
classObjectDetectorAnalyzer(privatevalcontext:Context,privatevalonResult:(ListDetectionResult,Int,Int)-Unit// 回调结果, 宽, 高):ImageAnalysis.Analyzer{//
加载模型privatevalinterpreter:Interpreterbylazy{valmodelFileUtil.loadMappedFile(context,yolov8n.tflite)valoptionsInterpreter.Options()Interpreter(model,options)}// 图像预处理调整大小 归一化privatevalimageProcessorImageProcessor.Builder().add(ResizeOp(640,640,ResizeOp.ResizeMethod.BILINEAR)).add(NormalizeOp(0f,255f)).build()androidx.annotation.OptIn(ExperimentalGetImage::class)overridefunanalyze(imageProxy:ImageProxy){valmediaImageimageProxy.imageif(mediaImage!null){//
转换为 BitmapvalbitmapimageProxy.toBitmap()//
核心步骤裁剪为正方形 (Center Crop) ✂️// 必须裁剪因为 YOLO 模型输入是正方形而我们为了 UI 显示不拉伸// 也在 UI 上强制显示为正方形。
这里必须保持一致。
valsizeminOf(bitmap.width,bitmap.height)valleft(bitmap.width-size)/2valtop(bitmap.height-size)/2valsquareBitmapBitmap.createBitmap(bitmap,left,top,size,size)//
预处理 (TensorImage)vartensorImageTensorImage.fromBitmap(squareBitmap)tensorImageimageProcessor.process(tensorImage)//
准备输出容器 [1, 84, 8400]valoutputBufferTensorBuffer.createFixedSize(intArrayOf(1,84,
,DataType.FLOAT
//
推理interpreter.run(tensorImage.buffer,outputBuffer.buffer.rewind())//
后处理 (NMS)valresultspostProcess(outputBuffer.floatArray)//
传回 UI (附带裁剪后的尺寸用于坐标映射)onResult(results,size,size)}imageProxy.close()}// 飞哥小贴士// postProcess 里面要做三件事//
维度转置把 [84, 8400] 转成 [8400, 84] 方便遍历。
//
阈值过滤置信度
5 的框直接扔掉。
//
NMS (非极大值抑制)同一个物体可能有很多重叠框只保留分最高的那个。
}补充关于toBitmap虽然 CameraX 提供了toBitmap()但性能一般。
生产环境建议使用YuvToRgbConverter(Google Sample 中有提供) 实现 GPU 加速转换。
第三步Compose UI 界面 用 Compose 写界面简直是享受。
我们把相机预览和画框图层叠在一起。
ComposablefunObjectDetectionScreen(){// 权限请求逻辑 (略) ...// 如果有权限显示 CameraContent()CameraContent()}ComposablefunCameraContent(){valcontextLocalContext.currentvallifecycleOwnerLocalLifecycleOwner.currentvardetectionsbyremember{mutableStateOf(emptyListDetectionResult())}varimageWidthbyremember{mutableIntStateOf(
}// 裁剪后的正方形边长varimageHeightbyremember{mutableIntStateOf(
}// 重点强制 UI 显示为正方形 (1:
与模型输入保持一致Box(modifierModifier.fillMaxWidth().aspectRatio(1f)){//
相机预览层 (AndroidView CameraX)AndroidView(factory{ctx-valpreviewViewPreviewView(ctx)valcameraProviderFutureProcessCameraProvider.getInstance(ctx)cameraProviderFuture.addListener({valcameraProvidercameraProviderFuture.get()// 配置 PreviewvalpreviewPreview.Builder().setTargetAspectRatio(AspectRatio.RATIO_4_
.build()preview.setSurfaceProvider(previewView.surfaceProvider)// 配置 AnalyzervalimageAnalyzerImageAnalysis.Builder().setTargetAspectRatio(AspectRatio.RATIO_4_
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST).build().also{it.setAnalyzer(Executors.newSingleThreadExecutor(),ObjectDetectorAnalyzer(ctx){results,w,h-detectionsresults imageWidthw imageHeighth})}// 关键点配置 ViewPort (1:1 视口)// 告诉 CameraX 只输出正方形画面自动裁剪掉多余部分valviewPortViewPort.Builder(Rational(1,
,Surface.ROTATION_
.build()valuseCaseGroupUseCaseGroup.Builder().addUseCase(preview).addUseCase(imageAnalyzer).setViewPort(viewPort)// 绑定视口.build()try{cameraProvider.unbindAll()cameraProvider.bindToLifecycle(lifecycleOwner,CameraSelector.DEFAULT_BACK_CAMERA,useCaseGroup// 绑定 UseCaseGroup 而不是单独的 use cases)}catch(e:Exception){Log.e(Camera,Binding failed,e)}},ContextCompat.getMainExecutor(ctx))previewView},modifierModifier.fillMaxSize())//
绘图层 (Canvas)Canvas(modifierModifier.fillMaxSize()){// 坐标映射屏幕显示尺寸 / 图片实际尺寸valscalesize.width/imageWidth detections.forEach{result-// 将模型输出坐标映射到屏幕坐标valleftresult.x*scalevaltopresult.y*scalevalwidthresult.w*scalevalheightresult.h*scale// 画框drawRect(colorColor.Red,topLeftOffset(left,top),sizeSize(width,height),styleStroke(width5f))// 画文字...}}}}效果演示⚠️ 飞哥避坑指南坐标转换模型输出的坐标是基于 640x640 的画到屏幕上时一定要根据屏幕实际宽高进行等比例映射否则框会歪到姥姥家去。
线程问题analyze方法运行在子线程更新 UI (detections results) 一定要切回主线程YUV 转 BitmapCameraX 给的是 YUV_420_888 格式直接转 Bitmap 很慢。
推荐用 Google 的YuvToRgbConverter或者 TFLite Support 库里的ImageProcessor。
4.
总结 一句话记住它Android 端侧推理 CameraX 喂图 ➕ TFLite Support 预处理 ➕ YOLO 解析。
核心三要点CameraX Analyzer每一帧图片的入口要在这里做线程切换和数据处理。
TFLite SupportGoogle 提供的神器一行代码搞定缩放和归一化别再手写 Bitmap 操作了。
后处理 (NMS)模型输出的只是候选框必须通过 NMS 算法去重才能得到最终结果。
示例源码实战代码源码包内含✅ 完整的 Android Studio 工程✅ 已封装好的YoloV8Detector.kt(含 NMS 算法实现)✅ 预训练好的 TFLite 模型创作不易记得关注飞哥 点赞、收藏哦~~下篇见