implementado opcao para salvar frame cientifico para pos processamento

This commit is contained in:
Diego Freitas 2026-05-27 16:32:31 -03:00
parent 4ab5d2c0c2
commit c304401ef0
8 changed files with 244 additions and 52 deletions

View File

@ -372,7 +372,7 @@
MatrizCusto = 5, MatrizCusto = 5,
Deteccoes = 6, Deteccoes = 6,
Corredor = 7, Corredor = 7,
Raw4 = 8, PosProcessamento = 8,
} }
public enum ModoAgitadorCalda public enum ModoAgitadorCalda

View File

@ -2139,7 +2139,7 @@ namespace AgroBase.Models
string nome = Variaveis.OperacaoEmAndamento.idxLog.ToString(); string nome = Variaveis.OperacaoEmAndamento.idxLog.ToString();
var frames_salvar = new List<TipoFrameCamera>() { TipoFrameCamera.Rgb, TipoFrameCamera.Segmentacao }; var frames_salvar = new List<TipoFrameCamera>() { TipoFrameCamera.Rgb, TipoFrameCamera.Segmentacao };
if (Variaveis.OperacaoEmAndamento.Parametros.Controle.RegistrarDadosPosProcessamento) frames_salvar.Add(TipoFrameCamera.Raw4); if (Variaveis.OperacaoEmAndamento.Parametros.Controle.RegistrarDadosPosProcessamento) frames_salvar.Add(TipoFrameCamera.PosProcessamento);
Camera.SaveFrames(frames_salvar, nome, Caminho); Camera.SaveFrames(frames_salvar, nome, Caminho);
var cameras_conectadas = JsonConvert.DeserializeObject<Dictionary<string, object>>(RedisService.Get(CtxKey.DadosCameras)); var cameras_conectadas = JsonConvert.DeserializeObject<Dictionary<string, object>>(RedisService.Get(CtxKey.DadosCameras));
@ -2164,7 +2164,7 @@ namespace AgroBase.Models
string nome = Variaveis.OperacaoEmAndamento.idxLog.ToString(); string nome = Variaveis.OperacaoEmAndamento.idxLog.ToString();
var frames_salvar = new List<TipoFrameCamera>() { TipoFrameCamera.Rgb, TipoFrameCamera.Segmentacao }; var frames_salvar = new List<TipoFrameCamera>() { TipoFrameCamera.Rgb, TipoFrameCamera.Segmentacao };
if (Variaveis.OperacaoEmAndamento.Parametros.Controle.RegistrarDadosPosProcessamento) frames_salvar.Add(TipoFrameCamera.Heatmap); if (Variaveis.OperacaoEmAndamento.Parametros.Controle.RegistrarDadosPosProcessamento) frames_salvar.AddRange(new List<TipoFrameCamera>() { TipoFrameCamera.PosProcessamento, TipoFrameCamera.Heatmap });
Camera.SaveFrames(frames_salvar, nome, Caminho); Camera.SaveFrames(frames_salvar, nome, Caminho);
var cameras_conectadas = JsonConvert.DeserializeObject<Dictionary<string, object>>(RedisService.Get(CtxKey.DadosCameras)); var cameras_conectadas = JsonConvert.DeserializeObject<Dictionary<string, object>>(RedisService.Get(CtxKey.DadosCameras));

View File

@ -576,7 +576,6 @@ class ControladorMPC:
# Convenção: e_lat > 0 quando você está mais perto da ESQUERDA (dir > esq) # Convenção: e_lat > 0 quando você está mais perto da ESQUERDA (dir > esq)
# (Se quiser o oposto, troque o sinal.) # (Se quiser o oposto, troque o sinal.)
e_lat = float((dist_dir - dist_esq) / 2.0) e_lat = float((dist_dir - dist_esq) / 2.0)
print(e_lat)
# 3) Largura efetiva e peso para o ângulo do corredor # 3) Largura efetiva e peso para o ângulo do corredor
half = max((dist_esq + dist_dir) * 0.5, 1e-6) half = max((dist_esq + dist_dir) * 0.5, 1e-6)
@ -1024,7 +1023,7 @@ class ControladorMPC:
"angulo": comando_anterior.get("angulo", 0), "angulo": comando_anterior.get("angulo", 0),
"tipo": comando_anterior.get("tipo", TipoMovimentoDirecional.RodasDianteiras.value), "tipo": comando_anterior.get("tipo", TipoMovimentoDirecional.RodasDianteiras.value),
"simulacao": [], "simulacao": [],
"erro_latreral": 0, "erro_lateral": 0,
"erro_orientacao": 0, "erro_orientacao": 0,
"debug_custo": {}, "debug_custo": {},
"candidatos_testados": 0, "candidatos_testados": 0,

View File

@ -183,4 +183,4 @@ class TipoFrameCamera(IntEnum):
MatrizCusto = 5 MatrizCusto = 5
Deteccoes = 6 Deteccoes = 6
Corredor = 7 Corredor = 7
Raw4 = 8 PosProcessamento = 8

View File

@ -819,25 +819,41 @@ class CameraManager:
# ============================================================ # ============================================================
def get_selected_frame(self, frame_type: TipoFrameCamera): def get_selected_frame(self, frame_type: TipoFrameCamera):
with self._cache_lock: if not self._visual_disponivel_para_frame():
rgb = self._ultimo_rgb_frame return None
depth = self._ultimo_depth_frame
pred = self._ultimo_predictions
dets = list(self._ultimo_detections or [])
snapshot = self._ultimo_snapshot
params = getattr(self.camera, "parametros", {}) if self.camera is not None else {} try:
with self._cache_lock:
rgb = self._ultimo_rgb_frame
depth = self._ultimo_depth_frame
pred = self._ultimo_predictions
dets = list(self._ultimo_detections or [])
snapshot = self._ultimo_snapshot
return self.renderer.get_selected_frame( params = getattr(self.camera, "parametros", {}) if self.camera is not None else {}
frame_type=frame_type,
rgb_frame=rgb, # PosProcessamento no Visual Worker = imagem RGB original em melhor qualidade.
pred_ids=pred, # Aqui não renderiza overlay, segmentação ou debug.
depth_frame=depth, if frame_type == TipoFrameCamera.PosProcessamento:
detections=dets, if rgb is None or not hasattr(rgb, "size") or rgb.size <= 0:
snapshot=snapshot, return None
camera_params=params,
alpha=0.50, return rgb.copy()
)
return self.renderer.get_selected_frame(
frame_type=frame_type,
rgb_frame=rgb,
pred_ids=pred,
depth_frame=depth,
detections=dets,
snapshot=snapshot,
camera_params=params,
alpha=0.50,
)
except Exception as e:
self.mostrar_log(f"[visual] erro em get_selected_frame({frame_type}): {e}")
return None
# ============================================================ # ============================================================
# Loops # Loops
@ -1177,15 +1193,17 @@ class CameraManager:
camera_ctx = ContextoGlobalRedis.get_camera(self.mx_id) or {} camera_ctx = ContextoGlobalRedis.get_camera(self.mx_id) or {}
stream_on = bool(camera_ctx.get("streaming", False)) stream_on = bool(camera_ctx.get("streaming", False))
if stream_on: if stream_on and self._visual_disponivel_para_frame():
frame_type = TipoFrameCamera( frame_type = TipoFrameCamera(
camera_ctx.get("frame_type", TipoFrameCamera.Rgb.value) camera_ctx.get("frame_type", TipoFrameCamera.Rgb.value)
) )
frame = self.get_selected_frame(frame_type) frame = self.get_selected_frame(frame_type)
self.camera.enviar_frame_tcp(frame)
if self.debug_visual: if frame is not None:
self.camera.enviar_frame_tcp(frame)
if self.debug_visual and self._visual_disponivel_para_frame():
dbg = self.get_selected_frame(TipoFrameCamera.Debug) dbg = self.get_selected_frame(TipoFrameCamera.Debug)
if dbg is not None: if dbg is not None:
cv2.imshow("Visual Worker Debug", dbg) cv2.imshow("Visual Worker Debug", dbg)
@ -1401,30 +1419,97 @@ class CameraManager:
# ============================================================ # ============================================================
def salvar_frames(self, tipos: list, nome: str, pasta="frames_salvos"): def salvar_frames(self, tipos: list, nome: str, pasta="frames_salvos"):
if not self.operante: """
Salva frames sob demanda.
Padrão:
- Frames de replay/debug:
JPEG leve, qualidade configurada em replay_jpeg_quality.
- Tipo PosProcessamento:
imagem original em qualidade máxima, sem compressão destrutiva forte.
No Visual Worker, isso é apenas a imagem RGB limpa.
"""
if not self._visual_disponivel_para_frame():
return [] return []
try: try:
os.makedirs(pasta, exist_ok=True) os.makedirs(pasta, exist_ok=True)
frames_salvos = [] frames_salvos = []
frame_name = nome or datetime.datetime.now().strftime("%Y%m%d_%H%M%S") frame_name = nome.strip() if nome else datetime.datetime.now().strftime("%Y%m%d_%H%M%S_%f")[:-3]
replay_jpeg_quality = 70
pos_jpeg_quality = 100
for frame_type in tipos: for frame_type in tipos:
frame = self.get_selected_frame(TipoFrameCamera(frame_type)) try:
tipo_enum = TipoFrameCamera(frame_type)
except Exception:
self.mostrar_log(f"[visual] TipoFrameCamera inválido para salvar: {frame_type}")
continue
if frame is not None and hasattr(frame, "size") and frame.size > 0: frame = self.get_selected_frame(tipo_enum)
nome_frame = f"{frame_name}_{TipoFrameCamera(frame_type).name}.jpeg"
caminho = os.path.join(pasta, nome_frame) if frame is None or not hasattr(frame, "size") or frame.size <= 0:
cv2.imwrite(caminho, frame) continue
nome_base = f"{frame_name}_{tipo_enum.name}"
# ====================================================
# Pós-processamento
# ====================================================
# No Visual Worker, o dado científico é só a imagem limpa.
# Salva com qualidade máxima.
if tipo_enum == TipoFrameCamera.PosProcessamento:
caminho = os.path.join(pasta, f"{nome_base}.png")
ok = cv2.imwrite(caminho, frame)
if ok:
frames_salvos.append(caminho)
continue
# ====================================================
# Replay/debug
# ====================================================
# Salva JPEG leve para não entupir disco durante operação.
caminho = os.path.join(pasta, f"{nome_base}.jpg")
ok = cv2.imwrite(
caminho,
frame,
[int(cv2.IMWRITE_JPEG_QUALITY), int(replay_jpeg_quality)]
)
if ok:
frames_salvos.append(caminho) frames_salvos.append(caminho)
return frames_salvos return frames_salvos
except Exception as e: except Exception as e:
self.mostrar_log(f"❌ Erro ao salvar frames: {e}") self.mostrar_log(f"❌ Erro ao salvar frames visual: {e}")
return [] return []
def _visual_disponivel_para_frame(self) -> bool:
"""
Indica se o Visual Worker tem o mínimo necessário para gerar/salvar frames.
Evita erro quando a OAK-D Lite não está conectada ou o manager não iniciou.
"""
if self.camera is None:
return False
if not self.operante and not self._em_warmup:
return False
if self.renderer is None:
return False
return True
@staticmethod @staticmethod
def _fmt(v, casas=1, default=0.0): def _fmt(v, casas=1, default=0.0):
try: try:

View File

@ -127,16 +127,16 @@ VISUAL_DEFAULT_CONFIG = {
# 2) Frequências dos loops # 2) Frequências dos loops
# ============================================================ # ============================================================
# Loop supervisor: saúde, performance e GPU controller. # Loop supervisor: saúde, performance e GPU controller.
"analise_fps": 15.0, "analise_fps": 10.0,
# Loop de segmentação ONNX/TensorRT. # Loop de segmentação ONNX/TensorRT.
"inferencia_fps": 15.0, "inferencia_fps": 8.0,
# Loop de detecção onboard MobileNet-SSD na OAK. # Loop de detecção onboard MobileNet-SSD na OAK.
"deteccao_fps": 5.0, "deteccao_fps": 5.0,
# Loop de construção da matriz de confiança/custo. # Loop de construção da matriz de confiança/custo.
"grid_fps": 15.0, "grid_fps": 5.0,
# Loop de publicação Redis. # Loop de publicação Redis.
"publicacao_fps": 5.0, "publicacao_fps": 5.0,

View File

@ -4,6 +4,7 @@ import threading
import time import time
import cv2 import cv2
import numpy as np
from camera_worker.camera_multispectral import CameraMultispectral from camera_worker.camera_multispectral import CameraMultispectral
from camera_worker.oak_fcc3_core.segformer_service import MultiSpecSegformerService from camera_worker.oak_fcc3_core.segformer_service import MultiSpecSegformerService
@ -1498,35 +1499,139 @@ class CameraManager:
# Utilidades externas # Utilidades externas
# ============================================================ # ============================================================
def salvar_frames(self, tipos: list, nome: str, pasta="frames_salvos"): def salvar_frames(self, tipos: list, nome: str = "", pasta: str = "frames_salvos"):
if not self.operante: """
Salva frames sob demanda para replay e/ou pós-processamento.
Fluxos esperados:
- RGB / Segmentação / Prediction / Overlay:
salva imagem leve (.jpg), ideal para replay.
- MultispecRaw / RawMultispecBundle:
chama rotina dedicada para salvar pacote bruto científico,
com arquivos binários + metadados, semelhante ao capture.
"""
if not self._weed_disponivel_para_salvar():
return [] return []
try: try:
os.makedirs(pasta, exist_ok=True) os.makedirs(pasta, exist_ok=True)
frames_salvos = [] frames_salvos = []
frame_name = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") frame_name = nome.strip() if nome else datetime.datetime.now().strftime("%Y%m%d_%H%M%S_%f")[:-3]
if nome:
frame_name = nome
for frame_type in tipos: for frame_type in tipos:
try:
tipo_enum = TipoFrameCamera(frame_type)
tipo_nome = tipo_enum.name
except Exception:
tipo_enum = frame_type
tipo_nome = str(frame_type)
nome_frame = f"{frame_name}_{tipo_nome}"
if tipo_enum == TipoFrameCamera.PosProcessamento:
caminhos = self._salvar_bruto_multiespectral(
nome_base=nome_frame,
pasta=pasta
)
if caminhos:
if isinstance(caminhos, list):
frames_salvos.extend(caminhos)
else:
frames_salvos.append(caminhos)
continue
# ====================================================
# 2) FLUXO ATUAL: imagens para replay
# ====================================================
frame = self.get_selected_frame(frame_type) frame = self.get_selected_frame(frame_type)
if frame is not None and frame.size > 0: if frame is None:
nome_frame = f"{frame_name}_{TipoFrameCamera(frame_type).name}" continue
if frame_type == TipoFrameCamera.Raw4: if not hasattr(frame, "size") or frame.size <= 0:
caminho = os.path.join(pasta, f"{nome_frame}.raw") continue
frame.astype("float32").tofile(caminho)
else:
caminho = os.path.join(pasta, f"{nome_frame}.jpeg")
cv2.imwrite(caminho, frame)
frames_salvos.append(caminho) caminho = os.path.join(pasta, f"{nome_frame}.jpg")
# Replay leve: JPEG comprimido.
# Mantém baixo uso de disco durante operação longa.
if frame.dtype != "uint8":
frame_salvar = self._normalizar_frame_para_uint8(frame)
else:
frame_salvar = frame
cv2.imwrite(
caminho,
frame_salvar,
[int(cv2.IMWRITE_JPEG_QUALITY), 70]
)
frames_salvos.append(caminho)
return frames_salvos return frames_salvos
except Exception as e: except Exception as e:
self.mostrar_log(f"❌ Erro ao salvar frames: {e}") self.mostrar_log(f"❌ Erro ao salvar frames: {e}")
return [] return []
def _weed_disponivel_para_salvar(self) -> bool:
if not self.operante:
return False
if self.camera is None:
return False
return True
def _normalizar_frame_para_uint8(self, frame):
"""
Converte frame float/uint16/etc para uint8 apenas para replay visual.
Não deve ser usado para dado científico bruto.
"""
try:
arr = frame.astype("float32")
min_v = float(arr.min())
max_v = float(arr.max())
if max_v <= min_v:
return np.zeros(arr.shape, dtype=np.uint8)
arr = (arr - min_v) / (max_v - min_v)
arr = np.clip(arr * 255.0, 0, 255).astype("uint8")
return arr
except Exception:
return frame
def _salvar_bruto_multiespectral(self, nome_base: str, pasta: str):
try:
if not hasattr(self, "camera") or self.camera is None:
self.mostrar_log("⚠️ CameraMultispectral indisponível para salvar RAW multispec.")
return []
if not hasattr(self.camera, "salvar_bundle_raw_multispec"):
self.mostrar_log("⚠️ CameraMultispectral ainda não possui salvar_bundle_raw_multispec().")
return []
return self.camera.salvar_bundle_raw_multispec(
pasta=pasta,
nome=nome_base,
nota="pos_processamento_operacao",
extra_meta={
"origem_chamada": "weed_worker.salvar_frames",
"tipo_requisitado": "PosProcessamento",
},
)
except Exception as e:
self.mostrar_log(f"❌ Erro ao salvar bruto multiespectral: {e}")
return []

View File

@ -1190,6 +1190,7 @@ namespace OperationControl.ViewModels
return; return;
} }
var tipoMapa = _viewOperacaoCenter?.Mapa?.TipoMapaSelecionado ?? TipoMapaOperacao.Indefinido;
var dadosMapa = _viewOperacaoCenter?.Mapa?.CriarDadosMapa(); var dadosMapa = _viewOperacaoCenter?.Mapa?.CriarDadosMapa();
var ruasPercorrer = _viewOperacaoCenter?.Mapa?.RuasMapaCarregado?.Where(x => x.Selected)?.OrderBy(x => x.OrderSelection.Value)?.Select(x => x.Id)?.ToList() ?? new List<string>(); var ruasPercorrer = _viewOperacaoCenter?.Mapa?.RuasMapaCarregado?.Where(x => x.Selected)?.OrderBy(x => x.OrderSelection.Value)?.Select(x => x.Id)?.ToList() ?? new List<string>();
@ -1358,6 +1359,7 @@ namespace OperationControl.ViewModels
ModulosMandatorios = parametros?.ModulosMandatorios, ModulosMandatorios = parametros?.ModulosMandatorios,
ParametrosMandatorios = parametros?.ParametrosMandatorios, ParametrosMandatorios = parametros?.ParametrosMandatorios,
TipoMapa = tipoMapa,
RuasPercorrer = ruasPercorrer, RuasPercorrer = ruasPercorrer,
Mapa = dadosMapa Mapa = dadosMapa
}; };
@ -1372,8 +1374,9 @@ namespace OperationControl.ViewModels
rover.Controle = novosParametros.Controle; rover.Controle = novosParametros.Controle;
rover.ModulosMandatorios = novosParametros.ModulosMandatorios; rover.ModulosMandatorios = novosParametros.ModulosMandatorios;
rover.ParametrosMandatorios = novosParametros.ParametrosMandatorios; rover.ParametrosMandatorios = novosParametros.ParametrosMandatorios;
rover.RuasPercorrer = novosParametros.RuasPercorrer;
rover.PontosRetorno = novosParametros.PontosRetorno; rover.PontosRetorno = novosParametros.PontosRetorno;
rover.TipoMapa = novosParametros.TipoMapa;
rover.RuasPercorrer = novosParametros.RuasPercorrer;
rover.Mapa = novosParametros.Mapa; rover.Mapa = novosParametros.Mapa;
AtualizarDadosDiagnostico(rover); AtualizarDadosDiagnostico(rover);