2026-01-22 18:47:56 +00:00
|
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
"""
|
2026-02-05 19:39:59 +00:00
|
|
|
|
Augmenta imagens/máscaras *por grupo* usando RAW4 (float32, 0..1, R G IR B).
|
2026-01-22 18:47:56 +00:00
|
|
|
|
|
2026-02-05 19:39:59 +00:00
|
|
|
|
Novo fluxo (GAL5000):
|
2026-01-22 18:47:56 +00:00
|
|
|
|
|
2026-02-05 19:39:59 +00:00
|
|
|
|
Entrada:
|
|
|
|
|
|
MODELO/dataset/original/group/<grupo>/raws (.raw, 4 canais float32)
|
|
|
|
|
|
MODELO/dataset/original/group/<grupo>/masks (.png/.jpg)
|
|
|
|
|
|
(opcional) MODELO/dataset/original/group/<grupo>/masks2
|
2026-01-22 18:47:56 +00:00
|
|
|
|
|
2026-02-05 19:39:59 +00:00
|
|
|
|
Saída:
|
|
|
|
|
|
MODELO/dataset/augmented/group/<grupo>/previews (RGB gerado do RAW blurado)
|
|
|
|
|
|
MODELO/dataset/augmented/group/<grupo>/raws (RAW4 augmentado: geom + blur)
|
|
|
|
|
|
MODELO/dataset/augmented/group/<grupo>/masks
|
|
|
|
|
|
MODELO/dataset/augmented/group/<grupo>/masks2
|
2026-01-22 18:47:56 +00:00
|
|
|
|
|
2026-02-05 19:39:59 +00:00
|
|
|
|
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 já transformado (geom + blur).
|
|
|
|
|
|
- Máscaras não sofrem blur, apenas geom.
|
2026-01-22 18:47:56 +00:00
|
|
|
|
|
|
|
|
|
|
Uso:
|
2026-02-05 19:39:59 +00:00
|
|
|
|
python _5_augmentation_raw.py --copies 5
|
|
|
|
|
|
python _5_augmentation_raw.py --copies 5 --groups chao,chao_erva,cana
|
2026-01-22 18:47:56 +00:00
|
|
|
|
"""
|
|
|
|
|
|
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", ".")
|
2026-02-05 19:39:59 +00:00
|
|
|
|
RESOLUCAO = config.get("raw_size")
|
|
|
|
|
|
|
2026-01-30 19:22:44 +00:00
|
|
|
|
USE_MASKS2 = config.get("dual_head", False)
|
2026-01-22 18:47:56 +00:00
|
|
|
|
|
|
|
|
|
|
# Pastas base
|
|
|
|
|
|
DATASET_BASE = os.path.join(MODELO, "dataset")
|
|
|
|
|
|
ORIG_GROUP_ROOT = os.path.join(DATASET_BASE, "original", "group")
|
|
|
|
|
|
AUG_GROUP_ROOT = os.path.join(DATASET_BASE, "augmented", "group")
|
|
|
|
|
|
|
2026-02-05 19:39:59 +00:00
|
|
|
|
# Fallback (modo antigo, sem grupos) – mantido por compat
|
2026-01-22 18:47:56 +00:00
|
|
|
|
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")
|
2026-02-05 19:39:59 +00:00
|
|
|
|
MSK_EXTS = (".png", ".jpg", ".jpeg")
|
2026-01-22 18:47:56 +00:00
|
|
|
|
MSK2_EXTS = (".png", ".jpg", ".jpeg")
|
|
|
|
|
|
RAW_EXTS = (".raw",)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def garantir_dir(p):
|
|
|
|
|
|
os.makedirs(p, exist_ok=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ===============================
|
|
|
|
|
|
# Pipelines de augmentations
|
|
|
|
|
|
# ===============================
|
|
|
|
|
|
|
2026-02-05 19:39:59 +00:00
|
|
|
|
# 1) Geométricas: aplicam em preview “temporário”, mask, mask2 e RAW.
|
|
|
|
|
|
# RAW é passado como 'raw' em additional_targets para receber só geom.
|
2026-01-22 18:47:56 +00:00
|
|
|
|
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',
|
2026-02-05 19:39:59 +00:00
|
|
|
|
'raw': 'mask', # trata raw como “mask” para receber apenas geom
|
2026-01-22 18:47:56 +00:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-02-05 19:39:59 +00:00
|
|
|
|
# ===============================
|
|
|
|
|
|
# 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)
|
2026-01-22 18:47:56 +00:00
|
|
|
|
|
2026-02-05 19:39:59 +00:00
|
|
|
|
# Converte pra uint8 OpenCV-friendly
|
|
|
|
|
|
rgb8 = (rgb * 255.0).clip(0, 255).astype(np.uint8) # (H,W,3)
|
2026-01-22 18:47:56 +00:00
|
|
|
|
|
2026-02-05 19:39:59 +00:00
|
|
|
|
return rgb8
|
2026-01-22 18:47:56 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-02-05 19:39:59 +00:00
|
|
|
|
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]]
|
2026-02-05 19:39:59 +00:00
|
|
|
|
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})"
|
|
|
|
|
|
)
|
2026-01-22 18:47:56 +00:00
|
|
|
|
else:
|
2026-02-05 19:39:59 +00:00
|
|
|
|
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
|
2026-01-22 18:47:56 +00:00
|
|
|
|
|
|
|
|
|
|
|
2026-02-05 19:39:59 +00:00
|
|
|
|
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)
|
2026-01-22 18:47:56 +00:00
|
|
|
|
|
|
|
|
|
|
|
2026-02-05 19:39:59 +00:00
|
|
|
|
# ===============================
|
|
|
|
|
|
# Helpers de diretório / mapeamento
|
|
|
|
|
|
# ===============================
|
2026-01-22 18:47:56 +00:00
|
|
|
|
|
|
|
|
|
|
def list_groups(root):
|
2026-02-05 19:39:59 +00:00
|
|
|
|
"""
|
|
|
|
|
|
Lista grupos válidos para o NOVO fluxo:
|
|
|
|
|
|
- group/<g>/raws
|
|
|
|
|
|
- group/<g>/masks
|
|
|
|
|
|
(previews/images não são mais obrigatórios)
|
|
|
|
|
|
"""
|
2026-01-22 18:47:56 +00:00
|
|
|
|
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"))
|
2026-02-05 19:39:59 +00:00
|
|
|
|
has_raws = os.path.isdir(os.path.join(gdir, "raws"))
|
|
|
|
|
|
if has_masks and has_raws:
|
2026-01-22 18:47:56 +00:00
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-02-05 19:39:59 +00:00
|
|
|
|
# ===============================
|
|
|
|
|
|
# Núcleo: augmentação de um sample
|
|
|
|
|
|
# ===============================
|
|
|
|
|
|
|
2026-01-22 18:47:56 +00:00
|
|
|
|
def augment_sample(
|
2026-02-05 19:39:59 +00:00
|
|
|
|
raw_path,
|
2026-01-22 18:47:56 +00:00
|
|
|
|
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",
|
2026-01-22 18:47:56 +00:00
|
|
|
|
):
|
2026-02-05 19:39:59 +00:00
|
|
|
|
"""
|
|
|
|
|
|
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
|
2026-01-22 18:47:56 +00:00
|
|
|
|
|
|
|
|
|
|
base_msk, msk_ext = os.path.splitext(os.path.basename(msk_path))
|
2026-02-05 19:39:59 +00:00
|
|
|
|
if base_msk != base:
|
|
|
|
|
|
print(f"[WARN] RAW '{base}' e máscara '{base_msk}' com bases diferentes, mas seguindo mesmo assim.")
|
|
|
|
|
|
|
2026-01-22 18:47:56 +00:00
|
|
|
|
msk2_ext = os.path.splitext(os.path.basename(msk2_path))[1] if msk2_path else None
|
|
|
|
|
|
|
2026-02-05 19:39:59 +00:00
|
|
|
|
# 1) Máscaras
|
2026-01-22 18:47:56 +00:00
|
|
|
|
msk = load_rgb(msk_path)
|
|
|
|
|
|
msk2 = load_rgb(msk2_path) if msk2_path else None
|
|
|
|
|
|
|
2026-02-05 19:39:59 +00:00
|
|
|
|
# 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)
|
2026-01-22 18:47:56 +00:00
|
|
|
|
|
|
|
|
|
|
gen = 0
|
|
|
|
|
|
for i in range(copies):
|
2026-02-05 19:39:59 +00:00
|
|
|
|
# 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)
|
2026-01-22 18:47:56 +00:00
|
|
|
|
else:
|
2026-02-05 19:39:59 +00:00
|
|
|
|
aug_geo = train_geo(image=img, mask=msk, raw=raw)
|
2026-01-22 18:47:56 +00:00
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
2026-02-05 19:39:59 +00:00
|
|
|
|
# 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)
|
2026-01-22 18:47:56 +00:00
|
|
|
|
|
2026-02-05 19:39:59 +00:00
|
|
|
|
# 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}")
|
2026-02-05 19:39:59 +00:00
|
|
|
|
save_rgb(out_img, img_b)
|
2026-01-22 18:47:56 +00:00
|
|
|
|
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}")
|
2026-01-22 18:47:56 +00:00
|
|
|
|
save_rgb(out_msk2, msk2_g)
|
|
|
|
|
|
|
2026-02-05 19:39:59 +00:00
|
|
|
|
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}")
|
2026-02-05 19:39:59 +00:00
|
|
|
|
save_raw_any(out_raw, raw_b)
|
2026-01-22 18:47:56 +00:00
|
|
|
|
|
|
|
|
|
|
gen += 1
|
|
|
|
|
|
|
|
|
|
|
|
return gen
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-02-05 19:39:59 +00:00
|
|
|
|
# ===============================
|
|
|
|
|
|
# Processamento por grupo
|
|
|
|
|
|
# ===============================
|
|
|
|
|
|
|
2026-04-13 15:44:19 +00:00
|
|
|
|
def process_group(group_name, copies, limit=None, seed=42, aug_suffix="aug"):
|
2026-01-22 18:47:56 +00:00
|
|
|
|
"""
|
2026-02-05 19:39:59 +00:00
|
|
|
|
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).
|
2026-01-22 18:47:56 +00:00
|
|
|
|
"""
|
|
|
|
|
|
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")
|
|
|
|
|
|
|
2026-02-05 19:39:59 +00:00
|
|
|
|
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.")
|
2026-01-22 18:47:56 +00:00
|
|
|
|
return 0
|
|
|
|
|
|
|
2026-02-05 19:39:59 +00:00
|
|
|
|
use_raw = True
|
2026-01-30 19:22:44 +00:00
|
|
|
|
use_masks2 = USE_MASKS2 and os.path.isdir(msk2_dir)
|
2026-01-22 18:47:56 +00:00
|
|
|
|
|
2026-02-05 19:39:59 +00:00
|
|
|
|
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]
|
|
|
|
|
|
|
2026-02-05 19:39:59 +00:00
|
|
|
|
msk_map = map_by_base_priorizando_png(msk_dir, MSK_EXTS)
|
2026-01-22 18:47:56 +00:00
|
|
|
|
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,
|
2026-02-05 19:39:59 +00:00
|
|
|
|
is_preview_mode=True
|
2026-01-22 18:47:56 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
count = 0
|
2026-04-13 15:44:19 +00:00
|
|
|
|
for base, raw_file in items:
|
2026-01-22 18:47:56 +00:00
|
|
|
|
msk_file = msk_map.get(base)
|
|
|
|
|
|
if not msk_file:
|
2026-02-05 19:39:59 +00:00
|
|
|
|
print(f"[WARN] [{group_name}] Máscara não encontrada para RAW {base}, pulando.")
|
2026-01-22 18:47:56 +00:00
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
msk2_file = msk2_map.get(base) if use_masks2 else None
|
|
|
|
|
|
if use_masks2 and not msk2_file:
|
2026-02-05 19:39:59 +00:00
|
|
|
|
print(f"[WARN] [{group_name}] mask2 não encontrada para {base}, gerando só RAW+preview+mask.")
|
2026-01-22 18:47:56 +00:00
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
count += augment_sample(
|
2026-02-05 19:39:59 +00:00
|
|
|
|
raw_path=raw_file,
|
|
|
|
|
|
msk_path=msk_file,
|
|
|
|
|
|
img_out_dir=img_out_dir,
|
|
|
|
|
|
msk_out_dir=msk_out_dir,
|
2026-01-22 18:47:56 +00:00
|
|
|
|
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,
|
2026-01-22 18:47:56 +00:00
|
|
|
|
)
|
|
|
|
|
|
except Exception as e:
|
2026-02-05 19:39:59 +00:00
|
|
|
|
print(f"[ERRO] [{group_name}] {base}: {e}")
|
2026-01-22 18:47:56 +00:00
|
|
|
|
print(f"[OK] Grupo '{group_name}' → {count} amostras geradas.")
|
|
|
|
|
|
return count
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-02-05 19:39:59 +00:00
|
|
|
|
# ===============================
|
|
|
|
|
|
# Fallback legacy (sem RAW)
|
|
|
|
|
|
# ===============================
|
|
|
|
|
|
|
2026-01-22 18:47:56 +00:00
|
|
|
|
def process_legacy(copies):
|
2026-02-05 19:39:59 +00:00
|
|
|
|
"""
|
|
|
|
|
|
Fallback: modo sem grupos (original/images e original/masks).
|
|
|
|
|
|
Mantido por compat, mas não usa RAW.
|
|
|
|
|
|
"""
|
2026-01-22 18:47:56 +00:00
|
|
|
|
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)
|
2026-01-30 19:22:44 +00:00
|
|
|
|
use_masks2 = USE_MASKS2 and os.path.isdir(ORIG_OLD_MSK2)
|
2026-01-22 18:47:56 +00:00
|
|
|
|
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):
|
2026-02-05 19:39:59 +00:00
|
|
|
|
base, img_ext = os.path.splitext(img_file)
|
2026-01-22 18:47:56 +00:00
|
|
|
|
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:
|
2026-02-05 19:39:59 +00:00
|
|
|
|
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
|
|
|
|
|
|
|
2026-01-22 18:47:56 +00:00
|
|
|
|
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]]
|
|
|
|
|
|
|
2026-01-22 18:47:56 +00:00
|
|
|
|
|
2026-02-05 19:39:59 +00:00
|
|
|
|
# ===============================
|
|
|
|
|
|
# main
|
|
|
|
|
|
# ===============================
|
|
|
|
|
|
|
2026-04-13 15:44:19 +00:00
|
|
|
|
def main(copies=5, groups_csv=None, limit=None, seed=42, suffix="aug"):
|
2026-01-22 18:47:56 +00:00
|
|
|
|
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)
|
2026-01-22 18:47:56 +00:00
|
|
|
|
else:
|
|
|
|
|
|
total += process_legacy(copies)
|
|
|
|
|
|
|
|
|
|
|
|
print(f"\nAugmentation completed! Total: {total} amostras geradas.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2026-02-05 19:39:59 +00:00
|
|
|
|
ap = argparse.ArgumentParser(description="Augmentação por grupos usando RAW4 (preview gerado do RAW).")
|
2026-01-22 18:47:56 +00:00
|
|
|
|
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.")
|
2026-01-22 18:47:56 +00:00
|
|
|
|
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,
|
|
|
|
|
|
)
|