Implementacao do novo modelo de segmentacao ao visual worker

This commit is contained in:
Diego Freitas 2026-01-30 16:22:44 -03:00
parent 105660565d
commit c805f6103b
13 changed files with 978 additions and 1452 deletions

View File

@ -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:

View File

@ -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

View File

@ -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 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}")

View File

@ -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()

View File

@ -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)

View File

@ -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(

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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 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()

View File

@ -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"
}

View File

@ -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"
}