Skip to content
Туториалы7 мин чтения

Как рисовать ограничивающие рамки для детекции объектов

Соберите задачу детекции объектов в Potato, от стартовой конфигурации с тремя метками до боевой конфигурации для видео с дорог, и экспортируйте разметку в COCO или YOLO.

Potato Team

В этом руководстве мы собираем в Potato задачу разметки для детекции объектов: начинаем с конфигурации из трёх меток и заканчиваем экспортом в COCO или YOLO, на котором обучается детектор. Полный список инструментов для изображений и их параметров есть в документации по разметке изображений.

Файл данных

Каждая строка файла данных описывает одно изображение. Potato нужны идентификатор и поле с URL или путём к изображению, а item_properties указывает, какое поле за что отвечает:

json
{"id": "img_001", "image_url": "https://example.com/street_001.jpg"}
{"id": "img_002", "image_url": "/static/images/street_002.jpg"}

Относительный путь разрешается от media_directory, поэтому для локальных изображений не нужен запущенный файловый сервер.

Минимальная конфигурация

За область рисования отвечает схема image_annotation. Ей нужны имя, описание, которое разметчики читают как инструкцию, список включённых инструментов и набор меток:

yaml
annotation_task_name: "Object Detection Annotation"
task_dir: "."
output_annotation_dir: "annotation_output/"
 
data_files:
  - "data/images.json"
 
item_properties:
  id_key: id
  text_key: image_url
 
annotation_schemes:
  - annotation_type: image_annotation
    name: objects
    description: "Draw a box around every car, person, and bicycle"
    tools:
      - bbox
    labels:
      - name: car
        color: "#FF6B6B"
      - name: person
        color: "#4ECDC4"
      - name: bicycle
        color: "#45B7D1"

Три метки выбраны для старта намеренно. Каждая добавленная метка означает решение, которое разметчик должен принимать для каждого объекта, и пограничный случай, о котором его нужно заранее предупредить. Поэтому начните с классов, которые действительно нужны модели, а остальные добавляйте, когда инструкция устоится.

Запустите сервер и откройте задачу:

bash
potato start config.yaml -p 8000

Что делает разметчик

Разметчик выбирает метку, затем протягивает прямоугольник вокруг каждого объекта и подвигает углы, пока рамка не прилегает плотно:

Интерфейс разметки ограничивающими рамками для детекции объектовИнструмент ограничивающих рамок позволяет разметчикам рисовать и подписывать прямоугольные области на изображениях

Масштабирование и панорамирование включены по умолчанию, и это важно для мелких или удалённых объектов. Если изображения однородны и лишние элементы управления только мешают, оба можно отключить через zoom_enabled: false и pan_enabled: false.

Горячие клавиши меток

В профиле клавиш по умолчанию v7 инструменты привязаны к буквам: r выбирает рамку, p полигон, b кисть. Горячие клавиши задаются для каждой метки через key_value, и безопаснее всего использовать цифры, потому что метка, чей key_value совпадает с клавишей инструмента, запускает оба действия сразу:

yaml
annotation_schemes:
  - annotation_type: image_annotation
    name: objects
    description: "Annotate all visible objects"
    tools:
      - bbox
    labels:
      - name: person
        color: "#FF6B6B"
        key_value: "1"
 
      - name: car
        color: "#4ECDC4"
        key_value: "2"
 
      - name: motorcycle
        color: "#45B7D1"
        key_value: "3"
 
      - name: bicycle
        color: "#96CEB4"
        key_value: "4"
 
      - name: traffic_light
        color: "#FFEAA7"
        key_value: "5"
 
      - name: stop_sign
        color: "#DDA0DD"
        key_value: "6"

Цвета стоит подбирать, а не соглашаться с первыми попавшимися. В плотных дорожных сценах разметчик просматривает кадр в поисках пропущенного, и два похожих цвета у соседних классов замедляют этот просмотр. Установите keybinding_profile: legacy, если ваши разметчики привыкли к клавишам Potato до версии 2.8 и вы не хотите их переучивать.

Добавление атрибутов объектов

В Potato нет блока атрибутов для отдельной рамки. У метки image_annotation читаются name, color и key_value, а всё остальное в словаре метки отбрасывается. Поэтому вложенный список attributes: ничего не делает, и после того как рамка нарисована, никакой запрос не появляется.

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

yaml
annotation_schemes:
  - annotation_type: image_annotation
    name: objects
    description: "Draw a box around each object"
    tools:
      - bbox
    labels:
      - name: person
        color: "#FF6B6B"
      - name: person_occluded
        color: "#C44536"

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

yaml
  - annotation_type: multiselect
    name: image_conditions
    description: "Conditions affecting this image"
    labels:
      - Heavy occlusion
      - Truncated objects
      - Low light

Атрибуты сохраняются только при импорте из VIA: via_importer читает region_attributes каждой области и встраивает их в метку. Это понятие времени импорта, а не конфигурации.

Минимум одна рамка

Изображение без рамок неоднозначно: разметчик мог ничего не найти, а мог пропустить элемент. min_annotations снимает эту неоднозначность, отклоняя отправку, в которой фигур меньше порога, поэтому для действительно пустого изображения нужна явная отрицательная метка:

yaml
annotation_schemes:
  - annotation_type: image_annotation
    name: objects
    description: "Draw a box around every traffic participant"
    tools:
      - bbox
    labels: [...]
    min_annotations: 1

Размер отображаемого изображения

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

yaml
instance_display:
  fields:
    - key: image_url
      type: image
      label: "Frame"
      display_options:
        max_width: 1200
        max_height: 800
        zoomable: true

Боевая конфигурация

Конфигурация для дорожных кадров ниже содержит пять классов, которые нужны датасету для детекции. У меток остаются цифровые клавиши, чтобы левая рука разметчика не уходила с цифрового ряда, а min_annotations: 1 оправдан, потому что в каждом кадре этого корпуса есть хотя бы один участник дорожного движения:

yaml
annotation_task_name: "Autonomous Driving - Object Detection"
task_dir: "."
output_annotation_dir: "annotation_output/"
 
data_files:
  - "data/driving_frames.json"
 
item_properties:
  id_key: id
  text_key: frame_path
 
annotation_schemes:
  - annotation_type: image_annotation
    name: objects
    description: "Annotate all traffic participants and objects"
    tools:
      - bbox
    min_annotations: 1
    labels:
      - name: vehicle
        color: "#FF6B6B"
        key_value: "1"
 
      - name: pedestrian
        color: "#4ECDC4"
        key_value: "2"
 
      - name: cyclist
        color: "#45B7D1"
        key_value: "3"
 
      - name: traffic_sign
        color: "#FFEAA7"
        key_value: "4"
 
      - name: traffic_light
        color: "#DDA0DD"
        key_value: "5"

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

Как выглядит разметка

Potato хранит все фигуры одного изображения в плоском JSON-массиве, каким бы инструментом они ни были нарисованы. Координаты нормированы к диапазону от 0 до 1 относительно изображения, поэтому разметка остаётся верной, даже если позже изображение отдаётся в другом размере:

json
[
  {
    "type": "bbox",
    "label": "vehicle",
    "color": "#FF6B6B",
    "coordinates": {"x": 0.234, "y": 0.352, "width": 0.120, "height": 0.130}
  },
  {
    "type": "bbox",
    "label": "pedestrian",
    "color": "#4ECDC4",
    "coordinates": {"x": 0.427, "y": 0.370, "width": 0.026, "height": 0.139}
  }
]

bbox хранит свою геометрию в coordinates в виде {x, y, width, height}. Полигоны хранят в том же ключе список точек, а маски вместо этого хранят кодирование длин серий (run-length encoding) в rle.

Экспорт для обучения

Код обучения детекторов читает COCO или YOLO, и Potato пишет оба формата:

bash
python -m potato.export --config config.yaml --format coco --output ./export/
python -m potato.export --config config.yaml --format yolo --output ./export/

Эти два экспорта различаются не только синтаксисом. COCO пишет один файл annotations.json с рамками в пикселях в виде [x, y, width, height] и не копирует изображения, так что значения file_name указывают туда, где изображения уже лежат. YOLO пишет по одному текстовому файлу на изображение с нормированными координатами центра, раскладывает изображения и метки по каталогам train/ и val/ и добавляет data.yaml и classes.txt, чтобы запуск обучения мог прочитать список классов без отдельного указания.

Как сохранить единообразие рамок

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

Что дальше

Остальные параметры изображений описаны в разделе Разметка изображений.