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"
|
GST_LAUNCH = r"C:\Program Files\gstreamer\1.0\msvc_x86_64\bin\gst-launch-1.0.exe"
|
||||||
|
|
||||||
class CameraOak:
|
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.mostrar_log = mostrar_log
|
||||||
self.modelo_ia_seg = modelo_ia_seg
|
self.modelo_ia_seg = modelo_ia_seg
|
||||||
self.modelo_ia_det = modelo_ia_det
|
self.modelo_ia_det = modelo_ia_det
|
||||||
|
|
@ -81,7 +81,7 @@ class CameraOak:
|
||||||
_camera["modelo"] = self.modelo
|
_camera["modelo"] = self.modelo
|
||||||
_camera["dispositivo"] = self.dispositivo.value
|
_camera["dispositivo"] = self.dispositivo.value
|
||||||
_camera["tem_depht"] = self.tem_depth
|
_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
|
# Criar processo para transmissao de video
|
||||||
try:
|
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,
|
"debug_visual": True,
|
||||||
"ia_roi_begin": 0.0,
|
"ia_roi_begin": 0.0,
|
||||||
"ia_roi_size": 1.0,
|
"ia_roi_size": 1.0,
|
||||||
"ia_resolution": [512,288],
|
"ia_resolution": [1024,576],
|
||||||
"det_every_n": 1,
|
"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_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_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
|
return _CONFIG_CACHE
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,8 @@ from shared.utils import encode_image_base64
|
||||||
from shared.enums import StatusCarroMapa
|
from shared.enums import StatusCarroMapa
|
||||||
|
|
||||||
class ClassesSegmentacao(IntEnum):
|
class ClassesSegmentacao(IntEnum):
|
||||||
RUA = 0
|
NAONAVEGAVEL = 0
|
||||||
CANA = 1
|
NAVEGAVEL = 1
|
||||||
OBSTACULO = 2
|
|
||||||
|
|
||||||
|
|
||||||
class SegmentacaoManager:
|
class SegmentacaoManager:
|
||||||
|
|
@ -49,34 +48,6 @@ class SegmentacaoManager:
|
||||||
self.max_len = max_len or int(self.janela_s * fps_esperado * 1.5)
|
self.max_len = max_len or int(self.janela_s * fps_esperado * 1.5)
|
||||||
self._status_hist = deque(maxlen=self.max_len)
|
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):
|
def segmentar(self, predictions):
|
||||||
try:
|
try:
|
||||||
|
|
@ -99,326 +70,26 @@ class SegmentacaoManager:
|
||||||
self.log = f"❌ Erro na segmentação: {e}"
|
self.log = f"❌ Erro na segmentação: {e}"
|
||||||
return None, self.log
|
return None, self.log
|
||||||
|
|
||||||
def calcular_perfil_corredor(self, matriz_conf, fov_h_rad):
|
def _segmentar_predictions(self, predictions):
|
||||||
altura_grid = len(matriz_conf)
|
try:
|
||||||
largura_grid = len(matriz_conf[0])
|
self.pred_rgb[:] = self.lut[predictions]
|
||||||
perfil = []
|
|
||||||
|
|
||||||
for i in range(altura_grid):
|
mask_color = self.pred_rgb
|
||||||
x_esquerda = None
|
frame_color = encode_image_base64(mask_color)
|
||||||
x_direita = None
|
|
||||||
|
|
||||||
# Coleta todos os valores válidos de profundidade da linha
|
return {
|
||||||
profundidades = [cel.get("prof_ref", 0.0) for cel in matriz_conf[i] if cel.get("prof_ref", 0.0) > 0]
|
"timestamp": time.time(),
|
||||||
|
"frame": {
|
||||||
|
"timestamp": time.time(),
|
||||||
|
"frame": frame_color
|
||||||
|
},
|
||||||
|
"mask_color": mask_color,
|
||||||
|
"classes": predictions
|
||||||
|
}
|
||||||
|
|
||||||
if len(profundidades) == 0:
|
except Exception as e:
|
||||||
continue
|
print(f"Erro ao processar predictions: {e}")
|
||||||
|
return None
|
||||||
# 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),
|
|
||||||
}
|
|
||||||
|
|
||||||
def _analisar_corredor_visual(self, predictions, grid_rows_y_px=None, near_is_bottom=True):
|
def _analisar_corredor_visual(self, predictions, grid_rows_y_px=None, near_is_bottom=True):
|
||||||
# --- inputs/base ---
|
# --- inputs/base ---
|
||||||
|
|
@ -426,21 +97,18 @@ class SegmentacaoManager:
|
||||||
H, W = self.predictions.shape
|
H, W = self.predictions.shape
|
||||||
cx_img = W // 2
|
cx_img = W // 2
|
||||||
|
|
||||||
# máscara do "corredor principal" (telemetria/ângulo); pode vir vazia
|
mask_corredor = self._extrair_corredor_principal(self.predictions).astype(bool)
|
||||||
mask_main = self._extrair_corredor_principal(self.predictions).astype(bool)
|
|
||||||
# opcional (se ainda usar em outros pontos)
|
|
||||||
mask_cana = (self.predictions == ClassesSegmentacao.CANA.value)
|
|
||||||
|
|
||||||
# --- score de corredor por grid (robusto) ---
|
# --- 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) ---
|
# --- scanlines para centro/ângulo (telemetria) ---
|
||||||
ys = self._get_scanlines_y(H, grid_rows_y_px, near_is_bottom)
|
ys = self._get_scanlines_y(H, grid_rows_y_px, near_is_bottom)
|
||||||
centros, larguras = [], []
|
centros, larguras = [], []
|
||||||
|
|
||||||
if mask_main.any():
|
if mask_corredor.any():
|
||||||
for y in ys:
|
for y in ys:
|
||||||
row = mask_main[y]
|
row = mask_corredor[y]
|
||||||
idx = np.flatnonzero(row)
|
idx = np.flatnonzero(row)
|
||||||
if idx.size == 0:
|
if idx.size == 0:
|
||||||
centros.append((None, y))
|
centros.append((None, y))
|
||||||
|
|
@ -478,9 +146,9 @@ class SegmentacaoManager:
|
||||||
|
|
||||||
# pesos: linhas "mais perto" pesam mais
|
# pesos: linhas "mais perto" pesam mais
|
||||||
if near_is_bottom:
|
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)
|
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)
|
Wm = np.diag(wts)
|
||||||
Y = ys_fit.reshape(-1, 1)
|
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)
|
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)
|
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 {
|
return {
|
||||||
"timestamp": time.time(),
|
"timestamp": time.time(),
|
||||||
|
|
@ -529,75 +198,74 @@ class SegmentacaoManager:
|
||||||
"width": W,
|
"width": W,
|
||||||
"erro_angular": ang_out,
|
"erro_angular": ang_out,
|
||||||
"erro_lateral_pct": lat_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,
|
"centros_corredor": centros,
|
||||||
"larguras_px": larguras,
|
"larguras_px": larguras,
|
||||||
"confianca": round(float(score), 3), # agora a confiança é o score de corredor
|
"confianca": round(float(score), 3),
|
||||||
# (se quiser debugar) "near_ok": near_ok, "far_ok": far_ok,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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):
|
num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(mask_nav, connectivity=8)
|
||||||
try:
|
if num_labels <= 1:
|
||||||
original = cv2.resize(frame, self.resolucao)
|
return np.zeros_like(mask_nav, dtype=np.uint8)
|
||||||
|
|
||||||
seg_color = np.zeros_like(original)
|
best_i, best_score = -1, -1e9
|
||||||
for class_id, color in enumerate(self.color_map):
|
|
||||||
seg_color[self.predictions == class_id] = color
|
|
||||||
|
|
||||||
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ô
|
for i in range(1, num_labels): # 0 = fundo
|
||||||
if centros_corredor and len(centros_corredor) >= 2:
|
x, y, w, h, area = stats[i]
|
||||||
for ponto in centros_corredor:
|
if area < 50: # lixo
|
||||||
x, y = ponto
|
continue
|
||||||
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)
|
|
||||||
|
|
||||||
# 4. Texto de métricas
|
cx = x + w // 2
|
||||||
if self.dados_visuais:
|
# normalizações 0..1
|
||||||
erro_lateral_pct = self.dados_visuais["erro_lateral_pct"]
|
area_n = area / float(H * W)
|
||||||
erro_angular = np.degrees(self.dados_visuais["erro_angular"]) if self.dados_visuais["erro_angular"] else 0
|
center_n = 1.0 - min(1.0, abs(cx - cx_img) / (W * 0.5))
|
||||||
texto = [
|
vertical = h / max(1.0, w) # alongamento vertical
|
||||||
f"Erro angular: {erro_angular:.2f} graus",
|
bottom_touch = 1.0 if (y + h >= H - 2) else 0.0
|
||||||
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
|
prev_bias = 0.0
|
||||||
legenda_inicio_y = 140
|
if prev_cx is not None:
|
||||||
for i, cor in enumerate(self.color_map):
|
prev_bias = 1.0 - min(1.0, abs(cx - prev_cx) / (W * 0.5))
|
||||||
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)
|
|
||||||
|
|
||||||
# Mostrar
|
score = (w_area*area_n +
|
||||||
cv2.imshow("Segmentacao - Overlay", overlay)
|
w_center*center_n +
|
||||||
cv2.waitKey(1)
|
w_bottom*bottom_touch +
|
||||||
#cv2.destroyAllWindows()
|
w_vertical*vertical +
|
||||||
except Exception as e:
|
w_prev*prev_bias)
|
||||||
print(f"Erro ao gerar display_segmentation_debug: {e}")
|
|
||||||
|
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):
|
def _corridor_score_grid(self, mask_classes, rows=6, cols=21):
|
||||||
CANA = ClassesSegmentacao.CANA.value
|
|
||||||
RUA = ClassesSegmentacao.RUA.value
|
|
||||||
|
|
||||||
H, W = mask_classes.shape
|
H, W = mask_classes.shape
|
||||||
row_h = H // rows
|
row_h = H // rows
|
||||||
col_w = W // cols
|
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)
|
frac = np.zeros((rows, cols), np.float32)
|
||||||
for i in range(rows):
|
for i in range(rows):
|
||||||
y0, y1 = i*row_h, H if i==rows-1 else (i+1)*row_h
|
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
|
x0, x1 = j*col_w, W if j==cols-1 else (j+1)*col_w
|
||||||
cell = mask_classes[y0:y1, x0:x1]
|
cell = mask_classes[y0:y1, x0:x1]
|
||||||
if cell.size:
|
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)
|
# limiares por faixa (modelo em “A”: mais chão embaixo, mais cana no topo)
|
||||||
near_lado_min, near_canal_max = 0.05, 0.70
|
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)
|
idx = np.linspace(0, 1, rows) # 0=embaixo (perto), 1=topo (longe)
|
||||||
lado_min = near_lado_min + (far_lado_min - near_lado_min) * idx
|
lado_min = near_lado_min + (far_lado_min - near_lado_min) * idx
|
||||||
canal_max = near_canal_max + (far_canal_max - near_canal_max) * 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
|
left_col[i], right_col[i] = lf, rf
|
||||||
|
|
||||||
score = valid_row.mean() # 0..1
|
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:
|
with open("config.json", "r", encoding="utf-8") as f:
|
||||||
config = json.load(f)
|
config = json.load(f)
|
||||||
MODELO = config["camera"]
|
MODELO = config["camera"]
|
||||||
|
USE_MASKS2 = config["dual_head"]
|
||||||
|
|
||||||
EXT_PREVIEWS = (".jpg", ".jpeg", ".png")
|
EXT_PREVIEWS = (".jpg", ".jpeg", ".png")
|
||||||
EXT_MASKS = (".png", ".jpg", ".jpeg")
|
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):
|
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/")
|
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)
|
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)
|
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:
|
with open("config.json", "r", encoding="utf-8") as f:
|
||||||
config = json.load(f)
|
config = json.load(f)
|
||||||
MODELO = config.get("camera", ".")
|
MODELO = config.get("camera", ".")
|
||||||
|
USE_MASKS2 = config.get("dual_head", False)
|
||||||
|
|
||||||
# Pastas base
|
# Pastas base
|
||||||
DATASET_BASE = os.path.join(MODELO, "dataset")
|
DATASET_BASE = os.path.join(MODELO, "dataset")
|
||||||
|
|
@ -343,7 +344,7 @@ def process_group(group_name, copies):
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
use_raw = os.path.isdir(raw_dir)
|
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]
|
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)
|
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]
|
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)
|
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 {}
|
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(
|
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)
|
config = json.load(f)
|
||||||
|
|
||||||
MODELO = config["camera"]
|
MODELO = config["camera"]
|
||||||
|
USE_MASKS2 = config["dual_head"]
|
||||||
RESOLUCAO = tuple(config["resolucao"]) # [W,H]
|
RESOLUCAO = tuple(config["resolucao"]) # [W,H]
|
||||||
pasta_base = os.path.join(MODELO, "dataset")
|
pasta_base = os.path.join(MODELO, "dataset")
|
||||||
labelmap_path = os.path.join(pasta_base, "labelmap.txt")
|
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
|
continue
|
||||||
|
|
||||||
usar_raw = os.path.isdir(in_raw)
|
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_prev = os.path.join(out_root, grupo, "previews")
|
||||||
out_raw = os.path.join(out_root, grupo, "raws") if usar_raw else None
|
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:
|
with open("config.json", "r", encoding="utf-8") as f:
|
||||||
config = json.load(f)
|
config = json.load(f)
|
||||||
MODELO = config.get("camera")
|
MODELO = config.get("camera")
|
||||||
|
USE_MASKS2 = config.get("dual_head", False)
|
||||||
RESOLUCAO = tuple(config.get("resolucao"))
|
RESOLUCAO = tuple(config.get("resolucao"))
|
||||||
|
|
||||||
# Pastas (ajustadas para PREVIEWS/RAWS)
|
# 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_msk2_dir = os.path.join(pasta_origem, group_name, "masks2")
|
||||||
src_raw_dir = os.path.join(pasta_origem, group_name, "raws")
|
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)
|
use_raw = os.path.isdir(src_raw_dir)
|
||||||
|
|
||||||
familias = build_family_index(src_prev_dir, src_msk_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)
|
# _8_train_segformer_b3.py (PATCH)
|
||||||
import os
|
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).
|
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 json
|
||||||
import os
|
import os
|
||||||
import time
|
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("--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("--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("--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()
|
args = parser.parse_args()
|
||||||
|
|
||||||
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
||||||
|
|
@ -264,7 +267,7 @@ def main():
|
||||||
pipeline = dai.Pipeline()
|
pipeline = dai.Pipeline()
|
||||||
cam_rgb = pipeline.createColorCamera()
|
cam_rgb = pipeline.createColorCamera()
|
||||||
cam_rgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1080_P)
|
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.setColorOrder(dai.ColorCameraProperties.ColorOrder.RGB)
|
||||||
cam_rgb.setInterleaved(False)
|
cam_rgb.setInterleaved(False)
|
||||||
cam_rgb.setFps(30)
|
cam_rgb.setFps(30)
|
||||||
|
|
@ -279,7 +282,8 @@ def main():
|
||||||
prev_time = time.time()
|
prev_time = time.time()
|
||||||
while True:
|
while True:
|
||||||
in_rgb = rgb_queue.get()
|
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]
|
H, W = frame.shape[:2]
|
||||||
y_fim, y_inicio = compute_roi_indices(H, ROI_INICIO, ROI_TAMANHO)
|
y_fim, y_inicio = compute_roi_indices(H, ROI_INICIO, ROI_TAMANHO)
|
||||||
|
|
||||||
|
|
@ -325,38 +329,76 @@ def main():
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# === Modo imagens (agrupado + fallback) ===
|
# === Modo imagens (agrupado + fallback) ===
|
||||||
# Igual teu fastscnn: você pode usar dataset/512x288 diretamente
|
def collect_images_only(folder):
|
||||||
test_root = os.path.join(dataset_path, "512x288")
|
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)
|
image_paths = []
|
||||||
if not image_paths:
|
mask_paths = []
|
||||||
# fallback clássico
|
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)
|
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)
|
image_paths, mask_paths, groups_idx = collect_pairs_grouped(
|
||||||
if not image_paths:
|
test_root, want_groups=args.groups
|
||||||
image_paths, mask_paths, groups_idx = collect_pairs_legacy(test_root)
|
)
|
||||||
|
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
|
idx = 0
|
||||||
window_name = "Original | GroundTruth | Predito (SegFormer)"
|
window_name = "Original | GroundTruth | Predito (SegFormer)"
|
||||||
cv2.namedWindow(window_name, cv2.WINDOW_NORMAL) # permite redimensionar/maximizar
|
cv2.namedWindow(window_name, cv2.WINDOW_NORMAL) # permite redimensionar/maximizar
|
||||||
while True:
|
while True:
|
||||||
img_path = image_paths[idx]
|
img_path = image_paths[idx]
|
||||||
mask_path = mask_paths[idx]
|
|
||||||
grupo = groups_idx[idx] if groups_idx else "?"
|
grupo = groups_idx[idx] if groups_idx else "?"
|
||||||
|
|
||||||
img_rgb = np.array(Image.open(img_path).convert("RGB"))
|
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]
|
H, W = img_rgb.shape[:2]
|
||||||
y_fim, y_inicio = compute_roi_indices(H, ROI_INICIO, ROI_TAMANHO)
|
y_fim, y_inicio = compute_roi_indices(H, ROI_INICIO, ROI_TAMANHO)
|
||||||
|
|
||||||
img_roi = img_rgb[y_fim:y_inicio, 0:W]
|
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)
|
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_norm = img_resized.astype(np.float32) / 255.0
|
||||||
img_tensor = torch.from_numpy(img_norm).permute(2, 0, 1).unsqueeze(0).to(device)
|
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_ids = segformer_predict_ids(model, img_tensor)
|
||||||
|
|
||||||
pred_rgb = converter_mask_ids_para_rgb(pred_ids, colormap_rgb, ignore_id)
|
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)
|
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 = desenhar_legenda_horizontal(colormap_rgb, classes)
|
||||||
legenda_resized = cv2.resize(legenda, (resultado.shape[1], legenda.shape[0]), interpolation=cv2.INTER_NEAREST)
|
legenda_resized = cv2.resize(legenda, (resultado.shape[1], legenda.shape[0]), interpolation=cv2.INTER_NEAREST)
|
||||||
resultado_completo = np.concatenate([resultado, legenda_resized], axis=0)
|
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)
|
cv2.putText(resultado_completo, f"grupo: {grupo}", (10, 24), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
|
||||||
# Converte pra BGR pra exibir
|
# Converte pra BGR pra exibir
|
||||||
vis_bgr = cv2.cvtColor(resultado_completo, cv2.COLOR_RGB2BGR)
|
vis_bgr = cv2.cvtColor(resultado_completo, cv2.COLOR_RGB2BGR)
|
||||||
|
|
@ -401,5 +450,160 @@ def main():
|
||||||
cv2.destroyAllWindows()
|
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__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
{
|
{
|
||||||
"camera": "gal5000",
|
"camera": "oak-d",
|
||||||
"modelo": "segformer_b0",
|
"modelo": "segformer_b0",
|
||||||
"model_name": "ndvi_big",
|
"model_name": "nav",
|
||||||
"dual_head": false,
|
"dual_head": false,
|
||||||
"main_class_name": "erva",
|
"main_class_name": "navegavel",
|
||||||
"es_classes": "",
|
"es_classes": "",
|
||||||
"model_to_use": "geral",
|
"model_to_use": "geral",
|
||||||
"raw_size": [1296, 1028],
|
"raw_size": [1296, 1028],
|
||||||
"resolucao": [1008, 800],
|
"resolucao": [1024, 576],
|
||||||
"roi_inicio": 0.0,
|
"roi_inicio": 0.0,
|
||||||
"roi_tamanho": 1.0,
|
"roi_tamanho": 1.0,
|
||||||
"shaves": 3,
|
"shaves": 3,
|
||||||
"channels": 5,
|
"channels": 3,
|
||||||
"use_ndvi": true,
|
"use_ndvi": true,
|
||||||
"backbone": "nvidia/segformer-b0-finetuned-ade-512-512"
|
"backbone": "nvidia/segformer-b0-finetuned-ade-512-512"
|
||||||
}
|
}
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
{
|
{
|
||||||
"camera": "oak-d",
|
"camera": "gal5000",
|
||||||
"modelo": "segformer_b0",
|
"modelo": "segformer_b0",
|
||||||
"model_name": "nav",
|
"model_name": "ndvi_big",
|
||||||
"dual_head": false,
|
"dual_head": false,
|
||||||
"main_class_name": "navegavel",
|
"main_class_name": "erva",
|
||||||
"es_classes": "",
|
"es_classes": "",
|
||||||
"model_to_use": "geral",
|
"model_to_use": "geral",
|
||||||
"raw_size": [1296, 1028],
|
"raw_size": [1296, 1028],
|
||||||
"resolucao": [1024, 576],
|
"resolucao": [1008, 800],
|
||||||
"roi_inicio": 0.0,
|
"roi_inicio": 0.0,
|
||||||
"roi_tamanho": 1.0,
|
"roi_tamanho": 1.0,
|
||||||
"shaves": 3,
|
"shaves": 3,
|
||||||
"channels": 3,
|
"channels": 5,
|
||||||
"use_ndvi": true,
|
"use_ndvi": true,
|
||||||
"backbone": "nvidia/segformer-b0-finetuned-ade-512-512"
|
"backbone": "nvidia/segformer-b0-finetuned-ade-512-512"
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue