Computer Vision Engineer: от детекции и Doc-AI до продакшнаДанные и разметкаСбор данных и схема классов

Сбор данных и схема классов

Уроки курсаСбор данных и схема классов

Форматы: YOLO/COCO, data.yaml, class-map и алиасы

Для обучения детектора нужны изображения и аннотации — координаты bbox и класс объекта. Два популярных формата: YOLO (текстовые файлы, простой) и COCO (JSON, подробный). data.yaml описывает датасет: пути к train/val/test, число классов, их имена. Class-map связывает индекс класса с названием (0 → car, 1 → person). Алиасы позволяют использовать синонимы (automobile → car).

Ключевые идеи

  • YOLO: текстовые файлы, каждая строка — один объект. Координаты в относительных единицах (0-1).
  • COCO: JSON с полной информацией (изображения, аннотации, категории). Используется для бенчмарков.
  • data.yaml: конфиг датасета (пути, число классов, имена). Нужен для обучения.
  • Class-map: индекс → имя класса. Предотвращает ошибки при разметке.

Формат YOLO

YOLO использует текстовые файлы `.txt` — один файл на изображение. Имя файла совпадает с изображением: `image001.jpg` → `image001.txt`.

Формат строки:

<class_id> <x_center> <y_center> <width> <height>

Координаты нормализованы (0-1): делим на ширину/высоту изображения.

Пример: изображение 640×480, объект класса 0 (car) с bbox [100, 150, 200, 100] (x, y, w, h в пикселях).

# Нормализация
x_center = (100 + 200/2) / 640 = 0.3125
y_center = (150 + 100/2) / 480 = 0.4167
width = 200 / 640 = 0.3125
height = 100 / 480 = 0.2083

# Результат в image001.txt
0 0.3125 0.4167 0.3125 0.2083

Если на изображении несколько объектов — несколько строк:

0 0.3125 0.4167 0.3125 0.2083
1 0.7500 0.2500 0.1500 0.2000

Структура папок:

dataset/
├── images/
│   ├── train/
│   │   ├── image001.jpg
│   │   └── image002.jpg
│   └── val/
│       └── image003.jpg
└── labels/
    ├── train/
    │   ├── image001.txt
    │   └── image002.txt
    └── val/
        └── image003.txt

Формат COCO

COCO использует один JSON-файл для всех аннотаций. Подробнее, чем YOLO, но сложнее редактировать вручную.

Структура:

{
  "images": [
    {
      "id": 1,
      "file_name": "image001.jpg",
      "width": 640,
      "height": 480
    }
  ],
  "annotations": [
    {
      "id": 1,
      "image_id": 1,
      "category_id": 1,
      "bbox": [100, 150, 200, 100],  # [x, y, width, height] в пикселях
      "area": 20000,
      "iscrowd": 0  # 0 — отдельный объект, 1 — группа
    }
  ],
  "categories": [
    {
      "id": 1,
      "name": "car",
      "supercategory": "vehicle"
    },
    {
      "id": 2,
      "name": "person",
      "supercategory": "human"
    }
  ]
}

bbox в COCO: `[x_top_left, y_top_left, width, height]` в пикселях (не нормализованы, в отличие от YOLO).

COCO используется для официальных бенчмарков, YOLO — для быстрого обучения и inference.

Файл data.yaml

data.yaml описывает датасет для обучения. Используется в YOLO, Ultralytics, многих фреймворках.

Пример:

# Пути к данным (относительно data.yaml или абсолютные)
train: data/images/train
val: data/images/val
test: data/images/test  # опционально

# Число классов
nc: 3

# Имена классов (порядок важен: индекс 0 → первое имя)
names: ['car', 'person', 'dog']

Обучение с этим конфигом:

python train.py --data data.yaml --epochs 50

Если пути относительные, они разрешаются относительно расположения data.yaml. Лучше использовать относительные пути — проект переносимый.

Class-map и алиасы

Class-map — явное сопоставление индекс → имя класса. Предотвращает ошибки, когда индексы перепутаны.

Пример class_map.yaml:

0: car
1: person
2: dog

Используется при конвертации форматов или валидации аннотаций.

Алиасы — синонимы классов. Полезны, если разметчики использовали разные названия для одного объекта.

Пример с алиасами:

nc: 3
names: ['car', 'person', 'dog']

# Алиасы: разные названия → один класс
aliases:
  automobile: car
  vehicle: car
  human: person
  pedestrian: person
  canine: dog

При загрузке данных алиасы автоматически заменяются основным именем: `automobile` → `car`.

Конвертация форматов

Часто нужно конвертировать COCO → YOLO или наоборот.

Пример COCO → YOLO:

import json
from pathlib import Path

def coco_to_yolo(coco_json_path, output_dir):
    with open(coco_json_path) as f:
        coco = json.load(f)
    
    # Создаём mapping image_id → file_name, width, height
    images = {img['id']: img for img in coco['images']}
    
    output_dir = Path(output_dir)
    output_dir.mkdir(parents=True, exist_ok=True)
    
    for ann in coco['annotations']:
        img = images[ann['image_id']]
        img_w, img_h = img['width'], img['height']
        
        # bbox в COCO: [x, y, w, h] в пикселях
        x, y, w, h = ann['bbox']
        
        # Конвертируем в YOLO: center, normalized
        x_center = (x + w / 2) / img_w
        y_center = (y + h / 2) / img_h
        width = w / img_w
        height = h / img_h
        
        class_id = ann['category_id'] - 1  # COCO id с 1, YOLO с 0
        
        # Сохраняем в txt
        txt_path = output_dir / f"{Path(img['file_name']).stem}.txt"
        with open(txt_path, 'a') as f:
            f.write(f"{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\n")

coco_to_yolo('annotations.json', 'labels/train/')

Валидация аннотаций

Проверяем, что аннотации корректны:

from pathlib import Path

def validate_yolo_annotations(labels_dir, num_classes):
    errors = []
    
    for txt_path in Path(labels_dir).glob('*.txt'):
        with open(txt_path) as f:
            for line_num, line in enumerate(f, 1):
                parts = line.strip().split()
                
                if len(parts) != 5:
                    errors.append(f"{txt_path.name}:{line_num} - неверное число значений")
                    continue
                
                class_id, x, y, w, h = map(float, parts)
                
                # Проверки
                if not (0 <= class_id < num_classes):
                    errors.append(f"{txt_path.name}:{line_num} - class_id вне диапазона")
                
                if not (0 <= x <= 1 and 0 <= y <= 1 and 0 < w <= 1 and 0 < h <= 1):
                    errors.append(f"{txt_path.name}:{line_num} - координаты вне диапазона [0,1]")
    
    return errors

errors = validate_yolo_annotations('labels/train/', num_classes=3)
if errors:
    print("Найдены ошибки:")
    for err in errors[:10]:  # Показываем первые 10
        print(f"  {err}")

Частые проблемы

  1. Координаты вне [0,1] в YOLO: забыли нормализовать. Модель выдаст некорректные bbox.
  2. Несовпадение имён файлов: `image001.jpg` есть, а `image001.txt` нет. Модель пропустит изображение.
  3. Неверный class_id: в data.yaml 3 класса, в аннотации class_id=5. Обучение упадёт с ошибкой.
  4. Разные форматы bbox: перепутали YOLO (center) и COCO (top-left). Bbox окажутся не там.
  5. Пустые аннотации: если на изображении нет объектов, создайте пустой .txt файл (не удаляйте).

Связанные понятия

  • Pascal VOC: формат XML, старый стандарт. Сейчас реже используется.
  • Labelme: инструмент для разметки, экспортирует в JSON (похож на COCO).
  • CVAT: веб-инструмент для разметки, поддерживает YOLO, COCO, Pascal VOC.

Итоги

  • YOLO: текстовые файлы, координаты нормализованы (0-1), формат `class x_center y_center width height`.
  • COCO: JSON, координаты в пикселях, формат `[x, y, width, height]` (top-left).
  • data.yaml: пути к данным, число классов, имена классов.
  • Class-map: индекс → имя класса. Алиасы: синонимы для одного класса.
  • Валидация аннотаций обязательна — проверяем координаты, class_id, наличие файлов.

Дальше по курсу

В следующем шаге соберём датасет: 300 изображений (или используем демо-пак), разметим в CVAT, экспортируем в YOLO формат. Создадим data.yaml, проверим аннотации скриптом валидации, разобьём на train/val/test.