agrobot_base/Python/gal5000/quick_sugar_vs_weed_overlay.py

177 lines
5.9 KiB
Python
Raw Normal View History

2026-01-28 18:05:51 +00:00
import os
import cv2
import numpy as np
# ========= CONFIG =========
RAW_PATH = r"dataset/ds/raws/20260115_152748_007.raw" # ajuste se precisar
W = 2592 # largura do RAW (pixels do mosaico)
H = 2056 # altura do RAW
UPSCALE = 2 # aumenta preview (2x fica bom)
# overlay
ALPHA = 0.45 # transparência da máscara
MIN_IR = 15 # ignora pixels muito escuros no IR (ruído)
MIN_G = 20 # ignora pixels muito escuros no G (ruído)
# ========= RAW decode =========
def read_raw_mosaic(path, w, h):
raw = np.fromfile(path, dtype=np.uint8)
if raw.size != w * h:
raise RuntimeError(f"RAW size mismatch: got {raw.size}, expected {w*h}. "
f"Confira W/H.")
return raw.reshape(h, w)
def split_4ch(raw):
# 2x2 pattern:
# [R, G]
# [IR,B]
R = raw[0::2, 0::2]
G = raw[0::2, 1::2]
IR = raw[1::2, 0::2]
B = raw[1::2, 1::2]
return R, G, IR, B
def norm8(x, p_lo=2, p_hi=98):
lo = np.percentile(x, p_lo)
hi = np.percentile(x, p_hi)
if hi <= lo + 1:
return x.astype(np.uint8)
y = (x.astype(np.float32) - lo) * (255.0 / (hi - lo))
return np.clip(y, 0, 255).astype(np.uint8)
def make_rgb_preview(R, G, B, upscale=2):
Rn, Gn, Bn = norm8(R), norm8(G), norm8(B)
bgr = np.dstack([Bn, Gn, Rn]) # OpenCV = BGR
if upscale != 1:
bgr = cv2.resize(bgr, (bgr.shape[1]*upscale, bgr.shape[0]*upscale), interpolation=cv2.INTER_NEAREST)
return bgr
# ========= Simple spectral classifier =========
def classify_cane_weed(G, IR, thr_ratio, thr_ir_bias):
"""
Retorna mask_cane, mask_weed em resolução H/2 x W/2.
Padrões:
- ERVA: ratio = G/(IR+1) maior
- CANA: IR relativamente maior + ratio menor
thr_ratio: limiar principal de G/IR
thr_ir_bias: adicional: favorece CANA quando IR está alto
"""
Gf = G.astype(np.float32)
IRf = IR.astype(np.float32)
ratio = Gf / (IRf + 1.0)
valid = (Gf >= MIN_G) & (IRf >= MIN_IR)
# regra: erva se ratio > thr_ratio
weed = valid & (ratio >= thr_ratio)
# cana: ratio baixo OU IR alto (bias)
# IR alto relativo: IR > (G - thr_ir_bias) ajuda puxar cana
cane = valid & (ratio < thr_ratio)
# resolve conflitos: se cair em ambos, usa ratio como desempate
both = weed & cane
if np.any(both):
# se ratio alto -> weed, senão -> cane
weed[both] = ratio[both] >= thr_ratio
cane[both] = ~weed[both]
# pixels válidos mas não classificados: decide pelo ratio
undec = valid & ~(weed | cane)
if np.any(undec):
weed[undec] = ratio[undec] >= thr_ratio
cane[undec] = ~weed[undec]
return cane, weed, ratio, valid
def morph_cleanup(mask, k=3):
if k <= 1:
return mask
ker = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (k, k))
m = mask.astype(np.uint8) * 255
m = cv2.medianBlur(m, 3)
m = cv2.morphologyEx(m, cv2.MORPH_OPEN, ker, iterations=1)
m = cv2.morphologyEx(m, cv2.MORPH_CLOSE, ker, iterations=1)
return m > 0
def overlay_classes(bgr, cane_mask, weed_mask, upscale=2):
# sobe masks pro tamanho do preview
h2, w2 = cane_mask.shape
if upscale != 1:
cane = cv2.resize(cane_mask.astype(np.uint8)*255, (w2*upscale, h2*upscale), interpolation=cv2.INTER_NEAREST)
weed = cv2.resize(weed_mask.astype(np.uint8)*255, (w2*upscale, h2*upscale), interpolation=cv2.INTER_NEAREST)
else:
cane = cane_mask.astype(np.uint8)*255
weed = weed_mask.astype(np.uint8)*255
out = bgr.copy()
# cores (BGR): cana=azul, erva=verde
cane_col = np.zeros_like(out)
cane_col[:, :, 0] = cane # Blue
weed_col = np.zeros_like(out)
weed_col[:, :, 1] = weed # Green
# combina overlays
mask_any = (cane > 0) | (weed > 0)
overlay = np.clip(cane_col + weed_col, 0, 255).astype(np.uint8)
out[mask_any] = (out[mask_any].astype(np.float32) * (1 - ALPHA) + overlay[mask_any].astype(np.float32) * ALPHA).astype(np.uint8)
return out
def main():
raw = read_raw_mosaic(RAW_PATH, W, H)
R, G, IR, B = split_4ch(raw)
base = make_rgb_preview(R, G, B, upscale=UPSCALE)
cv2.namedWindow("overlay", cv2.WINDOW_NORMAL)
cv2.namedWindow("debug", cv2.WINDOW_NORMAL)
# sliders
# ratio em escala 0..300 -> 0.00..3.00
cv2.createTrackbar("thr_ratio x100", "overlay", 270, 500, lambda v: None) # 2.70 inicial
cv2.createTrackbar("ir_bias", "overlay", 5, 100, lambda v: None) # 5 inicial
cv2.createTrackbar("morph_k", "overlay", 5, 21, lambda v: None) # 5 inicial
while True:
thr_ratio = cv2.getTrackbarPos("thr_ratio x100", "overlay") / 100.0
thr_ir_bias = float(cv2.getTrackbarPos("ir_bias", "overlay"))
mk = cv2.getTrackbarPos("morph_k", "overlay")
if mk % 2 == 0:
mk += 1
cane, weed, ratio, valid = classify_cane_weed(G, IR, thr_ratio, thr_ir_bias)
cane2 = morph_cleanup(cane, k=mk)
weed2 = morph_cleanup(weed, k=mk)
out = overlay_classes(base, cane2, weed2, upscale=UPSCALE)
# debug views
ratio_vis = norm8(ratio, 2, 98)
if UPSCALE != 1:
ratio_vis = cv2.resize(ratio_vis, (ratio_vis.shape[1]*UPSCALE, ratio_vis.shape[0]*UPSCALE), interpolation=cv2.INTER_NEAREST)
# desenha texto rápido
txt = f"thr_ratio={thr_ratio:.2f} ir_bias={thr_ir_bias:.0f} morph_k={mk}"
cv2.putText(out, txt, (12, 28), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0,0,0), 3, cv2.LINE_AA)
cv2.putText(out, txt, (12, 28), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255,255,255), 2, cv2.LINE_AA)
cv2.imshow("overlay", out)
cv2.imshow("debug", ratio_vis)
k = cv2.waitKey(10) & 0xFF
if k in (ord('q'), 27):
break
cv2.destroyAllWindows()
if __name__ == "__main__":
main()