Аугментация acmenra-ds¶
In-memory трансформы для онлайн-обучения и офлайн-экспорта. Единый контракт:
image— HWCuint8, 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 в новую директорию. Исходный датасет не трогать.