Ultralytics YOLO27: Встать в лист ожидания.
Get Started
Основы компьютерного зрения·Наборы данных и разметка·Урок 7/10
Урокначинающий

Качество аннотаций

Разберись, что содержит метка YOLO, проверяй файлы меток на повреждённые строки и поддерживай единообразие аннотаторов с помощью инструкций и измеряемого раунда калибровки.

Модель, обученная на непоследовательных метках, усваивает эту непоследовательность. Пятьдесят тысяч рамок, размеченных по одному ясному правилу, лучше полумиллиона рамок с двадцатью индивидуальными трактовками, а повторная разметка почти всегда обходится дешевле, чем более крупная модель. В этом уроке ты получишь линтер меток, одностраничные инструкции и способ измерить согласованность аннотаторов.

Результат

Проверь линтером файлы меток набора данных, составь одностраничные инструкции для одного класса и измерь согласованность двух аннотаторов.

Ускоренное прохождение
Если ты уже освоился, вот краткая версия.
  1. Разберись, какой формат меток нужен для твоей задачи: рамки, полигоны, ключевые точки или углы.

  2. Проверь линтером каждый файл меток: количество столбцов, идентификаторы классов, координаты в диапазоне 0–1, дублирующиеся рамки.

  3. Одностраничные инструкции с примером изображения для каждого правила и списком объектов, которые не нужно размечать.

  4. Сначала проведи раунд калибровки: все размечают одни и те же 50 изображений; масштабируй процесс, только когда согласованность достигнет нужного уровня.

  5. Рецензенты отклоняют разметку с указанием причины; они никогда не исправляют её молча.

Практические занятия

Что такое метка на самом деле#

В формате YOLO для каждого изображения есть соответствующий файл .txt с одной строкой на каждый объект. Координаты нормализованы относительно ширины и высоты изображения до диапазона 0–1, поэтому метки сохраняются при изменении размера. Структура строки зависит от задачи:

ЗадачаОдна меткаПримечания
Детекцияclass x_center y_center width heightЦентр и размер рамки
Сегментация экземпляровclass x1 y1 x2 y2 ... xn ynКонтур полигона
Оценка позыclass x y w h px1 py1 v1 ... pxn pyn vnРамка, затем ключевые точки с необязательным признаком видимости
OBBclass x1 y1 x2 y2 x3 y3 x4 y4Четыре угла повёрнутой рамки
КлассификацияФайл меток не нуженИзображения распределены по отдельным папкам для каждого класса
Семантическая сегментацияPNG-маска для каждого изображенияЗначение каждого пикселя — индекс класса
Оценка глубиныPNG- или NPY-файл глубины для каждого изображенияЗначение каждого пикселя — расстояние

Индексы классов начинаются с 0 и сопоставляются с названиями в YAML-файле набора данных. Для изображения без объектов файл меток не нужен — так работают фоновые изображения. На страницах о формате детекции, семантической сегментации и глубине приведены рабочие примеры.

Проверь линтером файлы меток#

Повреждённые строки могут приводить к ошибкам без предупреждения: координата больше 1, идентификатор класса, не определённый в YAML, или одна и та же рамка, нарисованная для объекта дважды, — всё это становится шумом, который модель пытается усвоить. Короткий скрипт-линтер выявит такие ошибки до обучения:

from itertools import combinations
from pathlib import Path

from ultralytics.data.utils import check_det_dataset

def iou(a, b):
    """IoU of two normalized (x_center, y_center, width, height) boxes."""
    ax1, ay1, ax2, ay2 = a[0] - a[2] / 2, a[1] - a[3] / 2, a[0] + a[2] / 2, a[1] + a[3] / 2
    bx1, by1, bx2, by2 = b[0] - b[2] / 2, b[1] - b[3] / 2, b[0] + b[2] / 2, b[1] + b[3] / 2
    inter = max(0, min(ax2, bx2) - max(ax1, bx1)) * max(0, min(ay2, by2) - max(ay1, by1))
    return inter / (a[2] * a[3] + b[2] * b[3] - inter)

def check_labels(text, names):
    """Return a list of problems in one YOLO detection label file."""
    problems, boxes = [], []
    for i, row in enumerate(text.split("\n"), 1):
        values = row.split()
        if not values:
            continue
        if len(values) != 5:
            problems.append(f"row {i}: {len(values)} values, expected 5")
        elif int(values[0]) not in names:
            problems.append(f"row {i}: unknown class {values[0]}")
        elif not all(0 <= float(v) <= 1 for v in values[1:]):
            problems.append(f"row {i}: coordinates outside 0-1")
        else:
            boxes.append((i, int(values[0]), [float(v) for v in values[1:]]))
    for (i, ca, a), (j, cb, b) in combinations(boxes, 2):
        if ca == cb and iou(a, b) > 0.8:
            problems.append(f"row {j}: duplicate of row {i} (IoU {iou(a, b):.2f})")
    return problems

broken = "0 0.50 0.50 0.20 0.30\n0 0.51 0.50 0.20 0.30\n3 0.40 0.40 0.10 0.10\n0 1.20 0.50 0.10 0.10\n0 0.50 0.50 0.10"
print("\n".join(check_labels(broken, {0: "pill"})))

data = check_det_dataset("medical-pills.yaml")
files = sorted(Path(data["path"], "labels").rglob("*.txt"))
found = sum(len(check_labels(f.read_text(), data["names"])) for f in files)
print(f"medical-pills: {found} problems in {len(files)} label files")
row 3: unknown class 3
row 4: coordinates outside 0-1
row 5: 4 values, expected 5
row 2: duplicate of row 1 (IoU 0.90)
medical-pills: 0 problems in 115 label files

Самодельный пример показывает все ошибки, которые обнаруживает линтер; реальный набор данных проходит проверку без ошибок. Запускай линтер после каждой партии разметки и перед каждым запуском обучения — это занимает считаные секунды.

Почему разметка становится непоследовательной#

Редактор аннотаций Ultralytics Platform в полноэкранном режиме: картонные коробки на складских роботах обведены полигонами, рядом открыт список классов

Посмотри на панель классов в этом редакторе: 14 полигонов коробок сохранены под классом Grapes, а класс Box находится ниже в списке — кроме того, plane и Plane существуют как два отдельных класса. Линтер не выявит ни одну из этих проблем: каждая строка формально корректна. Но рецензент, сверяющий метки с инструкциями, заметит обе проблемы за считаные секунды. Большинство ошибок разметки именно такие: файлы корректны, но решения неверны.

Аннотаторы редко размечают плохо из-за небрежности. Разметка становится непоследовательной, потому что правила неоднозначны и каждый делает разумный, но отличный от остальных выбор:

  • Где должна заканчиваться рамка? Включать ли тень, переносимую сумку или груз на вилах погрузчика?
  • Обрезанные объекты. Если человек наполовину за пределами кадра, это всё ещё person? Какая часть объекта должна быть видна?
  • Окклюзия. Если видна только часть машины — например, одно колесо, — её размечать или пропускать?
  • Границы классов. Пикап — это car или truck? Самокат — это bicycle или other?
  • Случаи, которые не нужно размечать. Отражения, плакаты и экраны, на которых изображён объект.

Любой вариант подходит, если все выбирают одинаково.

Одностраничные инструкции#

Они умещаются на одной странице, содержат пример изображения для каждого правила и в первую очередь описывают неоднозначные случаи:

## Class: forklift

Include:
- Forklifts with at least 30% of the body visible, moving or parked, any size or color.

Do not label:
- Pallet jacks (no overhead mast) → class `pallet_jack`.
- Reflections in glass or polished floors, and forklifts on posters or screens.

Box rules:
- Include the mast and the full visible body.
- Exclude the load (pallets, boxes) — those have their own classes.
- For partly occluded forklifts, box the visible part only.

Измерь согласованность в раунде калибровки#

Прежде чем перейти от одного аннотатора к двадцати, попроси всех разметить одни и те же 50 изображений, а затем сравни результаты:

Согласованность — это доля объектов, для которых оба аннотатора указали один класс и достигли IoU не менее 0.5; это практический вариант согласованности между экспертами:

def iou(a, b):
    """IoU of two (x1, y1, x2, y2) pixel boxes."""
    w = max(0, min(a[2], b[2]) - max(a[0], b[0]))
    h = max(0, min(a[3], b[3]) - max(a[1], b[1]))
    inter = w * h
    return inter / ((a[2] - a[0]) * (a[3] - a[1]) + (b[2] - b[0]) * (b[3] - b[1]) - inter)

def agreement(boxes_a, boxes_b, threshold=0.5):
    """Share of objects both annotators drew with the same class and IoU >= threshold."""
    unmatched, matched = list(boxes_b), 0
    for cls_a, box_a in boxes_a:
        best = max(unmatched, key=lambda b: iou(box_a, b[1]) if b[0] == cls_a else -1, default=None)
        if best and best[0] == cls_a and iou(box_a, best[1]) >= threshold:
            unmatched.remove(best)
            matched += 1
    return matched / max(len(boxes_a), len(boxes_b))

# Two annotators label the same calibration image
alice = [("forklift", (100, 200, 400, 600)), ("person", (450, 220, 520, 480)), ("person", (600, 230, 660, 470))]
bob = [("forklift", (100, 120, 400, 600)), ("person", (452, 218, 522, 478)), ("pallet_jack", (600, 230, 660, 470))]
print(f"agreement: {agreement(alice, bob):.0%}")
agreement: 67%

За показателем 67% скрываются два вида расхождений. Рамка Боба вокруг погрузчика начинается на 80 пикселей выше — он включил верхушку мачты, — но всё равно совпадает с рамкой Алисы по IoU 0.83, поэтому это считается согласованностью; если продукт измеряет высоту, уточни правило построения рамок. Третий объект — реальное расхождение: Алиса указывает person, а Боб — pallet_jack. Для этого нужны более ясное правило и пример изображения, после чего следует провести ещё один раунд. Сохрани эти 50 калибровочных изображений: они станут проверочным заданием для каждого нового аннотатора.

Проверяющие отклоняют разметку, а не исправляют её молча

Проверяющий, который незаметно исправляет метки, разрывает цикл обратной связи, и разметчики так и не узнают, в чём ошиблись. Отклоняй разметку с указанием причины и дай разметчику исправить её. На первых ста изображениях это медленнее, зато потом намного быстрее.

Что отслеживать#

МетрикаЧто она показывает
Производительность (объектов в час)Объём и стоимость работы
Доля отклонённых разметокУхудшается ли качество
Согласованность на общем наборе с пересекающимися даннымиПрименяют ли аннотаторы по-прежнему одни и те же правила

Когда согласованность снижается, значит, что-то изменилось: появился новый аннотатор или камера, партия оказалась неоднозначной либо накопилась усталость. Если позже модель ошибается при определённых условиях, отправь именно эти изображения на повторную разметку — этот цикл автоматизирует активное обучение, которое даёт большую часть реального прироста точности. Просматривай и фильтруй ошибки в своих наборах данных на Ultralytics Platform, а о масштабировании процесса читай в материалах Контроль качества наборов данных и Лучшие практики аннотирования.

Попробовать

Запусти линтер для папки с метками своего набора данных. Затем составь одностраничные инструкции для самого неоднозначного класса: добавь пример изображения для каждого правила и список объектов, которые не нужно размечать. Передай инструкции и 10 неразмеченных изображений коллеге, ничего не объясняя; сам разметь те же изображения и вычисли согласованность с помощью скрипта. Запиши оценку и все правила, которые ты изменил из-за расхождений.

Критерии готовности
Урок считается завершённым, если выполнены все условия ниже.
  • Линтер не обнаружил проблем в твоих метках или ты исправил каждую найденную проблему.

  • У тебя есть одностраничные инструкции с примером для каждого правила и списком объектов, которые не нужно размечать.

  • У тебя есть оценка согласованности двух аннотаторов и список правил, изменённых по итогам проверки.

Показать решение

Инструкции по разметке класса pill для medical-pills, составленные после раунда калибровки на 10 изображениях:

## Class: pill

Include:
- Every tablet inside a blister cavity, including half-visible ones at the image edge.
- Tablets seen through clear plastic, even when glare covers part of them.

Do not label:
- Empty cavities, torn foil, or tablets outside the pack.
- Reflections of tablets in the foil.

Box rules:
- Box the tablet itself, not the plastic bubble around it.
- If two tablets touch, draw two boxes.

Как интерпретировать оценку согласованности: допустим, в первом раунде результат — 82%. Отсортируй несопоставленные объекты по причинам расхождений. Для блистерных упаковок обычно встречаются две причины: рамки обведены вокруг пластикового блистера, а не таблетки (исправляется первым правилом построения рамок с примером изображения), и один из аннотаторов пропустил таблетки, скрытые бликами (исправляется вторым правилом включения объектов). Повтори проверку на 10 новых изображениях; масштабируй процесс, только когда оценка превысит порог 90%.

Что дальше

Далее: разделение размеченных данных, чтобы метрики оставались достоверными, — начнём с утечки, скрытой прямо в этом наборе данных.

Войди, чтобы сохранять прогресс