Пользовательские ключевые точки и метрики OKS
Спроектируй схему ключевых точек для объектов, не являющихся людьми, создай и проверь YAML-файл датасета, а также задай сигмы OKS в соответствии с точностью, с которой можно расположить каждую точку.
Оценка позы нужна не только для людей. Кончики вил погрузчика, углы этикетки посылки, нос и хвост животного — любой объект со стабильными точками, которым можно дать названия, подходит для датасета поз. Схема, которую ты задашь в первый день, останется неизменной: от неё зависят каждая метка, каждая пара для отражения и каждая метрика, а её изменение позже потребует повторной разметки всего датасета. В этом уроке ты правильно задашь схему до того, как кто-либо разметит тысячу изображений, и узнаешь, как именно метрика будет оценивать результат.
Практические занятия
Спроектируй схему ключевых точек#
У хороших ключевых точек есть три свойства: однозначность (два разметчика помещают точку в одно и то же место), стабильность (точка присутствует во всех возможных позах объекта) и практическая польза (её использует последующий расчёт). Рассмотрим погрузчик, снятый потолочной камерой, когда нужно определить, подняты ли вилы во время движения машины:
| Индекс | Ключевая точка | Зачем она нужна | Парная точка при отражении |
|---|---|---|---|
| 0 | left_fork_tip | Длина и направление вил | 1 |
| 1 | right_fork_tip | Длина и направление вил | 0 |
| 2 | mast_top | Высота вил относительно мачты | 2 (сама себе) |
| 3 | mast_base | Опорная точка на шасси | 3 (сама себе) |
Каждая точка нужна для расчёта: кончики вил показывают направление, а пара точек на мачте — высоту. Прежде чем добавить точку, проверь её по одним и тем же критериям:
Не добавляй точки «на всякий случай»: каждая увеличивает время разметки каждого экземпляра, а точки, которые никто не использует, лишь добавляют шум в метрику. Публичные датасеты показывают диапазон: Tiger-Pose использует 12 точек без значений видимости, Dog-Pose — 24, а Hand Keypoints — 21:


Создай YAML-файл датасета#
path: forklift-pose
train: images/train
val: images/val
kpt_shape: [4, 3] # 4 keypoints, each x, y, visibility
flip_idx: [1, 0, 2, 3] # fork tips swap, mast points stay
kpt_oks_sigmas: [0.05, 0.05, 0.08, 0.08] # tips are sharp, mast ends are fuzzier
names:
0: forklift
kpt_names:
0:
- left_fork_tip
- right_fork_tip
- mast_top
- mast_basekpt_namesзадаёт порядок точек для людей и инструментов отдельно для каждого класса.flip_idxдолжен быть сам себе обратным: после двух применений каждый индекс должен вернуться к исходному значению.kpt_oks_sigmas— необязательный параметр, который задаёт веса потерь ключевых точек при обучении и OKS при валидации; если он указан, в нём должно быть по одному положительному значению на ключевую точку, иначе запуск остановится сValueError.
На Ultralytics Platform та же схема используется как шаблон скелета. Новый шаблон можно создать на основе существующего (Start from…), чтобы переименовать и удалить ненужные точки вместо размещения каждой ключевой точки вручную. Сохрани порядок индексов из таблицы схемы:

Проверь метки перед обучением#
Ошибки в датасетах для оценки позы могут оставаться незамеченными: если в строке не хватает одной ключевой точки, все последующие значения смещаются, а значение видимости 3 из-за ошибки экспортёра обрабатывается как обычное число. Эта проверка загружает YAML через собственный загрузчик Ultralytics и проверяет каждую строку. Скрипт сразу работает с COCO8-pose; замени путь на путь к своему YAML-файлу:
from pathlib import Path
from ultralytics.data.utils import check_det_dataset
data = check_det_dataset("coco8-pose.yaml") # resolves paths, downloads if needed
k, dims = data["kpt_shape"]
flip = data.get("flip_idx", [])
assert not flip or sorted(flip) == list(range(k)), "flip_idx must list every keypoint index once"
assert all(flip[flip[i]] == i for i in range(len(flip))), "flip_idx must undo itself: pairs swap, center points stay"
expected = 5 + k * dims # class + 4 box values + k keypoints of `dims` values
bad = 0
for label in Path(data["path"], "labels").rglob("*.txt"):
for n, row in enumerate(label.read_text().splitlines(), 1):
values = row.split()
if len(values) != expected:
bad += 1
print(f"{label.name}:{n} has {len(values)} values, expected {expected}")
elif dims == 3 and not {float(v) for v in values[7::3]} <= {0, 1, 2}:
bad += 1
print(f"{label.name}:{n} has a visibility flag outside 0/1/2")
print(f"kpt_shape={data['kpt_shape']}, flip_idx ok, {bad} bad rows")kpt_shape=[17, 3], flip_idx ok, 0 bad rowsvalues[7::3] начинается с первого флага видимости (класс, четыре значения рамки, затем x y v) и берёт каждое третье значение после него.
Как OKS оценивает ключевую точку#
В mAP для оценки позы вместо IoU рамок используется сходство ключевых точек объекта (OKS). Для каждой размеченной ключевой точки OKS преобразует расстояние в пикселях между предсказанием и меткой в оценку от 0 до 1, учитывая размер объекта и сигму этой ключевой точки, а затем усредняет оценки по размеченным точкам. Pose mAP50-95 — это средняя точность при порогах OKS от 0.50 до 0.95, как и mAP для рамок усредняется по порогам IoU; общий механизм описан в руководстве по метрикам производительности.
Проще всего понять сигмы на измерениях. Этот скрипт оценивает одно и то же предсказание погрузчика с двумя сигмами, используя функцию kpt_iou, которую вызывает валидатор оценки позы:
import torch
from ultralytics.utils.metrics import kpt_iou
# One labeled forklift, 4 keypoints (x, y, visibility) in pixels, inside a 200 x 120 px box
label = torch.tensor([[[40.0, 30, 2], [40, 90, 2], [170, 20, 2], [170, 100, 2]]])
area = torch.tensor([200 * 120 * 0.53]) # Ultralytics scales box area by 0.53 to approximate object area
for offset in (2, 5, 10, 20):
pred = label.clone()
pred[..., 0] += offset # every predicted keypoint lands `offset` pixels to the right
sharp = kpt_iou(label, pred, area, sigma=[0.025] * 4).item()
loose = kpt_iou(label, pred, area, sigma=[0.10] * 4).item()
print(f"{offset:2d} px off: OKS {sharp:.2f} with sigma 0.025, {loose:.2f} with sigma 0.10") 2 px off: OKS 0.94 with sigma 0.025, 1.00 with sigma 0.10
5 px off: OKS 0.67 with sigma 0.025, 0.98 with sigma 0.10
10 px off: OKS 0.21 with sigma 0.025, 0.91 with sigma 0.10
20 px off: OKS 0.00 with sigma 0.025, 0.67 with sigma 0.10Для погрузчика размером 200 × 120 пикселей предсказание с ошибкой 10 пикселей не проходит порог OKS 0.50 при сигме 0.025, но проходит все пороги вплоть до 0.90 при сигме 0.10. Одна модель, одна ошибка — но значения mAP для позы сильно различаются.
| Сигма | Эффект | Применение |
|---|---|---|
| Малая (≈ 0.025–0.05) | Даже ошибка в несколько пикселей уже снижает оценку | Чётко различимые точки: глаза, кончики вил, углы |
| Большая (≈ 0.08–0.10) | Широкий допуск | Неоднозначные точки: бёдра, середина мачты |
| По умолчанию | Сигмы COCO для kpt_shape: [17, 3]; в противном случае для каждой точки используется значение 1/K | Начни с этого значения, затем измерь |
Сигмы должны отражать, насколько согласованно разметчики располагают каждую точку. Измерь это: попроси двух человек разметить одни и те же 50 изображений и сравни расстояния для каждой ключевой точки. Для точек, по которым разметчики расходятся во мнениях, нужны более крупные сигмы; иначе метрика будет штрафовать модель за ошибки разметки.
Изменение kpt_oks_sigmas меняет pose mAP, даже если модель остаётся прежней, как показывает таблица выше. Задай значения сигм до первого базового замера, записывай их с каждым результатом и никогда не сравнивай запуски, проверенные с разными значениями.
Показать решение
Пример посылки на конвейере, снятой сверху, когда нужно определить, обращена ли этикетка вверх и в какую сторону она повёрнута:
| Индекс | Ключевая точка | Назначение | Парная точка при отражении | Сигма |
|---|---|---|---|---|
| 0 | label_top_left | Ориентация этикетки | 1 | 0.04 |
| 1 | label_top_right | Ориентация этикетки | 0 | 0.04 |
| 2 | label_bottom_right | Ориентация этикетки | 3 | 0.04 |
| 3 | label_bottom_left | Ориентация этикетки | 2 | 0.04 |
kpt_shape: [4, 3]
flip_idx: [1, 0, 3, 2]
kpt_oks_sigmas: [0.04, 0.04, 0.04, 0.04]flip_idx проходит проверку на саму себя как обратное преобразование: 0 → 1 → 0 и 2 → 3 → 2. При отражении метки левая и правая стороны меняются местами, а верхняя и нижняя остаются на месте, поэтому верхняя левая точка образует пару с верхней правой, а не с нижней правой.
Чтобы определить допуск, задай sigma=[0.04] * 4 в oks.py и увеличивай смещения; масштаб задаётся рамкой этикетки, поэтому используй рамку размером с типичную этикетку на посылках в твоих изображениях. Смещение, при котором OKS впервые падает ниже 0.5, — это максимальная ошибка, которую допускает точность разметки.