2025-09-15 10:23:07 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
"""
|
2026-05-21 22:57:35 +00:00
|
|
|
Normaliza/redimensiona imagens, máscaras, masks2 e labels mantendo a ESTRUTURA POR GRUPO.
|
2025-09-15 10:23:07 +00:00
|
|
|
|
2026-05-21 22:57:35 +00:00
|
|
|
Entradas via config.json -> camera, resolucao:
|
|
|
|
|
- MODELO/dataset/original/group/<grupo>/{images,masks,(masks2),(labels)}
|
|
|
|
|
- MODELO/dataset/augmented/group/<grupo>/{images,masks,(masks2),(labels)}
|
2025-09-15 10:23:07 +00:00
|
|
|
|
2026-05-21 22:57:35 +00:00
|
|
|
Saídas por resolução:
|
|
|
|
|
- MODELO/dataset/<WxH>/group/<grupo>/{images,masks,(masks2),(labels)}
|
2025-09-15 10:23:07 +00:00
|
|
|
|
2026-05-21 22:57:35 +00:00
|
|
|
Fallback legado, se não houver group/:
|
|
|
|
|
- original/{images,masks,(masks2),(labels)}
|
|
|
|
|
- augmented/{images,masks,(masks2),(labels)}
|
|
|
|
|
- saída: <WxH>/{images,masks,(masks2),(labels)}
|
2025-09-15 10:23:07 +00:00
|
|
|
|
|
|
|
|
Conversão de máscara:
|
2026-05-21 22:57:35 +00:00
|
|
|
- Lê máscara RGB e converte para IDs via utils.converter_mask_rgb_para_ids.
|
|
|
|
|
- Ignore conforme labelmap, usando índice 255 como fallback.
|
|
|
|
|
|
|
|
|
|
Labels:
|
|
|
|
|
- Ativado por config['dual_head_label'].
|
|
|
|
|
- Copia labels .json/.txt para a saída.
|
|
|
|
|
- Se JSON, atualiza image/mask/mask2/label/base/source/normalized.
|
|
|
|
|
- Se houver label_id, salva também um .npy com o inteiro para facilitar o Dataset no treino.
|
2025-09-15 10:23:07 +00:00
|
|
|
"""
|
2026-05-21 22:57:35 +00:00
|
|
|
|
2025-09-15 10:23:07 +00:00
|
|
|
import argparse
|
|
|
|
|
import os
|
|
|
|
|
import json
|
|
|
|
|
import cv2
|
2026-05-21 22:57:35 +00:00
|
|
|
from typing import Dict, List, Tuple, Optional
|
2026-03-03 12:36:34 +00:00
|
|
|
|
|
|
|
|
import numpy as np
|
2025-09-15 10:23:07 +00:00
|
|
|
from utils import carregar_labelmap_completo, converter_mask_rgb_para_ids
|
|
|
|
|
|
2026-05-21 22:57:35 +00:00
|
|
|
# ===================== CONFIG =====================
|
|
|
|
|
|
|
|
|
|
with open("config.json", "r", encoding="utf-8") as f:
|
2025-09-15 10:23:07 +00:00
|
|
|
config = json.load(f)
|
2026-05-21 22:57:35 +00:00
|
|
|
|
2025-09-15 10:23:07 +00:00
|
|
|
MODELO = config["camera"]
|
2026-03-03 12:36:34 +00:00
|
|
|
MODEL_NAME = config["model_name"]
|
2026-05-21 22:57:35 +00:00
|
|
|
|
|
|
|
|
# Compatibilidade:
|
|
|
|
|
# - scripts antigos usam dual_head para masks2
|
|
|
|
|
# - scripts novos podem usar dual_head_mask e dual_head_label separados
|
|
|
|
|
USE_MASKS2 = bool(config.get("dual_head_mask", config.get("dual_head", False)))
|
|
|
|
|
USE_LABELS = bool(config.get("dual_head_label", False))
|
|
|
|
|
|
|
|
|
|
RESOLUCAO = tuple(config["resolucao"]) # [W, H]
|
2025-09-15 10:23:07 +00:00
|
|
|
pasta_base = os.path.join(MODELO, "dataset")
|
|
|
|
|
labelmap_path = os.path.join(pasta_base, "labelmap.txt")
|
2026-05-21 22:57:35 +00:00
|
|
|
mode = "single"
|
|
|
|
|
if USE_MASKS2:
|
|
|
|
|
mode = "mask2"
|
|
|
|
|
elif USE_LABELS:
|
|
|
|
|
mode = "label"
|
|
|
|
|
suffix = {
|
|
|
|
|
"single": "_single",
|
|
|
|
|
"mask2": "_dual_mask",
|
|
|
|
|
"label": "_dual_label",
|
|
|
|
|
}[mode]
|
|
|
|
|
save_path = os.path.join(MODELO, "backup", config["modelo"], MODEL_NAME + suffix)
|
2025-09-15 10:23:07 +00:00
|
|
|
|
|
|
|
|
RESOLUCOES = {
|
|
|
|
|
f"{RESOLUCAO[0]}x{RESOLUCAO[1]}": (RESOLUCAO[0], RESOLUCAO[1]),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
IMG_EXTS = (".jpg", ".jpeg", ".png")
|
2026-05-21 22:57:35 +00:00
|
|
|
MSK_EXTS = (".png", ".jpg", ".jpeg")
|
|
|
|
|
MSK2_EXTS = (".png", ".jpg", ".jpeg")
|
|
|
|
|
LABEL_EXTS = (".json", ".txt")
|
|
|
|
|
|
|
|
|
|
GLOBAL_SUM = None
|
|
|
|
|
GLOBAL_SUMSQ = None
|
|
|
|
|
GLOBAL_PIXELS = 0
|
2025-09-15 10:23:07 +00:00
|
|
|
|
2026-05-21 22:57:35 +00:00
|
|
|
|
|
|
|
|
# ===================== HELPERS =====================
|
2026-03-03 12:36:34 +00:00
|
|
|
|
2025-09-15 10:23:07 +00:00
|
|
|
def infer_ignore_id(ignore_rgb, default_id=255):
|
|
|
|
|
if isinstance(ignore_rgb, (list, tuple)):
|
|
|
|
|
if len(ignore_rgb) == 1:
|
|
|
|
|
try:
|
|
|
|
|
return int(ignore_rgb[0])
|
|
|
|
|
except Exception:
|
|
|
|
|
return default_id
|
|
|
|
|
if len(ignore_rgb) == 3:
|
|
|
|
|
return default_id
|
|
|
|
|
if isinstance(ignore_rgb, int):
|
|
|
|
|
return ignore_rgb
|
|
|
|
|
return default_id
|
|
|
|
|
|
2026-05-21 22:57:35 +00:00
|
|
|
|
2025-09-15 10:23:07 +00:00
|
|
|
def garantir_dir(p):
|
|
|
|
|
os.makedirs(p, exist_ok=True)
|
|
|
|
|
|
2026-05-21 22:57:35 +00:00
|
|
|
|
2025-09-15 10:23:07 +00:00
|
|
|
def list_groups(root) -> List[str]:
|
|
|
|
|
if not os.path.isdir(root):
|
|
|
|
|
return []
|
2026-05-21 22:57:35 +00:00
|
|
|
|
2025-09-15 10:23:07 +00:00
|
|
|
grupos = []
|
|
|
|
|
for name in sorted(os.listdir(root)):
|
|
|
|
|
gdir = os.path.join(root, name)
|
|
|
|
|
if not os.path.isdir(gdir):
|
|
|
|
|
continue
|
|
|
|
|
if os.path.isdir(os.path.join(gdir, "images")) and os.path.isdir(os.path.join(gdir, "masks")):
|
|
|
|
|
grupos.append(name)
|
|
|
|
|
return grupos
|
|
|
|
|
|
2026-05-21 22:57:35 +00:00
|
|
|
|
|
|
|
|
def normalizar_base(stem: str) -> str:
|
|
|
|
|
sufixos = [
|
|
|
|
|
"_rgb", "_RGB", "_Rgb",
|
|
|
|
|
"_image", "_img", "_frame",
|
|
|
|
|
"_mask", "_masks",
|
|
|
|
|
"_seg", "_SEG", "_segment", "_segmentacao", "_Segmentacao",
|
|
|
|
|
"_label", "_labels",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
out = stem
|
|
|
|
|
mudou = True
|
|
|
|
|
while mudou:
|
|
|
|
|
mudou = False
|
|
|
|
|
for sfx in sufixos:
|
|
|
|
|
if out.endswith(sfx):
|
|
|
|
|
out = out[: -len(sfx)]
|
|
|
|
|
mudou = True
|
|
|
|
|
break
|
|
|
|
|
return out
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def map_files_by_base(folder: str, exts: Tuple[str, ...]) -> Dict[str, str]:
|
2025-09-15 10:23:07 +00:00
|
|
|
by_base = {}
|
2026-05-21 22:57:35 +00:00
|
|
|
if not os.path.isdir(folder):
|
2025-09-15 10:23:07 +00:00
|
|
|
return by_base
|
2026-05-21 22:57:35 +00:00
|
|
|
|
|
|
|
|
prioridade = {
|
|
|
|
|
".json": 0,
|
|
|
|
|
".png": 1,
|
|
|
|
|
".jpg": 2,
|
|
|
|
|
".jpeg": 3,
|
|
|
|
|
".txt": 4,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for fname in os.listdir(folder):
|
|
|
|
|
lower = fname.lower()
|
|
|
|
|
if not lower.endswith(exts):
|
2025-09-15 10:23:07 +00:00
|
|
|
continue
|
2026-05-21 22:57:35 +00:00
|
|
|
|
|
|
|
|
stem, ext = os.path.splitext(fname)
|
|
|
|
|
base = normalizar_base(stem)
|
|
|
|
|
cand = os.path.join(folder, fname)
|
|
|
|
|
|
2025-09-15 10:23:07 +00:00
|
|
|
if base not in by_base:
|
|
|
|
|
by_base[base] = cand
|
|
|
|
|
else:
|
|
|
|
|
cur_ext = os.path.splitext(by_base[base])[1].lower()
|
2026-05-21 22:57:35 +00:00
|
|
|
if prioridade.get(ext.lower(), 99) < prioridade.get(cur_ext, 99):
|
2025-09-15 10:23:07 +00:00
|
|
|
by_base[base] = cand
|
2026-05-21 22:57:35 +00:00
|
|
|
|
2025-09-15 10:23:07 +00:00
|
|
|
return by_base
|
|
|
|
|
|
2026-05-21 22:57:35 +00:00
|
|
|
|
|
|
|
|
def map_masks_by_base(msk_dir: str) -> Dict[str, str]:
|
|
|
|
|
return map_files_by_base(msk_dir, MSK_EXTS)
|
|
|
|
|
|
|
|
|
|
|
2026-01-28 17:52:31 +00:00
|
|
|
def map_masks2_by_base(msk2_dir: str) -> Dict[str, str]:
|
2026-05-21 22:57:35 +00:00
|
|
|
return map_files_by_base(msk2_dir, MSK2_EXTS)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def map_labels_by_base(label_dir: str) -> Dict[str, str]:
|
|
|
|
|
return map_files_by_base(label_dir, LABEL_EXTS)
|
2026-01-28 17:52:31 +00:00
|
|
|
|
2026-05-21 22:57:35 +00:00
|
|
|
|
|
|
|
|
def trocar_ext_para_png(nome: str) -> str:
|
|
|
|
|
for ext in (".jpg", ".jpeg", ".png"):
|
|
|
|
|
if nome.lower().endswith(ext):
|
|
|
|
|
return nome[: -len(ext)] + ".png"
|
|
|
|
|
return nome + ".png"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def safe_rel(path: str, root: str) -> str:
|
|
|
|
|
try:
|
|
|
|
|
return os.path.relpath(path, root).replace("\\", "/")
|
|
|
|
|
except Exception:
|
|
|
|
|
return str(path).replace("\\", "/")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ===================== NORMALIZAÇÃO =====================
|
|
|
|
|
|
|
|
|
|
def normalize_pair(
|
|
|
|
|
caminho_rgb: str,
|
|
|
|
|
caminho_mask: str,
|
|
|
|
|
cor_para_id,
|
|
|
|
|
ignore_id: int,
|
|
|
|
|
out_img_dir: str,
|
|
|
|
|
out_msk_dir: str,
|
|
|
|
|
dim: Tuple[int, int],
|
|
|
|
|
prefix: str = "",
|
|
|
|
|
):
|
|
|
|
|
"""Redimensiona e grava imagem + máscara ID."""
|
|
|
|
|
img_bgr = cv2.imread(caminho_rgb)
|
|
|
|
|
if img_bgr is None:
|
2025-09-15 10:23:07 +00:00
|
|
|
print(f"[!] Erro ao ler imagem: {caminho_rgb}")
|
2026-05-21 22:57:35 +00:00
|
|
|
return False, None, None
|
|
|
|
|
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
|
2025-09-15 10:23:07 +00:00
|
|
|
|
|
|
|
|
nome = os.path.basename(caminho_rgb)
|
2026-05-21 22:57:35 +00:00
|
|
|
nome_saida_img = f"{prefix}{nome}" if prefix else nome
|
|
|
|
|
nome_saida_msk = trocar_ext_para_png(nome_saida_img)
|
2025-09-15 10:23:07 +00:00
|
|
|
|
2026-05-21 22:57:35 +00:00
|
|
|
img_resized_rgb = cv2.resize(img_rgb, dim, interpolation=cv2.INTER_AREA)
|
2026-03-03 12:36:34 +00:00
|
|
|
|
|
|
|
|
global GLOBAL_SUM, GLOBAL_SUMSQ, GLOBAL_PIXELS
|
2026-05-21 22:57:35 +00:00
|
|
|
img_float = img_resized_rgb.astype(np.float32) / 255.0
|
2026-03-03 12:36:34 +00:00
|
|
|
h, w, c = img_float.shape
|
|
|
|
|
flat = img_float.reshape(-1, c).astype(np.float64)
|
2026-05-21 22:57:35 +00:00
|
|
|
|
|
|
|
|
if GLOBAL_SUM is None:
|
2026-03-03 12:36:34 +00:00
|
|
|
GLOBAL_SUM = np.zeros(c, dtype=np.float64)
|
|
|
|
|
GLOBAL_SUMSQ = np.zeros(c, dtype=np.float64)
|
2026-05-21 22:57:35 +00:00
|
|
|
|
2026-03-03 12:36:34 +00:00
|
|
|
GLOBAL_SUM += flat.sum(axis=0)
|
|
|
|
|
GLOBAL_SUMSQ += (flat ** 2).sum(axis=0)
|
|
|
|
|
GLOBAL_PIXELS += h * w
|
|
|
|
|
|
2025-09-15 10:23:07 +00:00
|
|
|
garantir_dir(out_img_dir)
|
2026-05-21 22:57:35 +00:00
|
|
|
out_img_path = os.path.join(out_img_dir, nome_saida_img)
|
|
|
|
|
img_resized_bgr = cv2.cvtColor(img_resized_rgb, cv2.COLOR_RGB2BGR)
|
|
|
|
|
cv2.imwrite(out_img_path, img_resized_bgr)
|
2025-09-15 10:23:07 +00:00
|
|
|
|
2026-05-21 22:57:35 +00:00
|
|
|
out_msk_path = None
|
2025-09-15 10:23:07 +00:00
|
|
|
if caminho_mask and os.path.isfile(caminho_mask):
|
|
|
|
|
msk_bgr = cv2.imread(caminho_mask, cv2.IMREAD_COLOR)
|
|
|
|
|
if msk_bgr is None:
|
|
|
|
|
print(f"[!] Erro ao ler máscara: {caminho_mask}")
|
|
|
|
|
else:
|
|
|
|
|
msk_rgb = cv2.cvtColor(msk_bgr, cv2.COLOR_BGR2RGB)
|
|
|
|
|
mask_ids = converter_mask_rgb_para_ids(msk_rgb, cor_para_id, ignore_id)
|
|
|
|
|
mask_resized = cv2.resize(mask_ids, dim, interpolation=cv2.INTER_NEAREST)
|
2026-05-21 22:57:35 +00:00
|
|
|
|
2025-09-15 10:23:07 +00:00
|
|
|
garantir_dir(out_msk_dir)
|
2026-05-21 22:57:35 +00:00
|
|
|
out_msk_path = os.path.join(out_msk_dir, nome_saida_msk)
|
|
|
|
|
cv2.imwrite(out_msk_path, mask_resized)
|
2025-09-15 10:23:07 +00:00
|
|
|
|
2026-05-21 22:57:35 +00:00
|
|
|
return True, out_img_path, out_msk_path
|
2026-03-03 12:36:34 +00:00
|
|
|
|
2025-09-15 10:23:07 +00:00
|
|
|
|
2026-05-21 22:57:35 +00:00
|
|
|
def normalize_pair_mask2(
|
|
|
|
|
caminho_rgb: str,
|
|
|
|
|
caminho_mask2: str,
|
|
|
|
|
out_msk2_dir: str,
|
|
|
|
|
dim: Tuple[int, int],
|
|
|
|
|
prefix: str = "",
|
|
|
|
|
):
|
2026-01-28 17:52:31 +00:00
|
|
|
if not caminho_mask2 or not os.path.isfile(caminho_mask2):
|
2026-05-21 22:57:35 +00:00
|
|
|
return False, None
|
2026-01-28 17:52:31 +00:00
|
|
|
|
|
|
|
|
nome = os.path.basename(caminho_rgb)
|
|
|
|
|
nome_saida = f"{prefix}{nome}" if prefix else nome
|
2026-05-21 22:57:35 +00:00
|
|
|
nome_saida = trocar_ext_para_png(nome_saida)
|
2026-01-28 17:52:31 +00:00
|
|
|
|
|
|
|
|
m2 = cv2.imread(caminho_mask2, cv2.IMREAD_UNCHANGED)
|
|
|
|
|
if m2 is None:
|
|
|
|
|
print(f"[!] Erro ao ler máscara2: {caminho_mask2}")
|
2026-05-21 22:57:35 +00:00
|
|
|
return False, None
|
2026-01-28 17:52:31 +00:00
|
|
|
|
|
|
|
|
if len(m2.shape) == 3:
|
|
|
|
|
m2g = cv2.cvtColor(m2, cv2.COLOR_BGR2GRAY)
|
|
|
|
|
else:
|
|
|
|
|
m2g = m2
|
|
|
|
|
|
|
|
|
|
_, m2bin = cv2.threshold(m2g, 127, 255, cv2.THRESH_BINARY)
|
|
|
|
|
m2res = cv2.resize(m2bin, dim, interpolation=cv2.INTER_NEAREST)
|
2026-05-21 22:57:35 +00:00
|
|
|
|
2026-01-28 17:52:31 +00:00
|
|
|
garantir_dir(out_msk2_dir)
|
2026-05-21 22:57:35 +00:00
|
|
|
out_msk2_path = os.path.join(out_msk2_dir, nome_saida)
|
|
|
|
|
cv2.imwrite(out_msk2_path, m2res)
|
|
|
|
|
|
|
|
|
|
return True, out_msk2_path
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def normalize_label(
|
|
|
|
|
caminho_rgb: str,
|
|
|
|
|
caminho_label: Optional[str],
|
|
|
|
|
out_label_dir: Optional[str],
|
|
|
|
|
out_img_path: Optional[str],
|
|
|
|
|
out_msk_path: Optional[str],
|
|
|
|
|
out_msk2_path: Optional[str],
|
|
|
|
|
source_root: str,
|
|
|
|
|
output_root: str,
|
|
|
|
|
grupo: Optional[str],
|
|
|
|
|
fonte_nome: str,
|
|
|
|
|
prefix: str = "",
|
|
|
|
|
):
|
|
|
|
|
"""Copia/atualiza label global e salva label_id como .npy quando possível."""
|
|
|
|
|
if not caminho_label or not out_label_dir or not os.path.isfile(caminho_label):
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
nome = os.path.basename(caminho_rgb)
|
|
|
|
|
nome_saida = f"{prefix}{nome}" if prefix else nome
|
|
|
|
|
base_saida, _ = os.path.splitext(nome_saida)
|
|
|
|
|
|
|
|
|
|
label_ext = os.path.splitext(caminho_label)[1].lower()
|
|
|
|
|
garantir_dir(out_label_dir)
|
|
|
|
|
|
|
|
|
|
out_label_path = os.path.join(out_label_dir, base_saida + label_ext)
|
|
|
|
|
out_label_id_path = os.path.join(out_label_dir, base_saida + ".npy")
|
|
|
|
|
|
|
|
|
|
label_id = None
|
|
|
|
|
|
|
|
|
|
if label_ext == ".json":
|
|
|
|
|
try:
|
|
|
|
|
with open(caminho_label, "r", encoding="utf-8") as f:
|
|
|
|
|
data = json.load(f)
|
|
|
|
|
except Exception:
|
|
|
|
|
data = {}
|
|
|
|
|
|
|
|
|
|
label_id = data.get("label_id")
|
|
|
|
|
|
|
|
|
|
data["normalized"] = True
|
|
|
|
|
data["source"] = fonte_nome
|
|
|
|
|
data["group"] = grupo if grupo is not None else data.get("group")
|
|
|
|
|
data["base"] = base_saida
|
|
|
|
|
data["image"] = safe_rel(out_img_path, output_root) if out_img_path else None
|
|
|
|
|
data["mask"] = safe_rel(out_msk_path, output_root) if out_msk_path else None
|
|
|
|
|
data["label"] = safe_rel(out_label_path, output_root)
|
|
|
|
|
data["source_label"] = safe_rel(caminho_label, source_root)
|
|
|
|
|
|
|
|
|
|
if out_msk2_path:
|
|
|
|
|
data["mask2"] = safe_rel(out_msk2_path, output_root)
|
|
|
|
|
|
|
|
|
|
with open(out_label_path, "w", encoding="utf-8") as f:
|
|
|
|
|
json.dump(data, f, indent=2, ensure_ascii=False)
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
with open(caminho_label, "r", encoding="utf-8") as f:
|
|
|
|
|
txt = f.read().strip()
|
|
|
|
|
with open(out_label_path, "w", encoding="utf-8") as f:
|
|
|
|
|
f.write(txt)
|
|
|
|
|
label_id = None
|
|
|
|
|
|
|
|
|
|
if label_id is not None:
|
|
|
|
|
try:
|
|
|
|
|
np.save(out_label_id_path, np.array(int(label_id), dtype=np.int64))
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"[WARN] Não consegui salvar label_id npy para {caminho_label}: {e}")
|
|
|
|
|
|
2026-01-28 17:52:31 +00:00
|
|
|
return True
|
|
|
|
|
|
2026-05-21 22:57:35 +00:00
|
|
|
|
|
|
|
|
# ===================== PROCESSAMENTO =====================
|
|
|
|
|
|
|
|
|
|
def process_group_root(
|
|
|
|
|
fonte_root: str,
|
|
|
|
|
fonte_nome: str,
|
|
|
|
|
cor_para_id,
|
|
|
|
|
ignore_id: int,
|
|
|
|
|
groups_except: str = "",
|
|
|
|
|
strict_label: bool = False,
|
|
|
|
|
):
|
2025-09-15 10:23:07 +00:00
|
|
|
total = 0
|
|
|
|
|
grupos = list_groups(fonte_root)
|
|
|
|
|
if not grupos:
|
|
|
|
|
return 0
|
2026-05-21 22:57:35 +00:00
|
|
|
|
2025-09-15 10:23:07 +00:00
|
|
|
not_want = {g.strip() for g in groups_except.split(",") if g.strip()}
|
|
|
|
|
grupos_desconsiderar = [g for g in grupos if g in not_want]
|
|
|
|
|
|
|
|
|
|
for nome_res, dim in RESOLUCOES.items():
|
|
|
|
|
out_root = os.path.join(pasta_base, nome_res, "group")
|
2026-05-21 22:57:35 +00:00
|
|
|
|
2025-09-15 10:23:07 +00:00
|
|
|
for grupo in grupos:
|
|
|
|
|
if grupo in grupos_desconsiderar:
|
2026-05-21 22:57:35 +00:00
|
|
|
print(f"[WARN] Grupo desconsiderado não será processado: {grupo}")
|
2025-09-15 10:23:07 +00:00
|
|
|
continue
|
2026-05-21 22:57:35 +00:00
|
|
|
|
2025-09-15 10:23:07 +00:00
|
|
|
in_img_dir = os.path.join(fonte_root, grupo, "images")
|
|
|
|
|
in_msk_dir = os.path.join(fonte_root, grupo, "masks")
|
2026-01-28 17:52:31 +00:00
|
|
|
in_msk2_dir = os.path.join(fonte_root, grupo, "masks2")
|
2026-05-21 22:57:35 +00:00
|
|
|
in_label_dir = os.path.join(fonte_root, grupo, "labels")
|
|
|
|
|
|
2025-09-15 10:23:07 +00:00
|
|
|
if not (os.path.isdir(in_img_dir) and os.path.isdir(in_msk_dir)):
|
2026-05-21 22:57:35 +00:00
|
|
|
print(f"[WARN] Grupo inválido sem images/masks: {grupo}")
|
2025-09-15 10:23:07 +00:00
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
out_img_dir = os.path.join(out_root, grupo, "images")
|
|
|
|
|
out_msk_dir = os.path.join(out_root, grupo, "masks")
|
2026-05-21 22:57:35 +00:00
|
|
|
|
2026-01-28 17:52:31 +00:00
|
|
|
usar_masks2 = USE_MASKS2 and os.path.isdir(in_msk2_dir)
|
2026-05-21 22:57:35 +00:00
|
|
|
usar_labels = USE_LABELS and os.path.isdir(in_label_dir)
|
|
|
|
|
|
2026-01-28 17:52:31 +00:00
|
|
|
out_msk2_dir = os.path.join(out_root, grupo, "masks2") if usar_masks2 else None
|
2026-05-21 22:57:35 +00:00
|
|
|
out_label_dir = os.path.join(out_root, grupo, "labels") if usar_labels else None
|
|
|
|
|
|
2025-09-15 10:23:07 +00:00
|
|
|
msk_map = map_masks_by_base(in_msk_dir)
|
2026-01-28 17:52:31 +00:00
|
|
|
msk2_map = map_masks2_by_base(in_msk2_dir) if usar_masks2 else {}
|
2026-05-21 22:57:35 +00:00
|
|
|
label_map = map_labels_by_base(in_label_dir) if usar_labels else {}
|
|
|
|
|
|
|
|
|
|
if USE_LABELS and not usar_labels:
|
|
|
|
|
msg = f"[WARN] [{fonte_nome} | {grupo}] dual_head_label=true, mas labels/ não existe."
|
|
|
|
|
if strict_label:
|
|
|
|
|
print(msg + " Pulando grupo.")
|
|
|
|
|
continue
|
|
|
|
|
print(msg + " Seguindo sem labels.")
|
2025-09-15 10:23:07 +00:00
|
|
|
|
|
|
|
|
imgs = [f for f in os.listdir(in_img_dir) if os.path.splitext(f.lower())[1] in IMG_EXTS]
|
|
|
|
|
n = len(imgs)
|
2026-05-21 22:57:35 +00:00
|
|
|
|
2025-09-15 10:23:07 +00:00
|
|
|
for i, fname in enumerate(sorted(imgs), 1):
|
|
|
|
|
base, _ = os.path.splitext(fname)
|
2026-05-21 22:57:35 +00:00
|
|
|
base_norm = normalizar_base(base)
|
|
|
|
|
|
2025-09-15 10:23:07 +00:00
|
|
|
caminho_rgb = os.path.join(in_img_dir, fname)
|
2026-05-21 22:57:35 +00:00
|
|
|
caminho_mask = msk_map.get(base_norm)
|
|
|
|
|
caminho_mask2 = msk2_map.get(base_norm) if usar_masks2 else None
|
|
|
|
|
caminho_label = label_map.get(base_norm) if usar_labels else None
|
|
|
|
|
|
|
|
|
|
if usar_labels and not caminho_label:
|
|
|
|
|
msg = f"[WARN] [{fonte_nome} | {grupo}] label não encontrado para {fname}."
|
|
|
|
|
if strict_label:
|
|
|
|
|
print(msg + " Pulando item.")
|
|
|
|
|
continue
|
|
|
|
|
print(msg + " Seguindo sem label.")
|
|
|
|
|
|
|
|
|
|
ok, out_img_path, out_msk_path = normalize_pair(
|
|
|
|
|
caminho_rgb,
|
|
|
|
|
caminho_mask,
|
|
|
|
|
cor_para_id,
|
|
|
|
|
ignore_id,
|
|
|
|
|
out_img_dir,
|
|
|
|
|
out_msk_dir,
|
|
|
|
|
dim,
|
|
|
|
|
prefix=f"{fonte_nome}_",
|
2025-09-15 10:23:07 +00:00
|
|
|
)
|
2026-05-21 22:57:35 +00:00
|
|
|
|
|
|
|
|
out_msk2_path = None
|
2026-01-28 17:52:31 +00:00
|
|
|
if usar_masks2 and out_msk2_dir:
|
|
|
|
|
if not caminho_mask2:
|
2026-05-21 22:57:35 +00:00
|
|
|
print(f"[WARN] [{fonte_nome} | {grupo}] masks2 existe, mas não achei mask2 p/ {fname}.")
|
2026-01-28 17:52:31 +00:00
|
|
|
else:
|
2026-05-21 22:57:35 +00:00
|
|
|
_, out_msk2_path = normalize_pair_mask2(
|
|
|
|
|
caminho_rgb,
|
|
|
|
|
caminho_mask2,
|
|
|
|
|
out_msk2_dir,
|
|
|
|
|
dim,
|
|
|
|
|
prefix=f"{fonte_nome}_",
|
2026-01-28 17:52:31 +00:00
|
|
|
)
|
2026-05-21 22:57:35 +00:00
|
|
|
|
|
|
|
|
if usar_labels and caminho_label and out_label_dir:
|
|
|
|
|
normalize_label(
|
|
|
|
|
caminho_rgb=caminho_rgb,
|
|
|
|
|
caminho_label=caminho_label,
|
|
|
|
|
out_label_dir=out_label_dir,
|
|
|
|
|
out_img_path=out_img_path,
|
|
|
|
|
out_msk_path=out_msk_path,
|
|
|
|
|
out_msk2_path=out_msk2_path,
|
|
|
|
|
source_root=fonte_root,
|
|
|
|
|
output_root=os.path.join(pasta_base, nome_res),
|
|
|
|
|
grupo=grupo,
|
|
|
|
|
fonte_nome=fonte_nome,
|
|
|
|
|
prefix=f"{fonte_nome}_",
|
|
|
|
|
)
|
|
|
|
|
|
2025-09-15 10:23:07 +00:00
|
|
|
if ok:
|
|
|
|
|
total += 1
|
2026-05-21 22:57:35 +00:00
|
|
|
|
2025-09-15 10:23:07 +00:00
|
|
|
print(f"[{fonte_nome} | {grupo} | {nome_res}] {i}/{n} → {fname}")
|
2026-05-21 22:57:35 +00:00
|
|
|
|
2025-09-15 10:23:07 +00:00
|
|
|
return total
|
|
|
|
|
|
2026-05-21 22:57:35 +00:00
|
|
|
|
|
|
|
|
def process_legacy_root(
|
|
|
|
|
legacy_img: str,
|
|
|
|
|
legacy_msk: str,
|
|
|
|
|
fonte_nome: str,
|
|
|
|
|
cor_para_id,
|
|
|
|
|
ignore_id: int,
|
|
|
|
|
strict_label: bool = False,
|
|
|
|
|
):
|
2025-09-15 10:23:07 +00:00
|
|
|
if not (os.path.isdir(legacy_img) and os.path.isdir(legacy_msk)):
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
total = 0
|
2026-05-21 22:57:35 +00:00
|
|
|
|
2025-09-15 10:23:07 +00:00
|
|
|
for nome_res, dim in RESOLUCOES.items():
|
|
|
|
|
out_img_dir = os.path.join(pasta_base, nome_res, "images")
|
|
|
|
|
out_msk_dir = os.path.join(pasta_base, nome_res, "masks")
|
2026-05-21 22:57:35 +00:00
|
|
|
|
|
|
|
|
legacy_root = os.path.dirname(legacy_msk)
|
|
|
|
|
legacy_msk2 = os.path.join(legacy_root, "masks2")
|
|
|
|
|
legacy_labels = os.path.join(legacy_root, "labels")
|
|
|
|
|
|
2026-01-28 17:52:31 +00:00
|
|
|
usar_masks2 = USE_MASKS2 and os.path.isdir(legacy_msk2)
|
2026-05-21 22:57:35 +00:00
|
|
|
usar_labels = USE_LABELS and os.path.isdir(legacy_labels)
|
|
|
|
|
|
2026-01-28 17:52:31 +00:00
|
|
|
out_msk2_dir = os.path.join(pasta_base, nome_res, "masks2") if usar_masks2 else None
|
2026-05-21 22:57:35 +00:00
|
|
|
out_label_dir = os.path.join(pasta_base, nome_res, "labels") if usar_labels else None
|
2025-09-15 10:23:07 +00:00
|
|
|
|
|
|
|
|
msk_map = map_masks_by_base(legacy_msk)
|
2026-01-28 17:52:31 +00:00
|
|
|
msk2_map = map_masks2_by_base(legacy_msk2) if usar_masks2 else {}
|
2026-05-21 22:57:35 +00:00
|
|
|
label_map = map_labels_by_base(legacy_labels) if usar_labels else {}
|
|
|
|
|
|
|
|
|
|
if USE_LABELS and not usar_labels:
|
|
|
|
|
msg = f"[WARN] [{fonte_nome} | legacy] dual_head_label=true, mas labels/ não existe."
|
|
|
|
|
if strict_label:
|
|
|
|
|
print(msg + " Pulando legacy.")
|
|
|
|
|
return total
|
|
|
|
|
print(msg + " Seguindo sem labels.")
|
|
|
|
|
|
2025-09-15 10:23:07 +00:00
|
|
|
imgs = [f for f in os.listdir(legacy_img) if os.path.splitext(f.lower())[1] in IMG_EXTS]
|
|
|
|
|
n = len(imgs)
|
2026-05-21 22:57:35 +00:00
|
|
|
|
2025-09-15 10:23:07 +00:00
|
|
|
for i, fname in enumerate(sorted(imgs), 1):
|
|
|
|
|
base, _ = os.path.splitext(fname)
|
2026-05-21 22:57:35 +00:00
|
|
|
base_norm = normalizar_base(base)
|
|
|
|
|
|
2025-09-15 10:23:07 +00:00
|
|
|
caminho_rgb = os.path.join(legacy_img, fname)
|
2026-05-21 22:57:35 +00:00
|
|
|
caminho_mask = msk_map.get(base_norm)
|
|
|
|
|
caminho_mask2 = msk2_map.get(base_norm) if usar_masks2 else None
|
|
|
|
|
caminho_label = label_map.get(base_norm) if usar_labels else None
|
|
|
|
|
|
|
|
|
|
if usar_labels and not caminho_label:
|
|
|
|
|
msg = f"[WARN] [{fonte_nome} | legacy] label não encontrado para {fname}."
|
|
|
|
|
if strict_label:
|
|
|
|
|
print(msg + " Pulando item.")
|
|
|
|
|
continue
|
|
|
|
|
print(msg + " Seguindo sem label.")
|
|
|
|
|
|
|
|
|
|
ok, out_img_path, out_msk_path = normalize_pair(
|
|
|
|
|
caminho_rgb,
|
|
|
|
|
caminho_mask,
|
|
|
|
|
cor_para_id,
|
|
|
|
|
ignore_id,
|
|
|
|
|
out_img_dir,
|
|
|
|
|
out_msk_dir,
|
|
|
|
|
dim,
|
|
|
|
|
prefix=f"{fonte_nome}_",
|
2025-09-15 10:23:07 +00:00
|
|
|
)
|
2026-05-21 22:57:35 +00:00
|
|
|
|
|
|
|
|
out_msk2_path = None
|
2026-01-28 17:52:31 +00:00
|
|
|
if usar_masks2 and out_msk2_dir:
|
|
|
|
|
if not caminho_mask2:
|
2026-05-21 22:57:35 +00:00
|
|
|
print(f"[WARN] [{fonte_nome} | legacy] masks2 existe, mas não achei mask2 p/ {fname}.")
|
2026-01-28 17:52:31 +00:00
|
|
|
else:
|
2026-05-21 22:57:35 +00:00
|
|
|
_, out_msk2_path = normalize_pair_mask2(
|
|
|
|
|
caminho_rgb,
|
|
|
|
|
caminho_mask2,
|
|
|
|
|
out_msk2_dir,
|
|
|
|
|
dim,
|
|
|
|
|
prefix=f"{fonte_nome}_",
|
2026-01-28 17:52:31 +00:00
|
|
|
)
|
2026-05-21 22:57:35 +00:00
|
|
|
|
|
|
|
|
if usar_labels and caminho_label and out_label_dir:
|
|
|
|
|
normalize_label(
|
|
|
|
|
caminho_rgb=caminho_rgb,
|
|
|
|
|
caminho_label=caminho_label,
|
|
|
|
|
out_label_dir=out_label_dir,
|
|
|
|
|
out_img_path=out_img_path,
|
|
|
|
|
out_msk_path=out_msk_path,
|
|
|
|
|
out_msk2_path=out_msk2_path,
|
|
|
|
|
source_root=legacy_root,
|
|
|
|
|
output_root=os.path.join(pasta_base, nome_res),
|
|
|
|
|
grupo=None,
|
|
|
|
|
fonte_nome=fonte_nome,
|
|
|
|
|
prefix=f"{fonte_nome}_",
|
|
|
|
|
)
|
|
|
|
|
|
2025-09-15 10:23:07 +00:00
|
|
|
if ok:
|
|
|
|
|
total += 1
|
2026-05-21 22:57:35 +00:00
|
|
|
|
2025-09-15 10:23:07 +00:00
|
|
|
print(f"[{fonte_nome} | legacy | {nome_res}] {i}/{n} → {fname}")
|
2026-05-21 22:57:35 +00:00
|
|
|
|
2025-09-15 10:23:07 +00:00
|
|
|
return total
|
|
|
|
|
|
2026-05-21 22:57:35 +00:00
|
|
|
|
|
|
|
|
# ===================== MAIN =====================
|
|
|
|
|
|
2025-09-15 10:23:07 +00:00
|
|
|
def main(args):
|
|
|
|
|
cor_para_id, _colormap_rgb, _id_para_nome, ignore_rgb = carregar_labelmap_completo(labelmap_path)
|
|
|
|
|
ignore_id = infer_ignore_id(ignore_rgb, default_id=255)
|
2026-05-21 22:57:35 +00:00
|
|
|
|
|
|
|
|
print(f"[INFO] cor_para_id: {cor_para_id}")
|
|
|
|
|
print(f"[INFO] classes: {_id_para_nome}")
|
|
|
|
|
print(f"[INFO] dual_head_mask/masks2: {USE_MASKS2}")
|
|
|
|
|
print(f"[INFO] dual_head_label/labels: {USE_LABELS}")
|
2025-09-15 10:23:07 +00:00
|
|
|
|
|
|
|
|
total_geral = 0
|
2026-05-21 22:57:35 +00:00
|
|
|
|
2025-09-15 10:23:07 +00:00
|
|
|
orig_group_root = os.path.join(pasta_base, "original", "group")
|
|
|
|
|
if os.path.isdir(orig_group_root):
|
2026-05-21 22:57:35 +00:00
|
|
|
total_geral += process_group_root(
|
|
|
|
|
orig_group_root,
|
|
|
|
|
"original",
|
|
|
|
|
cor_para_id,
|
|
|
|
|
ignore_id,
|
|
|
|
|
groups_except=args.groups_except,
|
|
|
|
|
strict_label=args.strict_label,
|
|
|
|
|
)
|
2025-09-15 10:23:07 +00:00
|
|
|
else:
|
|
|
|
|
legacy_img = os.path.join(pasta_base, "original", "images")
|
|
|
|
|
legacy_msk = os.path.join(pasta_base, "original", "masks")
|
2026-05-21 22:57:35 +00:00
|
|
|
total_geral += process_legacy_root(
|
|
|
|
|
legacy_img,
|
|
|
|
|
legacy_msk,
|
|
|
|
|
"original",
|
|
|
|
|
cor_para_id,
|
|
|
|
|
ignore_id,
|
|
|
|
|
strict_label=args.strict_label,
|
|
|
|
|
)
|
2025-09-15 10:23:07 +00:00
|
|
|
|
|
|
|
|
aug_group_root = os.path.join(pasta_base, "augmented", "group")
|
|
|
|
|
if os.path.isdir(aug_group_root):
|
2026-05-21 22:57:35 +00:00
|
|
|
total_geral += process_group_root(
|
|
|
|
|
aug_group_root,
|
|
|
|
|
"augmented",
|
|
|
|
|
cor_para_id,
|
|
|
|
|
ignore_id,
|
|
|
|
|
groups_except=args.groups_except,
|
|
|
|
|
strict_label=args.strict_label,
|
|
|
|
|
)
|
2025-09-15 10:23:07 +00:00
|
|
|
else:
|
|
|
|
|
legacy_img = os.path.join(pasta_base, "augmented", "images")
|
|
|
|
|
legacy_msk = os.path.join(pasta_base, "augmented", "masks")
|
2026-05-21 22:57:35 +00:00
|
|
|
total_geral += process_legacy_root(
|
|
|
|
|
legacy_img,
|
|
|
|
|
legacy_msk,
|
|
|
|
|
"augmented",
|
|
|
|
|
cor_para_id,
|
|
|
|
|
ignore_id,
|
|
|
|
|
strict_label=args.strict_label,
|
|
|
|
|
)
|
2025-09-15 10:23:07 +00:00
|
|
|
|
|
|
|
|
print(f"\n✅ Concluído! Total normalizados: {total_geral}")
|
|
|
|
|
|
2026-03-03 12:36:34 +00:00
|
|
|
global GLOBAL_SUM, GLOBAL_SUMSQ, GLOBAL_PIXELS
|
|
|
|
|
if GLOBAL_SUM is not None and GLOBAL_PIXELS > 0:
|
2026-05-21 22:57:35 +00:00
|
|
|
mean = GLOBAL_SUM / GLOBAL_PIXELS
|
|
|
|
|
var = (GLOBAL_SUMSQ / GLOBAL_PIXELS) - mean ** 2
|
|
|
|
|
std = np.sqrt(np.maximum(var, 1e-6))
|
2026-03-03 12:36:34 +00:00
|
|
|
|
|
|
|
|
mean_list = mean.tolist()
|
2026-05-21 22:57:35 +00:00
|
|
|
std_list = std.tolist()
|
2026-04-20 18:53:28 +00:00
|
|
|
channel_names = ["R", "G", "B"]
|
2026-03-03 12:36:34 +00:00
|
|
|
|
|
|
|
|
stats = {
|
|
|
|
|
"channels": channel_names[:len(mean_list)],
|
|
|
|
|
"mean": mean_list,
|
|
|
|
|
"std": std_list,
|
|
|
|
|
"pixels_per_channel": int(GLOBAL_PIXELS),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
garantir_dir(save_path)
|
|
|
|
|
stats_path = os.path.join(save_path, "norm_stats.json")
|
|
|
|
|
with open(stats_path, "w", encoding="utf-8") as f:
|
|
|
|
|
json.dump(stats, f, indent=2, ensure_ascii=False)
|
|
|
|
|
|
|
|
|
|
print(f"📁 Stats salvos em: {stats_path}")
|
|
|
|
|
print(f" mean: {mean_list}")
|
|
|
|
|
print(f" std : {std_list}")
|
|
|
|
|
else:
|
2026-05-21 22:57:35 +00:00
|
|
|
print("⚠️ Nenhuma imagem processada, não há stats para salvar.")
|
|
|
|
|
|
2026-03-03 12:36:34 +00:00
|
|
|
|
2025-09-15 10:23:07 +00:00
|
|
|
if __name__ == "__main__":
|
2026-05-21 22:57:35 +00:00
|
|
|
ap = argparse.ArgumentParser(description="Normalização por grupos images/masks/masks2/labels.")
|
|
|
|
|
ap.add_argument(
|
|
|
|
|
"--groups-except",
|
|
|
|
|
type=str,
|
|
|
|
|
default="",
|
|
|
|
|
help="Lista de grupos para não usar, separados por vírgula. Ex: chao,erva_cana",
|
|
|
|
|
)
|
|
|
|
|
ap.add_argument(
|
|
|
|
|
"--strict-label",
|
|
|
|
|
action="store_true",
|
|
|
|
|
help="Se dual_head_label=true e faltar label, pula item/grupo.",
|
|
|
|
|
)
|
2025-09-15 10:23:07 +00:00
|
|
|
args = ap.parse_args()
|
|
|
|
|
main(args)
|