
简介本资源是一套基于人体姿态识别技术实现舞蹈动作自动评分的安卓应用完整开发包面向移动开发初学者、AI算法实践者及教育类App开发者解决舞蹈学习中缺乏实时动作反馈与量化评估的痛点。压缩包共72个文件含9个核心Java算法模块负责姿态估计与动作比对、13个XML布局与配置文件、10个PNG/JPG界面资源、4个.so动态库集成底层推理引擎以及3个.pb模型文件轻量级姿态检测模型整体大小为38.49MB。已有41人下载学习适合希望理解端侧AI落地流程的开发者。读者可直接运行APK体验打分功能通过源码深入学习Android Camera2图像采集、TensorFlow Lite模型部署、关键点相似度计算逻辑及前后端协同架构设计项目目录结构清晰包含独立的app、libs、recscore等模块便于按功能解耦研究与二次开发。 去年我把“人体姿态识别”从PC端搬到了安卓手机上做了一个以舞蹈评分为核心的应用从模型选型、源码编写到最终打出可安装的APK前后折腾了大半个月。这个项目最大的价值在于它不是一套只能在电脑上跑的姿态识别Demo而是一个完整可落地的安卓应用打开摄像头就能看到骨架跟着参考动作跳完App会给出准确度、节奏和舒展度等评分。现在把整个过程拆开讲一遍涉及姿态识别模型的移动端接入、摄像头的实时帧处理、评分算法的实现、以及源码和APK构建的经验。想直接用它来改二开的可以跳到第三节想确认技术方案和踩坑细节的可以通读。1. 项目概述为什么做一个舞蹈评分类的姿态识别安卓应用1.1 对“舞蹈评分”场景的拆解舞蹈和健身动作打分看起来是一个很垂直的需求但背后其实是一套通用的“人体姿态识别”应用范式摄像头捕捉画面姿态识别模型找到人体关键点算法把关键点和标准动作进行比较最终输出一个可理解的分数。只要把“标准动作”替换成别的动作库这套东西马上就能变成体育训练评分、瑜伽姿态纠正、甚至体感游戏的控制输入。我之所以选舞蹈评分作为切入点是因为舞蹈动作非常适合用来验证姿态识别算法的效果。舞蹈本身有明确的肢体路径手脚位置、关节角度、动作弧度都很容易量化。比如一个“挥手”动作手肘弯曲多少度、大臂抬到多高、动作是否到位都能用关键点坐标计算出来。这样就避免了一上来就做“全身姿态识别”这种过于宽泛、没有清晰反馈的项目。另一个原因是舞蹈评分应用对实时性要求不算极端但也不低。用户会看着屏幕上的骨架跟着做动作如果画面明显延迟体验会非常差。所以这个项目同时也是在验证在移动端算力限制下姿态识别模型能做到多快的推理速度、多大的帧率以及如何用工程手段去平衡卡顿、发热和准确度。这些问题才是真正有价值的经验。1.2 目标用户与应用价值做这个应用之前我首先明确了使用场景。用户不是专业舞蹈演员而是普通人。他们希望在手机前跟着老师动作跳舞然后得到一个客观反馈看看自己哪里不到位。所以产品形态必须简单打开App选择或者录制一个参考动作跟着跳结束之后显示分数。如果用户愿意可以反复回放自己的骨架轨迹找出问题。从应用价值来看这种姿态识别评分应用可以直接用在几个方向舞蹈培训机构的课后练习反馈学生在家里用手机就能知道动作是否标准。健身App里的动作纠正例如深蹲幅度不够、左右倾斜、膝盖内扣等问题。体感类游戏和互动教学用骨架关键点替代传统的手势识别交互更自然。当然也需要提醒一点姿态识别评分不是医学级别的动作分析它更适合作为“辅助反馈工具”而不是“绝对裁判”。我在设计评分算法时刻意避免给出过于绝对的评价而是用“相似度百分比”和“角度误差”来衡量动作完成度让用户看到更大的趋势和差异。2. 技术选型从姿态识别算法到安卓端落地2.1 姿态识别引擎选型MediaPipe Pose 为什么适合人体姿态识别的开源方案不少常见的有OpenPose、AlphaPose、MediaPipe Pose还有基于YOLO的骨骼关键点检测等。但在安卓端做落地我几乎毫不犹豫选了MediaPipe Pose。原因很简单OpenPose算法精度不错但模型体积和计算量都太大中端手机上跑实时推断非常吃力。自研模型则需要准备大量标注数据还需要训练和调参时间成本完全不可控。而MediaPipe Pose正好处在“精度够用”和“移动端可跑”的中间位置官方提供了面向Android的Task API封装能直接通过Maven依赖集成不需要自己写复杂的处理原生库代码。具体来说MediaPipe Pose可以检测人体全身的33个关键点包括鼻尖、眼睛、耳朵、双肩、双肘、双腕、双胯、双膝、双踝等。每个关键点包含归一化的x坐标、y坐标、z坐标以及可见度visibility。这些信息对评分算法来说已经足够。我在项目中重点使用肩、肘、腕、胯、膝、踝这几个节点计算肢体角度和相对位置。MediaPipe还有一个优势是它支持硬件加速。Android端可以开启GPU delegate让检测模型跑在GPU上如果设备不支持也能回退到CPU。虽然不同型号手机表现有差异但整体上比纯CPU方案快很多。我在开发早期用一款中端测试机跑开启GPU delegate之后单帧推理时间能缩短接近一半。2.2 安卓端架构与开发语言方案项目主开发语言我选择了Kotlin因为它现在是Android官方推荐语言协程处理相机帧回调、线程切替都更方便。UI层我没有上太复杂的架构直接用传统View和Jetpack Compose结合的方式。主界面是相机预览叠加一个自定义View来绘制骨架点和连线底部放动作选择、录制、评分按钮。相机部分使用CameraX而不是底层的Camera2。CameraX封装了生命周期、旋转、预览输出等一大堆细节尤其适合这种“从相机回传每一帧给算法”的场景。我在自定义分析器里拿到ImageProxy对象转换为MediaPipe调用需要的位图然后调用PoseLandmarker进行检测最后把结果通过回调传给UI层。使用CameraX还有一个好处它能自动适配不同手机的屏幕方向和输出尺寸省去了很多判断系统版本和摄像头传感器方向的脏活。源码模块上我按职责拆了几个关键类MainActivity负责应用入口权限申请页面跳转。CameraPreviewFragment相机预览和取帧。PoseDetector封装MediaPipe Pose Landmarker的初始化与检测调用。PoseVisualizer在预览画面上绘制关节点和骨骼连线。ScoringEngine将检测结果转换成特征向量并计算分数。ActionRecorder录制参考动作把关键点序列保存成本地文件。这样的分层结构主要是为了后面二次开发方便。如果你想换掉检测模型只需要改PoseDetector想调整评分规则只需要改ScoringEngine不会牵一发动全身。2.3 模型轻量化与硬件加速的基本考量移动端接入姿态识别模型不能直接把PC上的大模型拿过来用。MediaPipe官方提供了多档模型从Lite到Full。我当时先用Full模型在模拟器里跑发现帧率非常低后来换到Lite模型中端机实时推理才勉强可用。需要说明的是模型越轻关键点位置的抖动会越明显评分时分数波动也会更大。所以除了选Lite模型外我还在评分侧做了平滑处理对连续几帧的特征向量做滑动平均减少异常点对整体分数的影响。硬件加速方面MediaPipe Task API支持BaseOptions里设置Delegate.GPU我用的是GPU。但在部分低端设备上GPU delegate反而可能出现初始化失败或者模型加载变慢。稳妥做法是做一个fallback初始化时先尝试GPU如果抛异常就回到CPU。与此同时我建议把模型文件的加载放在后台线程并加一个“模型加载中”的UI状态避免用户在启动时看到白屏误以为应用崩溃。3. 核心实现人体姿态识别与舞蹈评分的完整流程3.1 视频帧捕获与姿态关键点提取整个识别链路的第一步是从CameraX拿帧。CameraX的ImageAnalysis.Analyzer会不断回调ImageProxy我们需要在回调里把图像数据转成姿态检测模型需要的格式。MediaPipe的PoseLandmarker接受Bitmap或MPImage我在项目中直接用Bitmap。这里有一个特别容易被忽略的坑Analyzer回调默认运行在后台线程但ImageProxy如果处理不及时不释放会导致预览卡住或者内存上升。所以我在拿到ImageProxy后立刻转成Bitmap然后调用MediaPipe检测不管检测成功与否最后都要调用image.close()释放资源。核心代码大概是这样的class PoseAnalyzer( private val detector: PoseDetector, private val onLandmarkResult: (ListLandmark) - Unit ) : ImageAnalysis.Analyzer { override fun analyze(imageProxy: ImageProxy) { val bitmap imageProxy.toBitmap() val result detector.detect(bitmap) if (result ! null) { onLandmarkResult(result) } imageProxy.close() } }检测结果是一组关键点。MediaPipe的坐标都是归一化到0到1之间的浮点数所以不同分辨率图像之间可以直接比较不需要关心摄像头实际输出多大。关键点里的visibility衡量的是这个点被模型“看到的自信程度”低于某个阈值时后续计算角度和相似度应该把这个点标记为不可信。3.2 骨架特征计算关节角度与坐标归一化拿到关键点之后不能直接拿坐标去比。因为同样一个“手臂平举”的动作如果摄像头离人远近不同手臂水平方向上的像素长度会差很多。所以在评分之前必须先把关键点转换成尺度无关、位置无关的特征。我主要用到两类特征关节角度和相对坐标。关节角度计算用的是三点夹角的余弦定理。比如我们要算右手肘的弯曲角度需要三个点右肩、右肘、右腕。先构造两个向量左手指向肘肘指向腕然后求出这两个向量的夹角。这样就算人脸靠近或远离摄像头肘关节的角度也不会变化太大。代码可以这样实现fun angle(p1: PointF, p2: PointF, p3: PointF): Double { val v1 PointF(p1.x - p2.x, p1.y - p2.y) val v2 PointF(p3.x - p2.x, p3.y - p2.y) val dot v1.x * v2.x v1.y * v2.y val len1 hypot(v1.x, v1.y) val len2 hypot(v2.x, v2.y) return Math.toDegrees(acos((dot / (len1 * len2)).toDouble())) }相对坐标归一化则以髋部中心作为原点以双肩宽度或躯干长度作为尺度。比如所有关键点都减去髋中心坐标再除以肩宽这样人物站得离摄像头远或近产生的特征向量尺度都是一致的。此时左右手等二维坐标变成了一系列量纲统一的值可以直接进相似度算法。我建议把每一帧处理成一组固定顺序的特征例如左右肘角度左右肩角度左右髋角度左右膝角度手腕相对于髋中心的归一化坐标脚踝相对于髋中心的归一化坐标注意所有检测不到的点都要填一个特殊值比如-1评分时跳过。我在前期没处理不可见点导致动作被衣服遮住时分数乱跳后来加了过滤才好很多。3.3 舞蹈评分算法时间对齐与相似度打分评分算法是整个项目的灵魂。最简单粗暴的方案是把用户当前帧的动作特征和参考动作每一帧的特征做余弦相似度但这样有一个天然问题用户做动作的快慢不可能和参考视频完全一致。同一套舞蹈可能用户前半段做慢了、后半段做快了逐帧一一对应下来相似度分数会惨不忍睹。所以我用了动态时间规整DTWDynamic Time Warping来做时间对齐。DTW可以在两个时间序列长度不一致、节奏不一致的情况下找到一条累积距离最小的路径相当于把用户动作和参考动作在时间轴上“拉开”对齐。实现上我把参考动作预处理成一组特征向量序列R用户实时动作也持续累积成一组特征向量序列U。评分时计算两个序列之间的DTW距离。DTW的核心是构造一个二维累计代价矩阵递推公式很简单dp[i][j] cost(i, j) min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1])这里cost(i, j)是用户第i帧特征和参考第j帧特征之间的距离我采用的是1减去余弦相似度也就是“余弦距离”。最终DTW距离越小说明动作越接近。因为加了“时间对齐”这一步即使用户动作节奏和参考不完全一样只要有动作路径和角度接近得分依然会高。最后的总分我会拆成三块姿态准确度主要由关键点角度的相似度决定。动作完整度由所有关键点可见性和是否进入有效区域决定。节奏一致性由DTW对齐后用户序列与参考序列的累积距离决定。三块按不同权重相加得到一个0到100的分数。我实际的权重分配是姿态准确度0.6、动作完整度0.2、节奏一致性0.2。你完全可以根据不同舞蹈类型调整。比如偏力量感动作节奏一致性权重可以提高。3.4 源码结构说明与参考动作生成从源码组织角度我不希望把评分逻辑和相机逻辑全部塞进Activity所以保持了前面的模块划分。在“参考动作”这一块我允许用户自己录制标准动作也内置了几组基础动作。录制动作的关键点序列会被保存成JSON文件里面存的是每一帧的特征向量和关键点可见度。比如JSON结构大概是这样{ actionName: 右手上举, frameCount: 120, features: [ { elbowLeft: 165.2, elbowRight: 172.1, hipLeft: 178.3, ... } ] }这样做的最大好处是标准动作库可以随时扩充不需要改代码。比如用户今天想练一套新的操只要录一遍参考动作系统就会自动生成新的JSON文件下次就能选这个动作进行评分。在实际开发过程中我发现录制参考动作的质量直接影响评分效果。如果参考动作只有一个人录而且露腿被摄像机切掉一半后面的评分分数会偏低。所以我在录制界面做了“人物必须在画面框内”的检测逻辑只有肩、肘、腕、髋、膝、踝等关键点全部可见时才允许开始录制。否则提示用户调整距离或位置。4. 从源码到APK构建、调试与真机部署4.1 项目构建配置与依赖项构建一个可直接安装的APK最重要的是把Gradle配置弄对。MediaPipe的Task Vision库已经发布到Google Maven仓库不需要手动下载.aar文件。我项目里的核心依赖大概这样implementation(com.google.mediapipe:tasks-vision:0.10.14) implementation(androidx.camera:camera-camera2:1.3.4) implementation(androidx.camera:camera-lifecycle:1.3.4) implementation(androidx.camera:camera-view:1.3.4) implementation(org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3)除此之外还需要在android块里设置好compileSdk和minSdk。我的minSdk设成24也就是Android 7.0以上。因为MediaPipe官方要求Android API 24以上才有较稳定的兼容性。targetSdk则设成33或者34跟随Android权限模型变化。一个很容易踩的坑是打包时没有限制ABI。MediaPipe自带多个平台的native库如果全量打包APK体积会非常大我见过直接超过150MB的。我加了一条abiFilters配置只保留arm64-v8a和armeabi-v7a这两个主流架构。这样APK体积能砍掉一半左右。如果你只面向新手机甚至可以只留arm64-v8a。android { defaultConfig { ndk { abiFilters listOf(armeabi-v7a, arm64-v8a) } } packagingOptions { jniLibs { useLegacyPackaging true } } }4.2 权限申请与相机预览适配安卓应用使用相机必须动态申请CAMERA权限。如果用的是Android 6.0以上设备在OnCreate里先检查权限没有就弹申请框。申请完权限还需要在系统设置里确认是否允许否则CameraX打开预览时会直接报错。权限申请代码不复杂但要注意的是不要只申请一次就不管。有些国产ROM在用户拒绝两次后会把权限按钮变成“去设置”而不是再弹一次。所以我在MainActivity里加了权限结果回调如果拒绝就提示用户去系统设置手动开启同时禁用应用里的所有功能按钮。相机旋转适配也是一个重点。手机竖屏时CameraX默认输出的分析图像可能是横向的。如果不做旋转MediaPipe检测出来的关键点坐标对应的画面方向和屏幕显示不一致骨架会画歪评分也会错。我会在Analyzer里根据ImageProxy.imageInfo.rotationDegrees做旋转处理或者在显示时旋转视图。这个方向处理在很多教程里都没写清楚但实际影响非常明显。4.3 打包APK与安装验证打包APK比较直接。Android Studio里选Build Build Bundle(s) / APK(s) Build APK(s)编译完成后会生成app-debug.apk路径一般在这个位置app/build/outputs/apk/debug/。如果用命令行也可以执行./gradlew assembleDebug但要注意第一次构建MediaPipe相关依赖需要下载很多包网络不好时容易卡住。第二次构建通常就快很多。打包完成后把APK传到手机上安装。如果是debug包签名用的是系统默认的debug.keystore可以直接安装。如果要给别人用最好用正式签名产出一个release APK否则第三方应用商店和部分手机系统会拒绝安装。正式签名我一般这样弄keytool -genkey -v -keystore dancekey.jks -keyalg RSA -keysize 2048 -validity 10000 -alias dance然后在app/build.gradle里配置signingConfig。这里要特别提醒jks签名文件和密码务必妥善保存。如果丢失了签名文件未来任何更新都无法覆盖安装只能卸载重装用户数据也保不住。安装时如果手机提示“未知来源应用”需要允许安装权限。不同品牌手机入口不一样但基本都在“安全设置”里。为了避免每次安装都跑一遍我直接把build目录下的debug APK共享到网盘或者聊天工具里在真机上快速迭代测试。5. 实战中的常见问题与排查技巧实录5.1 性能问题卡顿、发热与掉帧姿态识别实时推理是典型的计算密集型任务性能问题几乎是绕不开的。我实测中发现中端机型使用MediaPipe Lite模型、开启GPU delegate之后检测一帧大概需要80到150毫秒。这意味着一秒只能跑7到12帧对于姿态识别来说基本可用但也不是很流畅。我做了几层优化。第一是降低CameraX分析分辨率从默认的1920x1080降到1280x720关键点检测精度依然足够但推理耗时明显下降。第二是在Analyzer里加入丢帧逻辑比如定义“上一帧处理中”的标志位如果上一帧还没处理完就直接跳过当前帧。第三是关闭应用里的多余动画和日志输出减少UI线程压力。发热问题也很现实。跑几分钟后手机摄像头附近会明显发热机型较差的还会触发系统降频导致检测帧率越来越低。我的处理方式是在评分结束时释放模型资源而不是一直持有模型。进入评分页才初始化退出评分页立即释放这样至少分散了发热时间。5.2 识别精度问题遮挡、背景复杂与弱光姿态识别模型并不是万能的。如果用户穿着大T恤腰部被宽大衣服挡住髋关节和膝关节的关键点可能会被模型误判如果背景杂乱人物又背对镜头检测结果会更不稳定。我在实际测试中总结了几条经验尽量要求用户正面朝向摄像头侧身角度超过45度时很多关键点可见度会快速下降。建议穿贴身一点的服装测试过于宽松的衣服会让肩、髋的中心点偏移。背景不要太杂纯色墙或低对比背景检测更稳。弱光环境对姿态识别影响很大如果房间太暗模型的检测率几乎不可用至少要有较好照明。在算法侧我会把关键点可见度低于0.5的点视为“不可信”计算角度和相似度时直接跳过。如果一帧里不可信关键点超过一定数量比如5个那就认为这一帧无效不参与评分。这个策略虽然会让评分结果略微保守但能明显减少因为检测错误导致的分数剧烈跳动。5.3 评分一致性问题身高差异与机位不固定这是评分类应用最容易引起争议的地方。两个人身高完全不同做同一个动作时手臂移动的绝对距离差很多。如果直接比较关键点坐标高个子大概率吃亏。所以评分前必须做好尺度和位置归一化。我在前面提到的以肩宽作为尺度归一化的方式基本上能解决身高差异问题。但还有一个更隐蔽的问题摄像头角度。如果参考动作是平视角度录制而用户手机放在偏非常低或非常高的位置那么同一个人做同样的动作画面中的肩髋位置和腿部角度都会改变最终得分会偏低。解决思路是在App里加入机位引导。录制参考动作和正式评分的页面都显示一个半透明的“人物站位提示框”让用户把整个身体放在框内并且手机保持在一个相对固定高度的支架上。同时在代码里计算骨盆倾斜角等全局姿态指标如果和参考动作偏差过大会在评分报告里提示“请检查手机高度和角度”。这样做之后评分一致性明显提高。5.4 源码二次开发与常见问题速查如果你打算基于这套源码继续开发下面这几个点我觉得最值得关注想换检测模型MediaPipe官方还有其他姿态模型比如BlazePose可以通过替换Task API的模型文件来实现但要确认输出关键点格式一致。想加动作库不用改代码录制动作存成JSON在Action选择列表里新增即可。想改变评分权重直接改ScoringEngine里的score()方法把三块得分的权重系数调整一下。想接入更多传感器比如让手机震动作为动作到位的提示只需要在ScoringEngine返回结果后由Activity触发Vibrator。最后整理一份常见问题速查表方便开发时排查现象可能原因解决办法打开相机黑屏或闪退权限未申请、模拟器不支持相机、相机被占用检查权限换真机测试检查应用是否独占相机检测结果一直为空模型未加载、图像方向不对、人物未在画面中确认模型加载成功检查图像旋转让人物完整入镜骨架绘制错位分析图像尺寸方向和预览不一致根据rotationDegrees旋转校准坐标映射分数跳动很大关键点抖动、可见度低、时间未对齐增加滑动平均过滤不可见点使用DTW对齐APK安装失败ABI不匹配、签名冲突、未知来源用abiFilters打对应架构包统一签名打开安装权限镜头前人物一跑就卡处理速度跟不上降低分辨率丢帧开GPU delegate换Lite模型我在实际使用中感受最深的一点是做一个能跑起来的APK并不难真正难的是让它在不同手机上都有稳定的体验。模型加载、权限、旋转、线程调度、评分偏差每一个环节都可能成为体验杀手。尤其是评分算法如果你不管节奏对齐和尺度归一化一开始看到分数非常唬人但用户换一台手机、换一个站位分数就完全失真。所以我的建议是先把参考动作录制和评分流程做成可视化把所有关键点连线和得分实时显示出来你一眼就能看出问题出在识别还是算法。调试工具比盲目调参重要得多。最后再分享一个小技巧录制参考动作时多录几遍取中间时间段最稳定的一段作为标准。这样可以过滤掉录制时起手和结束阶段的不稳定抖动评分效果会比只录一遍好很多。我把这个技巧做进了应用之后的参考动作库基本都是一次成型。本文还有配套的精品资源点击获取