Разметка и обучение моделей поз
Создавай разметку ключевых точек с указанием видимости, правильно задавай kpt_shape и flip_idx, обучайся на COCO8-pose и преобразуй ключевые точки в углы суставов с порогом уверенности.
Оценка позы представляет каждого человека как 17 пронумерованных точек. По этим точкам вычисляются показатели, на которых строятся продукты: углы локтя для эргономики, углы колена для реабилитации, высота таза для обнаружения падений. Два ключа набора данных определяют, можно ли доверять этим точкам. Ошибка в любом из них не вызовет сбой — просто модель будет путать левую и правую стороны или незаметно обучится без самой дешёвой аугментации.
Практические занятия
Строка разметки ключевых точек#

Строка разметки позы — это строка обнаружения с добавленными ключевыми точками. Каждая координата нормализована в диапазоне 0–1, а ключевые точки в каждой строке идут в одном и том же фиксированном порядке. Пример реальной строки COCO8-pose, сокращённой после четвёртой ключевой точки:
0 0.671279 0.617945 0.645759 0.726859 0.519751 0.381250 2.000000 0.550936 0.348438 2.000000 0.488565 0.367188 2.000000 0.642412 0.354687 2.000000 ...| Значения | Значение |
|---|---|
0 | индекс класса (человек) |
0.671279 0.617945 0.645759 0.726859 | центр рамки по x, центр по y, ширина, высота |
0.519751 0.381250 2 | ключевая точка 0 (нос): x, y, видимость |
| … | ещё 16 ключевых точек в том же формате: 5 + 17 × 3 = 56 значений в каждой строке |
Флаг видимости соответствует соглашению COCO: 2 — видна, 1 — размечена, но закрыта, 0 — не размечена. Ultralytics маскирует ключевые точки со значением v = 0 при вычислении функции потерь, поэтому отсутствующее запястье не учит модель предсказывать запястья в точке (0, 0).

Используй 1 для скрытого сустава, положение которого можно уверенно определить: например, бедра за столом или запястья за спиной; такая точка всё равно участвует в обучении. Оставляй 0 для суставов, положение которых невозможно определить. Если присваивать 0 каждой скрытой точке, модель лишится именно тех сложных примеров, которые ей нужны.
В руководстве по наборам данных поз также описан вариант с двумя значениями (x y only, kpt_shape: [K, 2]) для наборов данных без информации о видимости.
kpt_shape и flip_idx#
В YAML набора данных к обычным path, train, val и names добавляются два ключа:
kpt_shape: [17, 3] # 17 keypoints, each x, y, visibility
flip_idx: [0, 2, 1, 4, 3, 6, 5, 8, 7, 10, 9, 12, 11, 14, 13, 16, 15]
flip_idx указывает аугментации с горизонтальным отражением, в какую ключевую точку переходит каждая точка в зеркальном изображении. При отражении человека его левое запястье (9) оказывается там, где было правое запястье (10), поэтому в позиции 9 массива flip_idx хранится 10, а в позиции 10 — 9; нос (0) соответствует самому себе. При каждой аугментации скелет перемещается вместе с пикселями, а отражение — единственная аугментация, для которой также нужна таблица соответствия индексов:

Что происходит, если flip_idx задан неверно, как подтверждает проверка установленного исходного кода:
- Отсутствует: во время обучения выводится предупреждение, а
fliplrиflipudустанавливаются в 0, поэтому ты теряешь самую дешёвую аугментацию. - Неверная длина: обучение останавливается с
ValueError, указывающим на несоответствие, — это единственный случай с явной ошибкой. - Длина верная, пары неверные: предупреждений нет. На половине отражённых примеров модель усваивает, что левая сторона — это правая, а на изображениях для валидации появляются перестановки левой и правой сторон симметричных поз.
Обучи модель на COCO8-pose#

from ultralytics import YOLO
model = YOLO("yolo26n-pose.pt")
model.train(data="coco8-pose.yaml", epochs=1, imgsz=320, device="cpu")
metrics = model.val()
print(f"box mAP50-95: {metrics.box.map:.3f}")
print(f"pose mAP50-95: {metrics.pose.map:.3f}") Class Images Instances Box(P R mAP50 mAP50-95) Pose(P R mAP50 mAP50-95)
all 4 14 1 0.783 0.893 0.643 0.966 0.571 0.575 0.326
box mAP50-95: 0.643
pose mAP50-95: 0.326Как и в сегментации, таблица состоит из двух блоков. Box оценивает рамки людей, а Pose — расположение ключевых точек с помощью сходства ключевых точек объекта (OKS) в рамках того же механизма mAP. Pose mAP50-95 — критерий выпуска продукта для оценки позы: в следующем уроке показано, как OKS определяет, верно ли расположена ключевая точка.
От ключевых точек к углам суставов#
result.keypoints содержит xy (N × K × 2, пиксели), xyn (нормализованные координаты) и conf (N × K) в указанном выше порядке COCO. Угол в суставе — это угол между двумя векторами конечностей, которые в нём сходятся, — его можно вычислить через скалярное произведение:
import numpy as np
from ultralytics import YOLO
model = YOLO("yolo26n-pose.pt")
result = model("https://ultralytics.com/images/bus.jpg")[0]
kpts = result.keypoints.cpu().numpy() # people x 17 x (x, y, conf)
SHOULDER, ELBOW, WRIST = 5, 7, 9 # left arm, COCO keypoint order
for person, (xy, conf) in enumerate(zip(kpts.xy, kpts.conf)):
if conf[[SHOULDER, ELBOW, WRIST]].min() < 0.5:
print(f"person {person}: left arm not visible enough")
continue
a, b, c = xy[SHOULDER], xy[ELBOW], xy[WRIST]
cos = np.dot(a - b, c - b) / (np.linalg.norm(a - b) * np.linalg.norm(c - b))
print(f"person {person}: left elbow angle {np.degrees(np.arccos(cos)):.0f} deg")person 0: left elbow angle 71 deg
person 1: left elbow angle 96 deg
person 2: left elbow angle 59 deg
person 3: left arm not visible enough
person 4: left arm not visible enoughРазберём по строкам: kpts.xy[p] — это 17 точек человека p в пикселях; a - b и c - b — векторы плеча и предплечья, направленные от локтя; их нормализованное скалярное произведение — косинус угла между ними. У прямой руки угол близок к 180°, у полностью согнутой — к 30°. У людей 3 и 4 как минимум один сустав левой руки имеет уверенность ниже 0.5, поэтому скрипт не измеряет их.
Симптом: кажущиеся надёжными углы для людей, чьи руки находятся за пределами кадра. Причина: модель возвращает координаты каждой ключевой точки вне зависимости от того, видна ли она. Решение: пропускай расчёты, для которых уверенность по суставам ниже минимального порога (здесь это 0.5), и записывай количество пропусков в журнал: его рост — ранний признак изменения положения камеры или освещения.
Показать решение
import numpy as np
from ultralytics import YOLO
ARMS = {"left": (5, 7, 9), "right": (6, 8, 10)} # shoulder, elbow, wrist
kpts = YOLO("yolo26n-pose.pt")("https://ultralytics.com/images/bus.jpg")[0].keypoints.cpu().numpy()
for person, (xy, conf) in enumerate(zip(kpts.xy, kpts.conf)):
for side, (s, e, w) in ARMS.items():
if conf[[s, e, w]].min() < 0.5:
continue
a, b, c = xy[s], xy[e], xy[w]
cos = np.dot(a - b, c - b) / (np.linalg.norm(a - b) * np.linalg.norm(c - b))
print(f"person {person}: {side} elbow {np.degrees(np.arccos(cos)):.0f} deg")person 0: left elbow 71 deg
person 0: right elbow 94 deg
person 1: left elbow 96 deg
person 2: left elbow 59 deg
person 2: right elbow 135 degУ людей 0 и 2 можно измерить обе руки. Уверенность для правой руки человека 1 и обеих рук людей 3 и 4 ниже порога 0.5, поэтому скрипт пропускает их, а не пытается угадать.
Если flip_idx отсутствует, аугментация отражением отключается с предупреждением; неверная длина останавливает обучение с ValueError; корректно выглядящий flip_idx с неверными парами приводит к тому, что обучение без предупреждений использует отражённые метки, а на валидации проявляются перестановки левой и правой сторон.