agrobot_base/Python/OAK/datasets/gal5000/_5_augmentation.py

709 lines
24 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Augmenta imagens/máscaras *por grupo* usando RAW4 (float32, 0..1, R G IR B).
Novo fluxo (GAL5000):
Entrada:
dataset/original/group/<grupo>/raws (.raw, 4 canais float32)
dataset/original/group/<grupo>/masks (.png/.jpg)
(opcional) dataset/original/group/<grupo>/masks2
Saída:
dataset/augmented/group/<grupo>/previews (RGB gerado do RAW blurado)
dataset/augmented/group/<grupo>/raws (RAW4 augmentado: geom + blur)
dataset/augmented/group/<grupo>/masks
dataset/augmented/group/<grupo>/masks2
Regras:
- GEOMETRIA (flip, shift, rotate) aplicada em RAW + masks (+ masks2).
- BLUR / MOTION BLUR aplicado no RAW, canal por canal, com o mesmo kernel.
- Preview é SEMPRE derivado do RAW transformado (geom + blur).
- Máscaras não sofrem blur, apenas geom.
Uso:
python _5_augmentation_raw.py --copies 5
python _5_augmentation_raw.py --copies 5 --groups chao,chao_erva,cana
"""
import os
import json
import cv2
from PIL import Image
import albumentations as A
from albumentations import ReplayCompose
import argparse
import numpy as np
# ⚙️ Configurações
with open("config.json", "r", encoding="utf-8") as f:
config = json.load(f)
MODELO = config.get("camera", ".")
RESOLUCAO = config.get("raw_size")
USE_MASKS2 = config.get("dual_head", False)
# Pastas base
DATASET_BASE = os.path.join("dataset")
ORIG_GROUP_ROOT = os.path.join(DATASET_BASE, "original", "group")
AUG_GROUP_ROOT = os.path.join(DATASET_BASE, "augmented", "group")
# Fallback (modo antigo, sem grupos) mantido por compat
ORIG_OLD_IMG = os.path.join(DATASET_BASE, "original", "images")
ORIG_OLD_MSK = os.path.join(DATASET_BASE, "original", "masks")
ORIG_OLD_MSK2 = os.path.join(DATASET_BASE, "original", "masks2")
AUG_OLD_IMG = os.path.join(DATASET_BASE, "augmented", "images")
AUG_OLD_MSK = os.path.join(DATASET_BASE, "augmented", "masks")
AUG_OLD_MSK2 = os.path.join(DATASET_BASE, "augmented", "masks2")
# Extensões aceitas
IMG_EXTS = (".jpg", ".jpeg", ".png")
MSK_EXTS = (".png", ".jpg", ".jpeg")
MSK2_EXTS = (".png", ".jpg", ".jpeg")
RAW_EXTS = (".raw",)
def garantir_dir(p):
os.makedirs(p, exist_ok=True)
# ===============================
# Pipelines de augmentations
# ===============================
# 1) Geométricas: aplicam em preview “temporário”, mask, mask2 e RAW.
# RAW é passado como 'raw' em additional_targets para receber só geom.
train_geo = ReplayCompose([
A.HorizontalFlip(p=0.5),
A.ShiftScaleRotate(
shift_limit=0.01,
scale_limit=0.10,
rotate_limit=5,
border_mode=cv2.BORDER_REFLECT_101,
interpolation=cv2.INTER_LINEAR,
p=0.30
),
], additional_targets={
'mask2': 'mask',
'raw': 'mask', # trata raw como “mask” para receber apenas geom
})
# ===============================
# Funções de blur físico no RAW
# ===============================
def motion_blur_kernel(ksize=5, angle=0.0):
"""
Gera um kernel de motion blur 2D com tamanho ksize e ângulo em graus.
Usado tanto para RAW quanto para preview.
"""
ksize = int(ksize)
if ksize < 3:
ksize = 3
if ksize % 2 == 0:
ksize += 1
kernel = np.zeros((ksize, ksize), dtype=np.float32)
# linha horizontal no centro
kernel[ksize // 2, :] = 1.0
# rotaciona a linha para o ângulo desejado
center = (ksize / 2.0 - 0.5, ksize / 2.0 - 0.5)
M = cv2.getRotationMatrix2D(center, angle, 1.0)
kernel = cv2.warpAffine(kernel, M, (ksize, ksize))
s = kernel.sum()
if s > 0:
kernel /= s
return kernel
def apply_coherent_blur_raw(raw, p_motion=0.20, p_gauss=0.20):
"""
Aplica blur/motion blur de forma COERENTE no RAW, canal por canal.
- Mesmo kernel em TODOS os canais.
- Máscaras não passam aqui.
- Se nada for sorteado, retorna o RAW original.
"""
if raw is None:
return None
r = np.random.rand()
if r >= (p_motion + p_gauss):
# sem blur nesta amostra
return raw
# escolhe tamanho de kernel
k_choices = [3, 5, 7]
ksize = int(np.random.choice(k_choices))
if r < p_motion:
# Motion blur com ângulo pequeno (vibração / movimento leve)
angle = float(np.random.uniform(-20.0, 20.0))
kernel = motion_blur_kernel(ksize=ksize, angle=angle)
else:
# Gaussian blur (leve desfoque de foco)
if ksize % 2 == 0:
ksize += 1
g1d = cv2.getGaussianKernel(ksize, 0)
kernel = g1d @ g1d.T # vira kernel 2D
# Aplica kernel canal a canal
if raw.ndim == 2:
raw_blur = cv2.filter2D(raw, ddepth=-1, kernel=kernel)
else:
raw_blur = np.empty_like(raw)
h, w, c = raw.shape
for ch in range(c):
raw_blur[..., ch] = cv2.filter2D(
raw[..., ch],
ddepth=-1,
kernel=kernel,
borderType=cv2.BORDER_REFLECT_101,
)
# Garante que continua em [0,1] para float
if np.issubdtype(raw_blur.dtype, np.floating):
raw_blur = np.clip(raw_blur, 0.0, 1.0)
return raw_blur
# ===============================
# IO de imagens / RAW
# ===============================
def raw_to_preview_rgb(raw_np: np.ndarray) -> np.ndarray:
"""
raw_np: (H,W,C) float32 em 0..1, C=4 ou 5.
Layout esperado (após _load_raw):
- C=4 -> [R,G,B,IR]
- C=5 -> [R,G,B,IR,NDVI] (se um dia tiver)
Usamos sempre R,G,B para o preview e devolvemos (H,W,3) uint8.
"""
assert raw_np.ndim == 3, "raw_np deve ser (H,W,C)"
H, W, C = raw_np.shape
if C not in (4, 5):
raise RuntimeError(f"Esperado C=4 ou 5, veio {C} em raw_to_preview_rgb")
# RAW já vem como [R,G,B,IR] do _load_raw
r = raw_np[..., 0].astype(np.float32)
g = raw_np[..., 1].astype(np.float32)
b = raw_np[..., 2].astype(np.float32)
# Monta RGB em (H,W,3) para trabalhar
rgb = np.stack([r, g, b], axis=-1) # (H,W,3)
# Auto white balance simples (referenciado no G)
means = rgb.reshape(-1, 3).mean(axis=0) # (3,)
ref = means[1] # canal G
gains = ref / (means + 1e-6)
gains = np.clip(gains, 0.5, 2.0)
rgb_wb = rgb * gains # broadcasting em (3,)
# Força do WB
alpha = 1.0 # teste 0.4..0.8 se quiser misturar com original
if alpha >= 0.999:
rgb = rgb_wb
else:
rgb_orig = rgb.copy()
rgb = (1 - alpha) * rgb_orig + alpha * rgb_wb
rgb = np.clip(rgb, 0.0, 1.0)
# Gamma mais suave
rgb = np.power(rgb, 1/1.9, out=rgb)
# Converte pra uint8 OpenCV-friendly
rgb8 = (rgb * 255.0).clip(0, 255).astype(np.uint8) # (H,W,3)
return rgb8
def load_rgb(path):
im = cv2.imread(path, cv2.IMREAD_COLOR)
if im is None:
raise FileNotFoundError(path)
return cv2.cvtColor(im, cv2.COLOR_BGR2RGB)
def save_rgb(path, arr_rgb):
Image.fromarray(arr_rgb).save(path)
def _load_raw(path: str, raw_hw) -> np.ndarray:
ext = os.path.splitext(path)[1].lower()
if ext == ".npz":
z = np.load(path)
key = list(z.keys())[0]
arr = z[key]
elif ext == ".npy":
arr = np.load(path)
elif ext == ".raw":
if raw_hw is None:
raise RuntimeError(
f"Para ler .raw preciso de raw_hw=(H,W). "
f"Passe raw_hw ao criar o dataset."
)
H, W = raw_hw
size_bytes = os.path.getsize(path)
mosa_bytes = H * W # mosaico uint8
raw4_bytes = 4 * H * W * 4 # 4 canais * H * W * 4 bytes (float32)
if size_bytes == mosa_bytes:
# MODO ANTIGO: mosaico uint8
arr = np.fromfile(path, dtype=np.uint8)
raw2d = arr.reshape(H, W)
if (H % 2) != 0 or (W % 2) != 0:
raise RuntimeError(f"raw_hw deve ser par em H e W p/ mosaico 2x2 (R,G,IR,B), veio H={H}, W={W}")
H2, W2 = H // 2, W // 2
r_sub = raw2d[0::2, 0::2]
g_sub = raw2d[0::2, 1::2]
ir_sub = raw2d[1::2, 0::2]
b_sub = raw2d[1::2, 1::2]
from PIL import Image
def upsample(ch_2d: np.ndarray) -> np.ndarray:
im = Image.fromarray(ch_2d) # uint8
im = im.resize((W, H), resample=Image.BILINEAR)
return np.array(im)
r_full = upsample(r_sub)
g_full = upsample(g_sub)
ir_full = upsample(ir_sub)
b_full = upsample(b_sub)
2026-04-13 15:44:19 +00:00
# (H, W, 4) uint8 [R,G,IR,B]
arr = np.stack([r_full, g_full, ir_full, b_full], axis=-1).astype(np.uint8)
# REORGANIZA PARA CONTRATO INTERNO [R,G,B,IR]
arr = arr[..., [0, 1, 3, 2]]
elif size_bytes == raw4_bytes:
# NOVO MODO: RAW4 float32 salvo pelo normalize
arr_f32 = np.fromfile(path, dtype=np.float32)
raw4 = arr_f32.reshape(4, H, W) # (C,H,W) [R,G,IR,B]
arr = np.transpose(raw4, (1, 2, 0)) # (H,W,4) [R,G,IR,B]
# REORGANIZA PARA CONTRATO INTERNO: [R,G,B,IR]
arr = arr[..., [0, 1, 3, 2]]
else:
raise RuntimeError(
f"Tamanho inesperado em {path}: {size_bytes} bytes "
f"(esperado {mosa_bytes} ou {raw4_bytes})"
)
else:
raise RuntimeError(f"Extensão não suportada para RAW: {ext} ({path})")
# Até aqui queremos (H,W,4) base
if arr.ndim != 3:
raise RuntimeError(f"RAW precisa ser 3D, veio {arr.shape} em {path}")
if arr.shape[0] == 4 and arr.shape[-1] != 4:
arr = np.transpose(arr, (1, 2, 0)) # (H,W,4)
if arr.shape[-1] != 4:
raise RuntimeError(f"Esperava 4 canais base, veio shape={arr.shape} em {path}")
return arr # (H,W,4)
def _scale_to_float01(raw: np.ndarray, max_value = None) -> np.ndarray:
if raw.dtype == np.uint16:
mv = float(max_value) if max_value is not None else 65535.0
elif raw.dtype == np.uint8:
mv = float(max_value) if max_value is not None else 255.0
else:
# float já vem normalizado ou não; se passar max_value, aplica
mv = float(max_value) if max_value is not None else None
raw_f = raw.astype(np.float32)
if mv is not None and mv > 0:
raw_f = raw_f / mv
# clip pra não explodir
raw_f = np.clip(raw_f, 0.0, 1.0)
return raw_f
def save_raw_any(path, arr):
arr = np.asarray(arr)
2026-04-13 15:44:19 +00:00
if arr.ndim == 3 and arr.shape[-1] == 4:
# arr está no contrato interno [R,G,B,IR]
arr_cam = internal_rgbir_to_camera_raw4(arr) # [R,G,IR,B]
arr_cam = np.transpose(arr_cam, (2, 0, 1)) # (4,H,W)
arr_cam.astype(np.float32).tofile(path)
else:
arr.tofile(path)
# ===============================
# Helpers de diretório / mapeamento
# ===============================
def list_groups(root):
"""
Lista grupos válidos para o NOVO fluxo:
- group/<g>/raws
- group/<g>/masks
(previews/images não são mais obrigatórios)
"""
if not os.path.isdir(root):
return []
grupos = []
for name in sorted(os.listdir(root)):
gdir = os.path.join(root, name)
if not os.path.isdir(gdir):
continue
has_masks = os.path.isdir(os.path.join(gdir, "masks"))
has_raws = os.path.isdir(os.path.join(gdir, "raws"))
if has_masks and has_raws:
grupos.append(name)
return grupos
def map_by_base_priorizando_png(msk_dir, exts):
"""Mapeia arquivos por base (prioriza .png)."""
by_base = {}
if not os.path.isdir(msk_dir):
return by_base
for fname in os.listdir(msk_dir):
f_lower = fname.lower()
if not f_lower.endswith(exts):
continue
base, ext = os.path.splitext(fname)
cand = os.path.join(msk_dir, fname)
if base not in by_base:
by_base[base] = cand
else:
cur_ext = os.path.splitext(by_base[base])[1].lower()
if cur_ext != ".png" and ext.lower() == ".png":
by_base[base] = cand
return by_base
def map_raws_by_base(raw_dir):
by_base = {}
if not os.path.isdir(raw_dir):
return by_base
for fname in os.listdir(raw_dir):
f_lower = fname.lower()
if not f_lower.endswith(RAW_EXTS):
continue
base, _ = os.path.splitext(fname)
by_base[base] = os.path.join(raw_dir, fname)
return by_base
def ensure_aug_dirs(group_name=None, use_masks2=False, use_raw=False, is_preview_mode=True):
"""
Cria diretórios de saída para o grupo ou modo antigo.
is_preview_mode:
True -> saídas em 'previews' + 'raws'
False -> saídas em 'images' + (sem raws)
"""
if group_name:
base = os.path.join(AUG_GROUP_ROOT, group_name)
if is_preview_mode:
img_out = os.path.join(base, "previews")
else:
img_out = os.path.join(base, "images")
msk_out = os.path.join(base, "masks")
msk2_out = os.path.join(base, "masks2") if use_masks2 else None
raw_out = os.path.join(base, "raws") if use_raw else None
else:
# modo legacy
img_out = AUG_OLD_IMG
msk_out = AUG_OLD_MSK
msk2_out = AUG_OLD_MSK2 if use_masks2 else None
raw_out = None
garantir_dir(img_out)
garantir_dir(msk_out)
if use_masks2 and msk2_out:
garantir_dir(msk2_out)
if use_raw and raw_out:
garantir_dir(raw_out)
return img_out, msk_out, msk2_out, raw_out
# ===============================
# Núcleo: augmentação de um sample
# ===============================
def augment_sample(
raw_path,
msk_path,
img_out_dir,
msk_out_dir,
copies,
msk2_path=None,
msk2_out_dir=None,
raw_out_dir=None,
2026-04-13 15:44:19 +00:00
aug_suffix="aug",
):
"""
Faz a augmentação a partir de UM RAW4 + máscara (e opcionalmente máscara2).
Passos:
1) Carrega máscara (para saber HxW).
2) Carrega RAW4 float32 usando HxW da máscara.
3) Gera preview RGB a partir do RAW4 (para servir de "image" no Albumentations).
4) Aplica GEOMETRIA (mesma em preview, mask, mask2 e raw).
5) Aplica BLUR/MOTION BLUR no RAW geométrico, canal a canal, com o mesmo kernel.
6) Gera preview final a partir do RAW blurado.
7) Salva:
- preview_n em img_out_dir
- mask_n em msk_out_dir
- raw_n em raw_out_dir
- mask2_n em msk2_out_dir (se houver)
"""
base_raw, raw_ext = os.path.splitext(os.path.basename(raw_path))
base = base_raw
base_msk, msk_ext = os.path.splitext(os.path.basename(msk_path))
if base_msk != base:
print(f"[WARN] RAW '{base}' e máscara '{base_msk}' com bases diferentes, mas seguindo mesmo assim.")
msk2_ext = os.path.splitext(os.path.basename(msk2_path))[1] if msk2_path else None
# 1) Máscaras
msk = load_rgb(msk_path)
msk2 = load_rgb(msk2_path) if msk2_path else None
# 2) RAW4
raw = _load_raw(raw_path, raw_hw=(RESOLUCAO[1], RESOLUCAO[0])) # (H,W,4) uint8
raw = _scale_to_float01(raw) # float32 0..1
# 3) Preview inicial derivado do RAW (antes de geom/blur, só referência)
img = raw_to_preview_rgb(raw)
gen = 0
for i in range(copies):
# 4) GEOMETRIA: mesma transformação em img, mask, mask2 e raw
if msk2 is not None and msk2_out_dir:
aug_geo = train_geo(image=img, mask=msk, mask2=msk2, raw=raw)
else:
aug_geo = train_geo(image=img, mask=msk, raw=raw)
msk_g = aug_geo["mask"]
raw_g = aug_geo.get("raw", None)
msk2_g = aug_geo.get("mask2", None) if (msk2 is not None and msk2_out_dir) else None
# 5) BLUR FÍSICO: aplica (ou não) o MESMO blur no RAW geométrico
raw_b = apply_coherent_blur_raw(raw_g)
# 6) Preview final derivado do RAW blurado
img_b = raw_to_preview_rgb(raw_b)
# 7) Salva cópias
2026-04-13 15:44:19 +00:00
out_img = os.path.join(img_out_dir, f"{base}_{aug_suffix}_{i:02d}.png")
out_msk = os.path.join(msk_out_dir, f"{base}_{aug_suffix}_{i:02d}{msk_ext}")
save_rgb(out_img, img_b)
save_rgb(out_msk, msk_g)
if msk2_g is not None and msk2_out_dir:
2026-04-13 15:44:19 +00:00
out_msk2 = os.path.join(msk2_out_dir, f"{base}_{aug_suffix}_{i:02d}{msk2_ext}")
save_rgb(out_msk2, msk2_g)
if raw_b is not None and raw_out_dir:
2026-04-13 15:44:19 +00:00
out_raw = os.path.join(raw_out_dir, f"{base}_{aug_suffix}_{i:02d}{raw_ext}")
save_raw_any(out_raw, raw_b)
gen += 1
return gen
# ===============================
# Processamento por grupo
# ===============================
2026-04-13 15:44:19 +00:00
def process_group(group_name, copies, limit=None, seed=42, aug_suffix="aug"):
"""
Processa um grupo único usando APENAS:
- group/<g>/raws
- group/<g>/masks (+ masks2 opcional)
Previews de entrada são ignoradas.
As de saída são geradas do RAW4 (geom + blur).
"""
gdir = os.path.join(ORIG_GROUP_ROOT, group_name)
raw_dir = os.path.join(gdir, "raws")
msk_dir = os.path.join(gdir, "masks")
msk2_dir = os.path.join(gdir, "masks2")
if not (os.path.isdir(raw_dir) and os.path.isdir(msk_dir)):
print(f"[WARN] Grupo '{group_name}' inválido (precisa de 'raws' e 'masks'). Pulando.")
return 0
use_raw = True
use_masks2 = USE_MASKS2 and os.path.isdir(msk2_dir)
raw_map = map_raws_by_base(raw_dir)
2026-04-13 15:44:19 +00:00
items = sorted(raw_map.items())
if limit is not None and limit > 0:
rng = np.random.default_rng(seed)
if limit < len(items):
idx = rng.choice(len(items), size=limit, replace=False)
idx = sorted(idx.tolist())
items = [items[i] for i in idx]
msk_map = map_by_base_priorizando_png(msk_dir, MSK_EXTS)
msk2_map = map_by_base_priorizando_png(msk2_dir, MSK2_EXTS) if use_masks2 else {}
img_out_dir, msk_out_dir, msk2_out_dir, raw_out_dir = ensure_aug_dirs(
group_name,
use_masks2=use_masks2,
use_raw=use_raw,
is_preview_mode=True
)
count = 0
2026-04-13 15:44:19 +00:00
for base, raw_file in items:
msk_file = msk_map.get(base)
if not msk_file:
print(f"[WARN] [{group_name}] Máscara não encontrada para RAW {base}, pulando.")
continue
msk2_file = msk2_map.get(base) if use_masks2 else None
if use_masks2 and not msk2_file:
print(f"[WARN] [{group_name}] mask2 não encontrada para {base}, gerando só RAW+preview+mask.")
try:
count += augment_sample(
raw_path=raw_file,
msk_path=msk_file,
img_out_dir=img_out_dir,
msk_out_dir=msk_out_dir,
copies=copies,
msk2_path=msk2_file,
msk2_out_dir=msk2_out_dir,
raw_out_dir=raw_out_dir,
2026-04-13 15:44:19 +00:00
aug_suffix=aug_suffix,
)
except Exception as e:
print(f"[ERRO] [{group_name}] {base}: {e}")
print(f"[OK] Grupo '{group_name}'{count} amostras geradas.")
return count
# ===============================
# Fallback legacy (sem RAW)
# ===============================
def process_legacy(copies):
"""
Fallback: modo sem grupos (original/images e original/masks).
Mantido por compat, mas não usa RAW.
"""
if not (os.path.isdir(ORIG_OLD_IMG) and os.path.isdir(ORIG_OLD_MSK)):
print("[WARN] Modo legacy não encontrado. Nada a fazer.")
return 0
imgs = [f for f in os.listdir(ORIG_OLD_IMG) if os.path.splitext(f.lower())[1] in IMG_EXTS]
msk_map = map_by_base_priorizando_png(ORIG_OLD_MSK, MSK_EXTS)
use_masks2 = USE_MASKS2 and os.path.isdir(ORIG_OLD_MSK2)
msk2_map = map_by_base_priorizando_png(ORIG_OLD_MSK2, MSK2_EXTS) if use_masks2 else {}
img_out_dir, msk_out_dir, msk2_out_dir, _ = ensure_aug_dirs(
group_name=None,
use_masks2=use_masks2,
use_raw=False,
is_preview_mode=False
)
count = 0
for img_file in sorted(imgs):
base, img_ext = os.path.splitext(img_file)
msk_file = msk_map.get(base)
if not msk_file:
print(f"[WARN] (legacy) Máscara não encontrada para {img_file}, pulando.")
continue
msk2_file = msk2_map.get(base) if use_masks2 else None
if use_masks2 and not msk2_file:
print(f"[WARN] (legacy) mask2 não encontrada para {img_file}, gerando só img+mask.")
try:
img_path = os.path.join(ORIG_OLD_IMG, img_file)
img = load_rgb(img_path)
base_msk, msk_ext = os.path.splitext(os.path.basename(msk_file))
msk = load_rgb(msk_file)
msk2 = load_rgb(msk2_file) if msk2_file else None
for i in range(copies):
if msk2 is not None and msk2_out_dir:
aug_geo = train_geo(image=img, mask=msk, mask2=msk2)
else:
aug_geo = train_geo(image=img, mask=msk)
img_g = aug_geo["image"]
msk_g = aug_geo["mask"]
msk2_g = aug_geo.get("mask2", None) if (msk2 is not None and msk2_out_dir) else None
out_img = os.path.join(img_out_dir, f"{base}_aug_{i:02d}{img_ext}")
out_msk = os.path.join(msk_out_dir, f"{base_msk}_aug_{i:02d}{msk_ext}")
save_rgb(out_img, img_g)
save_rgb(out_msk, msk_g)
if msk2_g is not None and msk2_out_dir:
msk2_ext = os.path.splitext(msk2_file)[1]
out_msk2 = os.path.join(msk2_out_dir, f"{base_msk}_aug_{i:02d}{msk2_ext}")
save_rgb(out_msk2, msk2_g)
count += 1
except Exception as e:
print(f"[ERRO] (legacy) {img_file}: {e}")
print(f"[OK] Legacy → {count} amostras geradas.")
return count
2026-04-13 15:44:19 +00:00
def internal_rgbir_to_camera_raw4(arr: np.ndarray) -> np.ndarray:
"""
Converte do contrato interno (H,W,4) [R,G,B,IR]
para o contrato bruto da câmera (H,W,4) [R,G,IR,B].
"""
if arr is None:
return None
if arr.ndim != 3 or arr.shape[-1] != 4:
raise RuntimeError(f"Esperado array (H,W,4), veio {arr.shape}")
return arr[..., [0, 1, 3, 2]]
# ===============================
# main
# ===============================
2026-04-13 15:44:19 +00:00
def main(copies=5, groups_csv=None, limit=None, seed=42, suffix="aug"):
total = 0
if os.path.isdir(ORIG_GROUP_ROOT):
grupos = list_groups(ORIG_GROUP_ROOT)
if groups_csv:
want = {g.strip() for g in groups_csv.split(",") if g.strip()}
grupos = [g for g in grupos if g in want]
if not grupos:
print("[WARN] Nenhum grupo válido encontrado após filtro.")
if not grupos:
print("[WARN] Nenhum grupo encontrado em original/group. Tentando modo legacy...")
total += process_legacy(copies)
else:
print(f"Grupos encontrados: {', '.join(grupos)}")
for g in grupos:
2026-04-13 15:44:19 +00:00
total += process_group(g, copies, limit=limit, seed=seed, aug_suffix=suffix)
else:
total += process_legacy(copies)
print(f"\nAugmentation completed! Total: {total} amostras geradas.")
if __name__ == "__main__":
ap = argparse.ArgumentParser(description="Augmentação por grupos usando RAW4 (preview gerado do RAW).")
ap.add_argument("--copies", type=int, default=5, help="Número de cópias augmentadas por imagem (default=5).")
ap.add_argument("--groups", type=str, default=None, help="Lista de grupos separados por vírgula (ex: chao,erva_cana).")
2026-04-13 15:44:19 +00:00
ap.add_argument("--limit", type=int, default=None, help="Quantidade máxima de imagens originais do grupo a augmentar.")
ap.add_argument("--seed", type=int, default=42, help="Seed para seleção reproduzível quando usar --limit.")
ap.add_argument("--suffix", type=str, default="aug", help="Sufixo usado no nome dos arquivos gerados.")
args = ap.parse_args()
2026-04-13 15:44:19 +00:00
main(
copies=args.copies,
groups_csv=args.groups,
limit=args.limit,
seed=args.seed,
suffix=args.suffix,
)