ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

姿态识别实战:从关键点检测原理到MMPose代码落地

姿态识别实战:从关键点检测原理到MMPose代码落地 做计算机视觉这几年姿态识别是我接触过最容易被误解的一项任务。外行看到一张骨架图觉得很简单内行知道从二维图像里稳定提取人体关节点的坐标背后牵扯到目标检测、关键点回归、遮挡处理、跨尺度特征融合一整套问题。这篇东西不打算做教科书式的理论堆砌我会把从原理选型到代码落地的完整路径捋一遍重点放在“为什么这么做”和“实际操作时怎么不掉链子”上适合刚入手姿态识别、打算用它做毕设或者解决业务问题的朋友参考。姿态识别和普通的目标检测有个本质区别目标检测回答的是“哪里有物体”姿态识别回答的是“物体的关键部位在哪里”。放到人体场景里就是给定一张图输出每个人的肩膀、手肘、手腕、膝盖、脚踝等关键点的像素坐标。有了这些坐标上层可以去做动作分析、健身计数、跌倒检测、人机交互甚至动画驱动。我见过很多初学者一上来就抱着论文啃结果被各种网络结构劝退。说实话如果你不是要发顶会主流方案选一个成熟的框架直接用把原理理解到“能改参数、能调优、能排查问题”这个程度就够用了。后面的内容我会按照这个标准来展开。1. 姿态识别任务定义与场景全景1.1 姿态识别到底在解决什么问题要理解姿态识别先得把它和动作识别区分开。动作识别回答的是“这个人在做什么”比如跑步、挥手、跳跃本质是个时序分类问题。姿态识别回答的是“这个人的关节在什么位置”本质是个结构化回归问题。两者有关系但技术路线差异很大多数业务场景是先做姿态识别拿到关键点序列后再做动作分类。从输出形式来看姿态识别通常分成两类2D姿态估计和3D姿态估计。2D输出的是关键点在图像平面的像素坐标3D输出的是关键点在相机坐标系或世界坐标系下的三维坐标。3D姿态需要额外的深度信息或者多视角几何约束对工程落地要求更高。我见过不少团队在业务需求还没想清楚时就开始上3D方案结果被标定、深度估计、多视角同步这些环节拖得疲惫不堪。如果你的场景是健身计数、跌倒检测、安防告警2D姿态在绝大多数情况下已经足够。还有一个容易混淆的概念是“单人姿态估计”和“多人姿态估计”。单人场景假设图像里只有一个目标人多人场景需要先定位所有人再分别估计姿态。多人场景又可细分为“自顶向下”和“自底向上”两种路线后面的章节会专门展开。1.2 2D/3D、单人/多人如何选型我在实际项目里选型的判断逻辑其实很简单摄像头是单目的且实时性要求高优先选2D多人姿态估计场景固定、人数少、算力有限自顶向下方案足够要驱动3D虚拟角色或者做精细的康复评估再考虑3D方案人数很多且对密集场景敏感比如地铁站、商场自底向上方案更有优势。很多人忽略的一点是场景约束。固定的摄像头角度、已知的背景区域、受控的光照条件这些先验信息能帮你省掉大量网络调优的功夫。比如在健身房场景下摄像头角度基本固定背景变化不大模型精度会比在开放数据集上好很多。姿态识别不是万能魔法越能利用场景约束落地效果越稳定。另外关键点数量的选择也值得说一句。COCO数据集的17点方案覆盖了人体主要关节点足够支撑绝大多数应用。如果做手部或脸部精细识别需要单独换用21点的手部模型或68点的脸部模型。千万不要贪多关键点越多标注成本越高模型越容易在某些点上翻车。我的经验是够用就好。2. 主流技术方案与选型分析2.1 自顶向下与自底向上两种范式的博弈这一节是姿态识别里最重要的概念之一值得花点篇幅讲透。自顶向下Top-down的流程分两步先用目标检测模型把人检测出来得到一个个人的边界框再把每个人框裁剪出来送入单人姿态估计网络预测关键点。自底向上Bottom-up则反过来先用一个网络直接预测出图像中所有人的所有关键点再把属于同一个人的关键点通过聚类算法连接起来。自顶向下最大的优点在于精度高。因为每个检测框里只有一个人单人姿态估计网络不需要处理复杂的多人交叉遮挡问题。这也是目前在COCO数据集上刷榜的方案主流选择。缺点也很明显推理速度随人数线性增长图像里人越多需要跑的单人网络次数越多实时性就难以保证。自底向上恰好相反。它的推理时间与画面中人数基本无关更适合密集人群场景。但后处理的聚类配对算法相对复杂而且精度普遍略低于自顶向下。我把两种范式做了个简单对比对比维度自顶向下自底向上推理流程先检测后估计两阶段单阶段直接预测所有关键点精度表现通常更高略低但差距在缩小推理速度随人数增加而变慢基本稳定适合密集场景实现复杂度依赖目标检测精度关键点聚类配对较复杂典型代表HRNet, ViPNASOpenPose, HigherHRNet选哪条路线取决于你的业务场景更在乎精度还是时延。如果只有一两个人而且背景可控自顶向下是省心之选。我记得之前做过一个店铺客流的动作分析项目画面里人不多就用自顶向下方案快速上线稳定性很满意。2.2 热图回归与坐标回归关键点预测这块主流的监督方式可以分成热图回归Heatmap Regression和直接坐标回归Coordinate Regression两种理解它们的差异对后续调参很有帮助。热图回归的做法是给每个关键点生成一张概率图图上的值代表该位置是某个关节点的置信度。网络输出17张热图每张对应一个关键点最后取热图峰值位置映射回原图坐标。这种方式有一个很明显的好处保留了空间上下文信息网络更容易学到关键点与周围像素的关系。实现上也更自然因为卷积网络天生适合输出二维特征图。代价是输出分辨率比输入小坐标换算时需要做缩放存在一定的量化误差另外计算量相对大。直接坐标回归的做法是让网络直接输出关键点的x、y坐标数值类似目标检测里的框回归。这种方式更省资源推理速度更快但因为是个纯回归任务网络要直接从高维图像特征里回归出坐标点优化难度更大精度也常常不如热图回归。实际项目中除非对速度有极端要求否则我通常优先选热图回归。目前一些新工作尝试用Transformer直接回归关键点坐标比如TokenPose、TransPose精度在部分数据集上可以和热图方法比肩但工程成熟度还差一点。如果你不是做研究先用热图回归的成熟方案把业务跑起来更重要。2.3 开源工具链的横向对比直接造轮子做姿态识别是极其不划算的事情。圈子里已经有不少成熟开源工具我挑几个常用的做个横向对比。OpenPose是CMU开源的经典项目自底向上路线的代表很多人第一次接触姿态识别就是因为OpenPose。它支持2D多人姿态估计还能同时输出手部、面部关键点。缺点是模型相对笨重部署到嵌入式设备会比较吃力。MediaPipe是Google推出的跨平台方案轻量、移动端友好在手机上跑得飞快。它内置了姿态估计、手部关键点、面部网格等多种能力接入方式也简单适合快速做原型。但精度在复杂场景下不如重模型而且对自定义训练的支持没有其他框架方便。MMPose是OpenMMLab系列里的姿态估计工具箱基于PyTorch支持大量主流模型包括自顶向下和自底向上两类范式。它的好处是配置化程度高模型、数据集、优化器都可以通过配置文件切换非常方便做研究和定制训练。缺点是需要花一点时间学习它的配置体系和API。DeepLabCut最初是给动物姿态追踪设计的但在人体姿态上也有效果。它特别适合标注数据少、需要微调的科研场景支持主动学习和自定义循环。工具路线适合场景上手难度自定义训练OpenPose自底向上多人、学术研究中较复杂MediaPipe自底向上轻量移动端、实时原型低受限MMPose两者都支持定制训练、工程落地中灵活DeepLabCut自顶向下科研、动物姿势中灵活我在业务项目中用得最多的是MMPose后续的代码实操也是基于它展开因为它最容易从配置文件一路改到实际部署所有环节可控。3. 环境准备与数据集处理3.1 搭建可复现的Python环境老话说“配环境两小时训练十分钟”姿态识别项目对版本敏感度极高尤其是在GPU驱动和PyTorch版本上稍微不一致就会碰到各种奇怪的报错。我的建议是务必使用虚拟环境不要直接在系统环境里装。我是这样搭建的conda create -n mmpose python3.8 -y conda activate mmpose conda install pytorch1.13.0 torchvision0.14.0 pytorch-cuda11.7 -c pytorch -c nvidia pip install -U openmim mim install mmengine mim install mmcv2.0.0 git clone https://github.com/open-mmlab/mmpose.git cd mmpose pip install -e .这里有几个需要说明的点。Python版本我选了3.8不是追求新版本而是考虑到部分算子库对3.10以上支持还不完善。PyTorch版本要和CUDA版本匹配如果你是Ampere架构之后的显卡比如30系或者40系CUDA 11.7以上比较稳妥。MMPose 1.x版本要求MMCV 2.x别装成MMCV 1.x否则会直接报版本冲突。注意不要在Windows下裸跑训练除非你是老手。姿态识别训练过程中涉及大量文件IO和分布式同步Windows的路径格式和权限机制会带来额外的坑。建议直接用Ubuntu服务器或者用WSL2。3.2 COCO数据集的准备与踩坑记录COCO数据集是姿态识别最常用的基准数据集包含超过20万张图片和25万个标注人物实例其中人体关键点标注为17个点。虽然做业务时多半要标注自己的数据但用COCO做预训练还是很有价值的。数据准备流程一般是下载COCO train2017和val2017图片下载person_keypoints_train2017.json和person_keypoints_val2017.json标注文件把图片和标注文件按COCO目录结构放好修改MMPose配置里的data_root路径。好多人在第一步就卡住了COCO官网的下载速度在国内不太稳定建议用镜像源或者学术下载工具。另外注意COCO数据集的压缩包比较大train2017单图片包就有约19G硬盘空间需要提前规划。标注文件是JSON格式里面包含info、licenses、images、annotations、categories五个部分。annotations里每个人物实例对应一个segmentation、bbox、keypoints数组。keypoints数组的长度是17乘3每一组三个值分别是x、y、可见性标志可见性为0表示未标注1表示遮挡但存在2表示可见。做训练时通常会把可见性为0的关键点设成忽略不参与损失计算这个细节容易忽略但对训练收敛影响很大。我在数据准备阶段踩过一个很经典的坑图片路径没配对导致训练时大量图片加载失败。COCO的JSON里只是记录了图片文件名图片的实际存放路径要靠你在配置里指定img_prefix。路径配错后不是直接报错而是网络一直学不进去loss曲线异常波动查了半天才发现是数据加载不对。4. 基于MMPose的代码实操4.1 配置文件解析与改动MMPose 1.x的核心思想是“配置驱动”。模型结构、数据集、训练策略、评价指标全部写在配置文件里改配置比改代码安全得多也方便对比实验。以自顶向下的2D姿态估计为例我一般会以td-hm_hrnet-w32_8xb64-210e_coco-256x192.py作为baseline。HRNet是姿态识别里经典的骨干网络它的特点是在整个网络过程中始终保持高分辨率特征图而不是像ResNet那样先下采样再逐级恢复。对于关键点定位这种需要精细空间信息的任务高分辨率特征保留能够带来立竿见影的精度提升。打开配置文件你会看到几个核心部分model字段定义了骨干网络、关键点头和损失函数dataset_info定义了数据集类型、关键点数量和类别名称train_dataloader、val_dataloader定义了数据加载方式optim_wrapper定义了优化器配置train_cfg、val_cfg、test_cfg定义了训练与评估流程。实际使用中我通常会改动这几个地方# 数据集路径 data_root data/coco/ # 训练超参数根据显存调整batch size train_dataloader dict( batch_size64, num_workers4, persistent_workersTrue, samplerdict(typeDefaultSampler, shuffleTrue), datasetdict( typeCocoDataset, data_rootdata_root, data_modetopdown, ann_fileannotations/person_keypoints_train2017.json, data_prefixdict(imgtrain2017/), ) ) # 统一输入尺寸 train_pipeline [ dict(typeLoadImageFromFile), dict(typeGetBBoxCenterScale), dict(typeRandomFlip, directionhorizontal), dict(typeRandomBBoxTransform), dict(typeTopDownAffine, input_size(192, 256)), dict(typeGenerateTarget, encoderdict(typeMSRAHeatmap)), dict(typePackPoseInputs), ]这里重点解释一下input_size的选择。配置里默认是192x256也就是输入的宽高。理论上输入越大关键点定位精度越高因为热图的分辨率也越大但计算量和显存消耗也会同步增长。我在1080Ti环境下试过384x288输入精度大约提升1到2个点但训练时间长了近一倍。如果你的业务对精度没有极致追求192x256是性价比很高的选择。4.2 训练与评估配置改好之后就可以启动训练了。MMPose支持单机多卡训练命令很简单bash tools/dist_train.sh configs/body_2d_keypoint/topdown_heatmap/coco/td-hm_hrnet-w32_8xb64-210e_coco-256x192.py 4如果是单卡或者想Debug可以直接python tools/train.py configs/body_2d_keypoint/topdown_heatmap/coco/td-hm_hrnet-w32_8xb64-210e_coco-256x192.py训练日志默认会输出到work_dirs目录下。我建议训练过程中盯住两个指标loss曲线和验证集AP。loss在头几十个epoch内会快速下降之后进入平台期这是正常现象。如果loss一直不降或者NaN多半是学习率太大或数据加载出了问题需要及时停住排查。验证评估跑完后会输出类似下面的指标Average Precision (AP) [ IoU0.50:0.95 | area all | maxDets 20 ] 0.749 Average Precision (AP) [ IoU0.50 | area all | maxDets 20 ] 0.906 Average Recall (AR) [ IoU0.50:0.95 | area all | maxDets 20 ] 0.803AP是平均精度的缩写IoU阈值从0.5到0.95取平均这个值越接近1越好。HRNet-W32在COCO val上一般可以达到0.749左右的AP如果你用更强的HRNet-W48或者加入更多增广策略还能再往上提一点。训练阶段还有一个容易忽略的点是学习率调度策略。默认配置用的是cosine退火在训练后期学习率降得很低这有助于模型在小梯度下精细调整参数。如果你的数据集比较小可以把epoch数加大但适当降低初始学习率我一般会把默认的0.001降到0.0005防止在小数据集上过拟合。4.3 推理脚本从模型输出到骨骼可视化训练好的模型最终要跑推理。MMPose的推理API封装得比较完善不需要自己写太多胶水代码。我的一个推理脚本长这样import cv2 import numpy as np from mmpose.apis import init_model, inference_topdown from mmpose.structures import split_instances # 初始化模型 config_file configs/body_2d_keypoint/topdown_heatmap/coco/td-hm_hrnet-w32_8xb64-210e_coco-256x192.py checkpoint_file work_dirs/best_coco_AP_epoch_190.pth model init_model(config_file, checkpoint_file, devicecuda:0) # 加载图片检查维度 img cv2.imread(demo.jpg) img_rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 这里用一个人体检测器的输出我是用MMDet的RTMDet模型 from mmdet.apis import inference_detector, init_detector det_model init_detector(rtmdet_m_8xb32-300e_coco.py, rtmdet_m_8xb32-300e_coco_20221128_112346-15b6d126.pth, devicecuda:0) det_result inference_detector(det_model, img_rgb) # 取置信度大于0.3的person框 person_preds det_result.pred_instances.cpu().numpy() boxes person_preds[person_preds.scores 0.3] # 只保留类别为person的框 person_boxes [b for b, label in zip(boxes.bboxes, boxes.labels) if label 0] # 送入姿态估计模型 pose_results inference_topdown(model, img_rgb, person_boxes) # 可视化 from mmpose.apis import visualize visualize( img_rgb, pose_results, output_vis.jpg, dataset_namecoco, showFalse, )上面代码有几个细节要注意。inference_topdown接收的是numpy格式的RGB图像不是BGR直接用OpenCV读到的图要转一下。目标检测框是关键它框得不准姿态估计精度直接报废。如果你没有现成的检测模型也可以先用MMPose自带的inference_topdown配合手动指定框跑但实际落地时检测器是绕不开的。推理完之后pose_results里包含了每个人的关键点坐标和置信度关键点索引对应COCO 17点定义0鼻子1左眼2右眼3左耳4右耳5左肩6右肩7左肘8右肘9左腕10右腕11左髋12右髋13左膝14右膝15左踝16右踝。拿到这个数组后你可以自己画骨骼线也可以直接做后续的姿态分析。小提示画骨骼线时别只画点把肢体连接线一起画上观感完全不同。COCO数据集的连接关系是固定的官方给出的骨架连接对是(5,7)、(7,9)、(6,8)、(8,10)、(5,6)、(11,12)、(5,11)、(6,12)、(11,13)、(13,15)、(12,14)、(14,16)。把这12组索引存下来画线就行。5. 常见问题与排查技巧实录5.1 关键点坐标偏移严重最常见的问题是推理出来的关键点位置偏了比如手腕画到了前臂中间膝盖画到了大腿上。这个问题的成因通常是三个检测框不精准、输入分辨率太低、训练数据分布和测试场景差距大。排查思路从检测框开始。先把姿态模型屏蔽掉单独用检测模型看看人框画得准不准。如果检测框把人体截断了一大截姿态模型自然会被带偏。这时候优先提高检测模型的置信度阈值或者换更强的主干网络。如果检测框没问题再看输入分辨率有些模型默认输入尺寸只有192x256对于远距离小目标来说分辨率明显不够可以把输入尺寸提到256x320或者更高。如果前面两个都没问题那就是场景迁移的问题。比如你在COCO上训练的模型直接放到一个俯视视角的摄像头下效果大概率打折。解决方法是采集一批现场数据做微调哪怕只有一千张也能把精度拉回可接受的范围。5.2 训练loss不下降或者NaN训练阶段遇到的问题通常是两类。loss不下降的原因集中在学习率和数据预处理。学习率设置过大会导致loss在某个值上震荡过小则下降极其缓慢。我一般把初始学习率设置在0.0005到0.001之间如果用了比较大的batch size按照线性缩放法则适当调大学习率。数据预处理的问题更隐蔽比如归一化参数错乱、关键点可见性没过滤导致异常值参与损失计算。建议先跑一遍单batch训练把loss值打印出来对照一下确认数据读取无误再跑全量。NaN的问题多半来自浮点溢出。常见诱因包括学习率过大导致梯度爆炸、BN层的running stat在batch size太小时不稳定。如果用了混合精度训练也要检查一下是否启用了梯度缩放。我的经验是出现NaN时先关掉amp再把学习率降到当前值的十分之一大概率能定位到问题。5.3 多人场景下表现糟糕多人场景是姿态识别落地的重灾区两个常见问题是漏检和关键点串接。漏检往往是目标检测器的锅姿态模型压根没机会上场。检查一下检测器的recall看是不是在拥挤场景下人框漏掉太多。可以适当降低检测置信度阈值或者调整NMS的IoU阈值让检测结果保留更多候选框。关键点串接多发生在自顶向下方案中即检测框里需要判断正确的人但单人姿态模型会把同框里另外的人的关键点也预测出来。这个问题的根源在于检测框不够紧把旁边的人也框了进来。解决方法是在检测阶段用更紧凑的框或者对检测框做一点缩小处理。另一个思路是把自顶向下的单人网络换成基于中心点回归的变体让模型本身就具备一定遮挡抗性。我把这些常见问题整理成了速查表方便现场排查现象可能原因排查顺序关键点整体偏移检测框不准 / 分辨率低 / 数据分布差异检测框 - 输入分辨率 - 微调单点反复抖动遮挡 / 热图峰值不明确检查热图输出 / 关键点滤波loss下降缓慢学习率低 / 数据量少调学习率 / 加数据loss为NaN梯度爆炸 / 学习率大减学习率 / 关amp多人漏检检测器召回率低调阈值 / 换检测模型关键点串接检测框过宽 / 遮挡收窄检测框 / 模型升级6. 姿态识别项目落地的一些建议6.1 业务需求决定技术方案我见过太多团队在姿态识别项目上犯同一个错误先选了一个很酷的技术方案然后反过来找能套用的业务场景。正确的做法是倒过来先把业务问题定义清楚再选择合适的技术方案。举个例子如果你要做一个智能健身镜的应用核心是动作标准度评估那关键点精度是第一位实时性可以放宽一点。选型时可以走自顶向下路线用HRNet作为骨干把输入分辨率调高追求更准的坐标。如果你做的是实时跌倒检测快速响应比关节点的绝对精度更重要MediaPipe这种轻量方案就很有竞争力。同样做动作分析两个场景的技术选型完全不同。再比如同样是计算俯卧撑个数你可以用关键点之间的夹角变化来做判断也可以直接训练一个分类网络识别动作状态。前者的可解释性强方便调试后者的上限高但需要更多数据。多数场景我建议先用可解释的方案跑通再逐步迭代。6.2 数据标注和微调是长期工作姿态识别的模型效果上限很大程度取决于数据质量。公开数据集能让你快速跑通流程但业务场景中往往存在特殊的视角、目标尺寸、穿着习惯这些都需要用业务数据来微调。标注人体关键点是个细致活17个点的标注质量直接影响模型精度。建议标注时统一标注规范比如关节被遮挡时是否标注、边缘模糊时如何处理团队内需要达成一致。我常用LabelMe和X-Pose工具做标注前者老牌稳定后者在多人场景标注效率更高。微调的另一个重要经验是先冻结骨干网络只训练关键点头等loss下降平稳后再解冻骨干做全量微调。这个策略可以防止小数据集上骨干网络被带偏同时能加快训练速度。6.3 部署时别忘了关键点平滑和异常处理最后这一步最容易被人忽视却常常决定用户体验。姿态估计的输出是逐帧独立的直接使用会看到明显的抖动。运动平滑要用一维Kalman滤波器或者Savitzky-Golay平滑对关键点坐标做时序滤波。实时场景下我推荐用指数滑动平均EMA实现简单、延迟低效果足够好smooth_kps alpha * current_kps (1 - alpha) * smooth_kpsalpha通常取0.5到0.9之间的值alpha越大平滑效果越明显但滞后也越严重。具体数值需要根据帧率调整。异常处理是说连续几帧检测不到关键点时系统要有明确的降级策略。比如触发一次重检测、显示提示、或者用上一次有效状态保持输出。这些细节看上去不起眼但用户对姿态识别产品的第一印象往往就取决于此。我做健身项目时一开始只处理了正常检测的帧漏检一多计数就乱跳用户立刻反馈“像抽风一样”。后来加了漏检阈值和状态机体验才稳定下来。再分享一个我在实际项目里积累的小技巧把关键点置信度当成过滤条件而不是直接丢弃。有些关键点坐标虽然准但置信度很低这时候与其直接忽略不如把置信度作为后续逻辑里的一个权重。比如做动作相似度打分时低置信度关节点的权重可以调低避免一个遮挡点把整体分数带偏。姿态识别这个领域发展速度很快新的网络结构层出不穷但核心的工程方法论是稳定的。先把任务边界想清楚再选合适的开源工具用配置驱动的方式快速迭代最后在数据平滑和异常处理上花心思这套流程能覆盖大多数业务需求。我自己在做项目时特别深刻的体会是姿态识别模型调参的空间其实有限那些把效果拉开差距的动作往往发生在数据标注、场景预估和工程细节里。希望这篇文章能让你少走一些我走过的弯路。
返回列表