diff --git a/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/weed_worker/weed_detector.py b/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/weed_worker/weed_detector.py index d38488675..394d09fb6 100644 --- a/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/weed_worker/weed_detector.py +++ b/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/weed_worker/weed_detector.py @@ -296,6 +296,8 @@ class WeedDetector: cfg = self.config vel_mps = float(cfg.get("velocidade_robo", 0.0) or 0.0) + # A geometria de atuação foi definida para o avanço. A ré não é + # reinterpretada como um percurso espacial inverso dos bicos. if vel_mps < 0: vel_mps = 0.0 @@ -463,9 +465,14 @@ class WeedDetector: self._frac_grid_last = frac_grid # 3) Atualiza score espacial. + # Em movimento, acumula evidência e desloca a esteira normalmente. + # Parado, acompanha a evidência do frame atual por EMA: o mesmo ruído + # não pode ser somado indefinidamente, mas uma erva persistente continua + # podendo manter/ligar o bico. score_update_info = self._atualizar_score_memoria( frac_grid=frac_grid, cfg=cfg, + em_movimento=bool(shift_info["em_movimento"]), ) # 4) Decide bicos pela zona física de atuação, com compensação de latência. @@ -533,6 +540,7 @@ class WeedDetector: return { "dt_s": 0.0, "vel_mps": float(vel_mps), + "em_movimento": False, "delta_frac": 0.0, "shift_cells": 0.0, "distancia_deslocada_total_cm": float(self._distancia_deslocada_total_cm), @@ -545,10 +553,17 @@ class WeedDetector: if max_dt > 0: dt = min(dt, max_dt) - if dt <= 0 or vel_mps <= 0: + vel_min = max( + 0.0, + float(cfg.get("vel_min_movimento_memoria_mps", 0.03) or 0.03), + ) + em_movimento = bool(abs(vel_mps) >= vel_min) + + if dt <= 0 or not em_movimento: return { "dt_s": float(max(0.0, dt)), "vel_mps": float(vel_mps), + "em_movimento": False, "delta_frac": 0.0, "shift_cells": 0.0, "distancia_deslocada_total_cm": float(self._distancia_deslocada_total_cm), @@ -582,6 +597,7 @@ class WeedDetector: return { "dt_s": float(dt), "vel_mps": float(vel_mps), + "em_movimento": True, "delta_frac": float(delta_frac), "shift_cells": float(shift_cells), "k_shift_frame_por_mps": float(k_shift), @@ -723,6 +739,7 @@ class WeedDetector: self, frac_grid: np.ndarray, cfg: dict, + em_movimento: bool = True, ): min_frac_pred = float( cfg.get( @@ -748,14 +765,35 @@ class WeedDetector: evidencia = np.clip(frac_grid / percent_ref, 0.0, 1.0).astype(np.float32) cond_pos = frac_grid >= min_frac_pred - # Decaimento geral, depois reforço positivo. - score = np.maximum(0.0, self._score_grid - down_decay) + if em_movimento: + # Em movimento, cada frame traz evidência de uma nova faixa física + # do terreno; mantém a estratégia acumulativa original. + score = np.maximum(0.0, self._score_grid - down_decay) - score = np.where( - cond_pos, - np.minimum(1.0, score + up_gain * evidencia), - score, - ).astype(np.float32) + score = np.where( + cond_pos, + np.minimum(1.0, score + up_gain * evidencia), + score, + ).astype(np.float32) + modo_atualizacao = "acumulativo_movimento" + else: + # Parado, vários frames observam essencialmente o mesmo terreno. + # Uma EMA faz o score convergir para a evidência atual, em vez de + # transformar repetição temporal em confiança espacial infinita. + alpha_parado = float(cfg.get("score_alpha_parado", 0.70) or 0.70) + alpha_parado = float(np.clip(alpha_parado, 0.0, 0.99)) + + evidencia_atual = np.where( + cond_pos, + evidencia, + 0.0, + ).astype(np.float32) + + score = ( + alpha_parado * self._score_grid + + (1.0 - alpha_parado) * evidencia_atual + ).astype(np.float32) + modo_atualizacao = "ema_parado" self._score_grid = score @@ -764,6 +802,11 @@ class WeedDetector: "percent_ref_evidencia": float(percent_ref), "score_up_gain": float(up_gain), "score_down_decay": float(down_decay), + "em_movimento": bool(em_movimento), + "modo_atualizacao": modo_atualizacao, + "score_alpha_parado": float( + cfg.get("score_alpha_parado", 0.70) or 0.70 + ), "frac_grid_max": float(frac_grid.max()) if frac_grid.size else 0.0, "evidencia_max": float(evidencia.max()) if evidencia.size else 0.0, } @@ -788,20 +831,32 @@ class WeedDetector: k_shift = max(0.0, k_shift) - # Antecipação em fração da imagem: - # Se demora para atuar, avalia células um pouco acima da zona real, - # porque elas chegarão na zona quando o bico abrir de fato. - antecipacao_frac = max(0.0, vel_mps * lat_s * k_shift) + # A zona configurada representa a posição física fixa dos bicos. + # Com faixa=0,85, área=0,10 e inversão vertical, 85%..95% da imagem + # original vira 5%..15% na convenção interna. Essa zona NÃO muda de + # parado até a velocidade de referência. Somente a parcela de + # velocidade ACIMA da referência antecipa a avaliação para compensar + # o tempo de resposta do sistema. + vel_referencia = max( + 0.0, + float(cfg.get("velocidade_referencia_atuacao_mps", 0.65) or 0.65), + ) + velocidade_abs = abs(float(vel_mps)) + velocidade_excedente = max(0.0, velocidade_abs - vel_referencia) + antecipacao_extra_frac = velocidade_excedente * lat_s * k_shift - if getattr(self, "inverter_sentido_vertical_bicos", False): - # Se a erva vem do pé para o topo, antecipar significa olhar - # um pouco abaixo da zona real, pois ela ainda vai subir até o bico. - zona_eval_ini_frac = zona_ini_frac + antecipacao_frac - zona_eval_fim_frac = zona_fim_frac + antecipacao_frac - else: - # Lógica atual: erva vem do topo para o pé. - zona_eval_ini_frac = zona_ini_frac - antecipacao_frac - zona_eval_fim_frac = zona_fim_frac - antecipacao_frac + # A posição dos bicos é fixa. Não tentamos reinterpretar essa zona na + # marcha a ré; a antecipação só indica uma região anterior no sentido + # visual configurado para a operação normal de avanço. + sentido_vertical = ( + 1.0 + if getattr(self, "inverter_sentido_vertical_bicos", False) + else -1.0 + ) + offset_assinado = antecipacao_extra_frac * sentido_vertical + + zona_eval_ini_frac = zona_ini_frac + offset_assinado + zona_eval_fim_frac = zona_fim_frac + offset_assinado zona_eval_ini_frac = max(0.0, min(1.0, zona_eval_ini_frac)) zona_eval_fim_frac = max(0.0, min(1.0, zona_eval_fim_frac)) @@ -872,7 +927,12 @@ class WeedDetector: # Zona avaliada com compensação de latência. "latencia_total_atuacao_ms": float(lat_ms), - "antecipacao_frac": float(antecipacao_frac), + "antecipacao_frac": float(antecipacao_extra_frac), + "velocidade_referencia_atuacao_mps": float(vel_referencia), + "velocidade_excedente_atuacao_mps": float(velocidade_excedente), + "offset_base_referencia_frac": 0.0, + "antecipacao_extra_frac": float(antecipacao_extra_frac), + "offset_assinado_frac": float(offset_assinado), "zona_eval_inicio_frac": float(zona_eval_ini_frac), "zona_eval_fim_frac": float(zona_eval_fim_frac),