资讯动态

Android实时人体检测Demo:CameraX+TensorFlow Lite完整实现与踩坑指南

发布时间:2026/9/9 22:02:18 来源:尧图企业网站定制
简介一款面向安卓开发者的人体检测实时运行Demo聚焦行人或人体检测场景可在手机端调用摄像头实时识别画面中的人体并绘制检测框适合需要快速验证目标检测模型在移动端推理效果、进行安卓AI应用落地或二次开发的工程师与学生。压缩包共2个文件包含一个可直接安装的APK安装包与一个JSON配置文件整体大小约50.6MB其中APK文件负责完整的人体检测应用体验JSON文件用于存储项目构建元数据或模型相关配置打开即可使用无需额外搭建环境。目前已有2908人学习下载。Demo已生成Release构建产物省去自行编译的步骤安装后即可实测实时检测速度与精度压缩包内文件结构精简便于定位安卓调用相机、模型加载、前处理与可视化等关键逻辑可作为基础模板快速移植至自研项目再结合人体检测数据集与训练代码迭代优化大幅降低移动端人体检测功能的开发门槛。 前阵子我把一个Android人体检测APP Demo完整跑通了整理成zip之后发给几个朋友他们都成功在Android Studio里打开、运行、看到摄像头画面实时标出人体框。这个Demo的价值不在于“模型多牛”而在于它是从摄像头取流到AI推理再到界面绘制的完整闭环不是那种只能喂一张图片进去看效果的教学片段。一句话概括整个实现链路CameraX拿到摄像头帧转成模型需要的Bitmap喂给TensorFlow Lite做推理拿到人体框坐标后用自定义View叠加在预览画面上。整个过程在手机上实时运行不需要服务器不需要联网。这篇文章我就把这个Demo从方案选型到每一行关键代码、再到我踩过的坑原原本本拆给你看。适合Android开发基础还不错、想接AI能力做App或者正在准备毕业设计、作品集项目的朋友参考。1. 这个Demo到底是什么需求理解与方案选型1.1 目标定下来能实时跑而不是只能截图很多人一提到“人体检测”第一反应是找模型、训练模型其实这个方向一开始就跑偏了。我在做这个Demo之前明确给自己定了几条硬性要求打开App就能看到摄像头预览画面里有人就实时框出来不能只支持静态图片因为实际应用场景里没人会对着照片检测检测框要跟得上人移动的速度至少不能有明显的卡顿感全程离线运行不依赖任何云端API免得后面扩展时受网络限制。这几条听起来简单实际上决定了后面所有技术选择。比如你如果选了ML Kit接入是快但部分功能依赖Google Play服务国内环境下真机调试很容易遇到兼容性问题选了OpenCV自带的DNN模块跨平台是方便但移动端推理速度和内存占用都不够理想。最终我在几个方案里反复对比才确定用TensorFlow Lite这条路。1.2 技术方案对比TFLite、ML Kit、MediaPipe、OpenCV我先把我对比过的几个方案列出来免得你重复踩我走过的弯路方案模型大小Android集成难度实时性可定制性主要坑点TensorFlow Lite SSD MobileNet几MB到几十MB中等优秀高可换任意TFLite模型需要自己处理图像预处理和坐标转换ML Kit Pose Detection按需下载简单优秀较低封装程度高Google Play服务依赖离线部署麻烦MediaPipe Pose几MB中等偏高优秀高关键点数据丰富工程结构复杂Gradle依赖较新OpenCV DNN模型本身不大简单一般中等移动端推理速度不理想耗电明显看到这个对比你可能已经明白了。ML Kit和MediaPipe的“人体检测”体验其实更好因为它们自带姿态关键点连骨架都能画出来但如果你想要的是一个通用、可控、可替换模型的DemoTensorFlow Lite是最合适的底座。它把模型推理这块做得很干净输入输出都是标准的Tensor具体检测什么、怎么画框完全由你说了算。1.3 为什么我最终选了TensorFlow Lite SSD MobileNet我在Demo里用的模型是SSD MobileNet的COCO预训练版本COCO数据集里包含person这个类别所以它天然就能检测人体。选它的理由有三个第一模型体积小我这里用的TFLite版本大概20MB左右如果换成更激进的量化版本还能再小手机完全扛得住。第二SSD是单阶段检测器结构简单适合移动端实时推理不像Faster R-CNN那样有两个阶段在手机上跑会明显吃力。第三TFLite这个生态我熟悉后面想换YOLO的TFLite版本、换姿态估计模型改改输入输出解析就行不用推翻重来。另外说明一下这个Demo里的模型文件是可以替换的。如果你不想做人检测想检测猫猫狗狗换一个模型文件、改一下标签解析逻辑就行其他代码不用大动。这就是把方案底座选对的好处。2. 从Zip到工程环境准备与基础配置2.1 开发环境与真机要求先把环境说清楚。我用的Android Studio版本是KoalaGradle版本8.5以上AGP版本8.2以上JDK建议用17。minSdk我设的是24也就是Android 7.0及以上TFLite官方库对低版本的支持没有那么积极把门槛抬到7.0能省掉很多兼容性问题。顺带提一句如果你刚装的Android Studio是英文界面不习惯可以在Settings - Plugins里搜中文语言包安装重启之后就变中文菜单了对新手友好很多。但这一步不是必须的看个人习惯。必须要强调的一点是这种带摄像头预览的Demo老老实实用真机调试。Android模拟器虽然也能跑但模拟摄像头的帧率、图像格式和真机差很多经常出现“模拟器上画框正常一到真机上框就乱跑”的情况。我开发过程中基本全程插着手机用USB调试每次改完代码直接跑真机验证。2.2 Gradle依赖与assets模型准备打开zip之后你会在build.gradle里的dependencies块看到以下几组关键依赖我建议你不要随意改动版本号这些版本是我实测能稳定配合的dependencies { // CameraX 相机相关 implementation androidx.camera:camera-camera2:1.3.4 implementation androidx.camera:camera-lifecycle:1.3.4 implementation androidx.camera:camera-view:1.3.4 // TensorFlow Lite 基础库和任务库 implementation org.tensorflow:tensorflow-lite:2.14.0 implementation org.tensorflow:tensorflow-lite-support:0.4.4 implementation org.tensorflow:tensorflow-lite-task-vision:0.4.4 // 界面相关 implementation androidx.appcompat:appcompat:1.6.1 implementation com.google.android.material:material:1.11.0 }这里有个很容易被新手忽略的点TFLite模型文件要放在assets目录下。有些人下载了.zip之后把模型直接丢在res/raw或者项目根目录代码一运行就报模型找不到。assets目录的路径是app/src/main/assets把detect.tflite模型文件和labelmap.txt标签文件放进去就行。千万不要想着把模型放在下载目录或者外部存储再用绝对路径加载Android的文件访问权限会把你折磨疯。Assets是官方标准做法简单、可靠、随APK打包。2.3 页面布局PreviewView与检测框叠加层布局文件核心就两个控件一个负责显示摄像头画面一个负责在上面画检测框。androidx.constraintlayout.widget.ConstraintLayout !-- 摄像头预览 -- androidx.camera.view.PreviewView android:idid/previewView android:layout_widthmatch_parent android:layout_heightmatch_parent / !-- 检测框覆盖层 -- com.yourpackage.DetectOverlayView android:idid/overlayView android:layout_widthmatch_parent android:layout_heightmatch_parent app:layout_constraintTop_toTopOfparent app:layout_constraintBottom_toBottomOfparent / /androidx.constraintlayout.widget.ConstraintLayoutPreviewView是CameraX提供的官方预览控件内部封装好了相机预览流和SurfaceView/TextureView的切换逻辑。DetectOverlayView是我们自定义的View容纳检测结果并绘制矩形框和标签。两个View重叠检测框就能“贴”在真人身上。这里有个视觉上的小讲究预览流有画面比例问题PreviewView和OverlayView必须放在同一个父容器里并且大小一致否则画面和框会对不上。我测试中最常见的问题就是OverlayView被ConstraintLayout的约束条件给拉伸了导致框永远偏离人体。需要留意设置scaleType或者确保两者铺满同一区域。3. 核心实现一帧画面如何变成检测结果3.1 用CameraX拿摄像头帧CameraX把以前Camera2那套繁琐的拍照流程简化了很多不需要再写一堆Callback。绑定CameraProvider创建Preview和ImageAnalysis两个用例然后把它们bind到同一个LifecycleOwner上val cameraProviderFuture ProcessCameraProvider.getInstance(this) cameraProviderFuture.addListener({ val cameraProvider cameraProviderFuture.get() val preview Preview.Builder().build() val imageAnalysis ImageAnalysis.Builder() .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) .setTargetResolution(Size(640, 480)) .build() imageAnalysis.setAnalyzer(executor, { imageProxy - // 在这里拿到一帧图像做检测 detectAndDraw(imageProxy) }) cameraProvider.bindToLifecycle(this, CameraSelector.DEFAULT_BACK_CAMERA, preview, imageAnalysis) }, ContextCompat.getMainExecutor(this))setTargetResolution(Size(640, 480))这个值我是故意调低的你可能会奇怪为什么不用1080p。原因很直白人体检测模型普遍输入是300x300或者320x320你送进来的原始帧再高清最后还是要被缩到这么小与其在缩放上浪费性能不如直接在采集端把分辨率降下来。STRATEGY_KEEP_ONLY_LATEST也很关键。它表示ImageAnalysis只保留最新一帧如果你处理得太慢后面的帧会主动丢掉而不是排队。这样保证了预览永远展示最新画面不会因为攒帧导致越来越卡对实时性要求高的场景是必选项。3.2 图像预处理与镜像/旋转拿到ImageProxy之后不能直接扔给模型。原因有三格式不匹配、尺寸不匹配、方向不匹配。手机摄像头输出的原始数据是YUV格式通常是YUV_420_888而TFLite模型要的是RGB位图所以第一步要转换。CameraX虽然没有直接提供转换方法但通过ImageProxy.toBitmap()这个扩展函数可以一步到位。比较坑的是旋转问题。手机竖着拿摄像头传感器是横着的ImageProxy拿到的那一帧图像是旋转过的如果不矫正你检测到的人体框会整体偏转90度看起来就是框在画面里横着转。处理办法是读取imageProxy.imageInfo.rotationDegrees然后用Matrix旋转回去val rotationDegrees imageProxy.imageInfo.rotationDegrees val bitmap imageProxy.toBitmap() // 已经包含旋转信息 val modelInput Bitmap.createScaledBitmap(bitmap, 300, 300, true) val normalizedImage TensorImage.fromBitmap(modelInput)这里我用的是Google官方推荐的TensorImage包装类它内部会帮你做像素归一化把0到255的像素值变成模型需要的浮点数范围。不用手写三万个像素的循环省心很多。而且TensorImage是TFLite Support库的一部分你不需要额外引入OpenCV就能完成全部预处理。3.3 执行TFLite推理并解析输出如果你用原生Interpreter写要自己管理输入输出的TensorBuffer格式代码量大不说稍微弄错一个维度就白排查半天。我在Demo里直接用了TFLite Task库的ObjectDetector封装它是专门针对目标检测场景的高层API整个推理代码精简到十几行val options ObjectDetector.ObjectDetectorOptions.builder() .setScoreThreshold(0.5f) .setMaxResults(3) .setNumThreads(2) .build() val detector ObjectDetector.createFromFileAndOptions( this, detect.tflite, options ) val results detector.detect(image) for (detection in results) { val box detection.boundingBox val score detection.categories[0].score val label detection.categories[0].label // 把 box 交给 OverlayView 画出来 }setScoreThreshold(0.5f)是置信度阈值低于0.5的检测结果会被丢弃。这个值不能设太高设成0.8以上稍微远一点、小一点的人就检测不到了也不能设太低0.3以下满屏幕都是误检框。我最终定在0.5属于人体检测里比较稳妥的平衡点。detection.boundingBox返回的是一个RectF对象但要注意它里面的坐标是相对坐标取值在0到1之间表示人体在图像中的相对位置。屏幕上要画框的时候必须把它乘以View的实际宽高才能得到像素坐标。3.4 把检测框画到屏幕上自定义DetectOverlayView的核心逻辑在onDraw里。每检测到一帧就把最新的一组RectF丢给OverlayView然后调用invalidate()触发重绘class DetectOverlayView JvmOverloads constructor( context: Context, attrs: AttributeSet? null ) : View(context, attrs) { private val paint Paint().apply { style Paint.Style.STROKE strokeWidth 5f color Color.GREEN } var results: ListDetection emptyList() set(value) { field value postInvalidate() // 有新结果就重绘 } override fun onDraw(canvas: Canvas) { super.onDraw(canvas) for (detection in results) { val box detection.boundingBox // 相对坐标转换为绝对像素 val left box.left * width val top box.top * height val right box.right * width val bottom box.bottom * height canvas.drawRect(left, top, right, bottom, paint) // 这里还可以写文字、画置信度 } } }线条颜色我用的是绿色因为绿色在大部分摄像头画面里的辨识度最高。框线宽度设置在5像素左右太细了远处看不清太粗了影响视觉观察。实际项目里比代码更绕的问题是PreviewView自带画面裁剪。因为摄像头传感器比例和屏幕比例通常不一样PreviewView默认会填充整个View画面边缘会被裁掉一部分。此时模型检测到的相对坐标基于原始画面和屏幕上实际可见的部分会有偏差。我在Demo里偷了个懒设置PreviewView的scaleType为FILL_CENTER保证预览画面居中填充再通过计算裁剪偏移来做粗校准。这个偏差在多数场景下不明显能接受但如果你的需求是像素级精准框选就得考虑在坐标映射里去补偿裁剪偏移。4. 让它“实时”运行性能优化与实测4.1 分辨率和帧率怎么取舍实时性不是一个模糊概念它由两个数据决定单帧处理时长和帧率是否稳定。我最初把targetResolution设成1280x720模型推理一次耗时大约80到120毫秒看起来还行但换算下来每秒只有8到12帧人稍微一走画框就有拖影感。后来我把分辨率降到640x480推理耗时降到50到70毫秒帧率稳定在15到20帧实时体验立刻不一样了。这里面的原理很好理解送入模型的图像size固定是300x300前置的Bitmap转换和像素拷贝成本直接取决于原始分辨率降低原始帧率省下的是这部分CPU开销。分辨率不是越低越好。如果降到320x240虽然帧率能到30fps但小目标的人体检测率明显下降站在3米外的人经常检测不到。640x480是我实测出来的甜点值。4.2 线程、复用与GPU加速关于线程有一个新手最容易犯的错误在ImageAnalysis的analyzer回调里直接更新UI。CameraX的analyzer默认在后台线程执行而UI更新必须在主线程直接操作View会崩溃。稳妥的做法是后台线程里做完整推理然后通过runOnUiThread或postInvalidate触发绘制。我在Demo里用的是OverlayView的postInvalidate它能在任意线程安全请求重绘。GPU加速方面TFLite支持GPU Delegate但我最后没有启用。原因是硬件兼容性问题有些老手机的GPU驱动对TFLite支持不完善开了GPU Delegate反而导致初始化失败增加不必要的复杂度。在中低端手机上CPU推理配合2到4个线程已经够用了。如果你用的是高端旗舰可以试试开启GPU Delegate速度还能提一倍。4.3 我在两台手机上的实测数据为了验证“实时运行”这四个字我特地在两台定位完全不同的手机上做了测试测试项小米12 LiteRedmi 9ACPU型号骁龙778G联发科G25推理耗时单帧约45ms约130ms帧率20fps左右7-8fps3米内人体识别稳定基本可用5米外人体识别可检出经常漏检结论很清楚这个Demo在骁龙7系以上的手机上可以流畅运行在百元级的低端机上也能跑但实时性就谈不上好了。如果你的目标人群是低端机用户后续可以考虑把模型量化成INT8版本推理耗时有希望压到80ms以内。Demo压缩包里带的默认模型是FLOAT32版本兼容性最好量化版本我留了替换通道你可以自行尝试。5. 踩坑记录与常见问题速查5.1 最容易翻车的6个问题这一节是我觉得整个Demo里最有价值的部分因为代码逻辑其实不复杂真正耗时间的是Debug。我把遇到的典型问题整理成了表格现象根因解决方法启动App直接闪退模型文件缺失或路径写错确认detect.tflite在assets目录且文件名完全一致预览正常但画框偏移图像旋转未处理检查rotationDegrees是否参与Bitmap转换检测框粗大且跳动置信度阈值过低scoreThreshold至少调到0.5以上有时检测出“人”但明显是桌子模型猜错类别调高阈值或换专门的人体检测模型长时间运行越来越卡ImageProxy未close在analyzer返回前调用imageProxy.close()部分手机上初始化失败GPU/Delegate不兼容去掉GPU Delegate只用CPU线程其中ImageProxy不close这个问题最容易忽略因为前几分钟根本不会感觉到异样跑久了内存和线程越积越多最终导致相机直接停摆。我一开始没有意识到这点后来发现每次用一会就会黑屏排查了好久才在CameraX的官方文档里看到一句ImageAnalysis的每个imageProxy必须被关闭否则底层图像缓冲得不到释放。这个坑我在Demo里已经处理好了code review时务必注意别把它删掉。5.2 给想改造成自己项目的朋友几点建议如果你拿到这个zip之后不满足于只跑Demo想把它改成自己项目的一部分我给出几个方向供你参考第一人体计数。在OverlayView的绘制逻辑里维护一个mutableSet每次检测到新的人体框就加进去持续几帧消失的才移除这样就能粗略统计画面里出现过几个人。第二姿态估计扩展。把模型从SSD MobileNet换成PoseNet或MediaPipe Pose画框逻辑变成画关节点和骨架线就能做一个健身动作计数App。第三防走失提醒。把检测框与人体的重叠面积算出来做“离开画面”判断适合做监护类小工具。每个方向都不需要大改架构核心还是那句CameraX负责取流推理引擎负责算结果自定义View负责呈现。三块解耦清楚改起来非常快。最后再分享一个小技巧。如果你觉得调试时摄像头权限和运行时权限弹窗很烦可以在AndroidManifest里给app加上android.permission.CAMERA的同时在MainActivity里用ActivityResultLauncher统一处理权限请求。这个Demo在首次启动时会请求摄像头权限拒绝授权再开的话预览区域会黑屏但不会闪退方便你直接看到有没有把相机绑定成功。解压zip后如果遇到代码无法sync的情况多半是Gradle版本问题先检查本地Gradle分布版本是不是8.5以上。希望这个Demo能帮你省掉三天调研时间让你把精力真正花在更值得的地方。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价