From 56b268a8047e4dc05dc1ba4878a42158c0a3bdeb Mon Sep 17 00:00:00 2001 From: Diego Freitas Date: Thu, 7 Aug 2025 16:35:30 -0300 Subject: [PATCH] ajustes parametros deteccao ervas --- .../workers/manager_worker/modulos/pid.py | 90 ++++++++ .../camera_manager.cpython-311.pyc | Bin 14371 -> 14095 bytes .../__pycache__/config.cpython-311.pyc | Bin 4979 -> 5490 bytes .../__pycache__/weed_detector.cpython-311.pyc | Bin 21017 -> 27944 bytes .../workers/weed_worker/camera_manager.py | 7 +- .../Scripts/workers/weed_worker/config.json | 13 +- .../Scripts/workers/weed_worker/config.py | 13 +- .../workers/weed_worker/weed_detector.py | 211 +++++++++++++++--- 8 files changed, 301 insertions(+), 33 deletions(-) create mode 100644 AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/manager_worker/modulos/pid.py diff --git a/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/manager_worker/modulos/pid.py b/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/manager_worker/modulos/pid.py new file mode 100644 index 000000000..a09edab33 --- /dev/null +++ b/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/manager_worker/modulos/pid.py @@ -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() diff --git a/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/weed_worker/__pycache__/camera_manager.cpython-311.pyc b/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/weed_worker/__pycache__/camera_manager.cpython-311.pyc index d688853ce4995dbaaf8431fd15dd5e3e146d7b29..16274820fbe8d93221a77c3de757e7df994532a8 100644 GIT binary patch delta 1098 zcma)4T}V@582-Lieb(jDlYHp7VUqd%pL5&-0z@ zU8zpXEwecfk*NJ~`OsVOzC|d4r4C`;DolWh8o!--2Zorc0Z zQL>@27x5`kL&HYcqc66yH!(tY$Xbjfr*TkSQ4-cg(M^VA$Xd!6R9bZ@+Ou=ejVk0c zgxd3ST+^wmavUMeu)NEN9_E~Ot0niw;cORK&uEGGX@tPsR)se#gG3|<6e~lmE;e)HfRG3p#5x%;-P%D~B_=s_cS?&c!#HF7G zQ6R>as81EQ-c5BfxhW!fB(B5@_$9aR!i6z0Scd=L8FyKpJYxP5pZj_LVsv_^8WQEiz#iB}UxFz1v6>C)`IYwo(q$Z>mDIRW)w!{_1NHj+- zWO^BL6M^FnklYT#Afd`D4C>mG0gtywH^IB7OhU%woU*$n?KR`}n$bYU9-OcT$MVak zgvv?5J1%%{2OsXp2*C*<*t7EskIasA!c&GZ10hQhegikE+MrEuDQiG3q;jOgQiwGtoM5OkN%DklZ`{tx#pFQtW91sc6#8G0FIXJ@807gXxW`n# z0M)*LF=Gu(SOa7E0k~Ve2Ksz{n{6w4lo#@_Pj$ACi+#EpQ|{7-Jn30qiY3+_S>Lqs z9PM$Qz_h+gQiyrIFta{|pQXDtyl4EEXttli00nB3e1$-jadEh7RLI)RDT&y@=uvqH zPW$WeRe0(5;E%B2PvKrTyRp$h&6bZ5nAVZYQswP*f1Scr3UTkVvPKGnux|5G0^o{foZu_7pJb#!Dy*%<04H`t)r0vq|F4#ZCb$Ho|g!QLn76)GF}koTA8 ze!nNr{odzEF?X%tyW#b^35!2}{ot)|%jQx8fEbh;WD(aMaF zl@XHhS-EL9BrC_>>w7KnFxe^j8?1_BHQ!ou-9u1VUp*`2237aFs}B9bvo~671j=`U z(A79Bn1acx!Y^W@NY-mR*-v zs{3uQy!mvaukqsRm%^3MU^y^Y_6?omV0(9vzsr@Sv5GVX`?^~PABqh>iETfKZ7b~) zU+li^y5srMbN98C{RhfJ2P;Dd%l+e({&A+UPg#st#Q2IBe=JI+W8EQ2IT-ad(VtK*pn-d*;ORQw|=;s{)RaTA>B?F)v6$ye^tD0j~h91U~#Hc`yOj?rl8 z``#kQR&Z$Bp^X>t4Mhf3M?ulqP|w46+lusl>5c6VY<(Z1dKqC3VIEAw{WA}as@ diff --git a/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/weed_worker/__pycache__/config.cpython-311.pyc b/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/weed_worker/__pycache__/config.cpython-311.pyc index 636ad0d30f72873f3256c1918b442056575d6690..f0ae9cb1fedead57551c94e423cb1b07ab4f8bdd 100644 GIT binary patch delta 1330 zcmaJ=O>7%Q6rPE_@viOlU%YYLG(UD+8|SZyeBe@HOj4Qytx^$O;Idt3)|+i@Z`ie? zsvul$s23_t55NU;!dy^Mq+Z}an@hQ&a)FhQ;D8{9N)t*XgplCPx|I@v7->Fz-#72Q zefxIyasId2$S1NKM6il~-`=_@e;yG7p#Vr9?8kZkZYB`NBgPDBCwb6d<&gb4Ju8-) z&zgt`WSC+SAR?edf`B0s29!wzFzR89#7W#30-YcufJqM(G73CJ#sJgg1mL)bC&>ix zNiqdE?N!h4>hwMF63)_J#EZTelA|9<85SHdFCqEk}ZcN+TO(MxnUSi#HmFt`wP!$hxj40XqJI$C3cu7{NFKA(mg ztJ_*rchoJ@Y3sJ`==($54mHh|s@b}xZeKHRVQ}Q8c5UE5#-{P%2Yh{9c0;D7+Loz0 z=5^hbxzHV}(P^7j%e=u86S}>lscmapt!XWydfBB?sSEqtvMYP1+*I|Z<|YThTTo<6 z-O(GWp_#2G5Bt9Crg&R}U|!T{ST(JoS}o(RPY)iRuU#&?WB(mduN!~9edWtGeYNZ+ zy^yZ=$fvDYEmt|7$s4vHZBTi=?2aBk-?nVEW^P)>o5|14{dE0y*^PD_%~qS1UAG#R zVQOyZs&2R79k}KCD(#Ldz0oFKma|X43O(Ar8y?1)4H)<=Y$pp})- zhOVmi3Y^-j9L{=iFIk&c?K0Q=^4L`xO-$g+y9)^gFBfr|!E=v76=OWh(7(ZsK>9!4 C)}90a delta 785 zcmaKp!E4h{9LMvTrcIhAZIiBD+f_EZ*0ozv8Cs_uv$ayF0}*7yQ>}fm3vR6>?HFEG zZ{lh4yeJ-a(~F?&FIYVc5nq z6a*IqG5Y1{LYsdpvQFM)8rJ?cy)okSAvqhPnts#Nk5NiHO|G(&K{sZw13NK`Ie1*y zRkLCKg_G>C^TA;Ac4H*qR2x&aHnW5_E|ZJ_*^rzjQti3SQNlPiw4O@ zSCkInutgEV@i{a^uJP&Bn|tPHdo~bt4N+9>`7<-#&!}ynq|T*LjyobpJRc|VqqALc!q}Iz7O{tXcuNcBoWu*_`s~WsoDQgY2 ztg8^u{98YSalU{N`$tR(zh>aEJ~xQi9P^u=FMj*`{{5fgTlM=Fy?rB$D>L z;oY3nqxwR2J;PHDDxU0)PjttU-B_X2)$_0;;00L|e*KLe2X_Gu!sMHG zcQtdQfz|B0r9Y20{_h@29h>rq`QQTO4sF{9_1o}Lk5Dl{KhAmbtZg$C%w?I)GwwV~ z=A@xC9CTRM6~uI;cs|Q?GK`tYGLU(P3LL;zz+MbHx1y*RChJmxEkMB}(n{p3bds3` zC=pp6_f5mj;F=5YJFr@>Y3Ipx`60@XPjYm43eL2Prd%>z_Sr`Do_5Y;^RsKSMD$0| b+{T1|0G&O7LOtrZTt*0`depz7iE#Z7blKO7 diff --git a/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/weed_worker/__pycache__/weed_detector.cpython-311.pyc b/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/weed_worker/__pycache__/weed_detector.cpython-311.pyc index 9ec094fce30e4f87c0f9f5e9f0a4e6b7218986d0..8e58ced093f1a5b63c08b382a31b4a87ceaa5190 100644 GIT binary patch delta 11104 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 jdJdxP_^Unbv;((vJyY8wr= 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 +