做人体姿态估计的,应该都绕不开 OpenPose 这个名字。它是最早把“实时多人2D姿态估计”从实验室带到工程落地的那批工作之一,核心创新就是一个词:Part Affinity Fields,部件亲和场,后面大家习惯叫 PAF。这套方法解决的不光是“图上有没有人、人长什么样”,而是更棘手的一层——同一张图里站着好几个人,每个人十几二十个关键点,怎么把这些点正确拼回每一个人身上,还要拼得快、拼得准。
这篇博文适合正在入门人体关键点检测的开发者、要选型实时姿态解析方案的产品同学,还有想深入理解自底向上多目标匹配思路的算法工程师。我会把 OpenPose 的问题动机、PAF 原理、网络设计、复现流程、调参避坑和后续演进一次讲透,尽量说人话,让大家读完既能理解它为何是经典,也能动手把它跑起来。
1. 这篇论文到底解决了什么问题
1.1 人体姿态估计的主流做法:自顶向下 vs 自底向上
人体2D姿态估计,任务是用关键点描述人的骨架结构,通常输出头部、肩膀、手肘、手腕、髋、膝、踝这些关节位置。单人简单,多人就烦。多人场景下,业界当时分成两条路线:
自顶向下(Top-Down)的思路是先做人检测,把人框出来,再对每个框跑单人姿态估计。这类方法好处是每个目标独立处理,模型可以专注学“单个人”的姿态,精度容易做高;但坏处也很明显——推理耗时随人数上涨,图里有10个人就要跑10次单人网络,实时性很难保证。而且如果检测框漏了人,后面姿态也就全丢了。
自底向上(Bottom-Up)的思路反过来,先在整图上把所有人的关键点检测出来,再通过后处理把属于同一个人的点组装起来。OpenPose 走的就是这条路线。它的天然优势是检测阶段不区分人物实例,速度基本与人数无关,这在多人交互、密集场景下特别香。但难点全部集中在“组装”这一步:图上有5个人,检测出了几十个候选关键点,怎么正确区分哪些点是同一个人的?
我当时看到这里的第一反应是:这不就是聚类问题吗?确实可以聚类,但聚类容易踩的坑很多——关键点相互靠近、肢体交叉、人贴人的情况下一不小心就拼错人,拼错一个点后面全乱。
1.2 关键难点:多人时关键点怎么“拼”回一个人
早期自底向上的方法,普遍把“关键点之间是否有连接”建模成一个图切分问题。比如把每个关键点候选当作图节点,用节点之间预先训练好的关系模型算连接概率,然后做图优化。但这类方法有两个麻烦:一是关系模型只告诉模型“这两个点可能属于同一个人”,没有告诉它“这两个点之间该怎么连、连接路径是什么样的”;二是多人姿态的匹配在数学上常常是一个 NP-hard 的组合优化问题,规模一大人一多就跑不动。
OpenPose 的核心贡献,就是用一个可学习的向量场把“连接关系”嵌入到网络预测里。这个向量场就是 Part Affinity Fields,它不是简单地说“肩膀和手肘有关系”,而是把连接描成一幅逐像素的二维方向场:肢体覆盖范围的每个像素都携带一个向量,指向从起点关键点到终点关键点的方向。这样一来,网络输出的不仅是关键点在哪,还包括肢体的走向信息。到了后处理阶段,我们给任意两个候选关键点连线,沿线采样 PAF 向量并做积分,就能得到一个非常可靠的连接置信度。
这个设计聪明在哪儿呢?它把“匹配问题”变成了“对连续场做积分”的问题。匹配本身不再需要复杂的图切分,只需要对候选点对打分,再用二分图匹配或贪心算法做分配就行。OpenPose 论文里实际是利用了线性规划的松弛,找到一个高效率的优化方案,在保证精度的前提下做到了实时。
2. 先把原理吃透:置信图和PAF到底在算什么
2.1 从单人的“关键点热力图”说起
讲 PAF 之前,得先理解它旁边那个“置信图”(Confidence Map)。置信图本质上是一张把关键点位置转成热力分布的图。对于训练数据里的某一个关键点,比如右肩,我们把标注位置当作高斯分布的中心,在图像对应的位置上“抹”出一个柔和的高斯峰。网络要做的,就是对每个像素输出一个概率,表示“这里有多大的可能性是一个右肩”。
单张置信图的含义很直观:峰值位置就是关键点最可能出现的地方,峰高代表置信度。实际测试时,我们会在置信图上做非极大值抑制(NMS),找出局部峰值,作为候选关键点。
但问题马上来了:如果图里有3个人,右肩就有3个峰值。置信图本身并不区分这3个右肩分别属于谁。所以光靠置信图,我们只知道“这里有3个右肩位置”,并不知道哪一个是左边这个人的。于是需要第二个输出把点与点之间的关系“缝”起来,这就是 PAF 分支存在的意义。
2.2 PAF:不只是告诉“谁和谁相连”,还告诉“怎么连”
PAF 的每个通道对应一种肢体连接类型。比如“右肩到右手肘”这个连接,在 PAF 里占用两个通道:一个编码向量在 x 方向的分量,一个编码 y 方向的分量。对于肢体途经的每个像素,这两个通道给出从起点指向终点的单位向量;对于不在肢体范围内的像素,通道值直接为0。
为什么要用方向向量,而不是简单地输出“这两个点相连”的概率图?因为只有连接概率时,信息量不够,容易出歧义。举个例子,两个人手臂交叉时,肩膀A和手肘B、肩膀B和手肘A在像素距离上都很近,光靠“有没有连接”很难区分。但如果网络能够编码“肢体的走向”,那么哪条路径方向与手臂实际方向一致是一目了然的。
测试阶段的工作方式也很优雅。给出一对候选关键点 d1 和 d2,我们在这两点的连线上均匀采样若干像素点,把这些像素处的 PAF 向量与连线方向做点积并取平均值。当向量方向与连线方向一致时点积值大,说明这对关键点之间的连接方向和网络预测的肢体方向高度吻合,连接置信度就高。这个操作相当于计算一个带方向的积分,因此论文里称其为“关键点关联的线积分”。
我在实际跑代码时发现这个设计的好处不止是精度,它天然对关键点之间的距离不敏感。哪怕两个候选点距离很远,只要中间肢体覆盖的 PAF 方向一致,分数依然很高;反之,交叉手臂场景下即使距离很近,只要方向不一致,分数立刻就被拉低。这就是 PAF 能碾压普通成对关系模型的核心原因。
2.3 网络结构:两分支+多阶段的串行修正
OpenPose 的网络结构,在当年看也很有特点。它用 VGG 的前几层作为骨干特征提取,然后在特征图上分出两个并列的分支头:一个分支不断回归置信图 S,另一个分支不断回归 PAF 场 L。两个分支后面再接了多个阶段(stage),每个阶段都会把两个分支的输出和原始特征拼接起来继续精炼。
为什么搞这么多阶段?因为关键点检测和肢体连接这件事,早期预测往往是粗糙的,尤其是多人遮挡和肢体交叉区域,一阶段很难一步到位。通过串联多阶段,网络可以在大感受野里不断细化特征,逐渐把“模糊的关系”修正清楚。论文里常见配置是6个阶段,前几个阶段的结果可能很碎,越到后面关键点和 PAF 的一致性越好。
值得一提的还有中间监督(Intermediate Supervision)。每一个阶段结束后,都会计算当前阶段预测结果和真实标注之间的损失,参与梯度回传。这个设计让早期阶段也能学到有效特征,避免了深层网络中的梯度消失,也让网络的收敛速度和稳定性都更好。这种“多阶段中间监督”的思路,后来也被很多姿态估计、关键点检测网络借鉴。
整个训练目标就是两类损失相加:置信图的 L2 损失,加上 PAF 场的 L2 损失。对于图像中没有标注的位置,损失会被掩码掉,避免未知区域干扰训练。数据增强方面,随机缩放、旋转、翻转都是标配,对提高泛化能力帮助很大。
3. 实操复现:从环境配置到跑通官方 Demo
3.1 环境准备与编译要点
OpenPose 官方仓库提供了 C++ 和 Python 两套接口。如果你是第一次跑,我建议直接走官方编译流程,虽然麻烦一点,但后面的灵活性最高。
编译 OpenPose 有几个前置依赖:CUDA(如果没有 GPU 就纯 CPU 模式,但速度会慢到怀疑人生)、cuDNN、CMake、OpenCV。我的经验是提前把 CUDA 版本和 cuDNN 对应关系查好,别装个不匹配的组合,后面编译报错全是泪。官方仓库在编译时有很多 CMake 选项,最常用的两个是BUILD_PYTHON_API和USE_CUDA。如果只跑命令行 Demo,不编译 Python 接口也行;但如果你想做二次开发,记得打开 Python API 选项。
模型下载也是卡住新手的点。OpenPose 官方提供了多种预训练模型,最常见的有:
BODY_25:包含25个关键点,覆盖身体加脚的完整骨架;COCO:18个关键点,对应 COCO 数据集标准;MPI:15个关键点,旧版本用的多。
我自己的经验是个人练习优先用BODY_25,它关键点更全,部分拥挤场景下稳定性也更好。如果后面要对接 COCO 评估协议,再切成COCO模型。
3.2 命令行与 Python 调用示例
如果编译顺利,可以直接通过命令行跑:
./build/examples/openpose/openpose.bin \ --image_dir examples/media/ \ --write_json output_json/ \ --display 0 \ --render_pose 0这里--image_dir指定输入图片目录,--write_json指定输出关键点 JSON 的目录,--display 0表示不弹可视化窗口(适合服务器环境),--render_pose 0表示不渲染骨架图,只保留原始数据和关键点坐标。
输出 JSON 的结构值得说一下。每个文件里会有一个people数组,每个人包含pose_keypoints_2d,里面按照模型定义的关键点顺序依次列出 x、y、置信度三个值。比如BODY_25就有25个点,所以pose_keypoints_2d长度是 75。解析的时候注意别按 18 或 25 的个数直接除,要先确认模型类型。
Python 调用也很直接,官方提供了封装好的PyOpenPose类,基本用法是把图片传入forward方法,然后从返回对象里取关键点数组和热力图。如果你打算把 OpenPose 嵌入到自己的推理管线里,用 Python API 会比每次都跑子进程方便得多。
3.3 速度实测与参数调优思路
OpenPose 的实时性依赖几个关键参数,调好了能在精度和速度之间拿到很好的平衡:
--net_resolution:网络输入分辨率。官方默认在656x368附近,降低到320x256这类小分辨率能明显提速,但小目标检测会变差。如果场景是摄像头近景人像,小分辨率完全够用;如果是监控大场景,还是老老实实用大分辨率。--number_people_max:限制最大人数。检测人数少时,适当调低这个值可以大幅减少后处理计算量。--scale_number和--scale_gap:控制多尺度推理,也就是在多个图像尺度上分别推理再融合。开启多尺度对精度有提升,但代价是推理时间翻倍,实时场景通常保持scale_number=1。
我做过一个粗略对比:一张普通照片在单张消费级 GPU 上用默认分辨率和BODY_25模型,单图推理大概几十毫秒到一百多毫秒,基本可以满足实时视频流。如果降到小分辨率,帧率能往上走不少。这里没有神秘魔法,本质就是分辨率与精度的经典权衡。
4. 实战中高频踩坑与排查速查表
4.1 漏检、错检多的排查方向
我在实践中最常遇到的问题,就是图里人物靠得太近时,关键点串人或者直接漏检几个点。发生这类情况,我建议按下面顺序排查:
第一看输入分辨率是不是太小。分辨率不足时,间距很近的关键点在特征图上会糊成一片,网络根本分辨不出“这是两个人的手肘”。这时候优先调大--net_resolution。
第二看置信度和 PAF 的阈值。OpenPose 内部有多个阈值参数,比如关键点检测的最小置信度、PAF 关联的最低分数。阈值设置太高会把模糊点全部滤掉,造成漏检;阈值太低又会把错误连接放进来,造成错检。建议用小批量样本做阈值扫描,找到当前场景下的甜点。
第三看大姿态差异。健身动图、舞蹈视频里经常出现极端弯曲的肢体,这种状态下 PAF 的线积分也会打折。多尺度推理能部分缓解这个问题,代价是变慢。如果追求准确性胜过实时性,值得打开scale_number=3左右。
4.2 性能优化与部署经验
把 OpenPose 部署到实际产品里,性能和内存一般都需要专门优化。我踩过的坑包括:
- 显存占用比预期高。小批次推理时其实用不到太多显存,但如果 OpenCV 读图分辨率很高,预处理也会临时吃内存。建议先把输入图 Resize 到合理尺寸再送入 OpenPose。
- CPU 环境跑得很慢。OpenPose 官方实现主要面向 GPU,CPU 上跑大规模推理不太现实。如果只能上 CPU,考虑换用轻量姿态估计模型,或者对视频做抽帧处理。
- 多线程调用时模型加载会冲突。官方 Python 接口不是线程安全的,多路视频流建议用进程隔离,或者维护多个模型实例。
4.3 与其他框架集成时的坑
把 OpenPose 集成进其他项目时,最常见的坑是坐标系不一致。OpenPose 输出的关键点坐标是相对输入图的像素坐标,如果你的管线把图做了裁剪或缩放,一定要用相同的变换把坐标映射回原图。还有一点容易被忽略:OpenPose 输出的置信度分数在不同模型类型之间不完全可比,BODY_25的分数和COCO的分数不要直接混用做筛选。
另外,RGB 通道顺序也要注意。OpenCV 默认是 BGR,OpenPose 的模型训练使用 RGB 还是 BGR 不同版本有差异。我看到过不止一个项目,因为通道顺序问题导致检测效果莫名其妙变差,排查半天才发现是这里。推荐在集成初期就显示几组检测结果图,用人工肉眼确认骨架大体对齐,再继续往下做。
5. 跑通之后能做什么:场景落地与后续演进
5.1 典型应用:从健身分析到动捕参考
OpenPose 能火的另一个原因,是它的应用场景极其丰富。个人开发者最常做的几类:
- 姿态比对:录一段标准动作,再让用户跟着做,计算关节角度的误差,就能做一个简单的健身动作纠正工具。这里要额外做关键点平滑,因为单帧输出会有抖动。
- 动作计数:仰卧起坐、引体向上这类周期性动作,可以利用关键点角度随时间的变化做波峰检测,实现自动化计数。
- 影视动画动捕参考:把视频里的关键点序列导出成自定义 JSON 或 BVH,再导入到三维软件里做参考,可以省去大量人工关键帧动画时间。
- 人机交互:用身体姿态进行体感控制,比如举手触发菜单、倾斜控制方向,在展馆互动装置里很常见。
我自己的经验是,OpenPose 输出直接用于产品往往不够稳,最好再加一个时序平滑模块。最简单的做法是滑动窗口平均关键点坐标,或者用卡尔曼滤波跟踪每个关键点的轨迹。滤波之后,观众看到的骨架会稳定不少,没有那种“每帧都像在做随机抖动”的廉价感。
5.2 后续演进和替代方案
OpenPose 的 PAF 思想影响深远,但今天再选型,不一定非要直接部署老版本。后续出现了不少改进思路:
- 自底向上方向,OpenPifPaf 等方案在 PAF 基础上加入了更精细的字段建模,在密集场景的 AP 表现更好。
- 自顶向下方向,HRNet、RTMPose 等模型在单人姿态精度上进步巨大,配合高性能检测器也能做到视频流实时,这类方案在关键点数量少、对精度要求高的场景很常见。
- 轻量端侧方案,MediaPipe 等框架把姿态估计压缩到了手机端,适合移动 App 场景。
但这不意味着 OpenPose 该被淘汰。它依然是一个非常好的学习范本:自底向上怎么处理多目标匹配、连续场表征如何解决离散分配问题、多阶段结构如何逐步细化预测。读懂 PAF 的建模思路,很多后续论文的套路你都能一眼看穿。
我自己在实际项目中最大的一次感触,是意识到“把匹配问题编码成连续场”这类思路并不局限于姿态估计。像目标跟踪里的关联字段、实例分割里的嵌入向量,本质上都是在做类似的事:网络不只输出目标本身,还输出目标之间的关系特征。这个思路才是 OpenPose 真正值得记住的地方。
如果你现在正想复现或二次开发 OpenPose,我最后再给两个具体建议:第一,先用官方 Demo 跑通一张多人的复杂图片,亲眼看一下 PAF 可视化输出,理解它长什么样,比读十遍论文都有用;第二,动手改一次--net_resolution和阈值参数,观察输出变化,形成自己对“分辨率-精度-速度”的直觉。技术文章读再多都是别人的,只有亲手调过参数,这些东西才算长在你身上。