姿态估计与 OBB
读取 YOLO26 的关键点和旋转框,在其基础上编写规则,并识别姿态或角度何时才是真正的信号。
养老院需要知道是否有人跌倒;港口需要在无人机图像中统计并排密集停靠的船只。普通边界框对这两种情况都无能为力:边界框无法说明一个人是否站立,而给斜放船只套上正立的边界框,框内大部分都是水。姿态估计 和 OBB 正是为这两类问题而设计的。
实践操作
姿态:姿势本身传递信号时#

姿态模型 会为每个人返回一个边界框和一组固定的关键点。在 COCO-Pose 上训练的模型会为每个人预测 17 个关键点——鼻子、眼睛、耳朵、肩膀、肘部、手腕、髋部、膝盖和脚踝——顺序始终相同,因此索引 0 是鼻子,11 是左髋,15 是左脚踝。每个关键点还带有一个置信度。
最简单实用的规则是:站立的身体高大于宽;躺在地板上的身体宽大于高。下面是在公交车图像上的示例,只使用模型有把握的关键点:
from ultralytics import YOLO
model = YOLO("yolo26n-pose.pt")
result = model("https://ultralytics.com/images/bus.jpg", verbose=False)[0]
for xy, conf in zip(result.keypoints.xy, result.keypoints.conf):
visible = xy[conf > 0.5] # drop keypoints the model couldn't see
width = float(visible[:, 0].max() - visible[:, 0].min())
height = float(visible[:, 1].max() - visible[:, 1].min())
posture = "unknown" if len(visible) < 5 else "lying" if width > height else "upright"
print(f"{len(visible):2d}/17 keypoints visible, body {width:3.0f} px wide x {height:3.0f} px tall -> {posture}")16/17 keypoints visible, body 117 px wide x 427 px tall -> upright
6/17 keypoints visible, body 80 px wide x 359 px tall -> upright
16/17 keypoints visible, body 91 px wide x 384 px tall -> upright
1/17 keypoints visible, body 0 px wide x 0 px tall -> unknown
2/17 keypoints visible, body 3 px wide x 6 px tall -> unknown有三个人明显站立。最后两行分别是左边缘被截断的人,以及一个低置信度的重复检测:只有一两个可见关键点时,无法判断,所以规则会给出“未知”,而不是猜测。生产环境中的跌倒检测器还会通过跟踪加入时间条件——快速倒下后躺倒超过几秒。
症状:骨架的手腕悬在半空,姿态规则也会对只露出一部分的人触发。原因:即使模型看不到,keypoints.xy 也会返回全部 17 个关键点的位置。解决方法:像上面那样使用 keypoints.conf 进行筛选,并要求可见关键点达到最少数量后再做判断。
当答案取决于身体在做什么时,姿态估计才真正有用:用于安全监测的跌倒或弯腰搬举、运动中的关节角度和重复次数(如 运动监测),或牲畜姿态,以及采用自定义关键点布局的部件固定标记。要回答“这里有人吗?”,就用普通检测。
OBB:角度很重要时#

普通边界框的边缘与图像边缘平行。有向边界框增加了旋转角度,存储为中心、宽度、高度和角度(xywhr)。在俯视图像中——卫星、无人机、俯拍传送带相机——物体可能朝向任意角度;给斜放物体套上正立边界框,框内大多是背景,上面的游艇就是如此。用一张码头的无人机照片来测量差异:
from ultralytics import YOLO
model = YOLO("yolo26n-obb.pt")
result = model("https://ultralytics.com/images/boats.jpg", verbose=False)[0]
obb = result.obb
rotated_area = obb.xywhr[:, 2] * obb.xywhr[:, 3] # width x height of each rotated box
x1, y1, x2, y2 = obb.xyxy.T # the axis-aligned box that would enclose the same object
upright_area = (x2 - x1) * (y2 - y1)
print(f"{len(obb)} objects, classes: {sorted({result.names[int(c)] for c in obb.cls})}")
cx, cy, w, h, angle = obb.xywhr[0].tolist()
print(f"first box: center=({cx:.0f}, {cy:.0f}) size={w:.0f}x{h:.0f} angle={angle:.2f} rad")
ratio = float((upright_area / rotated_area).median())
print(f"an axis-aligned box would be {ratio:.1f}x larger (median)")173 objects, classes: ['harbor', 'ship']
first box: center=(619, 850) size=33x108 angle=-0.25 rad
an axis-aligned box would be 2.1x larger (median)围住同一艘船时,正立边界框覆盖的面积约为旋转框的两倍,多出来的面积都是水。这会带来双重影响:NMS 会看到相邻船只的正立框彼此重叠,可能因此抑制真实目标;任何尺寸估计也会被放大。在 DOTA 等航拍数据集上训练的 OBB 模型能直接处理这类场景。注意角度单位是弧度:-0.25 rad 约为 -14°。
如何在检测、姿态估计和 OBB 之间选择#
| 场景 | 最佳任务 | 原因 |
|---|---|---|
| 统计人行道上的行人 | 检测 | 答案是是否存在以及所在位置 |
| 检测养老院中的跌倒 | 姿态估计 | 身体朝向是信号 |
| 统计健身房里的深蹲次数 | 姿态估计 | 膝关节和髋关节角度是信号 |
| 用无人机统计停靠的船只 | OBB | 船只斜向停放且排列密集 |
| 从上方观察传送带上的包裹 | OBB | 朝向各不相同,且相邻物体彼此接触 |
| 街道上的汽车 | 检测 | 汽车在画面中大致正向排列 |
姿态任务为每个边界框添加关键点;OBB 则添加角度。如果你的代码从不读取关键点或角度,那么普通检测器标注更快、运行更快,也更容易调试。
显示解答
from ultralytics import YOLO
image = "https://ultralytics.com/images/boats.jpg"
upright = YOLO("yolo26n.pt")(image, verbose=False)[0]
rotated = YOLO("yolo26n-obb.pt")(image, verbose=False)[0]
print(f"axis-aligned detector: {len(upright.boxes)} boxes")
print(f"OBB model: {len(rotated.obb)} rotated boxes")axis-aligned detector: 0 boxes
OBB model: 173 rotated boxes差异主要来自训练数据,而不是几何形状。yolo26n.pt 在 COCO 上训练,COCO 中的船只大多是从侧面拍摄的;从上方拍摄时,它在默认阈值下什么也检测不到(将 conf 降到 0.05 后,它会把其中几艘识别成瓶子)。OBB 模型则是在航拍图像上训练的。几何形状仍然会带来影响:即使检测器是在航拍船只图像上训练的,它画出的边界框也会大约大一倍,并且在密集排列的行列中与 NMS 冲突。任务和训练数据都必须与部署时的视角相匹配。