
Когда в пайплайне детекции всё вроде настроено правильно, а mAP упорно не растёт, проблема нередко оказывается не в модели и не в оптимизаторе, а в разметке после аугментации. Вы отразили изображение, сделали кроп, повернули кадр, а координаты боксов либо не сдвинулись, либо были интерпретированы в неправильном формате. Код не падает, массивы выглядят валидно, обучение идёт — но модель учится на мусоре.
Именно поэтому аугментация для детекции отличается от аугментации для классификации. Здесь недостаточно «поменять пиксели». Нужно, чтобы каждая пространственная трансформация двигала и изображение, и все ограничивающие боксы строго синхронно.
Если вы только входите в тему аугментаций, полезно сначала посмотреть два предыдущих материала:
Этот текст опирается на те идеи, но отвечает на более узкий и практический вопрос: как правильно применять аугментации, если ваши метки — это ограничивающие боксы.
В основе статьи лежит документация Albumentations, open-source библиотеки для аугментации изображений с 15k+ звёзд на GitHub и 140M+ загрузок.
Содержание
Форматы ограничивающих боксов
Сборка пайплайна для детекции
Передача меток и метаданных
Что делает
A.BboxParamsСтратегии кадрирования
Типичные ошибки
Что почитать дальше
Форматы ограничивающих боксов
Разные датасеты и фреймворки используют разные соглашения о координатах. Albumentations поддерживает пять форматов. В A.BboxParams нужно передать тот, который уже использует ваша разметка, через параметр coord_format.
Формат | Координаты | Значения | Где встречается |
|---|---|---|---|
|
| Пиксели | PASCAL VOC, многие кастомные датасеты |
|
| Нормализованные значения | Внутренний формат Albumentations |
|
| Пиксели | COCO |
|
| Нормализованные значения | Ultralytics YOLO, Darknet |
|
| Пиксели | Формат как у YOLO, но без нормализации |
Если вы приходите из Ultralytics (YOLOv5/YOLOv8/YOLOv11), ваши аннотации уже лежат в формате yolo, значит в BboxParams нужно указывать coord_format='yolo'.
Для изображения размером 640 × 480 и бокса от пикселя (98, 345) до (420, 462) одни и те же координаты будут выглядеть так:

pascal_voc:[98, 345, 420, 462]— углы в пикселяхalbumentations:[0.153, 0.719, 0.656, 0.962]— те же углы, но нормализованные по размерам изображенияcoco:[98, 345, 322, 117]— верхний левый угол плюс ширина(420 - 98)и высота(462 - 345)yolo:[0.405, 0.841, 0.503, 0.244]— центр плюс размер, всё нормализованоcxcywh:[259, 403.5, 322, 117]— центр плюс размер в пикселях

Самая частая ошибка в работе с bbox — неверный coord_format. Числа всё равно будут выглядеть правдоподобно, пайплайн не выбросит исключение, но каждый ограничивающий бокс укажет не туда. Поэтому первое, что стоит перепроверить в любой задаче детекции, — какой именно формат отдаёт ваш инструмент разметки или исходный датасет.
Как собрать пайплайн для детекции
import albumentations as A import cv2 import numpy as np
Создайте A.Compose и передайте в него A.BboxParams, чтобы библиотека знала, как интерпретировать и обновлять ограничивающие боксы:
train_transform = A.Compose([ A.RandomCrop(width=450, height=450, p=1.0), A.HorizontalFlip(p=0.5), A.RandomBrightnessContrast(p=0.2), ], bbox_params=A.BboxParams( coord_format='coco', label_fields=['class_labels'], ), seed=137)
В одном пайплайне можно свободно смешивать разные типы трансформаций. Пиксельные трансформации вроде RandomBrightnessContrast меняют только изображение и не трогают боксы. Пространственные трансформации вроде HorizontalFlip обновляют и пиксели, и координаты боксов. За счёт этого результат остаётся согласованным: ограничивающие боксы всегда соответствуют аугментированному изображению.
Если хотите быстро проверить, какие трансформации вообще поддерживают bboxes, есть публичная таблица совместимости: Поддерживаемые типы таргетов по трансформациям.
Как применять пайплайн
Загрузите изображение и подготовьте боксы как массив NumPy формы (num_boxes, 4):
image = cv2.imread("image.jpg") image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) bboxes = np.array([ [23, 74, 295, 388], [377, 294, 252, 161], [333, 421, 49, 49], ], dtype=np.float32) class_labels = np.array(['dog', 'cat', 'sports ball'])
Дальше передайте всё в transform. Метки нужно подавать через именованные аргументы с теми же именами, которые указаны в label_fields:
result = train_transform(image=image, bboxes=bboxes, class_labels=class_labels) augmented_image = result['image'] augmented_bboxes = result['bboxes'] augmented_labels = result['class_labels']

На выходе боксов может оказаться меньше, чем было на входе. Это нормально: боксы, которые полностью ушли за пределы изображения или стали слишком маленькими после аугментации, Albumentations отфильтрует автоматически.
Как привязывать метаданные к bbox
Метки классов не обязательны. Можно передавать только координаты, вообще без дополнительной информации:
result = transform(image=image, bboxes=bboxes)
Но если к каждому боксу нужно привязать класс, идентификатор объекта, флаг сложности или что-то ещё, есть два нормальных способа это сделать.
Способ 1: отдельные поля через label_fields
Объявите имена полей в label_fields и передайте каждое отдельным аргументом. Значения могут быть и строками, и числами:

bbox_params = A.BboxParams( coord_format='pascal_voc', label_fields=['class_labels', 'difficult_flags'], ) result = transform( image=image, bboxes=bboxes, class_labels=['dog', 'cat', 'ball'], # строки difficult_flags=[0, 0, 1], # числа )
Таких полей можно объявить сколько угодно. Если в ходе аугментации какой-то ограничивающий бокс отфильтровывается, соответствующая запись удаляется из каждого поля автоматически. Синхронизацию руками поддерживать не нужно.
Этот механизм полезен не только для классов. Любую информацию, которую нужно сохранить вместе с боксом после кропов и фильтрации, можно передавать через label_fields.
Видео. Если вы складываете боксы из нескольких кадров в один массив bboxes, можно добавить поле frame_ids, чтобы отслеживать, из какого кадра пришёл каждый бокс:
bbox_params = A.BboxParams( coord_format='pascal_voc', label_fields=['class_labels', 'frame_ids'], ) bboxes = [[10, 20, 100, 200], [50, 60, 150, 250], [30, 40, 80, 180]] class_labels = ['car', 'car', 'person'] frame_ids = [0, 0, 1] result = transform(images=images, bboxes=bboxes, class_labels=class_labels, frame_ids=frame_ids)
После аугментации result['frame_ids'] покажет, какие боксы пережили фильтрацию и из каких кадров они пришли.
Instance segmentation. Если каждому боксу соответствует маска экземпляра, можно передать instance_ids, чтобы связь между маской и bbox не потерялась:
bbox_params = A.BboxParams( coord_format='pascal_voc', label_fields=['class_labels', 'instance_ids'], ) result = transform( image=image, bboxes=bboxes, class_labels=['person', 'person', 'car'], instance_ids=[0, 1, 2], ) # используйте result['instance_ids'], чтобы индексировать массив масок
Способ 2: упаковать метаданные прямо в массив bbox
Если вся метаинформация числовая, её можно просто добавить в массив боксов как дополнительные столбцы. Например, массив формы (num_boxes, 6) содержит 4 столбца координат и ещё 2 столбца метаданных:
bboxes = np.array([ [23, 74, 295, 388, 1, 17], # координаты + class_id + track_id [377, 294, 252, 161, 2, 23], ], dtype=np.float32) bbox_params = A.BboxParams(coord_format='coco') result = transform(image=image, bboxes=bboxes) # result['bboxes'] всё ещё имеет форму (n, 6) — дополнительные столбцы сохранены
Этот способ компактнее, но подходит только для числовых признаков. Если нужны строковые классы или доступ к полям по именам, label_fields удобнее.
Что реально управляет A.BboxParams
Именно A.BboxParams определяет, как Albumentations интерпретирует и фильтрует ограничивающие боксы:
coord_format— обязательный параметр. Один из'pascal_voc','albumentations','coco','yolo','cxcywh'.bbox_type—'hbb'для обычных осево-выровненных боксов с 4 координатами или'obb'для ориентированных боксов с углом. Для повернутых объектов есть отдельный разбор: Ориентированные ограничивающие боксы.label_fields— список имён аргументов, в которых лежат метки и другая информация, привязанная к каждому боксу.min_area— минимальная площадь бокса в пикселях после аугментации. Всё, что меньше, отбрасывается. По умолчанию0.0.min_visibility— минимальная доля исходной площади, которая должна остаться видимой после аугментации. По умолчанию0.0.min_width— минимальная ширина бокса в пикселях или нормализованных единицах. По умолчанию0.0.min_height— минимальная высота бокса в пикселях или нормализованных единицах. По умолчанию0.0.clip_bboxes_on_input— обрезать координаты по границам изображения до аугментации. Полезно, если исходная разметка частично выходит за пределы кадра. По умолчаниюFalse.filter_invalid_bboxes— удалить невалидные боксы вродеx_max < x_minдо аугментации. Если включёнclip_bboxes_on_input=True, фильтрация происходит уже после обрезки координат. По умолчаниюFalse.max_accept_ratio— максимально допустимое отношение сторонmax(w / h, h / w). Боксы, которые его превышают, отбрасываются. ЗначениеNoneотключает эту проверку.
На практике чаще всего важны не все параметры сразу, а четыре вещи: coord_format, label_fields, min_visibility и поведение на кривой разметке (clip_bboxes_on_input плюс filter_invalid_bboxes).
Что делать с неидеальной разметкой
В реальных датасетах боксы нередко выходят за границы изображения. Причин много: ошибки разметки, старые кропы, инструменты, которые позволяют рисовать bbox за пределами кадра.
В таком случае есть смысл включить:
bbox_params = A.BboxParams( coord_format='yolo', label_fields=['class_labels'], clip_bboxes_on_input=True, filter_invalid_bboxes=True, )
clip_bboxes_on_input=True жёстко ограничит координаты границами изображения до аугментации. filter_invalid_bboxes=True после этого уберёт вырожденные случаи вроде боксов нулевой ширины или высоты.
Когда нужны min_area и min_visibility
После кропа часть боксов превращается в тонкие обрезки по краям кадра. Формально они ещё существуют, но обучающего сигнала часто уже не несут.




min_area убирает боксы, которые стали слишком маленькими в абсолютном смысле. min_visibility убирает боксы, у которых после кропа осталось слишком мало от исходной площади. Выбор между ними зависит от задачи:
если важен физический размер бокса после трансформации — смотрите на
min_area;если важна доля объекта, которая реально осталась в кадре, — смотрите на
min_visibility.
Во многих детекционных пайплайнах именно min_visibility оказывается самым полезным фильтром: он не даёт обучать модель на почти полностью отрезанных объектах.
Кропы в детекции: обычный RandomCrop часто плохая идея
RandomCrop легко выдаёт кропы, на которых не остаётся ни одного ограничивающего бокса. Для классификации это не проблема. Для детекции — это впустую потраченный обучающий пример.
Поэтому в Albumentations есть bbox-aware варианты кропа:
AtLeastOneBboxRandomCropгарантирует, что после кропа в кадре останется хотя бы один бокс. Остальные могут потеряться. Это полезно, когда объектов много и вы хотите более разнообразные кропы.BBoxSafeRandomCropгарантирует, что сохранятся все боксы. Область кропа подбирается так, чтобы ничего не потерять. Это подходит для редких объектов или сценариев, где нельзя терять ни одной аннотации.RandomSizedBBoxSafeCropберёт случайную область изображения, сохраняет все боксы, а потом делает ресайз к целевому размеру. На практике это один из самых полезных вариантов для обучения детекторов: он даёт вариацию масштаба и кадрирования, не ломая разметку.
Если коротко: для детекции не стоит бездумно вставлять обычный RandomCrop только потому, что он хорошо работает в классификации.
Где пайплайн с bbox ломается чаще всего
Неверный coord_format
Это ошибка номер один. Если ваши аннотации лежат в формате YOLO, а вы передали coord_format='coco', код отработает без жалоб — но каждый ограничивающий бокс уедет в неправильную область изображения.
Именно поэтому визуальная проверка аугментированных примеров перед обучением обязательна. Смотрите не только на картинку, но и на наложенные боксы.
Все боксы отфильтровались
Агрессивные кропы в комбинации с жёсткими min_area или min_visibility легко приводят к ситуации, когда после аугментации bboxes становится пустым массивом.
Ваш класс датасета или тренировочный цикл должны уметь это переживать:
либо пропускать такие примеры;
либо использовать bbox-safe кропы, чтобы не получать пустые таргеты слишком часто.
Перепутали нормализованные и абсолютные координаты
Формат yolo ожидает значения в диапазоне [0, 1]. Если вы передали туда пиксельные координаты, пайплайн обрежет их к [0, 1], и в результате получится крошечный бокс в углу.
Обратная ошибка тоже типична: если подать нормализованные координаты как pascal_voc, боксы окажутся размером в доли пикселя и почти сразу отфильтруются.
Добавили трансформацию без поддержки bbox
Не каждая трансформация умеет обновлять координаты боксов. Если в пайплайн с A.BboxParams добавить несовместимую трансформацию, Albumentations выбросит исключение уже при инициализации. Это хорошее поведение: ошибка всплывает сразу, а не в середине обучения.
Но всё равно стоит заранее смотреть в таблицу поддерживаемых таргетов, особенно если вы собираете длинный кастомный пайплайн.
Визуализируете данные после A.Normalize
A.Normalize переводит пиксели в float, вычитает среднее и делит на стандартное отклонение. Если попытаться отрисовать изображение после этого шага, оно будет выглядеть как шум.
Поэтому для визуальной отладки детекционного пайплайна смотрите изображения до A.Normalize и до A.ToTensorV2.
Заключение
Аугментация в детекции — это не просто «сделать картинку разнообразнее». Здесь любая пространственная трансформация автоматически становится задачей синхронизации таргетов. Если формат координат выбран неверно, если кропы режут объекты слишком агрессивно, если фильтрация настроена без понимания min_visibility, модель начинает учиться на сломанной разметке.
Практически это означает очень простую вещь: сначала добейтесь корректной геометрии и корректной фильтрации bbox, и только потом наращивайте агрессивность аугментаций.
Практический чек-лист:
Проверьте, что
coord_formatсовпадает с реальным форматом вашей разметки.Вынесите все связанные с bbox метки и идентификаторы в
label_fields, если они не упакованы прямо в массив.Для кропов в детекции предпочитайте bbox-aware варианты вместо обычного
RandomCrop.Подберите
min_visibilityиmin_areaпод свою задачу, а не оставляйте значения по умолчанию наугад.Перед полноценным обучением обязательно визуализируйте несколько десятков аугментированных примеров с наложенными боксами.
Если хотите углубиться в тему, дальше логично посмотреть, как Как подбирать аугментации: гипотезы, протокол и метрики, какие трансформации поддерживают разные типы таргетов и как работать с ориентированными ограничивающими боксами.
