Implementacao do novo modelo de segmentacao ao visual worker
This commit is contained in:
parent
105660565d
commit
c805f6103b
|
|
@ -11,7 +11,7 @@ import cv2
|
|||
GST_LAUNCH = r"C:\Program Files\gstreamer\1.0\msvc_x86_64\bin\gst-launch-1.0.exe"
|
||||
|
||||
class CameraOak:
|
||||
def __init__(self, mostrar_log, mx_id, modelo_ia_seg=None, modelo_ia_det=None):
|
||||
def __init__(self, mostrar_log, mx_id, modelo_ia_seg=None, modelo_ia_det=None, iniciar_imu=False):
|
||||
self.mostrar_log = mostrar_log
|
||||
self.modelo_ia_seg = modelo_ia_seg
|
||||
self.modelo_ia_det = modelo_ia_det
|
||||
|
|
@ -81,7 +81,7 @@ class CameraOak:
|
|||
_camera["modelo"] = self.modelo
|
||||
_camera["dispositivo"] = self.dispositivo.value
|
||||
_camera["tem_depht"] = self.tem_depth
|
||||
_camera["tem_imu"] = self.tem_imu
|
||||
_camera["tem_imu"] = iniciar_imu and self.tem_imu
|
||||
|
||||
# Criar processo para transmissao de video
|
||||
try:
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -75,11 +75,13 @@ def load_seg_config(force_reload=False):
|
|||
"debug_visual": True,
|
||||
"ia_roi_begin": 0.0,
|
||||
"ia_roi_size": 1.0,
|
||||
"ia_resolution": [512,288],
|
||||
"det_every_n": 1,
|
||||
"ia_resolution": [1024,576],
|
||||
"seg_every_n": 1,
|
||||
"det_every_n": 3,
|
||||
}
|
||||
_CONFIG_CACHE["ia_model_path"] = ContextoGlobalRedis.get_equipamento().get("path_ia_model_ruas_seg")
|
||||
_CONFIG_CACHE["ia_labelmap_path"] = ContextoGlobalRedis.get_equipamento().get("path_ia_labelmap_ruas_seg")
|
||||
_CONFIG_CACHE["ia_backbone"] = ContextoGlobalRedis.get_equipamento().get("ia_backbone_seg", "nvidia/segformer-b0-finetuned-ade-512-512")
|
||||
|
||||
return _CONFIG_CACHE
|
||||
|
||||
|
|
|
|||
|
|
@ -8,9 +8,8 @@ from shared.utils import encode_image_base64
|
|||
from shared.enums import StatusCarroMapa
|
||||
|
||||
class ClassesSegmentacao(IntEnum):
|
||||
RUA = 0
|
||||
CANA = 1
|
||||
OBSTACULO = 2
|
||||
NAONAVEGAVEL = 0
|
||||
NAVEGAVEL = 1
|
||||
|
||||
|
||||
class SegmentacaoManager:
|
||||
|
|
@ -49,34 +48,6 @@ class SegmentacaoManager:
|
|||
self.max_len = max_len or int(self.janela_s * fps_esperado * 1.5)
|
||||
self._status_hist = deque(maxlen=self.max_len)
|
||||
|
||||
# histerese por banda
|
||||
self._has_top_prev = False
|
||||
self._has_bot_prev = False
|
||||
self._thr_top_on = 0.20
|
||||
self._thr_top_off = 0.10
|
||||
self._thr_bot_on = 0.20
|
||||
self._thr_bot_off = 0.10
|
||||
|
||||
def _segmentar_predictions(self, predictions):
|
||||
try:
|
||||
self.pred_rgb[:] = self.lut[predictions]
|
||||
|
||||
mask_color = self.pred_rgb
|
||||
frame_color = encode_image_base64(mask_color)
|
||||
|
||||
return {
|
||||
"timestamp": time.time(),
|
||||
"frame": {
|
||||
"timestamp": time.time(),
|
||||
"frame": frame_color
|
||||
},
|
||||
"mask_color": mask_color,
|
||||
"classes": predictions
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
print(f"Erro ao processar predictions: {e}")
|
||||
return None
|
||||
|
||||
def segmentar(self, predictions):
|
||||
try:
|
||||
|
|
@ -99,326 +70,26 @@ class SegmentacaoManager:
|
|||
self.log = f"❌ Erro na segmentação: {e}"
|
||||
return None, self.log
|
||||
|
||||
def calcular_perfil_corredor(self, matriz_conf, fov_h_rad):
|
||||
altura_grid = len(matriz_conf)
|
||||
largura_grid = len(matriz_conf[0])
|
||||
perfil = []
|
||||
def _segmentar_predictions(self, predictions):
|
||||
try:
|
||||
self.pred_rgb[:] = self.lut[predictions]
|
||||
|
||||
for i in range(altura_grid):
|
||||
x_esquerda = None
|
||||
x_direita = None
|
||||
mask_color = self.pred_rgb
|
||||
frame_color = encode_image_base64(mask_color)
|
||||
|
||||
# Coleta todos os valores válidos de profundidade da linha
|
||||
profundidades = [cel.get("prof_ref", 0.0) for cel in matriz_conf[i] if cel.get("prof_ref", 0.0) > 0]
|
||||
return {
|
||||
"timestamp": time.time(),
|
||||
"frame": {
|
||||
"timestamp": time.time(),
|
||||
"frame": frame_color
|
||||
},
|
||||
"mask_color": mask_color,
|
||||
"classes": predictions
|
||||
}
|
||||
|
||||
if len(profundidades) == 0:
|
||||
continue
|
||||
|
||||
# Usa a mediana como valor de z mais estável
|
||||
z = float(np.median(profundidades))
|
||||
|
||||
for j in range(largura_grid):
|
||||
cel = matriz_conf[i][j]
|
||||
ind_chao = cel.get("indice_seg_chao", 0.0)
|
||||
if ind_chao < 0.3:
|
||||
continue
|
||||
|
||||
# Ângulo relativo do centro da célula
|
||||
ang_normalizado = (j + 0.5 - (largura_grid / 2)) / (largura_grid / 2)
|
||||
ang_rad = ang_normalizado * (fov_h_rad / 2)
|
||||
|
||||
# Posição X central da célula
|
||||
x_centro = np.tan(ang_rad) * z
|
||||
|
||||
# Ângulo por célula para deslocamento parcial
|
||||
escala_angular = np.tan(fov_h_rad / largura_grid) * z
|
||||
|
||||
if j < largura_grid / 2:
|
||||
x_real = x_centro - ind_chao * (escala_angular / 1.6)
|
||||
if x_esquerda is None or x_real < x_esquerda:
|
||||
x_esquerda = x_real
|
||||
else:
|
||||
x_real = x_centro + ind_chao * (escala_angular / 1.6)
|
||||
if x_direita is None or x_real > x_direita:
|
||||
x_direita = x_real
|
||||
|
||||
if x_esquerda is not None and x_direita is not None:
|
||||
esquerda_m = abs(x_esquerda)
|
||||
direita_m = x_direita
|
||||
largura = direita_m + esquerda_m
|
||||
else:
|
||||
esquerda_m = direita_m = largura = 0.0
|
||||
|
||||
perfil.append({
|
||||
"distancia_m": round(z, 3),
|
||||
"esquerda_m": round(float(esquerda_m), 3),
|
||||
"direita_m": round(float(direita_m), 3),
|
||||
"largura_m": round(float(largura), 3)
|
||||
})
|
||||
|
||||
return perfil
|
||||
|
||||
|
||||
|
||||
@staticmethod
|
||||
def _now():
|
||||
# monotonic evita saltos de relógio
|
||||
return time.monotonic()
|
||||
|
||||
def _bool_hysteresis(self, prev: bool, x: float, thr_on: float, thr_off: float) -> bool:
|
||||
return (x >= (thr_off if prev else thr_on))
|
||||
|
||||
def _maioria_ultimos(self, janela_s: float | None = None) -> StatusCarroMapa:
|
||||
"""Maioria ponderada pelos últimos 'janela_s' segundos.
|
||||
Se 'janela_s' None → usa self.janela_s."""
|
||||
J = self.janela_s if janela_s is None else float(janela_s)
|
||||
t_now = self._now()
|
||||
|
||||
# 1) limpa itens FORA da janela
|
||||
while self._status_hist and (t_now - self._status_hist[0][1] > J):
|
||||
self._status_hist.popleft()
|
||||
|
||||
if not self._status_hist:
|
||||
# fallback razoável
|
||||
return StatusCarroMapa.Direcionando
|
||||
|
||||
# 2) maioria simples (pode trocar por peso exponencial se quiser)
|
||||
cont = {}
|
||||
for st, _t in self._status_hist:
|
||||
cont[st] = cont.get(st, 0) + 1
|
||||
|
||||
# regra de desempate: prioriza estado mais recente em caso de empate
|
||||
top_freq = max(cont.values())
|
||||
empatados = [st for st, c in cont.items() if c == top_freq]
|
||||
if len(empatados) == 1:
|
||||
return empatados[0]
|
||||
else:
|
||||
# desempata olhando do fim pro início (mais recente primeiro)
|
||||
for st, _t in reversed(self._status_hist):
|
||||
if st in empatados:
|
||||
return st
|
||||
|
||||
def classificar_por_cana(self, mask_cana, mask_main=None, near_is_bottom=True):
|
||||
H, W = mask_cana.shape
|
||||
mid = H // 2
|
||||
bottom = mask_cana[mid:, :] if near_is_bottom else mask_cana[:mid, :]
|
||||
top = mask_cana[:mid, :] if near_is_bottom else mask_cana[mid:, :]
|
||||
|
||||
area_top = top.size
|
||||
area_bot = bottom.size
|
||||
p_top = float(top.sum()) / max(1, area_top)
|
||||
p_bot = float(bottom.sum()) / max(1, area_bot)
|
||||
|
||||
has_top = self._bool_hysteresis(self._has_top_prev, p_top, self._thr_top_on, self._thr_top_off)
|
||||
has_bot = self._bool_hysteresis(self._has_bot_prev, p_bot, self._thr_bot_on, self._thr_bot_off)
|
||||
self._has_top_prev, self._has_bot_prev = has_top, has_bot
|
||||
|
||||
if not has_top and not has_bot:
|
||||
status_now = StatusCarroMapa.Direcionando
|
||||
elif has_top and not has_bot:
|
||||
status_now = StatusCarroMapa.EntrandoRua
|
||||
elif has_bot and not has_top:
|
||||
status_now = StatusCarroMapa.SaindoRua
|
||||
else:
|
||||
status_now = StatusCarroMapa.CaminhandoRua
|
||||
|
||||
# empilha (status, timestamp)
|
||||
self._status_hist.append((status_now, self._now()))
|
||||
|
||||
status_final = self._maioria_ultimos() # maioria na janela fixa
|
||||
return status_now, status_final, p_top, p_bot, has_top, has_bot
|
||||
|
||||
|
||||
|
||||
def _extrair_corredor_principal(self, mask_classes):
|
||||
H, W = mask_classes.shape
|
||||
cx_img = W // 2
|
||||
|
||||
mask_rua = (mask_classes == ClassesSegmentacao.RUA.value).astype(np.uint8)
|
||||
|
||||
# optional: fecha buracos pequenos
|
||||
# kernel = np.ones((3,3), np.uint8)
|
||||
# mask_rua = cv2.morphologyEx(mask_rua, cv2.MORPH_CLOSE, kernel, iterations=1)
|
||||
|
||||
num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(mask_rua, connectivity=8)
|
||||
if num_labels <= 1:
|
||||
return np.zeros_like(mask_rua, dtype=np.uint8)
|
||||
|
||||
best_i, best_score = -1, -1e9
|
||||
|
||||
# pesos do score
|
||||
w_area = 1.0
|
||||
w_center = 1.0
|
||||
w_bottom = 1.0
|
||||
w_vertical = 0.5
|
||||
w_prev = 0.75
|
||||
|
||||
prev_cx = self._prev_cx
|
||||
|
||||
for i in range(1, num_labels): # 0 = fundo
|
||||
x, y, w, h, area = stats[i]
|
||||
if area < 50: # lixo
|
||||
continue
|
||||
|
||||
cx = x + w // 2
|
||||
# normalizações 0..1
|
||||
area_n = area / float(H * W)
|
||||
center_n = 1.0 - min(1.0, abs(cx - cx_img) / (W * 0.5))
|
||||
vertical = h / max(1.0, w) # alongamento vertical
|
||||
bottom_touch = 1.0 if (y + h >= H - 2) else 0.0
|
||||
|
||||
prev_bias = 0.0
|
||||
if prev_cx is not None:
|
||||
prev_bias = 1.0 - min(1.0, abs(cx - prev_cx) / (W * 0.5))
|
||||
|
||||
score = (w_area*area_n +
|
||||
w_center*center_n +
|
||||
w_bottom*bottom_touch +
|
||||
w_vertical*vertical +
|
||||
w_prev*prev_bias)
|
||||
|
||||
if score > best_score:
|
||||
best_score = score
|
||||
best_i = i
|
||||
|
||||
self._prev_cx = None
|
||||
if best_i == -1:
|
||||
return np.zeros_like(mask_rua, dtype=np.uint8)
|
||||
|
||||
# guarda cx do blob vencedor pro próximo frame
|
||||
bx, by, bw, bh, _ = stats[best_i]
|
||||
self._prev_cx = bx + bw // 2
|
||||
self._prev_y0 = by
|
||||
|
||||
return (labels == best_i).astype(np.uint8)
|
||||
|
||||
def _get_scanlines_y(self, H, grid_rows_y_px=None, near_is_bottom=True):
|
||||
if grid_rows_y_px and len(grid_rows_y_px) >= 5:
|
||||
ys = [int(np.clip(y, 0, H-1)) for y in grid_rows_y_px]
|
||||
# reordena do "perto" para o "longe"
|
||||
ys = sorted(ys, reverse=near_is_bottom)
|
||||
return ys
|
||||
# fallback por frações
|
||||
fracs = [0.98, 0.85, 0.70, 0.55, 0.40, 0.25, 0.10]
|
||||
return [min(H-1, max(0, int(H*f))) for f in fracs]
|
||||
|
||||
def _analisar_corredor_visual_old(self, predictions, grid_rows_y_px=None, near_is_bottom=True):
|
||||
self.predictions = predictions
|
||||
H, W = self.predictions.shape
|
||||
cx_img = W // 2
|
||||
|
||||
# 1) máscara do corredor principal
|
||||
mask_main = self._extrair_corredor_principal(self.predictions).astype(bool)
|
||||
mask_cana = (self.predictions == ClassesSegmentacao.CANA.value)
|
||||
|
||||
# 2) scanlines (grid ou fallback)
|
||||
ys = self._get_scanlines_y(H, grid_rows_y_px, near_is_bottom)
|
||||
|
||||
centros, larguras = [], []
|
||||
for y in ys:
|
||||
row = mask_main[y]
|
||||
idx = np.flatnonzero(row)
|
||||
if idx.size == 0:
|
||||
centros.append((None, y))
|
||||
larguras.append(0)
|
||||
else:
|
||||
x0, x1 = idx[0], idx[-1]
|
||||
cx = (x0 + x1) // 2
|
||||
w = (x1 - x0 + 1)
|
||||
centros.append((int(cx), y))
|
||||
larguras.append(int(w))
|
||||
|
||||
# 3) Ângulo do corredor (rad) via ajuste linear x(y)
|
||||
pts = [(x, y) for (x, y) in centros if x is not None]
|
||||
ang_rad = None
|
||||
if len(pts) >= 2:
|
||||
ys_fit = np.array([p[1] for p in pts], dtype=np.float32)
|
||||
xs_fit = np.array([p[0] for p in pts], dtype=np.float32)
|
||||
|
||||
# pesos: linhas mais próximas ao robô pesam mais
|
||||
# (primeiros ys na lista são "perto" se near_is_bottom=True)
|
||||
n = len(ys_fit)
|
||||
wts = np.linspace(1.0, 2.0, n).astype(np.float32) # simples e eficaz
|
||||
|
||||
# polyfit ponderado (equivalente com normal equations)
|
||||
# x = a*y + b
|
||||
Wm = np.diag(wts)
|
||||
Y = ys_fit.reshape(-1,1)
|
||||
X = np.hstack([Y, np.ones_like(Y)])
|
||||
# a, b = (X^T W X)^-1 X^T W x
|
||||
XtW = X.T @ Wm
|
||||
beta = np.linalg.pinv(XtW @ X) @ (XtW @ xs_fit)
|
||||
a = float(beta[0])
|
||||
|
||||
ang_rad = np.arctan(a)
|
||||
# wrap correto em radianos
|
||||
ang_rad = (ang_rad + np.pi) % (2*np.pi) - np.pi
|
||||
|
||||
# 4) Erro lateral (% da largura na base)
|
||||
erro_lateral_pct = 0.0
|
||||
if centros and centros[0][0] is not None:
|
||||
x_base, y_base = centros[0]
|
||||
w_base = max(1, larguras[0])
|
||||
err_px = cx_img - x_base
|
||||
erro_lateral_pct = (err_px / w_base) * 100.0
|
||||
|
||||
# 5) Suavização (EMA)
|
||||
if ang_rad is not None:
|
||||
deg = np.degrees(ang_rad)
|
||||
if self._ema_ang is None:
|
||||
self._ema_ang = deg
|
||||
else:
|
||||
a = self._ema_alpha_ang
|
||||
# unwrap simples para evitar saltos de ±180
|
||||
delta = ((deg - self._ema_ang + 180) % 360) - 180
|
||||
self._ema_ang = self._ema_ang + a * delta
|
||||
ang_out = round(self._ema_ang, 3)
|
||||
else:
|
||||
ang_out = None
|
||||
|
||||
if True: # sempre temos lateral pct numérico
|
||||
if self._ema_lat is None:
|
||||
self._ema_lat = erro_lateral_pct
|
||||
else:
|
||||
a = self._ema_alpha_lat
|
||||
self._ema_lat = (1 - a) * self._ema_lat + a * erro_lateral_pct
|
||||
lat_out = round(float(self._ema_lat), 3)
|
||||
|
||||
# 6) Status baseado em presença nas scanlines
|
||||
# proximidade: usa 2 mais perto e 2 mais longe
|
||||
near_valid = sum(1 for (x,_) in centros[:2] if x is not None)
|
||||
far_valid = sum(1 for (x,_) in centros[-2:] if x is not None)
|
||||
any_valid = sum(1 for (x,_) in centros if x is not None)
|
||||
|
||||
# --- STATUS pela regra das metades (CANA) ---
|
||||
status_now, status_final, p_top, p_bot, has_top, has_bot = self._classificar_status_por_cana(
|
||||
mask_cana.astype(np.uint8), mask_main, near_is_bottom=near_is_bottom
|
||||
)
|
||||
|
||||
# 7) Persistência (maioria em N frames)
|
||||
self._status_hist.append(status_now)
|
||||
status_final = max(set(self._status_hist), key=self._status_hist.count)
|
||||
self._last_status = status_final
|
||||
|
||||
# 8) Confiança
|
||||
confianca = any_valid / max(1, len(centros))
|
||||
|
||||
return {
|
||||
"height": H,
|
||||
"width": W,
|
||||
"erro_angular": ang_out,
|
||||
"erro_lateral_pct": lat_out,
|
||||
"status_corredor": status_final.value,
|
||||
"centros_corredor": centros,
|
||||
"larguras_px": larguras,
|
||||
"confianca": round(float(sum(1 for (x,_) in centros if x is not None) / max(1, len(centros))), 3),
|
||||
|
||||
# DEBUG/telemetria úteis
|
||||
"p_cana_top": round(float(p_top), 3),
|
||||
"p_cana_bottom": round(float(p_bot), 3),
|
||||
"has_top": bool(has_top),
|
||||
"has_bottom": bool(has_bot),
|
||||
}
|
||||
except Exception as e:
|
||||
print(f"Erro ao processar predictions: {e}")
|
||||
return None
|
||||
|
||||
def _analisar_corredor_visual(self, predictions, grid_rows_y_px=None, near_is_bottom=True):
|
||||
# --- inputs/base ---
|
||||
|
|
@ -426,21 +97,18 @@ class SegmentacaoManager:
|
|||
H, W = self.predictions.shape
|
||||
cx_img = W // 2
|
||||
|
||||
# máscara do "corredor principal" (telemetria/ângulo); pode vir vazia
|
||||
mask_main = self._extrair_corredor_principal(self.predictions).astype(bool)
|
||||
# opcional (se ainda usar em outros pontos)
|
||||
mask_cana = (self.predictions == ClassesSegmentacao.CANA.value)
|
||||
mask_corredor = self._extrair_corredor_principal(self.predictions).astype(bool)
|
||||
|
||||
# --- score de corredor por grid (robusto) ---
|
||||
score, centers_col, left_col, right_col, valid_row = self._corridor_score_grid(self.predictions)
|
||||
score, centers_col, left_col, right_col = self._corridor_score_grid(self.predictions)
|
||||
|
||||
# --- scanlines para centro/ângulo (telemetria) ---
|
||||
ys = self._get_scanlines_y(H, grid_rows_y_px, near_is_bottom)
|
||||
centros, larguras = [], []
|
||||
|
||||
if mask_main.any():
|
||||
if mask_corredor.any():
|
||||
for y in ys:
|
||||
row = mask_main[y]
|
||||
row = mask_corredor[y]
|
||||
idx = np.flatnonzero(row)
|
||||
if idx.size == 0:
|
||||
centros.append((None, y))
|
||||
|
|
@ -478,9 +146,9 @@ class SegmentacaoManager:
|
|||
|
||||
# pesos: linhas "mais perto" pesam mais
|
||||
if near_is_bottom:
|
||||
wts = np.linspace(1.0, 2.0, n, dtype=np.float32) # crescente da base ao topo do vetor pts
|
||||
else:
|
||||
wts = np.linspace(2.0, 1.0, n, dtype=np.float32)
|
||||
else:
|
||||
wts = np.linspace(1.0, 2.0, n, dtype=np.float32)
|
||||
|
||||
Wm = np.diag(wts)
|
||||
Y = ys_fit.reshape(-1, 1)
|
||||
|
|
@ -521,7 +189,8 @@ class SegmentacaoManager:
|
|||
self._ema_lat = (1 - self._ema_alpha_lat) * self._ema_lat + self._ema_alpha_lat * float(erro_lateral_pct)
|
||||
lat_out = round(float(self._ema_lat), 3)
|
||||
|
||||
status_now, status_final, p_top, p_bot, has_top, has_bot = self.classificar_por_cana(mask_cana, mask_main)
|
||||
mask_nav = (self.predictions == ClassesSegmentacao.NAVEGAVEL.value)
|
||||
status_now, status_final, prob, debug = self.classificar_status_corredor(mask_nav)
|
||||
|
||||
return {
|
||||
"timestamp": time.time(),
|
||||
|
|
@ -529,75 +198,74 @@ class SegmentacaoManager:
|
|||
"width": W,
|
||||
"erro_angular": ang_out,
|
||||
"erro_lateral_pct": lat_out,
|
||||
"status_corredor": status_final.value, # mantém teu contrato atual (enum -> int)
|
||||
"status_corredor": status_final.value,
|
||||
"centros_corredor": centros,
|
||||
"larguras_px": larguras,
|
||||
"confianca": round(float(score), 3), # agora a confiança é o score de corredor
|
||||
# (se quiser debugar) "near_ok": near_ok, "far_ok": far_ok,
|
||||
"confianca": round(float(score), 3),
|
||||
}
|
||||
|
||||
def _extrair_corredor_principal(self, mask_nav):
|
||||
H, W = mask_nav.shape
|
||||
cx_img = W // 2
|
||||
|
||||
def display_segmentation_debug(self, frame, largura_robo_px):
|
||||
try:
|
||||
original = cv2.resize(frame, self.resolucao)
|
||||
num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(mask_nav, connectivity=8)
|
||||
if num_labels <= 1:
|
||||
return np.zeros_like(mask_nav, dtype=np.uint8)
|
||||
|
||||
seg_color = np.zeros_like(original)
|
||||
for class_id, color in enumerate(self.color_map):
|
||||
seg_color[self.predictions == class_id] = color
|
||||
best_i, best_score = -1, -1e9
|
||||
|
||||
overlay = cv2.addWeighted(original, 0.5, seg_color, 0.5, 0)
|
||||
# pesos do score
|
||||
w_area = 1.0
|
||||
w_center = 1.0
|
||||
w_bottom = 1.0
|
||||
w_vertical = 0.5
|
||||
w_prev = 0.75
|
||||
|
||||
centros_corredor = self.dados_visuais.get("centros_corredor")
|
||||
prev_cx = self._prev_cx
|
||||
|
||||
# 1. Linha do centro do corredor + largura do robô
|
||||
if centros_corredor and len(centros_corredor) >= 2:
|
||||
for ponto in centros_corredor:
|
||||
x, y = ponto
|
||||
if x is None or y is None: continue
|
||||
cv2.circle(overlay, ponto, 4, (0, 255, 255), -1)
|
||||
for i in range(len(centros_corredor) - 1):
|
||||
cv2.line(overlay, centros_corredor[i], centros_corredor[i + 1], (0, 255, 255), 2)
|
||||
for ponto in centros_corredor:
|
||||
x, y = ponto
|
||||
if x is None or y is None: continue
|
||||
cv2.line(overlay, (x - largura_robo_px // 2, y), (x + largura_robo_px // 2, y), (255, 0, 255), 1)
|
||||
for i in range(1, num_labels): # 0 = fundo
|
||||
x, y, w, h, area = stats[i]
|
||||
if area < 50: # lixo
|
||||
continue
|
||||
|
||||
# 4. Texto de métricas
|
||||
if self.dados_visuais:
|
||||
erro_lateral_pct = self.dados_visuais["erro_lateral_pct"]
|
||||
erro_angular = np.degrees(self.dados_visuais["erro_angular"]) if self.dados_visuais["erro_angular"] else 0
|
||||
texto = [
|
||||
f"Erro angular: {erro_angular:.2f} graus",
|
||||
f"Erro lateral: {erro_lateral_pct:.2f} %",
|
||||
f"Status: {StatusCarroMapa(self.dados_visuais['status_corredor']).name}"
|
||||
]
|
||||
for i, t in enumerate(texto):
|
||||
cv2.putText(overlay, t, (10, 25 + i * 25), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
|
||||
cx = x + w // 2
|
||||
# normalizações 0..1
|
||||
area_n = area / float(H * W)
|
||||
center_n = 1.0 - min(1.0, abs(cx - cx_img) / (W * 0.5))
|
||||
vertical = h / max(1.0, w) # alongamento vertical
|
||||
bottom_touch = 1.0 if (y + h >= H - 2) else 0.0
|
||||
|
||||
# 5. Legenda das classes
|
||||
legenda_inicio_y = 140
|
||||
for i, cor in enumerate(self.color_map):
|
||||
nome = self.classes[i]
|
||||
pos_y = legenda_inicio_y + i * 30
|
||||
cv2.rectangle(overlay, (10, pos_y - 15), (30, pos_y + 5), cor, -1)
|
||||
cv2.putText(overlay, nome, (40, pos_y + 2), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255,255,255), 1, cv2.LINE_AA)
|
||||
prev_bias = 0.0
|
||||
if prev_cx is not None:
|
||||
prev_bias = 1.0 - min(1.0, abs(cx - prev_cx) / (W * 0.5))
|
||||
|
||||
# Mostrar
|
||||
cv2.imshow("Segmentacao - Overlay", overlay)
|
||||
cv2.waitKey(1)
|
||||
#cv2.destroyAllWindows()
|
||||
except Exception as e:
|
||||
print(f"Erro ao gerar display_segmentation_debug: {e}")
|
||||
score = (w_area*area_n +
|
||||
w_center*center_n +
|
||||
w_bottom*bottom_touch +
|
||||
w_vertical*vertical +
|
||||
w_prev*prev_bias)
|
||||
|
||||
if score > best_score:
|
||||
best_score = score
|
||||
best_i = i
|
||||
|
||||
self._prev_cx = None
|
||||
if best_i == -1:
|
||||
return np.zeros_like(mask_nav, dtype=np.uint8)
|
||||
|
||||
# guarda cx do blob vencedor pro próximo frame
|
||||
bx, by, bw, bh, _ = stats[best_i]
|
||||
self._prev_cx = bx + bw // 2
|
||||
self._prev_y0 = by
|
||||
|
||||
return (labels == best_i).astype(np.uint8)
|
||||
|
||||
def _corridor_score_grid(self, mask_classes, rows=6, cols=21):
|
||||
CANA = ClassesSegmentacao.CANA.value
|
||||
RUA = ClassesSegmentacao.RUA.value
|
||||
|
||||
H, W = mask_classes.shape
|
||||
row_h = H // rows
|
||||
col_w = W // cols
|
||||
|
||||
# fração de CANA por célula
|
||||
# fração de NAO NAVEGAVEL por célula
|
||||
frac = np.zeros((rows, cols), np.float32)
|
||||
for i in range(rows):
|
||||
y0, y1 = i*row_h, H if i==rows-1 else (i+1)*row_h
|
||||
|
|
@ -605,11 +273,11 @@ class SegmentacaoManager:
|
|||
x0, x1 = j*col_w, W if j==cols-1 else (j+1)*col_w
|
||||
cell = mask_classes[y0:y1, x0:x1]
|
||||
if cell.size:
|
||||
frac[i, j] = np.mean(cell == CANA)
|
||||
frac[i, j] = np.mean(cell == ClassesSegmentacao.NAONAVEGAVEL.value)
|
||||
|
||||
# limiares por faixa (modelo em “A”: mais chão embaixo, mais cana no topo)
|
||||
near_lado_min, near_canal_max = 0.05, 0.70
|
||||
far_lado_min, far_canal_max = 0.45, 0.15
|
||||
far_lado_min, far_canal_max = 0.25, 0.15
|
||||
idx = np.linspace(0, 1, rows) # 0=embaixo (perto), 1=topo (longe)
|
||||
lado_min = near_lado_min + (far_lado_min - near_lado_min) * idx
|
||||
canal_max = near_canal_max + (far_canal_max - near_canal_max) * idx
|
||||
|
|
@ -670,7 +338,225 @@ class SegmentacaoManager:
|
|||
left_col[i], right_col[i] = lf, rf
|
||||
|
||||
score = valid_row.mean() # 0..1
|
||||
return float(score), centers, left_col, right_col, valid_row
|
||||
return float(score), centers, left_col, right_col
|
||||
|
||||
def _get_scanlines_y(self, H, grid_rows_y_px=None, near_is_bottom=True):
|
||||
if grid_rows_y_px and len(grid_rows_y_px) >= 5:
|
||||
ys = [int(np.clip(y, 0, H-1)) for y in grid_rows_y_px]
|
||||
# reordena do "perto" para o "longe"
|
||||
ys = sorted(ys, reverse=near_is_bottom)
|
||||
return ys
|
||||
# fallback por frações
|
||||
fracs = [0.98, 0.85, 0.70, 0.55, 0.40, 0.25, 0.10]
|
||||
return [min(H-1, max(0, int(H*f))) for f in fracs]
|
||||
|
||||
def classificar_status_corredor(self, mask_nav: np.ndarray):
|
||||
"""
|
||||
mask_nav: (H,W) com 1 = navegável, 0 = não-navegável
|
||||
Retorna:
|
||||
status_now : StatusCarroMapa
|
||||
status_final : StatusCarroMapa (igual ao now, sem histerese por enquanto)
|
||||
probs : dict[StatusCarroMapa, float] (aqui só 1.0 pro escolhido)
|
||||
debug : métricas pra log
|
||||
"""
|
||||
H, W = mask_nav.shape
|
||||
nav = (mask_nav > 0).astype(np.float32)
|
||||
|
||||
def faixa_mean(y0, y1):
|
||||
fatia = nav[y0:y1, :]
|
||||
if fatia.size == 0:
|
||||
return 0.0
|
||||
return float(fatia.mean())
|
||||
|
||||
# corta em 3 faixas: far (topo), mid (meio), near (embaixo)
|
||||
y_far_top = 0
|
||||
y_far_bot = int(0.2 * H)
|
||||
y_mid_top = y_far_bot
|
||||
y_mid_bot = int(0.4 * H)
|
||||
y_near_top = y_mid_bot
|
||||
y_near_bot = H
|
||||
|
||||
nav_far = faixa_mean(y_far_top, y_far_bot)
|
||||
nav_mid = faixa_mean(y_mid_top, y_mid_bot)
|
||||
nav_near = faixa_mean(y_near_top, y_near_bot)
|
||||
nav_global = float(nav.mean()) if nav.size > 0 else 0.0
|
||||
|
||||
# limiares & delta
|
||||
THR_NAV_ALTO = 0.95 # "quase tudo navegável"
|
||||
THR_NAV_BAIXO = 0.30 # "quase nada navegável"
|
||||
THR_NEAR_ALTO = 1.00 # near "100%"
|
||||
DELTA = 0.05 # diferença mínima pra considerar > de verdade
|
||||
|
||||
def maior_que(a, b):
|
||||
return a > min(b - DELTA, 1.0)
|
||||
|
||||
def maior_igual_que(a, b):
|
||||
return a >= min(b - DELTA, 1.0)
|
||||
|
||||
status = None
|
||||
|
||||
# 1) PARADO: quase todo frame não navegável
|
||||
if nav_global < THR_NAV_BAIXO:
|
||||
status = StatusCarroMapa.Parado
|
||||
|
||||
# 2) DIRECIONANDO: quase todo frame navegável
|
||||
elif nav_global > THR_NAV_ALTO:
|
||||
status = StatusCarroMapa.Direcionando
|
||||
|
||||
else:
|
||||
# 3) ENTRANDO RUA
|
||||
cond_near_alto = nav_near >= THR_NEAR_ALTO
|
||||
cond_near_gt_mid = maior_igual_que(nav_near, nav_mid)
|
||||
cond_mid_gt_far = maior_que(nav_mid, nav_far)
|
||||
|
||||
# 4) SAINDO RUA
|
||||
cond_near_gt_mid2 = True or maior_que(nav_near, nav_mid)
|
||||
cond_far_gt_mid = maior_que(nav_far, nav_mid)
|
||||
|
||||
if cond_near_alto and cond_near_gt_mid and cond_mid_gt_far:
|
||||
status = StatusCarroMapa.EntrandoRua
|
||||
|
||||
elif cond_near_gt_mid2 and cond_far_gt_mid:
|
||||
status = StatusCarroMapa.SaindoRua
|
||||
|
||||
# 5) CAMINHANDO RUA (cone "normal" NEAR > MID > FAR)
|
||||
elif cond_near_gt_mid and cond_mid_gt_far:
|
||||
status = StatusCarroMapa.CaminhandoRua
|
||||
|
||||
else:
|
||||
# fallback: se ficar numa zona cinza, chama de Direcionando
|
||||
status = StatusCarroMapa.Manobrando
|
||||
|
||||
# monta probs "one-hot"
|
||||
probs = {s: 0.0 for s in StatusCarroMapa}
|
||||
probs[status] = 1.0
|
||||
|
||||
debug = {
|
||||
"nav_near": nav_near,
|
||||
"nav_mid": nav_mid,
|
||||
"nav_far": nav_far,
|
||||
"nav_global": nav_global,
|
||||
"THR_NAV_ALTO": THR_NAV_ALTO,
|
||||
"THR_NAV_BAIXO": THR_NAV_BAIXO,
|
||||
"THR_NEAR_ALTO": THR_NEAR_ALTO,
|
||||
}
|
||||
|
||||
status_now = status
|
||||
|
||||
# Histerese temporal: mantém teu esquema de histórico
|
||||
self._status_hist.append((status_now, self._now()))
|
||||
status_final = self._maioria_ultimos()
|
||||
|
||||
return status_now, status_final, probs, debug
|
||||
|
||||
@staticmethod
|
||||
def _now():
|
||||
# monotonic evita saltos de relógio
|
||||
return time.monotonic()
|
||||
|
||||
def _bool_hysteresis(self, prev: bool, x: float, thr_on: float, thr_off: float) -> bool:
|
||||
return (x >= (thr_off if prev else thr_on))
|
||||
|
||||
def _maioria_ultimos(self, janela_s: float | None = None) -> StatusCarroMapa:
|
||||
"""Maioria ponderada pelos últimos 'janela_s' segundos.
|
||||
Se 'janela_s' None → usa self.janela_s."""
|
||||
J = self.janela_s if janela_s is None else float(janela_s)
|
||||
t_now = self._now()
|
||||
|
||||
# 1) limpa itens FORA da janela
|
||||
while self._status_hist and (t_now - self._status_hist[0][1] > J):
|
||||
self._status_hist.popleft()
|
||||
|
||||
if not self._status_hist:
|
||||
# fallback razoável
|
||||
return StatusCarroMapa.Direcionando
|
||||
|
||||
# 2) maioria simples (pode trocar por peso exponencial se quiser)
|
||||
cont = {}
|
||||
for st, _t in self._status_hist:
|
||||
cont[st] = cont.get(st, 0) + 1
|
||||
|
||||
# regra de desempate: prioriza estado mais recente em caso de empate
|
||||
top_freq = max(cont.values())
|
||||
empatados = [st for st, c in cont.items() if c == top_freq]
|
||||
if len(empatados) == 1:
|
||||
return empatados[0]
|
||||
else:
|
||||
# desempata olhando do fim pro início (mais recente primeiro)
|
||||
for st, _t in reversed(self._status_hist):
|
||||
if st in empatados:
|
||||
return st
|
||||
|
||||
|
||||
def largura_robo_px_por_distancia(self, d_m, largura_robo_m, largura_frame_px, fov_h_graus):
|
||||
fov_h_rad = np.radians(fov_h_graus)
|
||||
largura_real_visivel_m = 2.0 * d_m * np.tan(fov_h_rad / 2.0)
|
||||
|
||||
if largura_real_visivel_m <= 1e-6:
|
||||
return None
|
||||
|
||||
frac = largura_robo_m / largura_real_visivel_m
|
||||
frac = np.clip(frac, 0.0, 1.2) # evita exagero visual
|
||||
|
||||
return int(frac * largura_frame_px)
|
||||
|
||||
def display_segmentation_debug(self, frame, largura_robo_m, grid_ref):
|
||||
try:
|
||||
original = cv2.resize(frame, self.resolucao)
|
||||
|
||||
seg_color = np.zeros_like(original)
|
||||
for class_id in range(len(self.color_map)):
|
||||
seg_color[self.predictions == class_id] = self.lut[class_id]
|
||||
|
||||
overlay = cv2.addWeighted(original, 0.5, seg_color, 0.5, 0)
|
||||
|
||||
centros_corredor = self.dados_visuais.get("centros_corredor")
|
||||
|
||||
# 1. Linha do centro do corredor + largura do robô
|
||||
if centros_corredor and len(centros_corredor) >= 2:
|
||||
for ponto in centros_corredor:
|
||||
x, y = ponto
|
||||
if x is None or y is None: continue
|
||||
cv2.circle(overlay, ponto, 4, (0, 255, 255), -1)
|
||||
for i in range(len(centros_corredor) - 1):
|
||||
cv2.line(overlay, centros_corredor[i], centros_corredor[i + 1], (0, 255, 255), 2)
|
||||
H, W = overlay.shape[:2]
|
||||
for idx, (x, y) in enumerate(centros_corredor):
|
||||
if x is None or y is None:
|
||||
continue
|
||||
# mapear idx -> distância real
|
||||
if idx >= len(grid_ref):
|
||||
continue
|
||||
d_m = grid_ref[len(grid_ref) - 1 - idx]
|
||||
largura_px = self.largura_robo_px_por_distancia(d_m, largura_robo_m=largura_robo_m, largura_frame_px=W, fov_h_graus=69.0)
|
||||
if largura_px is None:
|
||||
continue
|
||||
cv2.line(overlay, (x - largura_px // 2, y), (x + largura_px // 2, y), (255, 0, 255), 1)
|
||||
|
||||
# 4. Texto de métricas
|
||||
if self.dados_visuais:
|
||||
erro_lateral_pct = self.dados_visuais["erro_lateral_pct"]
|
||||
erro_angular = self.dados_visuais["erro_angular"] if self.dados_visuais["erro_angular"] else 0
|
||||
texto = [
|
||||
f"Erro angular: {erro_angular:.2f} graus",
|
||||
f"Erro lateral: {erro_lateral_pct:.2f} %",
|
||||
f"Status: {StatusCarroMapa(self.dados_visuais['status_corredor']).name}"
|
||||
]
|
||||
for i, t in enumerate(texto):
|
||||
cv2.putText(overlay, t, (10, 25 + i * 25), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
|
||||
|
||||
# 5. Legenda das classes
|
||||
legenda_inicio_y = 140
|
||||
for i, _ in enumerate(self.color_map):
|
||||
nome = self.classes[i]
|
||||
pos_y = legenda_inicio_y + i * 30
|
||||
cor_bgr = tuple(int(c) for c in self.lut[i])
|
||||
cv2.rectangle(overlay, (10, pos_y - 15), (30, pos_y + 5), cor_bgr, -1)
|
||||
cv2.putText(overlay, nome, (40, pos_y + 2), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255,255,255), 1, cv2.LINE_AA)
|
||||
|
||||
# Mostrar
|
||||
cv2.imshow("Segmentacao - Overlay", overlay)
|
||||
cv2.waitKey(1)
|
||||
#cv2.destroyAllWindows()
|
||||
except Exception as e:
|
||||
print(f"Erro ao gerar display_segmentation_debug: {e}")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,91 @@
|
|||
import argparse
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Renomeia imagens usando a data de modificação no formato ddMMyyyy_HHmmss.ext"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--images_dir",
|
||||
type=str,
|
||||
required=True,
|
||||
help="Pasta com as imagens (sem recursão). Ex: dataset/new_images",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--force_jpeg",
|
||||
action="store_true",
|
||||
help="Se setado, força a extensão .jpeg em todos os arquivos.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dry_run",
|
||||
action="store_true",
|
||||
help="Se setado, só mostra o que faria, sem renomear nada.",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
images_dir = Path(args.images_dir)
|
||||
if not images_dir.is_dir():
|
||||
raise SystemExit(f"Pasta não encontrada: {images_dir}")
|
||||
|
||||
exts = {".jpg", ".jpeg", ".png", ".bmp", ".tif", ".tiff"}
|
||||
|
||||
files = [
|
||||
p for p in sorted(images_dir.iterdir())
|
||||
if p.is_file() and p.suffix.lower() in exts
|
||||
]
|
||||
|
||||
if not files:
|
||||
print("[INFO] Nenhuma imagem encontrada na pasta.")
|
||||
return
|
||||
|
||||
print(f"[INFO] Encontrados {len(files)} arquivos de imagem em {images_dir}")
|
||||
|
||||
# Pra evitar conflitos, vamos ir gerando nomes únicos
|
||||
used_names = set()
|
||||
|
||||
for src in files:
|
||||
stat = src.stat()
|
||||
mtime = stat.st_mtime
|
||||
dt = datetime.fromtimestamp(mtime)
|
||||
|
||||
base = dt.strftime("%d%m%Y_%H%M%S")
|
||||
|
||||
if args.force_jpeg:
|
||||
target_ext = ".jpeg"
|
||||
else:
|
||||
target_ext = src.suffix.lower()
|
||||
|
||||
# Nome base desejado
|
||||
new_name = f"{base}{target_ext}"
|
||||
dst = images_dir / new_name
|
||||
|
||||
# Se já existe (ou já usamos esse nome pra outro arquivo), adiciona sufixo _01, _02, ...
|
||||
counter = 1
|
||||
while dst.exists() or dst.name in used_names:
|
||||
new_name = f"{base}_{counter:02d}{target_ext}"
|
||||
dst = images_dir / new_name
|
||||
counter += 1
|
||||
|
||||
used_names.add(dst.name)
|
||||
|
||||
if src == dst:
|
||||
# já está com o nome correto
|
||||
continue
|
||||
|
||||
print(f"{src.name} -> {dst.name}")
|
||||
|
||||
if not args.dry_run:
|
||||
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
src.rename(dst)
|
||||
|
||||
if args.dry_run:
|
||||
print("\n[INFO] DRY RUN: nada foi renomeado de verdade.")
|
||||
else:
|
||||
print("\n[OK] Renomeação concluída.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -33,6 +33,7 @@ from utils import carregar_labelmap_completo, converter_mask_rgb_para_ids
|
|||
with open("config.json", "r", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
MODELO = config["camera"]
|
||||
USE_MASKS2 = config["dual_head"]
|
||||
|
||||
EXT_PREVIEWS = (".jpg", ".jpeg", ".png")
|
||||
EXT_MASKS = (".png", ".jpg", ".jpeg")
|
||||
|
|
@ -221,7 +222,7 @@ def processar(originals_dir, labelmap_path, mover=False,
|
|||
if not os.path.isdir(previews_dir) or not os.path.isdir(raws_dir) or not os.path.isdir(masks_dir):
|
||||
raise RuntimeError("Estrutura inválida em originals/")
|
||||
|
||||
usar_masks2 = os.path.isdir(masks2_dir)
|
||||
usar_masks2 = USE_MASKS2 and os.path.isdir(masks2_dir)
|
||||
|
||||
cor_para_id, colormap_rgb, id_para_nome, ignore_rgb = carregar_labelmap_completo(labelmap_path)
|
||||
ignore_id = inferir_ignore_id(ignore_rgb, cor_para_id)
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ import numpy as np
|
|||
with open("config.json", "r", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
MODELO = config.get("camera", ".")
|
||||
USE_MASKS2 = config.get("dual_head", False)
|
||||
|
||||
# Pastas base
|
||||
DATASET_BASE = os.path.join(MODELO, "dataset")
|
||||
|
|
@ -343,7 +344,7 @@ def process_group(group_name, copies):
|
|||
return 0
|
||||
|
||||
use_raw = os.path.isdir(raw_dir)
|
||||
use_masks2 = os.path.isdir(msk2_dir)
|
||||
use_masks2 = USE_MASKS2 and os.path.isdir(msk2_dir)
|
||||
|
||||
imgs = [f for f in os.listdir(img_dir) if os.path.splitext(f.lower())[1] in IMG_EXTS]
|
||||
msk_map = map_by_base_priorizando_png(msk_dir, MSK_EXTS)
|
||||
|
|
@ -399,7 +400,7 @@ def process_legacy(copies):
|
|||
|
||||
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 = os.path.isdir(ORIG_OLD_MSK2)
|
||||
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(
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ with open("config.json", "r", encoding="utf-8") as f:
|
|||
config = json.load(f)
|
||||
|
||||
MODELO = config["camera"]
|
||||
USE_MASKS2 = config["dual_head"]
|
||||
RESOLUCAO = tuple(config["resolucao"]) # [W,H]
|
||||
pasta_base = os.path.join(MODELO, "dataset")
|
||||
labelmap_path = os.path.join(pasta_base, "labelmap.txt")
|
||||
|
|
@ -170,7 +171,7 @@ def normalize_group_raw(fonte_root: str, fonte_nome: str, cor_para_id, ignore_id
|
|||
continue
|
||||
|
||||
usar_raw = os.path.isdir(in_raw)
|
||||
usar_msk2 = os.path.isdir(in_msk2)
|
||||
usar_msk2 = USE_MASKS2 and os.path.isdir(in_msk2)
|
||||
|
||||
out_prev = os.path.join(out_root, grupo, "previews")
|
||||
out_raw = os.path.join(out_root, grupo, "raws") if usar_raw else None
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import argparse
|
|||
with open("config.json", "r", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
MODELO = config.get("camera")
|
||||
USE_MASKS2 = config.get("dual_head", False)
|
||||
RESOLUCAO = tuple(config.get("resolucao"))
|
||||
|
||||
# Pastas (ajustadas para PREVIEWS/RAWS)
|
||||
|
|
@ -270,7 +271,7 @@ def split_group(group_name, p_train, p_val, p_test, seed, mins, caps_map=None):
|
|||
src_msk2_dir = os.path.join(pasta_origem, group_name, "masks2")
|
||||
src_raw_dir = os.path.join(pasta_origem, group_name, "raws")
|
||||
|
||||
use_msk2 = os.path.isdir(src_msk2_dir)
|
||||
use_msk2 = USE_MASKS2 and os.path.isdir(src_msk2_dir)
|
||||
use_raw = os.path.isdir(src_raw_dir)
|
||||
|
||||
familias = build_family_index(src_prev_dir, src_msk_dir)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#python _8_train_segformer_b3.py --epochs 110 --batch 2 --lr 3e-5 --wd 0.01 --num_workers 4 --amp --amp_val --grad_accum 2 --class_weights auto --main_class navegavel --resume
|
||||
#python _8_train_segformer_b3.py --epochs 180 --batch 2 --lr 3e-5 --wd 0.01 --num_workers 4 --amp --amp_val --grad_accum 2 --class_weights auto --main_class navegavel --resume
|
||||
|
||||
# _8_train_segformer_b3.py (PATCH)
|
||||
import os
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ Obs:
|
|||
Este script assume que seus ids de classe batem com o labelmap.txt (mask em IDs 0..K-1).
|
||||
"""
|
||||
|
||||
from collections import deque
|
||||
from enum import IntEnum
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
|
|
@ -206,6 +208,7 @@ def main():
|
|||
parser.add_argument("--camera", action="store_true", help="Usar câmera em vez de imagens")
|
||||
parser.add_argument("--groups", type=str, default=None, help="Filtrar grupos (ex: chao,erva_cana)")
|
||||
parser.add_argument("--split_folder", type=str, default="val", help="split padrão (se usar split/val/test)")
|
||||
parser.add_argument("--test_folder", type=str, default=None, help="pasta para teste")
|
||||
args = parser.parse_args()
|
||||
|
||||
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
||||
|
|
@ -264,7 +267,7 @@ def main():
|
|||
pipeline = dai.Pipeline()
|
||||
cam_rgb = pipeline.createColorCamera()
|
||||
cam_rgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1080_P)
|
||||
cam_rgb.setBoardSocket(dai.CameraBoardSocket.RGB)
|
||||
cam_rgb.setBoardSocket(dai.CameraBoardSocket.CAM_A)
|
||||
cam_rgb.setColorOrder(dai.ColorCameraProperties.ColorOrder.RGB)
|
||||
cam_rgb.setInterleaved(False)
|
||||
cam_rgb.setFps(30)
|
||||
|
|
@ -279,7 +282,8 @@ def main():
|
|||
prev_time = time.time()
|
||||
while True:
|
||||
in_rgb = rgb_queue.get()
|
||||
frame = in_rgb.getCvFrame() # RGB
|
||||
frame_bgr = in_rgb.getCvFrame() # BGR
|
||||
frame = cv2.cvtColor(frame_bgr, cv2.COLOR_BGR2RGB)
|
||||
H, W = frame.shape[:2]
|
||||
y_fim, y_inicio = compute_roi_indices(H, ROI_INICIO, ROI_TAMANHO)
|
||||
|
||||
|
|
@ -325,38 +329,76 @@ def main():
|
|||
|
||||
else:
|
||||
# === Modo imagens (agrupado + fallback) ===
|
||||
# Igual teu fastscnn: você pode usar dataset/512x288 diretamente
|
||||
test_root = os.path.join(dataset_path, "512x288")
|
||||
def collect_images_only(folder):
|
||||
exts = (".jpg", ".jpeg", ".png")
|
||||
paths = [
|
||||
os.path.join(folder, f)
|
||||
for f in sorted(os.listdir(folder))
|
||||
if f.lower().endswith(exts)
|
||||
]
|
||||
return paths
|
||||
|
||||
test_root = args.test_folder if args.test_folder else ""
|
||||
|
||||
image_paths, mask_paths, groups_idx = collect_pairs_grouped(test_root, want_groups=args.groups)
|
||||
if not image_paths:
|
||||
# fallback clássico
|
||||
image_paths = []
|
||||
mask_paths = []
|
||||
groups_idx = []
|
||||
|
||||
if test_root:
|
||||
# tenta modo "imagens puras"
|
||||
image_paths = collect_images_only(test_root)
|
||||
if image_paths:
|
||||
mask_paths = None
|
||||
groups_idx = None
|
||||
print(f"[TEST] Modo inferência pura: {len(image_paths)} imagens")
|
||||
else:
|
||||
# fallback: dataset estruturado
|
||||
image_paths, mask_paths, groups_idx = collect_pairs_grouped(
|
||||
test_root, want_groups=args.groups
|
||||
)
|
||||
else:
|
||||
test_root = os.path.join(dataset_path, "split", args.split_folder)
|
||||
image_paths, mask_paths, groups_idx = collect_pairs_grouped(test_root, want_groups=args.groups)
|
||||
if not image_paths:
|
||||
image_paths, mask_paths, groups_idx = collect_pairs_legacy(test_root)
|
||||
image_paths, mask_paths, groups_idx = collect_pairs_grouped(
|
||||
test_root, want_groups=args.groups
|
||||
)
|
||||
if not image_paths:
|
||||
image_paths, mask_paths, groups_idx = collect_pairs_legacy(test_root)
|
||||
|
||||
assert len(image_paths) == len(mask_paths) and len(image_paths) > 0, "Nenhuma imagem/máscara encontrada."
|
||||
assert len(image_paths) > 0, "Nenhuma imagem encontrada."
|
||||
if mask_paths is not None:
|
||||
assert len(image_paths) == len(mask_paths), "Mismatch imagem/máscara"
|
||||
|
||||
idx = 0
|
||||
window_name = "Original | GroundTruth | Predito (SegFormer)"
|
||||
cv2.namedWindow(window_name, cv2.WINDOW_NORMAL) # permite redimensionar/maximizar
|
||||
while True:
|
||||
img_path = image_paths[idx]
|
||||
mask_path = mask_paths[idx]
|
||||
grupo = groups_idx[idx] if groups_idx else "?"
|
||||
|
||||
img_rgb = np.array(Image.open(img_path).convert("RGB"))
|
||||
mask_gt = np.array(Image.open(mask_path).convert("L"))
|
||||
|
||||
if mask_paths is not None:
|
||||
mask_path = mask_paths[idx]
|
||||
mask_gt = np.array(Image.open(mask_path).convert("L"))
|
||||
else:
|
||||
mask_gt = None
|
||||
|
||||
H, W = img_rgb.shape[:2]
|
||||
y_fim, y_inicio = compute_roi_indices(H, ROI_INICIO, ROI_TAMANHO)
|
||||
|
||||
img_roi = img_rgb[y_fim:y_inicio, 0:W]
|
||||
mask_roi = mask_gt[y_fim:y_inicio, 0:W]
|
||||
|
||||
if mask_gt is not None:
|
||||
mask_roi = mask_gt[y_fim:y_inicio, 0:W]
|
||||
else:
|
||||
mask_roi = None
|
||||
|
||||
img_resized = resize_keep_width(img_roi, RESOLUCAO[0], RESOLUCAO[1], cv2.INTER_AREA)
|
||||
mask_resized = resize_keep_width(mask_roi, RESOLUCAO[0], RESOLUCAO[1], cv2.INTER_NEAREST)
|
||||
|
||||
if mask_roi is not None:
|
||||
mask_resized = resize_keep_width(mask_roi, RESOLUCAO[0], RESOLUCAO[1], cv2.INTER_NEAREST)
|
||||
else:
|
||||
mask_resized = None
|
||||
|
||||
img_norm = img_resized.astype(np.float32) / 255.0
|
||||
img_tensor = torch.from_numpy(img_norm).permute(2, 0, 1).unsqueeze(0).to(device)
|
||||
|
|
@ -366,16 +408,23 @@ def main():
|
|||
pred_ids = segformer_predict_ids(model, img_tensor)
|
||||
|
||||
pred_rgb = converter_mask_ids_para_rgb(pred_ids, colormap_rgb, ignore_id)
|
||||
mask_gt_rgb = converter_mask_ids_para_rgb(mask_resized, colormap_rgb, ignore_id)
|
||||
#resultado = np.concatenate([img_resized, mask_gt_rgb, pred_rgb], axis=1)
|
||||
# Overlay da predição sobre a imagem original
|
||||
|
||||
overlay_pred = cv2.addWeighted(img_resized, 0.6, pred_rgb, 0.4, 0.0)
|
||||
resultado = np.concatenate([img_resized, mask_gt_rgb, overlay_pred], axis=1)
|
||||
if mask_gt is not None:
|
||||
mask_gt_rgb = converter_mask_ids_para_rgb(mask_resized, colormap_rgb, ignore_id)
|
||||
resultado = np.concatenate([img_resized, mask_gt_rgb, overlay_pred], axis=1)
|
||||
else:
|
||||
resultado = np.concatenate([img_resized, overlay_pred], axis=1)
|
||||
|
||||
legenda = desenhar_legenda_horizontal(colormap_rgb, classes)
|
||||
legenda_resized = cv2.resize(legenda, (resultado.shape[1], legenda.shape[0]), interpolation=cv2.INTER_NEAREST)
|
||||
resultado_completo = np.concatenate([resultado, legenda_resized], axis=0)
|
||||
|
||||
|
||||
mask_nav = (pred_ids == ClassesSegmentacao.NAVEGAVEL.value)
|
||||
classificar_status_corredor(mask_nav)
|
||||
|
||||
|
||||
cv2.putText(resultado_completo, f"grupo: {grupo}", (10, 24), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
|
||||
# Converte pra BGR pra exibir
|
||||
vis_bgr = cv2.cvtColor(resultado_completo, cv2.COLOR_RGB2BGR)
|
||||
|
|
@ -401,5 +450,160 @@ def main():
|
|||
cv2.destroyAllWindows()
|
||||
|
||||
|
||||
|
||||
class ClassesSegmentacao(IntEnum):
|
||||
NAONAVEGAVEL = 0
|
||||
NAVEGAVEL = 1
|
||||
|
||||
class StatusCarroMapa(IntEnum):
|
||||
Parado = 0
|
||||
EntrandoRua = 1,
|
||||
CaminhandoRua = 2
|
||||
SaindoRua = 3,
|
||||
Manobrando = 4
|
||||
Direcionando = 5
|
||||
RetornandoBase = 6
|
||||
|
||||
janela_s_padrao = 1.5
|
||||
_status_hist = deque(maxlen=1)
|
||||
|
||||
def _now():
|
||||
# monotonic evita saltos de relógio
|
||||
return time.monotonic()
|
||||
|
||||
def _maioria_ultimos(janela_s: float | None = None) -> StatusCarroMapa:
|
||||
"""Maioria ponderada pelos últimos 'janela_s' segundos.
|
||||
Se 'janela_s' for None → usa janela_s_padrao."""
|
||||
J = float(janela_s) if janela_s is not None else float(janela_s_padrao)
|
||||
|
||||
t_now = _now()
|
||||
|
||||
# 1) limpa itens FORA da janela
|
||||
while _status_hist and (t_now - _status_hist[0][1] > J):
|
||||
_status_hist.popleft()
|
||||
|
||||
if not _status_hist:
|
||||
# fallback razoável
|
||||
return StatusCarroMapa.Direcionando
|
||||
|
||||
# 2) maioria simples (pode trocar por peso exponencial se quiser)
|
||||
cont: dict[StatusCarroMapa, int] = {}
|
||||
for st, _t in _status_hist:
|
||||
cont[st] = cont.get(st, 0) + 1
|
||||
|
||||
top_freq = max(cont.values())
|
||||
empatados = [st for st, c in cont.items() if c == top_freq]
|
||||
|
||||
if len(empatados) == 1:
|
||||
return empatados[0]
|
||||
|
||||
# desempata olhando do fim pro início (mais recente primeiro)
|
||||
for st, _t in reversed(_status_hist):
|
||||
if st in empatados:
|
||||
return st
|
||||
|
||||
def classificar_status_corredor(mask_nav: np.ndarray):
|
||||
"""
|
||||
mask_nav: (H,W) com 1 = navegável, 0 = não-navegável
|
||||
Retorna:
|
||||
status_now : StatusCarroMapa
|
||||
status_final : StatusCarroMapa (igual ao now, sem histerese por enquanto)
|
||||
probs : dict[StatusCarroMapa, float] (aqui só 1.0 pro escolhido)
|
||||
debug : métricas pra log
|
||||
"""
|
||||
H, W = mask_nav.shape
|
||||
nav = (mask_nav > 0).astype(np.float32)
|
||||
|
||||
def faixa_mean(y0, y1):
|
||||
fatia = nav[y0:y1, :]
|
||||
if fatia.size == 0:
|
||||
return 0.0
|
||||
return float(fatia.mean())
|
||||
|
||||
# corta em 3 faixas: far (topo), mid (meio), near (embaixo)
|
||||
y_far_top = 0
|
||||
y_far_bot = int(0.2 * H)
|
||||
y_mid_top = y_far_bot
|
||||
y_mid_bot = int(0.4 * H)
|
||||
y_near_top = y_mid_bot
|
||||
y_near_bot = H
|
||||
|
||||
nav_far = faixa_mean(y_far_top, y_far_bot)
|
||||
nav_mid = faixa_mean(y_mid_top, y_mid_bot)
|
||||
nav_near = faixa_mean(y_near_top, y_near_bot)
|
||||
nav_global = float(nav.mean()) if nav.size > 0 else 0.0
|
||||
|
||||
# limiares & delta
|
||||
THR_NAV_ALTO = 0.95 # "quase tudo navegável"
|
||||
THR_NAV_BAIXO = 0.30 # "quase nada navegável"
|
||||
THR_NEAR_ALTO = 1.00 # near "100%"
|
||||
DELTA = 0.05 # diferença mínima pra considerar > de verdade
|
||||
|
||||
def maior_que(a, b):
|
||||
return a > min(b - DELTA, 1.0)
|
||||
|
||||
def maior_igual_que(a, b):
|
||||
return a >= min(b - DELTA, 1.0)
|
||||
|
||||
status = None
|
||||
|
||||
# 1) PARADO: quase todo frame não navegável
|
||||
if nav_global < THR_NAV_BAIXO:
|
||||
status = StatusCarroMapa.Parado
|
||||
|
||||
# 2) DIRECIONANDO: quase todo frame navegável
|
||||
elif nav_global > THR_NAV_ALTO:
|
||||
status = StatusCarroMapa.Direcionando
|
||||
|
||||
else:
|
||||
# 3) ENTRANDO RUA
|
||||
cond_near_alto = nav_near >= THR_NEAR_ALTO
|
||||
cond_near_gt_mid = maior_igual_que(nav_near, nav_mid)
|
||||
cond_mid_gt_far = maior_que(nav_mid, nav_far)
|
||||
|
||||
# 4) SAINDO RUA
|
||||
cond_near_gt_mid2 = True or maior_que(nav_near, nav_mid)
|
||||
cond_far_gt_mid = maior_que(nav_far, nav_mid)
|
||||
|
||||
if cond_near_alto and cond_near_gt_mid and cond_mid_gt_far:
|
||||
status = StatusCarroMapa.EntrandoRua
|
||||
|
||||
elif cond_near_gt_mid2 and cond_far_gt_mid:
|
||||
status = StatusCarroMapa.SaindoRua
|
||||
|
||||
# 5) CAMINHANDO RUA (cone "normal" NEAR > MID > FAR)
|
||||
elif cond_near_gt_mid and cond_mid_gt_far:
|
||||
status = StatusCarroMapa.CaminhandoRua
|
||||
|
||||
else:
|
||||
# fallback: se ficar numa zona cinza, chama de Direcionando
|
||||
status = StatusCarroMapa.Manobrando
|
||||
|
||||
# monta probs "one-hot"
|
||||
probs = {s: 0.0 for s in StatusCarroMapa}
|
||||
probs[status] = 1.0
|
||||
|
||||
debug = {
|
||||
"nav_near": nav_near,
|
||||
"nav_mid": nav_mid,
|
||||
"nav_far": nav_far,
|
||||
"nav_global": nav_global,
|
||||
"THR_NAV_ALTO": THR_NAV_ALTO,
|
||||
"THR_NAV_BAIXO": THR_NAV_BAIXO,
|
||||
"THR_NEAR_ALTO": THR_NEAR_ALTO,
|
||||
}
|
||||
|
||||
status_now = status
|
||||
print(status_now.name)
|
||||
# Histerese temporal: mantém teu esquema de histórico
|
||||
_status_hist.append((status_now, _now()))
|
||||
status_final = _maioria_ultimos()
|
||||
|
||||
return status_now, status_final, probs, debug
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
{
|
||||
"camera": "gal5000",
|
||||
"camera": "oak-d",
|
||||
"modelo": "segformer_b0",
|
||||
"model_name": "ndvi_big",
|
||||
"model_name": "nav",
|
||||
"dual_head": false,
|
||||
"main_class_name": "erva",
|
||||
"main_class_name": "navegavel",
|
||||
"es_classes": "",
|
||||
"model_to_use": "geral",
|
||||
"raw_size": [1296, 1028],
|
||||
"resolucao": [1008, 800],
|
||||
"resolucao": [1024, 576],
|
||||
"roi_inicio": 0.0,
|
||||
"roi_tamanho": 1.0,
|
||||
"shaves": 3,
|
||||
"channels": 5,
|
||||
"channels": 3,
|
||||
"use_ndvi": true,
|
||||
"backbone": "nvidia/segformer-b0-finetuned-ade-512-512"
|
||||
}
|
||||
|
|
@ -1,17 +1,17 @@
|
|||
{
|
||||
"camera": "oak-d",
|
||||
"camera": "gal5000",
|
||||
"modelo": "segformer_b0",
|
||||
"model_name": "nav",
|
||||
"model_name": "ndvi_big",
|
||||
"dual_head": false,
|
||||
"main_class_name": "navegavel",
|
||||
"main_class_name": "erva",
|
||||
"es_classes": "",
|
||||
"model_to_use": "geral",
|
||||
"raw_size": [1296, 1028],
|
||||
"resolucao": [1024, 576],
|
||||
"resolucao": [1008, 800],
|
||||
"roi_inicio": 0.0,
|
||||
"roi_tamanho": 1.0,
|
||||
"shaves": 3,
|
||||
"channels": 3,
|
||||
"channels": 5,
|
||||
"use_ndvi": true,
|
||||
"backbone": "nvidia/segformer-b0-finetuned-ade-512-512"
|
||||
}
|
||||
Loading…
Reference in New Issue