Перейти к содержанию

Аугментация acmenra-ds

In-memory трансформы для онлайн-обучения и офлайн-экспорта. Единый контракт:

(image: np.ndarray, labels: List[Instance]) → (image, List[Instance])
  • image — HWC uint8, OpenCV BGR (2D gray промоутится в BGR).
  • labels — List[Instance] из acmenra-cv, не dict.
  • Источники не мутируются: геометрические шаги собирают новые Instance, фотометрические возвращают list(labels).
  • Вероятность p крутится в Transform.__call__, не внутри apply().
flowchart TD
  In["image + List[Instance]"] --> Call["Transform.__call__"]
  Call --> Valid["_as_image / _as_labels"]
  Valid --> Roll{"p выстрелил?"}
  Roll -->|нет| Skip["вернуть как есть"]
  Roll -->|да| Apply["apply()"]
  Apply --> Photo["Photometric: пиксели, лейблы copy"]
  Apply --> Geo["Geometric: пиксели + map_instance"]
  Photo --> Out["image + List[Instance]"]
  Geo --> Out
  Skip --> Out

Compose

Последовательный пайплайн. Шаги можно мешать в любом порядке. Каждый шаг крутит свой p независимо: если HorizontalFlip пропустил кадр, Brightness всё равно может выстрелить.

from acmenra_ds.augmentation import (
    Compose,
    HorizontalFlip,
    VerticalFlip,
    Resize,
    Brightness,
    ChannelBrightness,
    Blur,
    Noise,
    Pixelate,
    Gray,
    Normalize,
)

train_aug = Compose([
    HorizontalFlip(p=0.5),
    VerticalFlip(p=0.1),
    Brightness(min_factor=0.6, max_factor=1.4, p=0.8),
    ChannelBrightness(p=0.3),
    Blur(max_ksize=5, p=0.3),
    Noise(max_ratio=0.03, p=0.3),
    Pixelate(max_pixel_size=6, p=0.1),
    Resize(size=(640, 640), p=1.0),
    Normalize(p=1.0),  # только в training-loop, последним
])

Пустой Compose запрещён

Compose([]) — это ошибка пользователя, не no-op. Getter transforms возвращает shallow-copy: append к копии пайплайн не меняет.


Geometric

Меняют пиксели и геометрию. Identity-поля (id, class_id, label, conf) копируются.

Класс Что делает Геометрия
HorizontalFlip(p=0.5) cv2.flip(..., 1) x' = 1 - x, у OBB ещё angle' = -angle
VerticalFlip(p=0.5) cv2.flip(..., 0) y' = 1 - y, у OBB angle' = -angle
Resize(size=(H, W), p=1.0) cv2.resize в (H, W) не трогает: YOLO-координаты уже в [0, 1]

OBB + неквадратный Resize

Анизотропный resize (разный scale по H и W) не angle-safe для OBB. Для OBB-задач держите квадрат / letterbox.


Photometric

Меняют только пиксели. Список Instance возвращается как shallow-copy.

Класс Эффект Заметки
Brightness convertScaleAbs, alpha ∈ [min_factor, max_factor] 1.0 — identity
ChannelBrightness B / G / R независимо alpha-канал копируется
Blur box blur, k ~ U{1, …, max_ksize} k=1 — почти identity
Noise vectorized salt, доля пикселей U(0, max_ratio) не Python-цикл по пикселям
Pixelate downsample + nearest upsample блок U{2, …, max_pixel_size}
Gray BGR → gray → BGR (3 канала) пайплайн не оставляет 1 канал
Normalize uint8 → float32 / 255 последний шаг training-loop

Normalize не для экспорта

Не кладите Normalize перед cv2.imshow / cv2.imwrite. Float JPEG будет чёрным или битым.


Связка со Split

Онлайн (DataLoader-воркеры): парсите кадр, берёте инстансы, гоняете Compose.

image = split.image_parser.parse(sample.image_path, is_strict=False)
labels = split.get_instances(idx)
aug_image, aug_labels = pipeline(image, labels)

Офлайн-экспорт: тот же Compose, запись через LabelWriter в новую директорию. Исходный датасет не трогать.


Следующие шаги

Модули | Быстрый старт