ajustes parametros deteccao ervas
This commit is contained in:
parent
1c79eb32d3
commit
56b268a804
|
|
@ -0,0 +1,90 @@
|
|||
import time
|
||||
|
||||
class PIDAdaptativo:
|
||||
def __init__(self, kp_base, kp, ki_base, ki, kd_base, kd, saida_min, saida_max):
|
||||
self.kp_base = kp_base
|
||||
self.ki_base = ki_base
|
||||
self.kd_base = kd_base
|
||||
self.kp = kp
|
||||
self.ki = ki
|
||||
self.kd = kd
|
||||
|
||||
self.saida_min = saida_min
|
||||
self.saida_max = saida_max
|
||||
self.saida = 0.0
|
||||
|
||||
self.setpoint = 0.0
|
||||
self.valor_atual = 0.0
|
||||
self.soma_erro = 0.0
|
||||
self.erro_anterior = 0.0
|
||||
self.derivada_suavizada = 0.0
|
||||
self.ultima_atualizacao = time.time()
|
||||
|
||||
self.limite_soma_erro = 50.0
|
||||
self.filtro_derivativo = 0.2
|
||||
|
||||
def ajustar_ganhos(self, velocidade_percentual):
|
||||
v = max(0.0, min(velocidade_percentual / 100.0, 1.0))
|
||||
|
||||
self.kp = self.kp_base * (1.0 - 0.4 * v ** 2)
|
||||
self.ki = 0.0 if v >= 0.6 else self.ki_base * (1.0 - v ** 1.5)
|
||||
self.kd = self.kd_base * (1.0 + 1.2 * v ** 1.2)
|
||||
|
||||
def atualizar(self, valor_atual, setpoint=None):
|
||||
agora = time.time()
|
||||
dt = max(0.01, min(agora - self.ultima_atualizacao, 0.5))
|
||||
self.ultima_atualizacao = agora
|
||||
|
||||
if setpoint is not None:
|
||||
self.setpoint = setpoint
|
||||
|
||||
self.valor_atual = valor_atual
|
||||
erro = self.setpoint - self.valor_atual
|
||||
|
||||
# Reset da integral se sinal do erro muda
|
||||
if (erro * self.erro_anterior) < 0:
|
||||
self.soma_erro = 0.0
|
||||
|
||||
# Atualiza integral com anti-windup
|
||||
self.soma_erro += erro * dt
|
||||
self.soma_erro = max(-self.limite_soma_erro, min(self.soma_erro, self.limite_soma_erro))
|
||||
|
||||
# Derivada com suavização
|
||||
delta_erro = (erro - self.erro_anterior) / dt
|
||||
if abs(erro - self.erro_anterior) > 10.0:
|
||||
delta_erro *= 0.5
|
||||
|
||||
self.derivada_suavizada = (
|
||||
self.filtro_derivativo * delta_erro +
|
||||
(1 - self.filtro_derivativo) * self.derivada_suavizada
|
||||
)
|
||||
self.derivada_suavizada = max(-500.0, min(500.0, self.derivada_suavizada))
|
||||
|
||||
# PID
|
||||
saida_temp = (
|
||||
self.kp * erro +
|
||||
self.ki * self.soma_erro +
|
||||
self.kd * self.derivada_suavizada
|
||||
)
|
||||
|
||||
# Anti-windup na saída
|
||||
if saida_temp > self.saida_max:
|
||||
saida_temp = self.saida_max
|
||||
self.soma_erro -= erro * dt
|
||||
elif saida_temp < self.saida_min:
|
||||
saida_temp = self.saida_min
|
||||
self.soma_erro -= erro * dt
|
||||
|
||||
self.saida = saida_temp
|
||||
self.erro_anterior = erro
|
||||
|
||||
return self.saida
|
||||
|
||||
def resetar(self):
|
||||
self.valor_atual = 0.0
|
||||
self.setpoint = 0.0
|
||||
self.saida = 0.0
|
||||
self.soma_erro = 0.0
|
||||
self.erro_anterior = 0.0
|
||||
self.derivada_suavizada = 0.0
|
||||
self.ultima_atualizacao = time.time()
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -156,10 +156,9 @@ class CameraManager:
|
|||
predictions, ts, res = self.get_segmentation_predictions()
|
||||
if ts == self._ts_segmentacao_anterior:
|
||||
return # já analisado
|
||||
fps = 1.0 / (ts - self._ts_segmentacao_anterior)
|
||||
self._ts_segmentacao_anterior = ts
|
||||
if predictions is not None:
|
||||
analise_completa = self.detectar_ervas(predictions, fps)
|
||||
analise_completa = self.detectar_ervas(predictions)
|
||||
analise = analise_completa.get("dados_visuais", {})
|
||||
|
||||
#self.mostrar_log(f"Deteccoes no radar: {len(analise.get('deteccoes', []))}")
|
||||
|
|
@ -186,12 +185,12 @@ class CameraManager:
|
|||
|
||||
self._ultima_analise = analise_completa.copy()
|
||||
|
||||
def detectar_ervas(self, predictions, fps):
|
||||
def detectar_ervas(self, predictions):
|
||||
if self.weed_detector is None:
|
||||
self.mostrar_log("WeedDetector não inicializado!")
|
||||
return []
|
||||
try:
|
||||
return self.weed_detector.detectar(predictions, fps)
|
||||
return self.weed_detector.detectar(predictions)
|
||||
except Exception as e:
|
||||
self.mostrar_log(f"Erro na detecção de ervas: {e}")
|
||||
return []
|
||||
|
|
|
|||
|
|
@ -5,5 +5,16 @@
|
|||
"min_area_px": 400,
|
||||
"max_area_frac": 0.2,
|
||||
"ia_roi_size": 0.2,
|
||||
"ia_resolution": [384,384]
|
||||
"ia_resolution": [384,384],
|
||||
|
||||
"erva_top_band_frac": 0.30,
|
||||
"erva_frac_ema": 0.3,
|
||||
"erva_thresh_vel_gain": 0.4,
|
||||
"min_frac_erva_global_on": 0.0020,
|
||||
"min_frac_erva_global_off": 0.0015,
|
||||
"min_frac_erva_top_on": 0.0015,
|
||||
"min_frac_erva_top_off": 0.0010,
|
||||
"min_frac_erva_por_bico": 0.02,
|
||||
"usar_morfologia": true,
|
||||
"kernel_morf": 3
|
||||
}
|
||||
|
|
@ -59,7 +59,18 @@ def load_config(force_reload=False):
|
|||
"min_area_px": 400,
|
||||
"max_area_frac": 0.2,
|
||||
"ia_roi_size": 0.2,
|
||||
"ia_resolution": [384,384]
|
||||
"ia_resolution": [384,384],
|
||||
|
||||
"erva_top_band_frac": 0.30,
|
||||
"erva_frac_ema": 0.3,
|
||||
"erva_thresh_vel_gain": 0.4,
|
||||
"min_frac_erva_global_on": 0.0020,
|
||||
"min_frac_erva_global_off": 0.0015,
|
||||
"min_frac_erva_top_on": 0.0015,
|
||||
"min_frac_erva_top_off": 0.0010,
|
||||
"min_frac_erva_por_bico": 0.02,
|
||||
"usar_morfologia": True,
|
||||
"kernel_morf": 3
|
||||
}
|
||||
dadosAtu = ContextoGlobalRedis.get_operacao().get("Atu", {})
|
||||
contexto = ContextoGlobalRedis.get_contexto()
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ class WeedDetector:
|
|||
print(f"Erro ao processar predictions: {e}")
|
||||
return None
|
||||
|
||||
def detectar(self, predictions, freq):
|
||||
def detectar(self, predictions):
|
||||
try:
|
||||
# 🔸 Constrói a máscara colorida e outras saídas com base na predictions já pronta
|
||||
resultado = self._segmentar_predictions(predictions)
|
||||
|
|
@ -86,37 +86,53 @@ class WeedDetector:
|
|||
return None
|
||||
|
||||
# 🔸 Extrai blobs da classe ERVA (classe_id = 0)
|
||||
mask_erva = (classes_mask == ClassesSegmentacao.ERVA.value).astype(np.uint8)
|
||||
num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(mask_erva, connectivity=8)
|
||||
#mask_erva = (classes_mask == ClassesSegmentacao.ERVA.value).astype(np.uint8)
|
||||
#num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(mask_erva, connectivity=8)
|
||||
|
||||
deteccoes = []
|
||||
for i in range(1, num_labels): # ignora fundo
|
||||
x, y, w, h, area = stats[i]
|
||||
deteccoes.append({
|
||||
'id': 0,
|
||||
'x': x,
|
||||
'y': y,
|
||||
'largura': w,
|
||||
'altura': h,
|
||||
'confianca': 1.0,
|
||||
'descricao': self.classes[ClassesSegmentacao.ERVA.value]
|
||||
})
|
||||
#deteccoes = []
|
||||
#for i in range(1, num_labels): # ignora fundo
|
||||
# x, y, w, h, area = stats[i]
|
||||
# deteccoes.append({
|
||||
# 'id': 0,
|
||||
# 'x': x,
|
||||
# 'y': y,
|
||||
# 'largura': w,
|
||||
# 'altura': h,
|
||||
# 'confianca': 1.0,
|
||||
# 'descricao': self.classes[ClassesSegmentacao.ERVA.value]
|
||||
# })
|
||||
|
||||
# 🔸 Calcula controle de bicos com base na segmentação
|
||||
controle_bicos, deteccoes_filtro = self._calcular_atuacao_bicos(deteccoes, classes_mask.shape)
|
||||
#controle_bicos, deteccoes_filtro = self._calcular_atuacao_bicos(deteccoes, classes_mask.shape)
|
||||
|
||||
# 🔸 Decisão por máscara (sem bbox)
|
||||
#controle_bicos, estat, ervas_no_radar = self._atuacao_por_mascara(classes_mask)
|
||||
#self.frame_idx += 1
|
||||
|
||||
from weed_worker.config import load_config
|
||||
config = load_config()
|
||||
|
||||
# vel_norm pode vir do contexto (0..1 da sua Vmax). Se não tiver, manda 0.0
|
||||
vel_norm = float(config.get("velocidade_robo", 0.0))
|
||||
ervas_no_radar, estat_erva = self._decidir_ervas_no_radar(classes_mask, config, vel_norm=vel_norm)
|
||||
controle_bicos, estat_bicos = self._atuacao_por_mascara(classes_mask, config) # sua lógica de CANA por setor
|
||||
|
||||
self.frame_idx += 1
|
||||
|
||||
resultado["dados_visuais"] = {
|
||||
"timestamp": time.time(),
|
||||
"height": classes_mask.shape[0],
|
||||
"width": classes_mask.shape[1],
|
||||
"deteccoes": deteccoes_filtro,
|
||||
"deteccoes": [], # deteccoes_filtro,
|
||||
"controle": controle_bicos,
|
||||
"ervas_identificadas": self.ervas_identificadas
|
||||
"ervas_identificadas": self.ervas_identificadas,
|
||||
"ervas_no_radar": ervas_no_radar, # <- NOVO sinal
|
||||
"estatisticas": {
|
||||
"erva": estat_erva,
|
||||
"bicos": estat_bicos
|
||||
}
|
||||
}
|
||||
|
||||
self._mostrar_debug_bicos(resultado["mask_color"], deteccoes_filtro, controle_bicos, freq)
|
||||
self._mostrar_debug_bicos(resultado["mask_color"], [], controle_bicos, ervas_no_radar)
|
||||
|
||||
return resultado
|
||||
|
||||
|
|
@ -283,7 +299,7 @@ class WeedDetector:
|
|||
self.ervas_registradas_bico[idx_bico].add(id_erva)
|
||||
|
||||
|
||||
def _mostrar_debug_bicos(self, frame, detections, atuacao_bicos, freq):
|
||||
def _mostrar_debug_bicos(self, frame, detections, atuacao_bicos, ervas_no_radar):
|
||||
try:
|
||||
# mede FPS do "ciclo de debug" (render + imshow)
|
||||
dbg_fps = self._fps_update('_dbg_last_ts', '_dbg_fps_ema')
|
||||
|
|
@ -294,7 +310,8 @@ class WeedDetector:
|
|||
# seg_color[self.predictions == class_id] = color
|
||||
#overlay = cv2.addWeighted(original, 0.5, seg_color, 0.5, 0)
|
||||
|
||||
debug_img = frame.copy()
|
||||
#debug_img = frame.copy()
|
||||
debug_img = cv2.resize(frame.copy(), (1920,1080), interpolation=cv2.INTER_NEAREST)
|
||||
H, W = debug_img.shape[:2]
|
||||
|
||||
from weed_worker.config import load_config
|
||||
|
|
@ -335,9 +352,9 @@ class WeedDetector:
|
|||
cv2.rectangle(overlay2, (x0, y_fim), (x1, y_inicio), cor, -1)
|
||||
cv2.addWeighted(overlay2, 0.15, debug_img, 0.85, 0, debug_img)
|
||||
cv2.rectangle(debug_img, (x0, y_fim), (x1, y_inicio), cor, 1)
|
||||
status = "ON" if controle_bicos.get(i, False) else "OFF"
|
||||
cv2.putText(debug_img, f"Bico {i} {status}", (x0+5, y_inicio+25),
|
||||
cv2.FONT_HERSHEY_SIMPLEX, 0.6, cor, 2)
|
||||
status = "ON" if atuacao_bicos.get(i, False) else "OFF"
|
||||
status_controle = "ON" if controle_bicos.get(i, False) else "OFF"
|
||||
cv2.putText(debug_img, f"Bico {i} {status} ({status_controle})", (x0+5, y_inicio+25), cv2.FONT_HERSHEY_SIMPLEX, 0.6, cor, 2)
|
||||
|
||||
escala_x = self.resolucao[0] / frame.shape[1]
|
||||
escala_y = self.resolucao[1] / frame.shape[0]
|
||||
|
|
@ -356,8 +373,7 @@ class WeedDetector:
|
|||
cv2.FONT_HERSHEY_SIMPLEX, 0.5, bbox_cor, 2)
|
||||
|
||||
# --- HUD de performance ---
|
||||
seg_fps = self._seg_fps_ema if self._seg_fps_ema is not None else 0.0
|
||||
cv2.putText(debug_img, f"Seg FPS: {freq:.1f}", (10, 30),
|
||||
cv2.putText(debug_img, f"Ervas no radar: {'Sim' if ervas_no_radar else 'Nao'}", (10, 30),
|
||||
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0,255,0), 2)
|
||||
cv2.putText(debug_img, f"Dbg FPS: {dbg_fps:.1f}", (10, 60),
|
||||
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0,255,0), 2)
|
||||
|
|
@ -381,3 +397,144 @@ class WeedDetector:
|
|||
setattr(self, ema_attr, fps_ema)
|
||||
return fps_ema if fps_ema is not None else 0.0
|
||||
|
||||
|
||||
def _atuacao_por_mascara(self, classes_mask, config):
|
||||
"""
|
||||
Decide atuação dos bicos por máscara (sem bbox):
|
||||
- Para cada bico: ativa se a fração de CANA no setor >= limiar.
|
||||
- Limiar aceita dois formatos:
|
||||
• < 1.0 => fração (recomendado, independente da resolução)
|
||||
• >= 1 => pixels absolutos (retrocompat)
|
||||
Retorna: (atuacao_bicos: dict, estatisticas: dict)
|
||||
"""
|
||||
qtd_bicos = int(config.get("qtd_bicos"))
|
||||
zona_inicio = float(config.get("ia_roi_begin"))
|
||||
zona_altura = float(config.get("ia_roi_size"))
|
||||
usar_morf = bool(config.get("usar_morfologia", False))
|
||||
kernel_morf = int(config.get("kernel_morf", 3))
|
||||
|
||||
# Limiar “dinâmico”: fração (<1) ou absoluto (>=1)
|
||||
thr_erva_cfg = config.get("min_frac_erva_por_bico", 0.02) # 2% do setor por padrão
|
||||
try:
|
||||
thr_erva_cfg = float(thr_erva_cfg)
|
||||
except:
|
||||
thr_erva_cfg = 0.02
|
||||
|
||||
H, W = classes_mask.shape[:2]
|
||||
y_inicio = int((1.0 - zona_inicio) * H)
|
||||
y_fim = int((1.0 - (zona_inicio + zona_altura)) * H)
|
||||
|
||||
y_top = min(y_inicio, y_fim)
|
||||
y_bot = max(y_inicio, y_fim)
|
||||
band = classes_mask[y_top:y_bot, :]
|
||||
|
||||
# Máscara binária de CANA na banda
|
||||
mask_erva = (band == ClassesSegmentacao.ERVA.value).astype(np.uint8)
|
||||
|
||||
# Anti-ruído opcional
|
||||
if usar_morf and kernel_morf >= 3 and kernel_morf % 2 == 1:
|
||||
k = cv2.getStructuringElement(cv2.MORPH_RECT, (kernel_morf, kernel_morf))
|
||||
mask_erva = cv2.morphologyEx(mask_erva, cv2.MORPH_OPEN, k)
|
||||
|
||||
largura_bico = W / float(qtd_bicos)
|
||||
atuacao_bicos = {i: False for i in range(qtd_bicos)}
|
||||
cont_erva_px = {}
|
||||
cont_erva_frac = {}
|
||||
|
||||
for i in range(qtd_bicos):
|
||||
x0 = int(i * largura_bico)
|
||||
x1 = int((i + 1) * largura_bico)
|
||||
x0 = max(0, min(W, x0))
|
||||
x1 = max(0, min(W, x1))
|
||||
if x1 <= x0:
|
||||
cont_erva_px[i] = 0
|
||||
cont_erva_frac[i] = 0.0
|
||||
continue
|
||||
|
||||
region = mask_erva[:, x0:x1]
|
||||
px_erva = int(region.sum())
|
||||
area_setor = float(region.size)
|
||||
frac_erva = (px_erva / area_setor) if area_setor > 0 else 0.0
|
||||
|
||||
cont_erva_px[i] = px_erva
|
||||
cont_erva_frac[i] = frac_erva
|
||||
|
||||
# Se threshold <1: comparar por fração; se >=1: comparar por pixels
|
||||
if thr_erva_cfg < 1.0:
|
||||
ativa = (frac_erva >= thr_erva_cfg)
|
||||
else:
|
||||
ativa = (px_erva >= int(thr_erva_cfg))
|
||||
|
||||
atuacao_bicos[i] = bool(ativa)
|
||||
|
||||
estatisticas = {
|
||||
"contagem_cana_px_por_bico": cont_erva_px,
|
||||
"contagem_cana_frac_por_bico": cont_erva_frac,
|
||||
"faixa": {"y_top": y_top, "y_bot": y_bot}
|
||||
}
|
||||
self.ultimo_status_bicos = atuacao_bicos.copy()
|
||||
return atuacao_bicos, estatisticas
|
||||
|
||||
def _fractions_erva(self, classes_mask):
|
||||
H, W = classes_mask.shape[:2]
|
||||
total_px = H * W
|
||||
|
||||
# global
|
||||
frac_global = np.count_nonzero(classes_mask == ClassesSegmentacao.ERVA.value) / float(total_px)
|
||||
|
||||
# lookahead (top band p/ antecipar redução)
|
||||
from weed_worker.config import load_config
|
||||
cfg = load_config()
|
||||
top_frac = float(cfg.get("erva_top_band_frac", 0.30)) # 30% do topo
|
||||
top_h = max(1, int(H * top_frac))
|
||||
band_top = classes_mask[0:top_h, :]
|
||||
frac_top = np.count_nonzero(band_top == ClassesSegmentacao.ERVA.value) / float(band_top.size)
|
||||
|
||||
return frac_global, frac_top
|
||||
|
||||
def _decidir_ervas_no_radar(self, classes_mask, cfg, vel_norm=0.0):
|
||||
"""
|
||||
Decide ErvasNoRadar por fração (global e lookahead).
|
||||
Aplica histerese e opcionalmente ajusta threshold pela velocidade.
|
||||
"""
|
||||
|
||||
# thresholds base (frações)
|
||||
on_global = float(cfg.get("min_frac_erva_global_on", 0.0020)) # 0,20%
|
||||
off_global = float(cfg.get("min_frac_erva_global_off", 0.0015)) # 0,15%
|
||||
on_top = float(cfg.get("min_frac_erva_top_on", 0.0015))
|
||||
off_top = float(cfg.get("min_frac_erva_top_off", 0.0010))
|
||||
|
||||
# ajuste por velocidade (opcional)
|
||||
k = float(cfg.get("erva_thresh_vel_gain", 0.0)) # 0.0 desliga
|
||||
adj = max(0.5, min(1.0, 1.0 - k * float(vel_norm))) # clamp [0.5, 1.0]
|
||||
on_global *= adj; on_top *= adj
|
||||
# (tipicamente só mexe no ON; OFF pode ficar fixo)
|
||||
|
||||
frac_global, frac_top = self._fractions_erva(classes_mask)
|
||||
|
||||
# EMA (suavização) opcional
|
||||
alpha = float(cfg.get("erva_frac_ema", 0.3))
|
||||
self._erva_frac_global_ema = (1-alpha)*getattr(self, "_erva_frac_global_ema", frac_global) + alpha*frac_global
|
||||
self._erva_frac_top_ema = (1-alpha)*getattr(self, "_erva_frac_top_ema", frac_top) + alpha*frac_top
|
||||
|
||||
# Histerese global
|
||||
prev = getattr(self, "_ervas_no_radar", False)
|
||||
hit_global = self._erva_frac_global_ema >= (on_global if not prev else off_global)
|
||||
hit_top = self._erva_frac_top_ema >= (on_top if not prev else off_top)
|
||||
|
||||
ervas_no_radar = bool(hit_global or hit_top)
|
||||
self._ervas_no_radar = ervas_no_radar
|
||||
|
||||
estat = {
|
||||
"frac_global": frac_global,
|
||||
"frac_top": frac_top,
|
||||
"ema_global": self._erva_frac_global_ema,
|
||||
"ema_top": self._erva_frac_top_ema,
|
||||
"thr_on_global": on_global,
|
||||
"thr_off_global": off_global,
|
||||
"thr_on_top": on_top,
|
||||
"thr_off_top": off_top,
|
||||
"vel_adj": adj
|
||||
}
|
||||
return ervas_no_radar, estat
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue