ajustes nas regras do visual worker

This commit is contained in:
Diego Freitas 2025-08-15 14:17:33 -03:00
parent fc68629218
commit c43e1daab8
62 changed files with 957 additions and 1292 deletions

Binary file not shown.

View File

@ -197,11 +197,11 @@ namespace AgroBase.Forms.Operacoes
ReqFrameSnr = true; ReqFrameSnr = true;
try try
{ {
var frameRgb = await VisualWorkerService.GetCameraFrame(CameraFrameType.Rgb); var frameRgb = await VisualWorkerService.GetCameraFrame(CameraFrameType.Debug);
AtualizarImagemPainel(pnlCameraRua, frameRgb?.image()); AtualizarImagemPainel(pnlCameraRua, frameRgb?.image());
var frameSeg = await VisualWorkerService.GetCameraFrame(CameraFrameType.Segmentacao); //var frameSeg = await VisualWorkerService.GetCameraFrame(CameraFrameType.Segmentacao);
AtualizarImagemPainel(pnlDeteccoesRua, frameSeg?.image()); //AtualizarImagemPainel(pnlDeteccoesRua, frameSeg?.image());
} }
finally finally
{ {

View File

@ -122,7 +122,7 @@ namespace AgroBase.Forms
{ {
//pic.Image = VariaveisOperacao.Operadores.VisualWorker.DadosLeitura.Leitura.obj.radar_2d.PlotarAnalsie(frame.image); //pic.Image = VariaveisOperacao.Operadores.VisualWorker.DadosLeitura.Leitura.obj.radar_2d.PlotarAnalsie(frame.image);
PlotarGraficoPerfilCorredor(VisualWorkerService.DadosLeitura.Analises.corredor_prof); //PlotarGraficoPerfilCorredor(VisualWorkerService.DadosLeitura.Analises.corredor_prof);
} }
} }
@ -156,9 +156,9 @@ namespace AgroBase.Forms
picDebug.Image?.Dispose(); picDebug.Image?.Dispose();
picDebug.Image = analise.PlotarAnalise(frame); picDebug.Image = analise.PlotarAnalise(frame);
PreencherGridAnomalias(analise.anomalias ?? new List<VisualWorkerMessageAnaliseBBoxModel>()); //PreencherGridAnomalias(analise.anomalias ?? new List<VisualWorkerMessageAnaliseBBoxModel>());
//PreencherGridSolo(analise.solo?.bbox_list ?? new List<VisualWorkerMessageAnaliseBBoxModel>()); //PreencherGridSolo(analise.solo?.bbox_list ?? new List<VisualWorkerMessageAnaliseBBoxModel>());
PlotarGraficoPerfilSolo(analise.solo?.perfil ?? new List<double>()); //PlotarGraficoPerfilSolo(analise.solo?.perfil ?? new List<double>());
} }
void ConfigurarGridDeteccoes(DataGridView dgv) void ConfigurarGridDeteccoes(DataGridView dgv)

View File

@ -60,10 +60,8 @@ namespace AgroBase.Forms
private async Task tmrLeitura_Tick() private async Task tmrLeitura_Tick()
{ {
var Imu = Variaveis.OperacaoEmAndamento.Sensoriamento.IMU; var Imu = Variaveis.OperacaoEmAndamento.Sensoriamento.IMU;
AnguloX = (Imu.InclinacaoFrontal + 180) % 360; AnguloX = -Imu.InclinacaoFrontal;
if (AnguloX > 180) AnguloX -= 360; AnguloY = -Imu.InclinacaoLateral;
AnguloY = (Imu.InclinacaoLateral + 180) % 360;
if (AnguloY > 180) AnguloY -= 360;
AnguloZ = -Imu.Rotacao; AnguloZ = -Imu.Rotacao;
OrientacaoMagnetica = Imu.RotacaoCorrigida; OrientacaoMagnetica = Imu.RotacaoCorrigida;
@ -227,7 +225,12 @@ namespace AgroBase.Forms
picBussola.Invalidate(); picBussola.Invalidate();
//glControl.Invalidate(); //glControl.Invalidate();
pnlInclinacao.Invalidate(); pnlInclinacao.Invalidate();
visualizador3D.AtualizarAngulos(AnguloX, AnguloY, AnguloZ);
double agX = (AnguloX + 180) % 360;
if (agX > 180) agX -= 360;
double agY = (AnguloY + 180) % 360;
if (agY > 180) agY -= 360;
visualizador3D.AtualizarAngulos(agX, agY, AnguloZ);
txtX.Text = AnguloX.ToString("0.00"); txtX.Text = AnguloX.ToString("0.00");
txtY.Text = AnguloY.ToString("0.00"); txtY.Text = AnguloY.ToString("0.00");

View File

@ -175,30 +175,33 @@ namespace AgroBase.Models.Operadores
public class VisualWorkerMessageAnaliseModel public class VisualWorkerMessageAnaliseModel
{ {
public DateTime timestamp { get; set; } public DateTime timestamp { get; set; }
public List<VisualWorkerMessageAnaliseBBoxModel> anomalias { get; set; } //public List<VisualWorkerMessageAnaliseBBoxModel> anomalias { get; set; }
public List<VisualWorkerMessageAnaliseBBoxModel> sombras { get; set; } //public List<VisualWorkerMessageAnaliseBBoxModel> sombras { get; set; }
public VisualWorkerMessageAnaliseSoloModel solo { get; set; } //public VisualWorkerMessageAnaliseSoloModel solo { get; set; }
public List<VisualWorkerMessageRadar2DPerfilModel> corredor_prof { get; set; } //public List<VisualWorkerMessageRadar2DPerfilModel> corredor_prof { get; set; }
public List<VisualWorkerMessageRadar2DPerfilModel> corredor_seg { get; set; } //public List<VisualWorkerMessageRadar2DPerfilModel> corredor_seg { get; set; }
public VisualWorkerMessageSegmentacaoSemanticaModel segmentacao { get; set; } public VisualWorkerMessageSegmentacaoSemanticaModel segmentacao { get; set; }
public VisualWorkerMessageMatrizConfiancaModel matriz_confianca { get; set; }
public VisualWorkerMessageAnaliseModel Clone() public VisualWorkerMessageAnaliseModel Clone()
{ {
return new VisualWorkerMessageAnaliseModel() return new VisualWorkerMessageAnaliseModel()
{ {
timestamp = timestamp, timestamp = timestamp,
anomalias = new List<VisualWorkerMessageAnaliseBBoxModel>(anomalias ?? new List<VisualWorkerMessageAnaliseBBoxModel>()), //anomalias = new List<VisualWorkerMessageAnaliseBBoxModel>(anomalias ?? new List<VisualWorkerMessageAnaliseBBoxModel>()),
sombras = new List<VisualWorkerMessageAnaliseBBoxModel>(sombras ?? new List<VisualWorkerMessageAnaliseBBoxModel>()), //sombras = new List<VisualWorkerMessageAnaliseBBoxModel>(sombras ?? new List<VisualWorkerMessageAnaliseBBoxModel>()),
solo = solo?.Clone(), //solo = solo?.Clone(),
corredor_prof = new List<VisualWorkerMessageRadar2DPerfilModel>(corredor_prof ?? new List<VisualWorkerMessageRadar2DPerfilModel>()), //corredor_prof = new List<VisualWorkerMessageRadar2DPerfilModel>(corredor_prof ?? new List<VisualWorkerMessageRadar2DPerfilModel>()),
corredor_seg = new List<VisualWorkerMessageRadar2DPerfilModel>(corredor_seg ?? new List<VisualWorkerMessageRadar2DPerfilModel>()), //corredor_seg = new List<VisualWorkerMessageRadar2DPerfilModel>(corredor_seg ?? new List<VisualWorkerMessageRadar2DPerfilModel>()),
segmentacao = segmentacao?.Clone() ?? new VisualWorkerMessageSegmentacaoSemanticaModel(), segmentacao = segmentacao?.Clone() ?? new VisualWorkerMessageSegmentacaoSemanticaModel(),
matriz_confianca = matriz_confianca?.Clone() ?? new VisualWorkerMessageMatrizConfiancaModel(),
}; };
} }
public Bitmap PlotarAnalise(OAKCameraFrameModel frame) public Bitmap PlotarAnalise(OAKCameraFrameModel frame)
{ {
Bitmap img = frame?.image(); return null;
/*Bitmap img = frame?.image();
if (img == null) return null; if (img == null) return null;
using (Graphics g = Graphics.FromImage(img)) using (Graphics g = Graphics.FromImage(img))
@ -296,7 +299,7 @@ namespace AgroBase.Models.Operadores
} }
} }
return img; return img;*/
} }
} }
@ -497,6 +500,83 @@ namespace AgroBase.Models.Operadores
} }
} }
public class VisualWorkerMessageMatrizConfiancaModel
{
public float ts { get; set; }
public int seq { get; set; }
public int grid_w { get; set; }
public int grid_h { get; set; }
public List<float> y_range_m { get; set; }
public List<float> row_dist_m { get; set; }
public List<float> row_scale_x_m { get; set; }
public VisualWorkerMessageMatrizConfiancaBlockModel block { get; set; }
public VisualWorkerMessageMatrizConfiancaModel Clone()
{
return new VisualWorkerMessageMatrizConfiancaModel()
{
ts = ts,
seq = seq,
grid_w = grid_w,
grid_h = grid_h,
y_range_m = y_range_m,
row_dist_m = new List<float>(row_dist_m),
row_scale_x_m = new List<float>(row_scale_x_m),
block = block.Clone()
};
}
}
public class VisualWorkerMessageMatrizConfiancaBlockModel
{
public float? d_block_line_m { get; set; }
public float d_obs_true_min_m { get; set; }
public bool blocked { get; set; }
public bool blocked_raw { get; set; }
public string reason { get; set; }
public string reason_detail { get; set; }
public VisualWorkerMessageMatrizConfiancaBlockDecisionModel decision { get; set; }
public VisualWorkerMessageMatrizConfiancaBlockModel Clone()
{
return new VisualWorkerMessageMatrizConfiancaBlockModel()
{
d_block_line_m = d_block_line_m,
d_obs_true_min_m = d_obs_true_min_m,
blocked = blocked,
blocked_raw = blocked_raw,
reason = reason,
reason_detail = reason_detail,
decision = decision.Clone()
};
}
}
public class VisualWorkerMessageMatrizConfiancaBlockDecisionModel
{
public bool parar { get; set; }
public float dist_necessaria { get; set; }
public float? v_max_sugerida_mps { get; set; }
public int frames_on { get; set; }
public int frames_off { get; set; }
public int N_on { get; set; }
public int N_off { get; set; }
public VisualWorkerMessageMatrizConfiancaBlockDecisionModel Clone()
{
return new VisualWorkerMessageMatrizConfiancaBlockDecisionModel()
{
parar = parar,
dist_necessaria = dist_necessaria,
v_max_sugerida_mps = v_max_sugerida_mps,
frames_on = frames_on,
frames_off = frames_off,
N_on = N_on,
N_off = N_off,
};
}
}
public enum VisualWorkerCommandType public enum VisualWorkerCommandType
{ {
ScriptCarregado = 1, ScriptCarregado = 1,

View File

@ -102,13 +102,18 @@ namespace AgroBase.Services.Operadores
if (cameras.Any()) if (cameras.Any())
{ {
CamerasConectadas.AddRange(cameras.Values.Select(x => new OAKCameraModel() foreach (var cam in cameras)
{ {
Dispositivo = x.dispositivo, var camera_str = RedisService.Get(RedisService.CamKey(cam.Key));
Version = x.versao, var camera = JsonConvert.DeserializeObject<CameraWorkerItemModel>(camera_str);
Name = x.modelo, CamerasConectadas.Add(new OAKCameraModel()
Id = x.mx_id, {
}).ToList()); Dispositivo = camera.dispositivo,
Version = camera.versao,
Name = camera.modelo,
Id = camera.mx_id,
});
}
} }
return CamerasConectadas; return CamerasConectadas;

View File

@ -1,6 +1,8 @@
using AgroBase.Models; using AgroBase.Models;
using AgroBase.Models.Operadores; using AgroBase.Models.Operadores;
using Microsoft.DirectX.DirectInput;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -16,6 +18,8 @@ namespace AgroBase.Services.Operadores
private bool DebugMode = true; private bool DebugMode = true;
static long _lastTsVW = -1;
public void MostrarLog(string message, int limite = 500) public void MostrarLog(string message, int limite = 500)
{ {
if (DebugMode) if (DebugMode)
@ -138,45 +142,67 @@ namespace AgroBase.Services.Operadores
public static void AtualizarAnalise() public static void AtualizarAnalise()
{ {
// IMU (simples e direto)
string imuJson = RedisService.Get(RedisService.ModKey(Enums.T_Code.Imu)); string imuJson = RedisService.Get(RedisService.ModKey(Enums.T_Code.Imu));
if (!string.IsNullOrEmpty(imuJson)) if (!string.IsNullOrWhiteSpace(imuJson))
{ {
DadosLeitura.Imu = JsonConvert.DeserializeObject<VisualWorkerMessageIMUModel>(imuJson); try
{
DadosLeitura.Imu = JsonConvert.DeserializeObject<VisualWorkerMessageIMUModel>(imuJson);
}
catch { /* opcional: logar erro */ }
} }
// VisualWorker
var json = RedisService.Get(CtxKey.DadosVisualWorker); var json = RedisService.Get(CtxKey.DadosVisualWorker);
var dados = JsonConvert.DeserializeObject<Dictionary<string, object>>(json); if (string.IsNullOrWhiteSpace(json)) return;
if (dados.ContainsKey("ts_analise")) JObject root;
try
{ {
DateTime timestamp = FuncoesGlobais.UnixToDateTime(dados["ts_analise"].ToString()); root = JObject.Parse(json);
}
catch
{
return; // payload inválido
}
//var matrizes = JsonConvert.DeserializeObject<Dictionary<string, object>>(dados["matrizes"].ToString()); // ts_analise (Unix)
//var confianca = JsonConvert.DeserializeObject<List<List<double>>>(JsonConvert.SerializeObject(matrizes["confianca"])); long tsUnix = root["ts_analise"]?.Value<long>() ?? 0;
//var custo = JsonConvert.DeserializeObject<List<List<double>>>(JsonConvert.SerializeObject(matrizes["custo"])); if (tsUnix != 0)
{
// atualiza o DateTime sempre que vier um ts válido
DadosLeitura.Analises.timestamp = FuncoesGlobais.UnixToDateTime(tsUnix.ToString());
}
var anomalias = JsonConvert.DeserializeObject<Dictionary<string, object>>(dados["anomalias"].ToString()); // Se o timestamp não mudou, evita desserializar objetos grandes
var deteccoes = JsonConvert.DeserializeObject<List<VisualWorkerMessageAnaliseBBoxModel>>(JsonConvert.SerializeObject(anomalias["deteccoes"])); if (tsUnix != 0 && tsUnix == _lastTsVW)
var sombras = JsonConvert.DeserializeObject<List<VisualWorkerMessageAnaliseBBoxModel>>(JsonConvert.SerializeObject(anomalias["sombras"])); return;
var corredor = JsonConvert.DeserializeObject<Dictionary<string, object>>(dados["perfil_corredor"].ToString()); _lastTsVW = tsUnix;
var corredor_seg = JsonConvert.DeserializeObject<List<VisualWorkerMessageRadar2DPerfilModel>>(JsonConvert.SerializeObject(corredor["segmentacao"]));
var profundidade = JsonConvert.DeserializeObject<List<VisualWorkerMessageRadar2DPerfilModel>>(JsonConvert.SerializeObject(corredor["profundidade"]));
var solo = JsonConvert.DeserializeObject<VisualWorkerMessageAnaliseSoloModel>(JsonConvert.SerializeObject(dados["solo"])); // segmentação (sem re-serializar o token)
var segTok = root["segmentacao"];
var segmentacao = JsonConvert.DeserializeObject<VisualWorkerMessageSegmentacaoSemanticaModel>(JsonConvert.SerializeObject(dados["segmentacao"])); if (segTok != null)
{
DadosLeitura.Analises = new VisualWorkerMessageAnaliseModel() try
{ {
timestamp = timestamp, var seg = segTok.ToObject<VisualWorkerMessageSegmentacaoSemanticaModel>();
solo = solo, if (seg != null) DadosLeitura.Analises.segmentacao = seg;
anomalias = deteccoes, }
sombras = sombras, catch { /* opcional: log */ }
corredor_prof = profundidade, }
corredor_seg = corredor_seg,
segmentacao = segmentacao // matriz de confiança
}; var mcTok = root["matriz_confianca"];
if (mcTok != null)
{
try
{
var mc = mcTok.ToObject<VisualWorkerMessageMatrizConfiancaModel>();
if (mc != null) DadosLeitura.Analises.matriz_confianca = mc;
}
catch { /* opcional: log */ }
} }
} }
@ -184,10 +210,8 @@ namespace AgroBase.Services.Operadores
{ {
RedisService.AtualizarCampos( RedisService.AtualizarCampos(
CtxKey.DadosWeedWorker, CtxKey.DadosWeedWorker,
("analise.anomalias", new Dictionary<string, object>()), ("analise.segmentacao", new VisualWorkerMessageSegmentacaoSemanticaModel()),
("analise.perfil_corredor", new Dictionary<string, object>()), ("analise.matriz_confianca", new VisualWorkerMessageMatrizConfiancaModel())
("analise.solo", new VisualWorkerMessageAnaliseSoloModel()),
("analise.segmentacao", new VisualWorkerMessageSegmentacaoSemanticaModel())
); );
} }

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using static AgroBase.Models.Operadores.OperadoresModels; using static AgroBase.Models.Operadores.OperadoresModels;
using Newtonsoft.Json.Linq;
namespace AgroBase.Services.Operadores namespace AgroBase.Services.Operadores
{ {
@ -15,6 +16,7 @@ namespace AgroBase.Services.Operadores
public SaudeWorkerModel Saude { get; set; } = new SaudeWorkerModel(); public SaudeWorkerModel Saude { get; set; } = new SaudeWorkerModel();
private bool DebugMode = true; private bool DebugMode = true;
static long _lastTs = -1;
public void MostrarLog(string message, int limite = 500) public void MostrarLog(string message, int limite = 500)
{ {
@ -164,53 +166,67 @@ namespace AgroBase.Services.Operadores
public static void AtualizarAnalise() public static void AtualizarAnalise()
{ {
var json = RedisService.Get(CtxKey.DadosWeedWorker); var json = RedisService.Get(CtxKey.DadosWeedWorker);
var dados = JsonConvert.DeserializeObject<Dictionary<string, object>>(json); if (string.IsNullOrWhiteSpace(json)) return;
if (dados.ContainsKey("ts_analise") && dados.ContainsKey("analise")) var root = JObject.Parse(json);
var analise = root["analise"] as JObject;
if (analise == null) return;
// 1) pega timestamp sem recriar dicionários
long tsUnix = analise["timestamp"]?.Value<long>() ?? 0;
if (tsUnix != 0)
{ {
var analise = JsonConvert.DeserializeObject<Dictionary<string, object>>(dados["analise"].ToString()); var ts = FuncoesGlobais.UnixToDateTime(tsUnix.ToString());
DadosLeitura.Analise.timestamp = ts;
// 1. Timestamp (vem como double ou string)
DateTime timestamp = FuncoesGlobais.UnixToDateTime(analise["timestamp"].ToString());
// 2. Deteccoes (vem como List<object> ou string)
var listaDeteccoes = JsonConvert.DeserializeObject<List<WeedWorkerAnaliseDeteccaoModel>>(
analise["deteccoes"].ToString()
);
// 3. Controle: pode vir como Dictionary<string, object>
var controleDict = JsonConvert.DeserializeObject<Dictionary<string, bool>>(
analise["controle"].ToString()
);
var ervasIdentificadas = JsonConvert.DeserializeObject<List<Dictionary<string, int>>>(
analise["ervas_identificadas"].ToString()
);
// 4. Monta o objeto manualmente
var analiseObj = new WeedWorkerAnaliseModel
{
timestamp = timestamp,
height = Convert.ToInt32(analise["height"].ToString()),
width = Convert.ToInt32(analise["width"].ToString()),
deteccoes = listaDeteccoes,
controle = controleDict.ToDictionary(x => Convert.ToInt32(x.Key), x => x.Value),
ervas_identificadas = ervasIdentificadas
};
Variaveis.OperacaoEmAndamento.DispAtu?.Dados?.BicosPulverizadores?.ForEach(bico =>
{
int atuacoes = 0;
var ervas = DadosLeitura?.Analise?.ervas_identificadas ?? new List<Dictionary<string, int>>();
if (ervas.Count < bico.Posicao || bico.Posicao <= 0)
atuacoes = 0;
else
atuacoes = ervas[bico.Posicao - 1]?.Sum(x => x.Value) ?? 0;
bico.Atuacoes = atuacoes;
});
DadosLeitura.Analise = analiseObj;
} }
// 2) se não mudou, pule deserializações pesadas
if (tsUnix == _lastTs) goto AtualizaBicos;
_lastTs = tsUnix;
// 3) deteccoes (se precisar manter o tipo forte)
var detToken = analise["deteccoes"];
if (detToken != null)
{
var lista = detToken.ToObject<List<WeedWorkerAnaliseDeteccaoModel>>();
DadosLeitura.Analise.deteccoes = lista;
}
// 4) controle: converte direto p/ int->bool sem ToString/dupla desserialização
var ctrlObj = analise["controle"] as JObject;
if (ctrlObj != null)
{
var dict = new Dictionary<int, bool>(ctrlObj.Count);
foreach (var prop in ctrlObj.Properties())
if (int.TryParse(prop.Name, out int k))
dict[k] = prop.Value.Value<bool>();
DadosLeitura.Analise.controle = dict;
}
// 5) ervas_identificadas: JArray -> List<Dictionary<string,int>>
var ervasTok = analise["ervas_identificadas"] as JArray;
if (ervasTok != null)
{
var ervas = new List<Dictionary<string, int>>(ervasTok.Count);
foreach (var item in ervasTok.OfType<JObject>())
{
var d = new Dictionary<string, int>(item.Count);
foreach (var p in item.Properties())
d[p.Name] = p.Value.Value<int>();
ervas.Add(d);
}
DadosLeitura.Analise.ervas_identificadas = ervas;
}
AtualizaBicos:
Variaveis.OperacaoEmAndamento.DispAtu?.Dados?.BicosPulverizadores?.ForEach(bico =>
{
int atuacoes = 0;
var ervas = DadosLeitura?.Analise?.ervas_identificadas ?? new List<Dictionary<string, int>>();
if (bico.Posicao > 0 && bico.Posicao <= ervas.Count)
atuacoes = ervas[bico.Posicao - 1]?.Sum(x => x.Value) ?? 0;
bico.Atuacoes = atuacoes;
});
} }
public static void ReiniciarLeituraAnalise() public static void ReiniciarLeituraAnalise()

View File

@ -26,6 +26,11 @@ namespace AgroBase.Services
return CtxKey.DadosModulo + dispositivo.ToString(); return CtxKey.DadosModulo + dispositivo.ToString();
} }
public static string CamKey(string mx_id)
{
return CtxKey.DadosCameras + mx_id;
}
public static string Get(string chave) public static string Get(string chave)
{ {
return _db.StringGet(chave); return _db.StringGet(chave);
@ -180,7 +185,7 @@ namespace AgroBase.Services
public static class CtxKey public static class CtxKey
{ {
public const string DadosCameras = "ctx:dados_cameras"; public const string DadosCameras = "ctx:dados_cameras_";
public const string DadosModulo = "ctx:dados_modulo_"; public const string DadosModulo = "ctx:dados_modulo_";
public const string DadosOperacao = "ctx:dados_operacao"; public const string DadosOperacao = "ctx:dados_operacao";
public const string DadosContexto = "ctx:dados_contexto"; public const string DadosContexto = "ctx:dados_contexto";

View File

@ -1,12 +1,5 @@
{ {
"epochs": [ { "epochs": [ {
"calculation_time": "13397225327956881",
"config_version": 0,
"model_version": "0",
"padded_top_topics_start_index": 0,
"taxonomy_version": 0,
"top_topics_and_observing_domains": [ ]
}, {
"calculation_time": "13397850728648898", "calculation_time": "13397850728648898",
"config_version": 0, "config_version": 0,
"model_version": "0", "model_version": "0",
@ -29,5 +22,5 @@
"top_topics_and_observing_domains": [ ] "top_topics_and_observing_domains": [ ]
} ], } ],
"hex_encoded_hmac_key": "40F346D3248C3AFDF2BEE1FE496DBD32F7CED6E5AE98B881ABC421AA7E7B5642", "hex_encoded_hmac_key": "40F346D3248C3AFDF2BEE1FE496DBD32F7CED6E5AE98B881ABC421AA7E7B5642",
"next_scheduled_calculation_time": "13399758321059135" "next_scheduled_calculation_time": "13399758321059321"
} }

View File

@ -1,3 +1,3 @@
2025/08/14-08:05:35.848 19d8 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Local Storage\leveldb/MANIFEST-000001 2025/08/15-13:16:10.784 5274 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Local Storage\leveldb/MANIFEST-000001
2025/08/14-08:05:35.853 19d8 Recovering log #3 2025/08/15-13:16:10.790 5274 Recovering log #3
2025/08/14-08:05:35.857 19d8 Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Local Storage\leveldb/000003.log 2025/08/15-13:16:10.794 5274 Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Local Storage\leveldb/000003.log

View File

@ -1,3 +1,3 @@
2025/08/13-17:43:50.172 2850 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Local Storage\leveldb/MANIFEST-000001 2025/08/15-10:42:52.375 713c Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Local Storage\leveldb/MANIFEST-000001
2025/08/13-17:43:50.179 2850 Recovering log #3 2025/08/15-10:42:52.380 713c Recovering log #3
2025/08/13-17:43:50.183 2850 Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Local Storage\leveldb/000003.log 2025/08/15-10:42:52.383 713c Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Local Storage\leveldb/000003.log

View File

@ -1 +1 @@
{"net":{"http_server_properties":{"servers":[{"alternative_service":[{"advertised_alpns":["h3"],"expiration":"13399729543529307","port":443,"protocol_str":"quic"}],"anonymization":["DAAAAAcAAABmaWxlOi8vAA==",false,0],"network_stats":{"srtt":18335},"server":"https://tile.openstreetmap.org","supports_spdy":true}],"supports_quic":{"address":"2804:d78:627:be00:409f:ed6:2a0d:4b81","used_quic":true},"version":5},"network_qualities":{"CAASABiAgICA+P////8B":"4G","CAESABiAgICA+P////8B":"4G","CAISABiAgICA+P////8B":"4G","CAYSABiAgICA+P////8B":"Offline"}}} {"net":{"http_server_properties":{"servers":[{"alternative_service":[{"advertised_alpns":["h3"],"expiration":"13399834582100494","port":443,"protocol_str":"quic"}],"anonymization":["DAAAAAcAAABmaWxlOi8vAA==",false,0],"network_stats":{"srtt":30536},"server":"https://tile.openstreetmap.org","supports_spdy":true}],"supports_quic":{"address":"192.168.26.32","used_quic":true},"version":5},"network_qualities":{"CAASABiAgICA+P////8B":"4G","CAESABiAgICA+P////8B":"4G","CAISABiAgICA+P////8B":"4G","CAYSABiAgICA+P////8B":"Offline"}}}

View File

@ -1 +1 @@
{"sts":[{"expiry":1786705536.321524,"host":"bWGAftl61rqoc0YzqPncsLvQQh/iC2Bdp3ejUeGC83w=","mode":"force-https","sts_include_subdomains":true,"sts_observed":1755169536.321528}],"version":2} {"sts":[{"expiry":1786792276.424571,"host":"bWGAftl61rqoc0YzqPncsLvQQh/iC2Bdp3ejUeGC83w=","mode":"force-https","sts_include_subdomains":true,"sts_observed":1755256276.424575}],"version":2}

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,3 @@
2025/08/14-08:08:01.412 19d8 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Session Storage/MANIFEST-000001 2025/08/15-13:21:01.455 5274 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Session Storage/MANIFEST-000001
2025/08/14-08:08:01.413 19d8 Recovering log #3 2025/08/15-13:21:01.456 5274 Recovering log #3
2025/08/14-08:08:01.418 19d8 Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Session Storage/000003.log 2025/08/15-13:21:01.459 5274 Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Session Storage/000003.log

View File

@ -1,3 +1,3 @@
2025/08/13-17:45:26.419 2850 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Session Storage/MANIFEST-000001 2025/08/15-10:44:53.150 713c Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Session Storage/MANIFEST-000001
2025/08/13-17:45:26.420 2850 Recovering log #3 2025/08/15-10:44:53.151 713c Recovering log #3
2025/08/13-17:45:26.423 2850 Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Session Storage/000003.log 2025/08/15-10:44:53.155 713c Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Session Storage/000003.log

View File

@ -1,3 +1,3 @@
2025/08/14-08:05:35.765 9414 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Site Characteristics Database/MANIFEST-000001 2025/08/15-13:16:10.702 6a74 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Site Characteristics Database/MANIFEST-000001
2025/08/14-08:05:35.766 9414 Recovering log #7 2025/08/15-13:16:10.703 6a74 Recovering log #7
2025/08/14-08:05:35.767 9414 Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Site Characteristics Database/000007.log 2025/08/15-13:16:10.704 6a74 Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Site Characteristics Database/000007.log

View File

@ -1,3 +1,3 @@
2025/08/13-17:43:50.080 675c Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Site Characteristics Database/MANIFEST-000001 2025/08/15-10:42:52.304 16a4 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Site Characteristics Database/MANIFEST-000001
2025/08/13-17:43:50.082 675c Recovering log #7 2025/08/15-10:42:52.306 16a4 Recovering log #7
2025/08/13-17:43:50.082 675c Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Site Characteristics Database/000007.log 2025/08/15-10:42:52.306 16a4 Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Site Characteristics Database/000007.log

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -17,7 +17,7 @@
<meta name="viewport" content="width=device-width, <meta name="viewport" content="width=device-width,
initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<style> <style>
#map_7792e3f9a50d24693529ab60471511c5 { #map_15b2c6f7832115c94dd9daa84035059e {
position: relative; position: relative;
width: 100.0%; width: 100.0%;
height: 100.0%; height: 100.0%;
@ -54,14 +54,14 @@
<body> <body>
<div class="folium-map" id="map_7792e3f9a50d24693529ab60471511c5" ></div> <div class="folium-map" id="map_15b2c6f7832115c94dd9daa84035059e" ></div>
</body> </body>
<script> <script>
var map_7792e3f9a50d24693529ab60471511c5 = L.map( var map_15b2c6f7832115c94dd9daa84035059e = L.map(
"map_7792e3f9a50d24693529ab60471511c5", "map_15b2c6f7832115c94dd9daa84035059e",
{ {
center: [0.0, 0.0], center: [0.0, 0.0],
crs: L.CRS.EPSG3857, crs: L.CRS.EPSG3857,
@ -78,7 +78,7 @@
var tile_layer_a04e5e8300b6eacc885e71a463b29158 = L.tileLayer( var tile_layer_76df0441e10db36359cf485a7152ff2d = L.tileLayer(
"https://tile.openstreetmap.org/{z}/{x}/{y}.png", "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
{ {
"minZoom": 0, "minZoom": 0,
@ -95,7 +95,7 @@
); );
tile_layer_a04e5e8300b6eacc885e71a463b29158.addTo(map_7792e3f9a50d24693529ab60471511c5); tile_layer_76df0441e10db36359cf485a7152ff2d.addTo(map_15b2c6f7832115c94dd9daa84035059e);
</script> </script>
@ -116,7 +116,7 @@
} }
trajeto_json_add({"features": []}); trajeto_json_add({"features": []});
trajeto_json.addTo(map_7792e3f9a50d24693529ab60471511c5); trajeto_json.addTo(map_15b2c6f7832115c94dd9daa84035059e);
function adicionarGeometria(novaGeometria) { function adicionarGeometria(novaGeometria) {
trajeto_json.addData(novaGeometria); trajeto_json.addData(novaGeometria);
@ -179,9 +179,9 @@
var marcadorEquipamento = L.marker([0, 0], { var marcadorEquipamento = L.marker([0, 0], {
icon: customIcon icon: customIcon
}).addTo(map_7792e3f9a50d24693529ab60471511c5); }).addTo(map_15b2c6f7832115c94dd9daa84035059e);
var marcadorBase = L.marker([0, 0], {}).addTo(map_7792e3f9a50d24693529ab60471511c5); var marcadorBase = L.marker([0, 0], {}).addTo(map_15b2c6f7832115c94dd9daa84035059e);
var icon = L.AwesomeMarkers.icon( var icon = L.AwesomeMarkers.icon(
{"extraClasses": "fa-rotate-0", "icon": "info-sign", "iconColor": "white", "markerColor": "red", "prefix": "glyphicon"} {"extraClasses": "fa-rotate-0", "icon": "info-sign", "iconColor": "white", "markerColor": "red", "prefix": "glyphicon"}
); );
@ -246,7 +246,7 @@
} }
if (foco) { if (foco) {
map_7792e3f9a50d24693529ab60471511c5.setView(novaPosicao, map_7792e3f9a50d24693529ab60471511c5.getZoom()); map_15b2c6f7832115c94dd9daa84035059e.setView(novaPosicao, map_15b2c6f7832115c94dd9daa84035059e.getZoom());
} }
} }
@ -268,7 +268,7 @@
marcadorDinamico.setRotationAngle(angulo); marcadorDinamico.setRotationAngle(angulo);
adicionarCoordenada("Tj", [novaLongitude, novaLatitude]); adicionarCoordenada("Tj", [novaLongitude, novaLatitude]);
map_7792e3f9a50d24693529ab60471511c5.setView(novaPosicao, map_7792e3f9a50d24693529ab60471511c5.getZoom());*/ map_15b2c6f7832115c94dd9daa84035059e.setView(novaPosicao, map_15b2c6f7832115c94dd9daa84035059e.getZoom());*/
}); });
function calcularOrientacao(P1latitude, P1longitude, P2latitude, P2longitude) { function calcularOrientacao(P1latitude, P1longitude, P2latitude, P2longitude) {

View File

@ -17,7 +17,7 @@
<meta name="viewport" content="width=device-width, <meta name="viewport" content="width=device-width,
initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<style> <style>
#map_fbec21a667da83a7a2d972cb198fec24 { #map_23f8e35f17470dacda23acd5395a7fac {
position: relative; position: relative;
width: 100.0%; width: 100.0%;
height: 100.0%; height: 100.0%;
@ -54,14 +54,14 @@
<body> <body>
<div class="folium-map" id="map_fbec21a667da83a7a2d972cb198fec24" ></div> <div class="folium-map" id="map_23f8e35f17470dacda23acd5395a7fac" ></div>
</body> </body>
<script> <script>
var map_fbec21a667da83a7a2d972cb198fec24 = L.map( var map_23f8e35f17470dacda23acd5395a7fac = L.map(
"map_fbec21a667da83a7a2d972cb198fec24", "map_23f8e35f17470dacda23acd5395a7fac",
{ {
center: [-22.172636164916668, -47.395186322185666], center: [-22.172636164916668, -47.395186322185666],
crs: L.CRS.EPSG3857, crs: L.CRS.EPSG3857,
@ -78,7 +78,7 @@
var tile_layer_d332ab9929d02ae6fda534ad4f881d9a = L.tileLayer( var tile_layer_683b3dff313760b3af581ab3e6a597ea = L.tileLayer(
"https://tile.openstreetmap.org/{z}/{x}/{y}.png", "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
{ {
"minZoom": 0, "minZoom": 0,
@ -95,7 +95,7 @@
); );
tile_layer_d332ab9929d02ae6fda534ad4f881d9a.addTo(map_fbec21a667da83a7a2d972cb198fec24); tile_layer_683b3dff313760b3af581ab3e6a597ea.addTo(map_23f8e35f17470dacda23acd5395a7fac);
@ -111,7 +111,7 @@
}*/ }*/
}); });
} }
function geo_json_2af9830ef68420da295e9fbbf418b0d8_onEachFeature(feature, layer) { function geo_json_1f8972074523b6427449f533ba057bf8_onEachFeature(feature, layer) {
layer.on({ layer.on({
@ -148,23 +148,23 @@
}*/ }*/
}); });
}; };
var geo_json_2af9830ef68420da295e9fbbf418b0d8 = L.geoJson(null, { var geo_json_1f8972074523b6427449f533ba057bf8 = L.geoJson(null, {
onEachFeature: geo_json_2af9830ef68420da295e9fbbf418b0d8_onEachFeature, onEachFeature: geo_json_1f8972074523b6427449f533ba057bf8_onEachFeature,
...{ ...{
} }
}); });
function geo_json_2af9830ef68420da295e9fbbf418b0d8_add (data) { function geo_json_1f8972074523b6427449f533ba057bf8_add (data) {
geo_json_2af9830ef68420da295e9fbbf418b0d8 geo_json_1f8972074523b6427449f533ba057bf8
.addData(data); .addData(data);
} }
geo_json_2af9830ef68420da295e9fbbf418b0d8_add({"features": [{"geometry": {"coordinates": [[-47.395205344, -22.172559531333334], [-47.395212610166666, -22.172614638833334], [-47.395219157, -22.172656417833334], [-47.395223544833335, -22.1726892105], [-47.39522414098443, -22.17269369161165], [-47.395225326538004, -22.172702654611555]], "id": null, "type": "LineString"}, "id": 0, "properties": {"Dist1": 14.558011415731592, "Dist2": 0.0, "Id": "1", "Length": 14.558011415731592, "Name": "CidadeJardimTerreno2"}, "type": "Feature"}, {"geometry": {"coordinates": [[-47.39521090233333, -22.172708800833334], [-47.395206943666665, -22.172679811], [-47.395202420666664, -22.1726491015], [-47.395198865666664, -22.172615782833333], [-47.395193255833334, -22.172577132833332], [-47.395192610024395, -22.17257265769541], [-47.395191325698136, -22.172563706509337]], "id": null, "type": "LineString"}, "id": 1, "properties": {"Dist1": 14.558011415731592, "Dist2": 0.0, "Id": "2", "Length": 14.771318274761821, "Name": "CidadeJardimTerreno2"}, "type": "Feature"}, {"geometry": {"coordinates": [[-47.3951762925, -22.172561603], [-47.395180824166665, -22.172591686166665], [-47.395185745333336, -22.172623080166666], [-47.395190433, -22.172656367166667], [-47.395195199, -22.172693641833334], [-47.39519576904436, -22.172698125891035], [-47.39519690267159, -22.172707094716973]], "id": null, "type": "LineString"}, "id": 2, "properties": {"Dist1": 14.558011415731592, "Dist2": 0.0, "Id": "3", "Length": 14.827915524386164, "Name": "CidadeJardimTerreno2"}, "type": "Feature"}, {"geometry": {"coordinates": [[-47.39518293216667, -22.1727127985], [-47.39517819266667, -22.172680514833335], [-47.3951732595, -22.172646752833334], [-47.39516880516667, -22.1726149155], [-47.39516381233334, -22.172581167166665], [-47.39516315429932, -22.172576693573966], [-47.39516184565584, -22.172567745443878]], "id": null, "type": "LineString"}, "id": 3, "properties": {"Dist1": 14.558011415731592, "Dist2": 0.0, "Id": "4", "Length": 14.785159385903514, "Name": "CidadeJardimTerreno2"}, "type": "Feature"}, {"geometry": {"coordinates": [[-47.395147317833334, -22.172566031], [-47.395151503166666, -22.172595452333333], [-47.3951561855, -22.172628011166665], [-47.39516159866667, -22.172663757333332], [-47.39516672716667, -22.172697770833334], [-47.395167397572706, -22.172702242832194], [-47.39516873082615, -22.17271118781009]], "id": null, "type": "LineString"}, "id": 4, "properties": {"Dist1": 14.558011415731592, "Dist2": 0.0, "Id": "5", "Length": 14.80117692191233, "Name": "CidadeJardimTerreno2"}, "type": "Feature"}], "type": "FeatureCollection"}); geo_json_1f8972074523b6427449f533ba057bf8_add({"features": [{"geometry": {"coordinates": [[-47.395205344, -22.172559531333334], [-47.395212610166666, -22.172614638833334], [-47.395219157, -22.172656417833334], [-47.395223544833335, -22.1726892105], [-47.39522414098443, -22.17269369161165], [-47.395225326538004, -22.172702654611555]], "id": null, "type": "LineString"}, "id": 0, "properties": {"Dist1": 14.558011415731592, "Dist2": 0.0, "Id": "1", "Length": 14.558011415731592, "Name": "CidadeJardimTerreno2"}, "type": "Feature"}, {"geometry": {"coordinates": [[-47.39521090233333, -22.172708800833334], [-47.395206943666665, -22.172679811], [-47.395202420666664, -22.1726491015], [-47.395198865666664, -22.172615782833333], [-47.395193255833334, -22.172577132833332], [-47.395192610024395, -22.17257265769541], [-47.395191325698136, -22.172563706509337]], "id": null, "type": "LineString"}, "id": 1, "properties": {"Dist1": 14.558011415731592, "Dist2": 0.0, "Id": "2", "Length": 14.771318274761821, "Name": "CidadeJardimTerreno2"}, "type": "Feature"}, {"geometry": {"coordinates": [[-47.3951762925, -22.172561603], [-47.395180824166665, -22.172591686166665], [-47.395185745333336, -22.172623080166666], [-47.395190433, -22.172656367166667], [-47.395195199, -22.172693641833334], [-47.39519576904436, -22.172698125891035], [-47.39519690267159, -22.172707094716973]], "id": null, "type": "LineString"}, "id": 2, "properties": {"Dist1": 14.558011415731592, "Dist2": 0.0, "Id": "3", "Length": 14.827915524386164, "Name": "CidadeJardimTerreno2"}, "type": "Feature"}, {"geometry": {"coordinates": [[-47.39518293216667, -22.1727127985], [-47.39517819266667, -22.172680514833335], [-47.3951732595, -22.172646752833334], [-47.39516880516667, -22.1726149155], [-47.39516381233334, -22.172581167166665], [-47.39516315429932, -22.172576693573966], [-47.39516184565584, -22.172567745443878]], "id": null, "type": "LineString"}, "id": 3, "properties": {"Dist1": 14.558011415731592, "Dist2": 0.0, "Id": "4", "Length": 14.785159385903514, "Name": "CidadeJardimTerreno2"}, "type": "Feature"}, {"geometry": {"coordinates": [[-47.395147317833334, -22.172566031], [-47.395151503166666, -22.172595452333333], [-47.3951561855, -22.172628011166665], [-47.39516159866667, -22.172663757333332], [-47.39516672716667, -22.172697770833334], [-47.395167397572706, -22.172702242832194], [-47.39516873082615, -22.17271118781009]], "id": null, "type": "LineString"}, "id": 4, "properties": {"Dist1": 14.558011415731592, "Dist2": 0.0, "Id": "5", "Length": 14.80117692191233, "Name": "CidadeJardimTerreno2"}, "type": "Feature"}], "type": "FeatureCollection"});
geo_json_2af9830ef68420da295e9fbbf418b0d8.setStyle(function(feature) {return feature.properties.style;}); geo_json_1f8972074523b6427449f533ba057bf8.setStyle(function(feature) {return feature.properties.style;});
geo_json_2af9830ef68420da295e9fbbf418b0d8.addTo(map_fbec21a667da83a7a2d972cb198fec24); geo_json_1f8972074523b6427449f533ba057bf8.addTo(map_23f8e35f17470dacda23acd5395a7fac);
</script> </script>
@ -185,7 +185,7 @@
} }
trajeto_json_add({"features": []}); trajeto_json_add({"features": []});
trajeto_json.addTo(map_fbec21a667da83a7a2d972cb198fec24); trajeto_json.addTo(map_23f8e35f17470dacda23acd5395a7fac);
function adicionarGeometria(novaGeometria) { function adicionarGeometria(novaGeometria) {
trajeto_json.addData(novaGeometria); trajeto_json.addData(novaGeometria);
@ -243,7 +243,7 @@
} }
trajeto_dinamico_json_add({"features": []}); trajeto_dinamico_json_add({"features": []});
trajeto_dinamico_json.addTo(map_fbec21a667da83a7a2d972cb198fec24); trajeto_dinamico_json.addTo(map_23f8e35f17470dacda23acd5395a7fac);
function adicionarGeometriaDinamica(novaGeometria) { function adicionarGeometriaDinamica(novaGeometria) {
trajeto_dinamico_json.addData(novaGeometria); trajeto_dinamico_json.addData(novaGeometria);
@ -296,9 +296,9 @@
var marcadorEquipamento = L.marker([0, 0], { var marcadorEquipamento = L.marker([0, 0], {
icon: customIcon icon: customIcon
}).addTo(map_fbec21a667da83a7a2d972cb198fec24); }).addTo(map_23f8e35f17470dacda23acd5395a7fac);
var marcadorBase = L.marker([0, 0], {}).addTo(map_fbec21a667da83a7a2d972cb198fec24); var marcadorBase = L.marker([0, 0], {}).addTo(map_23f8e35f17470dacda23acd5395a7fac);
var icon = L.AwesomeMarkers.icon( var icon = L.AwesomeMarkers.icon(
{"extraClasses": "fa-rotate-0", "icon": "info-sign", "iconColor": "white", "markerColor": "red", "prefix": "glyphicon"} {"extraClasses": "fa-rotate-0", "icon": "info-sign", "iconColor": "white", "markerColor": "red", "prefix": "glyphicon"}
); );
@ -380,7 +380,7 @@
} }
if (foco) { if (foco) {
map_fbec21a667da83a7a2d972cb198fec24.setView(novaPosicao, map_fbec21a667da83a7a2d972cb198fec24.getZoom()); map_23f8e35f17470dacda23acd5395a7fac.setView(novaPosicao, map_23f8e35f17470dacda23acd5395a7fac.getZoom());
} }
} }
@ -397,7 +397,7 @@
function atualizarSelecaoRuas(selecionadas) { function atualizarSelecaoRuas(selecionadas) {
selecionadas = JSON.parse(selecionadas); selecionadas = JSON.parse(selecionadas);
RuasSelecionadas = Array.isArray(selecionadas) ? [...selecionadas] : []; RuasSelecionadas = Array.isArray(selecionadas) ? [...selecionadas] : [];
geo_json_2af9830ef68420da295e9fbbf418b0d8.eachLayer(function (layer) { geo_json_1f8972074523b6427449f533ba057bf8.eachLayer(function (layer) {
if (RuasSelecionadas.includes(parseInt(layer.feature.id))) { if (RuasSelecionadas.includes(parseInt(layer.feature.id))) {
layer.setStyle({ color: 'blue' }); layer.setStyle({ color: 'blue' });
} else { } else {

View File

@ -23,7 +23,7 @@ class CameraOak:
#self.dev_info = dai.DeviceInfo(mx_id) #self.dev_info = dai.DeviceInfo(mx_id)
self.mx_id = self.dev_info.getMxId() self.mx_id = self.dev_info.getMxId()
self.versao = self.dev_info.name self.versao = self.dev_info.name
self.dispositivo = T_Code.Vzo.value self.dispositivo = T_Code.Vzo
self.modelo = "Desconhecido" self.modelo = "Desconhecido"
self.ultima_saude = {} self.ultima_saude = {}
self.timestamp_ultimo_frame_rgb = None self.timestamp_ultimo_frame_rgb = None
@ -34,8 +34,8 @@ class CameraOak:
self.rodando = False self.rodando = False
self.iniciado = False self.iniciado = False
_cameras = ContextoGlobalRedis.get_cameras() _camera = ContextoGlobalRedis.get_camera(self.mx_id) or {}
_camera = _cameras.get(self.mx_id, {}) _camera["mx_id"] = self.mx_id
_camera["versao"] = self.versao _camera["versao"] = self.versao
_camera["iniciando"] = True _camera["iniciando"] = True
_camera["iniciado"] = False _camera["iniciado"] = False
@ -49,21 +49,22 @@ class CameraOak:
if dai.CameraBoardSocket.LEFT in sensores and dai.CameraBoardSocket.RIGHT in sensores: if dai.CameraBoardSocket.LEFT in sensores and dai.CameraBoardSocket.RIGHT in sensores:
self.modelo = "OAK-D Lite" self.modelo = "OAK-D Lite"
self.dispositivo = T_Code.Snr.value self.dispositivo = T_Code.Snr
self.tem_depth = True self.tem_depth = True
self.tem_imu = True self.tem_imu = True
elif dai.CameraBoardSocket.CAM_A in sensores: elif dai.CameraBoardSocket.CAM_A in sensores:
self.modelo = "OAK-1 Lite W" self.modelo = "OAK-1 Lite W"
self.dispositivo = T_Code.Cam.value self.dispositivo = T_Code.Cam
self.tem_depth = False self.tem_depth = False
else: else:
self.dispositivo = T_Code.Vzo
self.mostrar_log(f"[WARN] Sensores desconhecidos: {sensores}") self.mostrar_log(f"[WARN] Sensores desconhecidos: {sensores}")
except Exception as e: except Exception as e:
pass pass
self.mostrar_log(f"Falha ao detectar sensores: {e}") self.mostrar_log(f"Falha ao detectar sensores: {e}")
_camera["modelo"] = self.modelo _camera["modelo"] = self.modelo
_camera["dispositivo"] = self.dispositivo _camera["dispositivo"] = self.dispositivo.value
_camera["tem_depht"] = self.tem_depth _camera["tem_depht"] = self.tem_depth
_camera["tem_imu"] = self.tem_imu _camera["tem_imu"] = self.tem_imu
@ -124,8 +125,11 @@ class CameraOak:
} }
_camera["parametros"] = self.parametros _camera["parametros"] = self.parametros
self.ultima_saude["timestamp"] = time.time()
self.iniciado = True self.iniciado = True
_camera["iniciado"] = True _camera["iniciado"] = True
_camera["iniciado_em"] = time.time()
except Exception as e: except Exception as e:
self.mostrar_log(f"Erro ao iniciar camera: {e}") self.mostrar_log(f"Erro ao iniciar camera: {e}")
@ -133,7 +137,7 @@ class CameraOak:
_camera["iniciando"] = False _camera["iniciando"] = False
ContextoGlobalRedis.set(CtxKey.DadosCameras, _cameras) ContextoGlobalRedis.set(ContextoGlobalRedis.CamKey(self.mx_id), _camera)
def _criar_pipeline(self): def _criar_pipeline(self):
pipeline = dai.Pipeline() pipeline = dai.Pipeline()
@ -311,14 +315,12 @@ class CameraOak:
return None, {"erro": str(e), "duracao": dur, "frame_valido": False} return None, {"erro": str(e), "duracao": dur, "frame_valido": False}
def atualizar_saude(self): def atualizar_saude(self):
#print("Atualizando saude") #self.mostrar_log(f"[{self.mx_id}] Atualizando saude {self.dispositivo.name}...")
if self.imu is not None: if self.imu is not None:
self.imu.atualizar_saude() self.imu.atualizar_saude()
_cameras = ContextoGlobalRedis.get_cameras() conectado = ContextoGlobalRedis.get_cameras().get(self.mx_id) is not None
conectado = _cameras.get(self.mx_id) is not None
saude = 50 saude = 50
motivos = [] motivos = []
@ -412,7 +414,10 @@ class CameraOak:
elif saude < 80: elif saude < 80:
status = StatusModulo.ALERTA status = StatusModulo.ALERTA
agora = time.time()
saude_geral = { saude_geral = {
"timestamp": agora,
"conectado": conectado, "conectado": conectado,
"status": status.value, "status": status.value,
"saude": saude, "saude": saude,
@ -422,14 +427,13 @@ class CameraOak:
self.ultima_saude = saude_geral self.ultima_saude = saude_geral
agora = time.time()
timeout = 2.0 timeout = 2.0
ts_depth = self.timestamp_ultimo_frame_depth or 0 ts_depth = self.timestamp_ultimo_frame_depth or 0
ts_rgb = self.timestamp_ultimo_frame_rgb or 0 ts_rgb = self.timestamp_ultimo_frame_rgb or 0
self.rodando = ((agora - ts_depth) <= timeout or (agora - ts_rgb) <= timeout) self.rodando = ((agora - ts_depth) <= timeout or (agora - ts_rgb) <= timeout)
from camera_worker.manager import definir_saude_camera from camera_worker.manager import definir_saude_camera
definir_saude_camera(self.mx_id, status, saude, motivos, self.rodando, performance) definir_saude_camera(self.mx_id, status, saude, motivos, self.rodando, performance, conectado, agora, self.dispositivo)

View File

@ -18,35 +18,28 @@ class CameraManager:
dispositivos_serializados = [d.getMxId() for d in dispositivos] dispositivos_serializados = [d.getMxId() for d in dispositivos]
except Exception as e: except Exception as e:
dispositivos_serializados = [] dispositivos_serializados = []
cameras_mapeadas = ContextoGlobalRedis.get_cameras() cameras_mapeadas = ContextoGlobalRedis.get_cameras()
for mx_id in dispositivos_serializados: for mx_id in dispositivos_serializados:
cam_existente = cameras_mapeadas.get(mx_id) cam_existente = cameras_mapeadas.get(mx_id)
if not cam_existente: if not cam_existente:
cameras_mapeadas[mx_id] = { cameras_mapeadas[mx_id] = {
"timestamp": self._ultimo_scan, "timestamp": self._ultimo_scan,
"mx_id": mx_id, "mx_id": mx_id,
"iniciado": False,
"dispositivo": T_Code.Vzo.value
} }
else: else:
cam_existente["timestamp"] = self._ultimo_scan cam_existente["timestamp"] = self._ultimo_scan
#mxids_ativos = set(dispositivos_serializados)
#mxids_mapeados = set(cameras_mapeadas.keys())
#mxids_removidos = mxids_mapeados - mxids_ativos
#for mx_id in mxids_removidos:
# self.mostrar_log(f"🟥 Camera desconectada: {mx_id}")
# cameras_mapeadas.pop(mx_id, None)
ContextoGlobalRedis.set(CtxKey.DadosCameras, cameras_mapeadas) ContextoGlobalRedis.set(CtxKey.DadosCameras, cameras_mapeadas)
for cam in cameras_mapeadas.values(): for cam_id in cameras_mapeadas.keys():
if cam.get("dispositivo", T_Code.Vzo.value) == T_Code.Snr.value: cam = ContextoGlobalRedis.get_camera(cam_id)
ContextoGlobalRedis.publicar_comando(CmdKey.VisualWorkerRx, { "cmd": VisualWorkerCommandType.AtualizarSaudeCamera.value } ) if cam is not None:
elif cam.get("dispositivo", T_Code.Vzo.value) == T_Code.Cam.value: if cam.get("dispositivo", T_Code.Vzo.value) == T_Code.Snr.value:
ContextoGlobalRedis.publicar_comando(CmdKey.WeedWorkerRx, { "cmd": WeedWorkerCommandType.AtualizarSaudeCamera.value } ) ContextoGlobalRedis.publicar_comando(CmdKey.VisualWorkerRx, { "cmd": VisualWorkerCommandType.AtualizarSaudeCamera.value } )
elif cam.get("dispositivo", T_Code.Vzo.value) == T_Code.Cam.value:
ContextoGlobalRedis.publicar_comando(CmdKey.WeedWorkerRx, { "cmd": WeedWorkerCommandType.AtualizarSaudeCamera.value } )
visual_worker_camera_id = ContextoGlobalRedis.get_equipamento().get("camera_caminho_id") visual_worker_camera_id = ContextoGlobalRedis.get_equipamento().get("camera_caminho_id")
if visual_worker_camera_id is not None and visual_worker_camera_id in dispositivos_serializados: if visual_worker_camera_id is not None and visual_worker_camera_id in dispositivos_serializados:
@ -56,26 +49,30 @@ class CameraManager:
ContextoGlobalRedis.publicar_comando(CmdKey.WeedWorkerRx, { "cmd": WeedWorkerCommandType.IniciarCameraManager.value, "params": weed_worker_camera_id } ) ContextoGlobalRedis.publicar_comando(CmdKey.WeedWorkerRx, { "cmd": WeedWorkerCommandType.IniciarCameraManager.value, "params": weed_worker_camera_id } )
def definir_saude_camera(mx_id: str, status: StatusModulo, saude: int, motivos: list, rodando: bool, performance: dict): def definir_saude_camera(mx_id: str, status: StatusModulo, saude: int, motivos: list, rodando: bool, performance: dict, conectado: bool, ts=None, disp: T_Code = None):
#print(f"Definindo saude da camera {mx_id}") #print(f"Definindo saude da camera {mx_id}")
_cameras = ContextoGlobalRedis.get_cameras() _camera = ContextoGlobalRedis.get_camera(mx_id)
conectado = _cameras.get(mx_id) is not None #print(_cameras)
saude_geral = { saude_geral = {
"timestamp": time.time() if ts is None else ts,
"conectado": conectado, "conectado": conectado,
"status": status.value, "status": status.value,
"saude": saude, "saude": saude,
"motivos": motivos, "motivos": motivos,
"saude_idividual": [] "saude_idividual": []
} }
if not conectado: #if not conectado:
_cameras[mx_id] = {} # _cameras[mx_id] = {}
_cameras[mx_id]["saude"] = saude_geral _camera["saude"] = saude_geral
_cameras[mx_id]["rodando"] = rodando _camera["rodando"] = rodando
_cameras[mx_id]["performance"] = performance _camera["performance"] = performance
disp = T_Code(_cameras[mx_id].get("dispositivo", T_Code.Vzo.value)) if disp is not None:
_camera["dispositivo"] = disp.value
disp = T_Code(_camera.get("dispositivo", T_Code.Vzo.value))
if disp != T_Code.Vzo: if disp != T_Code.Vzo:
ContextoGlobalRedis.atualizar_ctx_dict( ContextoGlobalRedis.atualizar_ctx_dict(
ContextoGlobalRedis.ModKey(disp), ContextoGlobalRedis.ModKey(disp),
saude=saude_geral saude=saude_geral
) )
ContextoGlobalRedis.set(CtxKey.DadosCameras, _cameras) ContextoGlobalRedis.set(ContextoGlobalRedis.CamKey(mx_id), _camera)

View File

@ -12,6 +12,7 @@ def main():
from health_worker.modulos.movimentacao import ModuloMovimentacao from health_worker.modulos.movimentacao import ModuloMovimentacao
from health_worker.modulos.sensoriamento import ModuloSensoriamento from health_worker.modulos.sensoriamento import ModuloSensoriamento
from health_worker.modulos.atuador import ModuloAtuador from health_worker.modulos.atuador import ModuloAtuador
from health_worker.modulos.imu import IMUCamera
from health_worker.config import mostrar_log from health_worker.config import mostrar_log
from shared.contexto_global_redis import ContextoGlobalRedis, CmdKey, CtxKey from shared.contexto_global_redis import ContextoGlobalRedis, CmdKey, CtxKey
@ -20,7 +21,8 @@ def main():
T_Code.Dir: ModuloDirecional(), T_Code.Dir: ModuloDirecional(),
T_Code.Mov: ModuloMovimentacao(), T_Code.Mov: ModuloMovimentacao(),
T_Code.Sen: ModuloSensoriamento(), T_Code.Sen: ModuloSensoriamento(),
T_Code.Atu: ModuloAtuador() T_Code.Atu: ModuloAtuador(),
T_Code.Imu: IMUCamera()
} }
def loop_ativo(): def loop_ativo():

View File

@ -8,8 +8,11 @@ from shared.enums import StatusModulo, T_Code
from health_worker.modulos.base import ModuloDiagnosticoBase from health_worker.modulos.base import ModuloDiagnosticoBase
class IMUCamera(ModuloDiagnosticoBase): class IMUCamera(ModuloDiagnosticoBase):
def __init__(self, queue, freq=10, angulo_inicial=26.3): def __init__(self, queue=None, freq=100, angulo_inicial=26.3):
self.freq = freq self.freq = freq
if queue is None: return
self.imu_queue = queue self.imu_queue = queue
self.filtro_imu = Madgwick(beta=0.8, frequency=freq) self.filtro_imu = Madgwick(beta=0.8, frequency=freq)

View File

@ -1,6 +1,5 @@
import time import time
import numpy as np
from shared.enums import ModoOperacao, StatusCarroMapa, StatusModulo, StatusOperacao, T_Code, TipoMovimentoDirecional, TiposControladorDirecional from shared.enums import ModoOperacao, StatusCarroMapa, StatusModulo, StatusOperacao, T_Code, TipoMovimentoDirecional, TiposControladorDirecional
from shared.contexto_global_redis import ContextoGlobalRedis, CtxKey from shared.contexto_global_redis import ContextoGlobalRedis, CtxKey
from manager_worker.config import mostrar_log from manager_worker.config import mostrar_log
@ -21,12 +20,11 @@ def definir_comando(pid: PIDAdaptativo):
visual_worker_ativado = _operacao.get("Snr", {}).get("sonar_ativado", False) visual_worker_ativado = _operacao.get("Snr", {}).get("sonar_ativado", False)
visual_worker_operante = (_snr.get("saude", {}).get("status", StatusModulo.DESCONECTADO.value)) == StatusModulo.OPERANTE.value visual_worker_operante = (_snr.get("saude", {}).get("status", StatusModulo.DESCONECTADO.value)) == StatusModulo.OPERANTE.value
_dados_vw = ContextoGlobalRedis.get(CtxKey.DadosVisualWorker, {}) _dados_vw = ContextoGlobalRedis.get(CtxKey.DadosVisualWorker, {})
_snapshot_vw = _dados_vw.get("matrizes", {}).get("confianca") _snapshot_vw = _dados_vw.get("matriz_confianca")
custo = None custo = None
nav = None nav = None
dists = None dists = None
escalas = None escalas = None
d_obs_min = None
block = None block = None
if not _snapshot_vw: if not _snapshot_vw:
visual_worker_atualizado = False visual_worker_atualizado = False
@ -36,7 +34,6 @@ def definir_comando(pid: PIDAdaptativo):
custo, conf, anom, nav = unpack_snapshot(_snapshot_vw) custo, conf, anom, nav = unpack_snapshot(_snapshot_vw)
dists = _snapshot_vw.get("row_dist_m", None) dists = _snapshot_vw.get("row_dist_m", None)
escalas = _snapshot_vw.get("row_scale_x_m", None) escalas = _snapshot_vw.get("row_scale_x_m", None)
d_obs_min = _snapshot_vw.get("d_obs_min", None)
block = _snapshot_vw.get("block", None) block = _snapshot_vw.get("block", None)
_gps = ContextoGlobalRedis.get_modulo(T_Code.Gps) _gps = ContextoGlobalRedis.get_modulo(T_Code.Gps)
@ -86,7 +83,6 @@ def definir_comando(pid: PIDAdaptativo):
"Navegavel": nav, "Navegavel": nav,
"DistanciasRef": dists, "DistanciasRef": dists,
"EscalasX": escalas, "EscalasX": escalas,
"DistanciaObsMin": d_obs_min,
"Block": block "Block": block
}, },
"Camera": { "Camera": {
@ -97,7 +93,7 @@ def definir_comando(pid: PIDAdaptativo):
}, },
"RegrasAtivas": { "RegrasAtivas": {
"matriz_custo": False, "matriz_custo": False,
"deteccao_obstaculos": True, "deteccao_obstaculos": False,
}, },
"Equipamento": { "Equipamento": {
"largura": _equipamento.get("largura", 0.85), "largura": _equipamento.get("largura", 0.85),
@ -201,37 +197,16 @@ def _regras_taticas(contexto):
# >>> NOVO: gate de bloqueio/parede com block + d_obs_min <<< # >>> NOVO: gate de bloqueio/parede com block + d_obs_min <<<
if vw_operante and contexto.get("RegrasAtivas", {}).get("deteccao_obstaculos", False): if vw_operante and contexto.get("RegrasAtivas", {}).get("deteccao_obstaculos", False):
block = dados_vw.get("MatrizCusto", {}).get("Block", None) block = dados_vw.get("MatrizCusto", {}).get("Block", {})
d_obs_min = dados_vw.get("MatrizCusto", {}).get("DistanciaObsMin", None) reason = block.get("reason", "none")
decision = block.get("decision", {})
#print(block) #print(block)
# parâmetros de parada (ajuste se quiser puxar do config) if decision.get("parar", False):
a_max_freio = 0.8 # m/s² mostrar_log(f"🟥 Parada necessaria por {reason}.")
margem_parada = 0.25
# distância necessária p/ parar com folga
dist_freio = max(0.30, (velocidade_media ** 2) / (2.0 * a_max_freio))
dist_necessaria = dist_freio + margem_parada
blocked = bool(block.get("blocked")) if isinstance(block, dict) else False
reason = (block.get("reason") if isinstance(block, dict) else "none") or "none"
if blocked and reason == "blackout":
mostrar_log(f"🟥 Blackout de percepção. Parando.")
return _comando_direcional_parado(True) return _comando_direcional_parado(True)
if blocked and reason == "obstacle":
if (d_obs_min is not None) and (d_obs_min <= dist_necessaria):
mostrar_log(f"🟥 Obstáculo a {d_obs_min:.2f} m ≤ {dist_necessaria:.2f} m (necessária). Parando.")
return _comando_direcional_parado(True)
else:
# opcional: limitar velocidade para manter margem de frenagem
# v_max_safe = sqrt(2*a*(dmin - margem)), se dmin existir
if d_obs_min is not None and d_obs_min > margem_parada:
v_max_safe = (2.0 * a_max_freio * max(0.0, d_obs_min - margem_parada)) ** 0.5
contexto.setdefault("DirecionalHints", {})["v_max_sugerida_mps"] = float(v_max_safe)
# motivo narrow: não para, mas dá dica lateral # motivo narrow: não para, mas dá dica lateral
if isinstance(block, dict) and reason == "narrow": if isinstance(block, dict) and reason == "narrow":
sb = (block.get("side_bias") or {}).get("value", 0.0) sb = (block.get("side_bias") or {}).get("value", 0.0)

View File

@ -251,81 +251,83 @@ class ControladorMPC:
except Exception as e: except Exception as e:
mostrar_log(f"Erro ao consultar proximo ponto nao visitado: {e}") mostrar_log(f"Erro ao consultar proximo ponto nao visitado: {e}")
def _corrigir_pontos_visitados( def _corrigir_pontos_visitados(self, x, y, pontos_visitados: np.ndarray, idx_atual: int = -1,
self, x, y, pontos_visitados: np.ndarray, idx_atual: int = -1, limite_max_avanco: float = 5.0, # em METROS
limite_max_avanço: float = 5.0 # em METROS limite_max_pontos: int = 10):
):
"""
Marca como visitados até o ponto mais recente cuja distância ao (x, y) cai dentro da 'margem' do ponto.
Usa janela limitada por distância acumulada (metros) para não varrer toda a lista.
"""
try: try:
# 1) caminho em arrays (pré-calcule isso uma vez no __init__ ou quando pontos mudarem) p_xy, p_mg, p_s = self._p_xy, self._p_margem, self._p_s
# self._p_xy : (N,2) float32 | self._p_margem : (N,) float32 | self._p_s : (N,) float32 (dist acumulada)
p_xy = self._p_xy # np.ndarray
p_mg = self._p_margem # np.ndarray
p_s = self._p_s # np.ndarray (metros)
N = p_xy.shape[0] N = p_xy.shape[0]
if N == 0: if N == 0:
return -1 return -1
# 2) fast-path: se idx_atual fornecido, só marca e sai # --- (1) Igual ao antigo: marcar EXCLUSIVO quando idx_atual vier do C# ---
if idx_atual > -1: if idx_atual > -1:
pontos_visitados[:idx_atual+1] = True pontos_visitados[:idx_atual] = True # <<< EXCLUSIVO (antes era :idx_atual+1)
return idx_atual return idx_atual
# 3) comece da "fronteira" atual (primeiro False)
# se já tiver tudo visitado, devolve último índice
if pontos_visitados.all(): if pontos_visitados.all():
return N - 1 return N - 1
# próximo não visitado (rápido)
start_idx = int(np.argmax(~pontos_visitados)) start_idx = int(np.argmax(~pontos_visitados))
# 4) limitar varredura por distância acumulada (em METROS) # janela por metros + clamp por nº de pontos (compatível com o antigo)
# queremos procurar até "start_idx" + Δs (metros), convertendo via s_acum
s_start = p_s[start_idx] s_start = p_s[start_idx]
s_lim = s_start + float(limite_max_avanço) s_lim = s_start + float(limite_max_avanco)
# encontre o último índice com s <= s_lim (searchsorted é O(log N))
# atenção: searchsorted trabalha com array crescente
end_idx = int(np.searchsorted(p_s, s_lim, side="right") - 1) end_idx = int(np.searchsorted(p_s, s_lim, side="right") - 1)
if end_idx < start_idx: if end_idx < start_idx:
end_idx = start_idx end_idx = start_idx
# também pode pôr um teto de tamanho de janela (evita pegar centenas de pontos de uma vez) end_idx = min(end_idx, start_idx + int(limite_max_pontos))
# end_idx = min(end_idx, start_idx + 150)
# 5) checagem vetorizada na janela [start_idx:end_idx] (inclusivo)
sl = slice(start_idx, end_idx + 1) sl = slice(start_idx, end_idx + 1)
dx = x - p_xy[sl, 0] dx = x - p_xy[sl, 0]
dy = y - p_xy[sl, 1] dy = y - p_xy[sl, 1]
dist2 = dx*dx + dy*dy dist2 = dx*dx + dy*dy
margem2 = p_mg[sl] * p_mg[sl] margem2 = p_mg[sl] * p_mg[sl]
hits = dist2 <= margem2 # --- (2) Opcional para fidelidade ao antigo: usar '<' em vez de '<=' ---
hits = dist2 < margem2 # era <=
if np.any(hits): if np.any(hits):
# pega o primeiro índice da janela que bateu off = int(np.argmax(hits))
off = int(np.argmax(hits)) # primeiro True
idx = start_idx + off idx = start_idx + off
# marca até ele (inclusive)
pontos_visitados[:idx+1] = True pontos_visitados[:idx+1] = True
return idx return idx
# 6) se ninguém bateu dentro da janela, mantém visitados como estão e retorna o próximo não visitado # --- (3) Fallback robusto para “próximo não visitado” ---
# (isso replica seu comportamento original) nv = np.flatnonzero(~pontos_visitados)
prox = int(np.argmax(~pontos_visitados)) return int(nv[0]) if nv.size else (N - 1)
return prox
except Exception as e: except Exception as e:
mostrar_log(f"Erro ao corrigir pontos visitados: {e}") mostrar_log(f"Erro ao corrigir pontos visitados: {e}")
# fallback conservador
try: try:
prox = int(np.argmax(~pontos_visitados)) nv = np.flatnonzero(~pontos_visitados)
return prox return int(nv[0]) if nv.size else (N - 1)
except Exception: except Exception:
return -1 return -1
def _corrigir_pontos_visitados_old(self, x, y, pontos_visitados, idx_atual=-1, limite_max_avanço=5.0):
if (idx_atual > -1):
for idx, ponto in enumerate(self.pontos_info):
if (idx < idx_atual):
pontos_visitados[idx] = True
else:
break
return idx_atual
for idx, ponto in enumerate(self.pontos_info):
if pontos_visitados[idx]:
continue
pos = ponto["xy"]
margem = ponto.get("distanciaMargem", 0.7)
dist = np.linalg.norm([x - pos[0], y - pos[1]])
if dist < margem:
for i in range(idx + 1):
pontos_visitados[i] = True
return idx
if idx > 0 and not pontos_visitados[idx - 1] and idx >= limite_max_avanço:
break
return self._proximo_nao_visitado(pontos_visitados)
def _calcular_pesos_movimento(self, contexto, erro_ori_rad): def _calcular_pesos_movimento(self, contexto, erro_ori_rad):
try: try:
peso_erro_pos = 1.2 # 0 peso_erro_pos = 1.2 # 0
@ -379,7 +381,7 @@ class ControladorMPC:
comando = self._processar_mpc_receding(contexto, comando_anterior) comando = self._processar_mpc_receding(contexto, comando_anterior)
t_exec = max((now() - self.ultima_atualizacao), 1e-3) t_exec = max((now() - self.ultima_atualizacao), 1e-3)
if True or comando['erro']: if comando['erro']:
mostrar_log(f"🧭 Direcional MPC | Erro: {comando['erro']} | Parada: {comando['parada_necessaria']} | Movimento: {TipoMovimentoDirecional(comando['tipo']).name} | Ângulo: {comando['angulo']}° | Horizonte: {len(comando['simulacao'])} | dt: {dt_raw:.2f} s | freq = {(1.0 / dt_raw):.2f} Hz | t_exec: {t_exec:.2} s | f_exec: {(1.0 / t_exec):.2f} Hz") mostrar_log(f"🧭 Direcional MPC | Erro: {comando['erro']} | Parada: {comando['parada_necessaria']} | Movimento: {TipoMovimentoDirecional(comando['tipo']).name} | Ângulo: {comando['angulo']}° | Horizonte: {len(comando['simulacao'])} | dt: {dt_raw:.2f} s | freq = {(1.0 / dt_raw):.2f} Hz | t_exec: {t_exec:.2} s | f_exec: {(1.0 / t_exec):.2f} Hz")
return comando return comando
@ -444,11 +446,11 @@ class ControladorMPC:
for passo in range(max_simulacoes): for passo in range(max_simulacoes):
if ms_left() <= 0: if ms_left() <= 0:
return self._comando_fallback_hot_stop(comando_anterior) return self._comando_fallback_hot_stop(comando_anterior)
_, posicao_futura, _ = self._simular_passo(x, y, theta, tipo_anterior, angulo_anterior, {}, { "tipo": tipo_anterior, "angulo": angulo_anterior }, contexto, self.visitados_execucao.copy()) _, posicao_futura, _, pts_visitados = self._simular_passo(x, y, theta, tipo_anterior, angulo_anterior, {}, { "tipo": tipo_anterior, "angulo": angulo_anterior }, contexto, self.visitados_execucao.copy())
x, y, theta = posicao_futura[-1] x, y, theta = posicao_futura[-1]
simulacao_latlon = [] simulacao_latlon = []
idx_alvo_correcao = self._corrigir_pontos_visitados(x, y, self.visitados_execucao, idx_alvo_real) # marca pontos antigos como visitados idx_alvo_correcao = self._corrigir_pontos_visitados(x, y, pts_visitados, idx_alvo_real) # marca pontos antigos como visitados
#mostrar_log(f"previsao_futura em {(t_1 - t_0):.4f}s, correcao_pontos_visitados em {(t_2 - t_1):.4f}") #mostrar_log(f"previsao_futura em {(t_1 - t_0):.4f}s, correcao_pontos_visitados em {(t_2 - t_1):.4f}")
@ -461,7 +463,7 @@ class ControladorMPC:
"custo": 0.0, "custo": 0.0,
"comandos": [(tipo_anterior, angulo_anterior)], "comandos": [(tipo_anterior, angulo_anterior)],
"trajetoria": [], "trajetoria": [],
"visitados": self.visitados_execucao.copy(), "visitados": pts_visitados,
"inicial": True "inicial": True
}] }]
@ -541,7 +543,7 @@ class ControladorMPC:
t_c_ini = now() t_c_ini = now()
try: try:
custo, sim, valido = self._simular_passo( custo, sim, valido, visitados_sim = self._simular_passo(
x_atual, y_atual, theta_atual, x_atual, y_atual, theta_atual,
tipo_k, ang_k, tipo_k, ang_k,
custos_candidatos, custos_candidatos,
@ -555,7 +557,7 @@ class ControladorMPC:
"custo": candidato["custo"] + float(custo), "custo": candidato["custo"] + float(custo),
"comandos": candidato["comandos"] + [(tipo_k, ang_k)], "comandos": candidato["comandos"] + [(tipo_k, ang_k)],
"trajetoria": candidato["trajetoria"] + sim, "trajetoria": candidato["trajetoria"] + sim,
"visitados": visitados.copy(), # copy apenas ao guardar "visitados": visitados_sim,
"inicial": False "inicial": False
} }
novos_candidatos.append(novo) novos_candidatos.append(novo)
@ -1475,10 +1477,10 @@ class ControladorMPC:
except Exception as e: except Exception as e:
mostrar_log(f"❌ Erro ao simular passo {passo}, {tipo.name}, {np.degrees(angulo_testado):.2f}: {e}") mostrar_log(f"❌ Erro ao simular passo {passo}, {tipo.name}, {np.degrees(angulo_testado):.2f}: {e}")
return custo_total, simulacoes, True return custo_total, simulacoes, True, pontos_visitados
except Exception as e: except Exception as e:
mostrar_log(f"Erro ao simular passo para {tipo.name} | angulo: {np.degrees(angulo_testado):.2f}: {e}") mostrar_log(f"Erro ao simular passo para {tipo.name} | angulo: {np.degrees(angulo_testado):.2f}: {e}")
return float('inf'), [(0, 0, 0)], False return float('inf'), [(0, 0, 0)], False, pontos_visitados
def _nova_posicao(self, x, y, theta, omega, velocidade, tipo, angulo_rad): def _nova_posicao(self, x, y, theta, omega, velocidade, tipo, angulo_rad):
distancia_m = velocidade * self.dt distancia_m = velocidade * self.dt

View File

@ -9,7 +9,7 @@ from shared.enums import ManagerWorkerCommandType, ModoOperacao, StatusModulo, S
from manager_worker.modulos.mpc import inicializar as iniciar_mpc from manager_worker.modulos.mpc import inicializar as iniciar_mpc
class CtxKey(str, Enum): class CtxKey(str, Enum):
DadosCameras = "ctx:dados_cameras" DadosCameras = "ctx:dados_cameras_"
DadosModulo = "ctx:dados_modulo_" DadosModulo = "ctx:dados_modulo_"
DadosOperacao = "ctx:dados_operacao" DadosOperacao = "ctx:dados_operacao"
DadosContexto = "ctx:dados_contexto" DadosContexto = "ctx:dados_contexto"
@ -154,10 +154,19 @@ class ContextoGlobalRedis:
def get_controle(cls): def get_controle(cls):
return cls.get(CtxKey.DadosControle, {}) return cls.get(CtxKey.DadosControle, {})
@classmethod
def CamKey(cls, mx_id: str):
"""Lê a estrutura e decodifica de volta para objeto"""
return CtxKey.DadosCameras.value + mx_id
@classmethod @classmethod
def get_cameras(cls): def get_cameras(cls):
return cls.get(CtxKey.DadosCameras, {}) return cls.get(CtxKey.DadosCameras, {})
@classmethod
def get_camera(cls, mx_id: str):
return cls.get(cls.CamKey(mx_id), None)
@classmethod @classmethod
def get_equipamento(cls): def get_equipamento(cls):
return cls.get(CtxKey.DadosEquipamento, {}) return cls.get(CtxKey.DadosEquipamento, {})

View File

@ -23,7 +23,6 @@ class CameraManager:
def __init__(self, mostrar_log): def __init__(self, mostrar_log):
self.mostrar_log = mostrar_log self.mostrar_log = mostrar_log
self.mx_id = None self.mx_id = None
self.tempo_saude = 10
self.reiniciar_status() self.reiniciar_status()
def reiniciar_status(self): def reiniciar_status(self):
@ -40,7 +39,6 @@ class CameraManager:
self._ultimo_depth_frame = None self._ultimo_depth_frame = None
self._ts_segmentacao_anterior = 0 self._ts_segmentacao_anterior = 0
self._pool = ThreadPoolExecutor(max_workers=6) self._pool = ThreadPoolExecutor(max_workers=6)
self._ultima_saude_ts = 0
def inicializar(self, mx_id): def inicializar(self, mx_id):
if self.iniciando: if self.iniciando:
@ -121,14 +119,13 @@ class CameraManager:
return d # <-- ndarray, não list return d # <-- ndarray, não list
def atualizar_saude_camera(self): def atualizar_saude_camera(self):
self._ultima_saude_ts = time.time()
#self.mostrar_log("Atualizando saude da camera...") #self.mostrar_log("Atualizando saude da camera...")
try: try:
if self.camera is not None: if self.camera is not None:
self.camera.atualizar_saude() self.camera.atualizar_saude()
elif self.mx_id is not None: elif self.mx_id is not None:
from camera_worker.manager import definir_saude_camera from camera_worker.manager import definir_saude_camera
definir_saude_camera(self.mx_id, StatusModulo.DESCONECTADO, 0, ["desconectado"], False, {}) definir_saude_camera(self.mx_id, StatusModulo.DESCONECTADO, 0, ["desconectado"], False, {}, disp=T_Code.Snr, conectado=False)
except Exception as e: except Exception as e:
self.mostrar_log(f"[saude] erro: {e}") self.mostrar_log(f"[saude] erro: {e}")
@ -224,19 +221,16 @@ class CameraManager:
def _iniciar_loop_analise_continua(self, freq): def _iniciar_loop_analise_continua(self, freq):
def loop(): def loop():
ultima_atualizacao = 0 ultima_atualizacao = 0
self._ultima_saude_ts = 0
while True: while True:
if self.camera is None: if self.camera is None:
time.sleep(5) time.sleep(5)
continue continue
t0 = time.time() t0 = time.time()
if (t0 - self._ultima_saude_ts) >= self.tempo_saude:
self._ultima_saude_ts = time.time()
self.atualizar_saude_camera()
try: try:
status = StatusModulo((self.camera.ultima_saude or {}).get("status", StatusModulo.DESCONECTADO.value)) status = StatusModulo((self.camera.ultima_saude or {}).get("status", StatusModulo.DESCONECTADO.value))
if status == StatusModulo.DESCONECTADO: ts_status = (self.camera.ultima_saude or {}).get("timestamp", 0)
if status == StatusModulo.DESCONECTADO and (t0 - ts_status) > 5.0:
if self.camera.imu: if self.camera.imu:
self.camera.imu.parar() self.camera.imu.parar()
self.reiniciar_status() self.reiniciar_status()
@ -276,33 +270,7 @@ class CameraManager:
def _log_performance(self, titulo, analise): def _log_performance(self, titulo, analise):
return f"{titulo}: {analise.get('latencia', 0):.3f} s, {analise.get('fps', 0):.2f} FPS, {analise.get('freq', 0):.2f} Hz; " return f"{titulo}: {analise.get('latencia', 0):.3f} s, {analise.get('fps', 0):.2f} FPS, {analise.get('freq', 0):.2f} Hz; "
def _mostrar_grid_distancias_sobre_rgb(self, rgb_frame, distancias_grid, cor_linha=(0,255,0)):
"""
Mostra o frame RGB com linhas horizontais do grid de referência
e escreve as distâncias médias de cada linha.
- rgb_frame: imagem original (BGR)
- distancias_grid: array/lista de distâncias (em metros) para cada linha do grid
- cor_linha: cor da linha do grid (B,G,R)
"""
img = cv2.resize(rgb_frame.copy(), (1600, 900))
h, w = img.shape[:2]
grid_h = len(distancias_grid)
step_h = h // grid_h
for i, dist in enumerate(distancias_grid):
y = int(i * step_h)
cv2.line(img, (0, y), (w, y), cor_linha, 1)
texto = f"{dist:.2f} m" if not np.isnan(dist) else "NaN"
cv2.putText(img, texto, (10, y + 15), cv2.FONT_HERSHEY_SIMPLEX, 0.5, cor_linha, 1, cv2.LINE_AA)
# Última linha do grid (topo)
y_last = int(grid_h * step_h)
cv2.line(img, (0, y_last), (w, y_last), (0,0,255), 1)
cv2.imshow("Grid de Referencia sobre RGB", img)
cv2.waitKey(1)
def _realizar_analises(self): def _realizar_analises(self):
if self._depth_frame_necessario: if self._depth_frame_necessario:
@ -316,6 +284,7 @@ class CameraManager:
self._analise_matriz_confianca(depth_frame_np, distancia_max_m, fov_h) self._analise_matriz_confianca(depth_frame_np, distancia_max_m, fov_h)
#key, vis = self.debug_show_costmap(rgb_frame=self._ultimo_rgb_frame, grid_dict=self._ultima_analise_matriz_confianca, grid_shape=self.grid_ref_shape, window_name="viz MPC", wait=1, text_mode="mini") #key, vis = self.debug_show_costmap(rgb_frame=self._ultimo_rgb_frame, grid_dict=self._ultima_analise_matriz_confianca, grid_shape=self.grid_ref_shape, window_name="viz MPC", wait=1, text_mode="mini")
#key, vis = self.debug_show_visualworker(frame_bgr=self._ultimo_rgb_frame, grid=self._ultima_analise_matriz_confianca, wait=1, text_mode="full", draw_grid=True, draw_cells=True, draw_legend=True) #key, vis = self.debug_show_visualworker(frame_bgr=self._ultimo_rgb_frame, grid=self._ultima_analise_matriz_confianca, wait=1, text_mode="full", draw_grid=True, draw_cells=True, draw_legend=True)
self.segmentacao_manager.display_segmentation_debug(self._ultimo_rgb_frame, 150)
def _realizar_analises_async(self): def _realizar_analises_async(self):
executor = self._pool executor = self._pool
@ -417,14 +386,14 @@ class CameraManager:
ContextoGlobalRedis.atualizar_ctx_dict( ContextoGlobalRedis.atualizar_ctx_dict(
CtxKey.DadosVisualWorker, CtxKey.DadosVisualWorker,
ts_analise=time.time(), ts_analise=time.time(),
matrizes__confianca=snapshot matriz_confianca=snapshot
#perfil_corredor__segmentacao=converter_valores_numpy(self._ultima_analise_segmentacao.get("corredor_perfil", [])) #perfil_corredor__segmentacao=converter_valores_numpy(self._ultima_analise_segmentacao.get("corredor_perfil", []))
) )
self._nova_grid_conf_disponivel = True self._nova_grid_conf_disponivel = True
#self._mostrar_debug_grid_confianca(self._ultimo_rgb_frame, grid_conf["matriz"], True, self._ultima_analise_segmentacao["mask_color"]) #self._mostrar_debug_grid_confianca(self._ultimo_rgb_frame, grid_conf["matriz"], True, self._ultima_analise_segmentacao["mask_color"])
#key, vis = self.debug_show_visualworker(frame_bgr=self._ultimo_rgb_frame, grid=grid_conf, wait=1, text_mode="full", draw_grid=True, draw_cells=True, draw_legend=True) #key, vis = self.debug_show_visualworker(frame_bgr=self._ultimo_rgb_frame, grid=grid_conf, wait=1, text_mode="full", draw_grid=True, draw_cells=True, draw_legend=True)
vis, metrics = self.debug_blockage_imshow(self._ultimo_rgb_frame, snapshot, velocidade_media=vel) #vis, metrics = self.debug_blockage_imshow(self._ultimo_rgb_frame, snapshot, velocidade_media=vel)
except Exception as e: except Exception as e:
self.mostrar_log(f"❌ Erro na geracao da matriz de confianca: {e}") self.mostrar_log(f"❌ Erro na geracao da matriz de confianca: {e}")
finally: finally:
@ -529,6 +498,7 @@ class CameraManager:
self._analisando_matriz_custo = False self._analisando_matriz_custo = False
#self.mostrar_log("Matriz de custo concluida") #self.mostrar_log("Matriz de custo concluida")
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: if not self.operante:
return [] return []
@ -582,444 +552,6 @@ class CameraManager:
return caminho_analise return caminho_analise
return None return None
def _as_ndarray(self, x, dtype=None):
if isinstance(x, np.ndarray):
return x.astype(dtype, copy=False) if dtype is not None else x
return np.array(x, dtype=dtype, copy=False)
def _gerar_grid_confianca(self, depth_frame, segmentacao_frame, limiar_prof, incluir_hist=False):
"""
depth_frame: np.ndarray HxW (milímetros; 0 = inválido)
segmentacao_frame: np.ndarray HxW (IDs de classe)
limiar_prof: limiar em METROS (ex.: 0.5) para índice de profundidade
incluir_hist: se True, retorna freq por classe em cada célula (custa alguns ms)
"""
try:
# --- força ndarray ---
depth = self._as_ndarray(depth_frame) # mm, 2D
seg = self._as_ndarray(segmentacao_frame) # IDs, 2D (NEAREST)
if depth.ndim != 2:
raise ValueError(f"depth_frame deve ser 2D, veio {depth.shape}")
if seg.ndim == 3 and seg.shape[2] == 3:
# se vier máscara colorida por engano, precisa converter antes (RGB->IDs)
raise ValueError("segmentacao_frame veio RGB; converta para IDs antes de chamar.")
if seg.shape != depth.shape:
seg = cv2.resize(seg, (depth.shape[1], depth.shape[0]), interpolation=cv2.INTER_NEAREST)
H, W = depth.shape
gh_cfg, gw_cfg = self.grid_ref_shape # alvo "ideal" (ex.: 20x20)
# calcula tamanho mínimo de célula (>=1px) e ajusta grid efetivo ao frame
h = max(1, H // gh_cfg)
w = max(1, W // gw_cfg)
gh_eff = max(1, H // h)
gw_eff = max(1, W // w)
# crop exato para poder reshape
H2 = gh_eff * h
W2 = gw_eff * w
depth = self._as_ndarray(depth[:H2, :W2])
seg = self._as_ndarray(seg[:H2, :W2])
# ---------- reshape em blocos ----------
depth_b = depth.reshape(gh_eff, h, gw_eff, w)
seg_b = seg.reshape(gh_eff, h, gw_eff, w)
# ---------- métricas vetorizadas ----------
valid = (depth_b > 0)
ignore_id = getattr(self, "ignore_id", 255)
not_ign = (seg_b != ignore_id)
rua_id = int(ClassesSegmentacao.RUA.value)
IS = (seg_b == rua_id).mean(axis=(1,3)) # presença de "rua"/chão [0..1] por célula
conf_segmentacao = not_ign.mean(axis=(1,3)) # fração de pixels não-ignorados
depth_f = np.where(valid, depth_b.astype(np.float32), np.nan)
prof_median = np.nanmedian(depth_f, axis=(1,3)) # mm
conf_profundidade = valid.mean(axis=(1,3))
# baseline por linha (grid_ref) em mm
if getattr(self, "grid_ref", None) is None or len(self.grid_ref) != gh_eff:
# mediana por faixa horizontal ao longo de toda a largura
ref = np.nanmedian(
np.where(depth > 0, depth, np.nan).reshape(gh_eff, h, W2),
axis=(1, 2)
)
self.grid_ref = np.nan_to_num(ref, nan=0.0).astype(np.float32)
# 🔒 garante ndarray e tamanho correto, mesmo se veio de sorted(...)
grid_ref_arr = np.asarray(self.grid_ref, dtype=np.float32)
if grid_ref_arr.ndim != 1 or grid_ref_arr.shape[0] != gh_eff:
# fallback: recalcula para o gh_eff atual
ref = np.nanmedian(
np.where(depth > 0, depth, np.nan).reshape(gh_eff, h, W2),
axis=(1, 2)
)
grid_ref_arr = np.nan_to_num(ref, nan=0.0).astype(np.float32)
self.grid_ref = grid_ref_arr # mantém coerente no objeto
prof_ref = np.repeat(grid_ref_arr.reshape(gh_eff, 1), gw_eff, axis=1) # (gh, gw) mm
delta = prof_median - prof_ref
limiar_mm = float(limiar_prof) * 1000.0
IP = 1.0 - np.minimum(np.abs(delta) / max(limiar_mm, 1e-6), 1.0)
IP = np.nan_to_num(IP, nan=0.0)
ICL = 0.6 * IS + 0.4 * IP
conf_geral = 0.5 * (conf_profundidade + conf_segmentacao)
# opcional: frequências por classe (se realmente precisar)
freq_por_classe = None
if incluir_hist:
# defina os IDs que interessam (evita iterar 0..254)
ids_classes = sorted({rua_id} | set(getattr(self, "ids_classes", [])) or {0,1,2})
ids_classes = [c for c in ids_classes if 0 <= c < 255]
inv_area = 1.0 / (h * w)
freq_por_classe = {c: (seg_b == c).sum(axis=(1,3)) * inv_area for c in ids_classes}
# ---------- monta saída (loop leve só pra dicionários) ----------
grid = [[None for _ in range(gw_eff)] for _ in range(gh_eff)]
for i in range(gh_eff):
for j in range(gw_eff):
freq_classes = {rua_id: float(IS[i, j])}
if incluir_hist:
freq_classes = {int(c): float(freq_por_classe[c][i, j]) for c in freq_por_classe}
grid[i][j] = {
"linha": i, "coluna": j,
"pix_x": w, "pix_y": h,
"segmentacao": freq_classes,
"prof_ref": float(prof_ref[i, j]) / 1000.0,
"prof_median": float(prof_median[i, j]) / 1000.0,
"prof_delta": float(delta[i, j]) / 1000.0,
"indice_seg_chao": float(IS[i, j]),
"indice_prof_delta": float(IP[i, j]),
"indice_caminho_livre": float(ICL[i, j]),
"conf_profundidade": float(conf_profundidade[i, j]),
"conf_segmentacao": float(conf_segmentacao[i, j]),
"indice_confiabilidade": float(conf_geral[i, j]),
}
# (opcional) guardar grid efetivo, útil pra debug
self.grid_ref_shape_eff = (gh_eff, gw_eff)
return {"timestamp": time.time(), "matriz": grid}
except Exception as e:
self.mostrar_log(f"❌ Erro ao gerar grid de confianca: {e}")
return {"timestamp": time.time(), "matriz": [[]]}
def _mostrar_debug_grid_confianca(self, rgb_frame: np.ndarray, grid_conf: list, exibir_debug: bool = False, segmentacao_colorida: np.ndarray = None):
try:
if not exibir_debug:
return
img_debug = rgb_frame.copy()
img_debug = cv2.resize(img_debug, (1280, 720))
# Se tiver segmentação colorida, aplica direto no img_debug (fundo)
if segmentacao_colorida is not None:
if segmentacao_colorida.shape[:2] != img_debug.shape[:2]:
segmentacao_colorida = cv2.resize(segmentacao_colorida, (img_debug.shape[1], img_debug.shape[0]), interpolation=cv2.INTER_NEAREST)
alpha_seg = 0.8
cv2.addWeighted(segmentacao_colorida, alpha_seg, img_debug, 1 - alpha_seg, 0, img_debug)
overlay = img_debug.copy()
altura, largura, _ = img_debug.shape
grid_h = len(grid_conf)
grid_w = len(grid_conf[0])
h_step = altura // grid_h
w_step = largura // grid_w
for i in range(grid_h):
for j in range(grid_w):
celula = grid_conf[i][j]
y0, y1 = i * h_step, (i + 1) * h_step
x0, x1 = j * w_step, (j + 1) * w_step
icl = np.clip(celula.get("indice_caminho_livre", 0.0), 0.0, 1.0)
is_seg = celula.get("indice_seg_chao", -1.0)
ip_prof = celula.get("indice_prof_delta", -1.0)
prf_delta = celula.get("prof_delta", -1.0)
# Cor interpolada entre vermelho e verde
r = int(255 * (1.0 - icl))
g = int(255 * icl)
cor = (0, g, r)
# Retângulo colorido semitransparente
cv2.rectangle(overlay, (x0, y0), (x1, y1), cor, -1)
# Texto vertical
pos_x, pos_y = x0 + 2, y0 + 12
font = cv2.FONT_HERSHEY_SIMPLEX
scale = 0.35
thickness = 1
color_texto = (255, 255, 255)
cv2.putText(overlay, f"ICL:{icl:.2f}", (pos_x, pos_y), font, scale, color_texto, thickness)
cv2.putText(overlay, f"ISeg:{is_seg:.2f}", (pos_x, pos_y + 12), font, scale, color_texto, thickness)
cv2.putText(overlay, f"IPrf:{ip_prof:.2f}", (pos_x, pos_y + 24), font, scale, color_texto, thickness)
cv2.putText(overlay, f"dPrf:{prf_delta:.2f}", (pos_x, pos_y + 34), font, scale, color_texto, thickness)
# Aplica overlay final
alpha = 0.4
cv2.addWeighted(overlay, alpha, img_debug, 1 - alpha, 0, img_debug)
cv2.imshow("Grid de Confianca (Debug)", img_debug)
cv2.waitKey(1)
except Exception as e:
self.mostrar_log(f"❌ Erro ao mostrar debug da grid de confianca: {e}")
def _mostrar_debug_integrado(self, rgb_frame: np.ndarray, grid_conf: list, perfil_solo: list = None, qtd_linhas_solo: int = 0, inclinacao: float = None,
bboxes_anomalias: list = None, perfil_corredor_prof: list = None, perfil_corredor_seg: list = None, fov_d: float = 0, bboxes_radar: list = None, grid_x = 0, grid_z = 0,
segmentacao_colorida: np.ndarray = None, exibir_debug: bool = False):
try:
if not exibir_debug:
return
img_debug = rgb_frame.copy()
img_debug = cv2.resize(img_debug, (1600, 900))
# Aplica segmentacao colorida como overlay
if segmentacao_colorida is not None and True:
if segmentacao_colorida.shape[:2] != img_debug.shape[:2]:
segmentacao_colorida = cv2.resize(segmentacao_colorida, (img_debug.shape[1], img_debug.shape[0]), interpolation=cv2.INTER_NEAREST)
alpha_seg = 0.8
cv2.addWeighted(segmentacao_colorida, alpha_seg, img_debug, 1 - alpha_seg, 0, img_debug)
overlay = img_debug.copy()
altura, largura, _ = img_debug.shape
grid_h = len(grid_conf)
grid_w = len(grid_conf[0])
h_step = altura // grid_h
w_step = largura // grid_w
# 🔹 Desenha perfil do corredor de profunidade
if perfil_corredor_prof and True:
#print(f"Pefil Corredor: {perfil_corredor_prof}")
pts_esquerda = []
pts_direita = []
for p in perfil_corredor_prof:
z = p["distancia_m"]
esq = -p["esquerda_m"]
dir = p["direita_m"]
largura_real = 2 * z * np.tan(fov_d / 2) * 1.0
pixels_por_metro_largura = largura / largura_real
#print(f"Distancia: {z} m, Esq: {esq} m, Dir: {dir} m, Largura Real: {largura_real} m, Px/m: {pixels_por_metro_largura}")
py = self._converter_profundidade_para_y(z, grid_conf, h_step)
if py is None:
py = altura
centro = largura // 2
px_esq = int(centro + (esq * pixels_por_metro_largura))
px_dir = int(centro + (dir * pixels_por_metro_largura))
pts_esquerda.append((px_esq, py))
pts_direita.append((px_dir, py))
#print(f"pts_esq: {pts_esquerda}")
#print(f"pts_dir: {pts_direita}")
# Desenha as linhas do corredor (em azul e laranja)
if len(pts_esquerda) > 1:
cv2.polylines(overlay, [np.array(pts_esquerda, dtype=np.int32)], isClosed=False, color=(0, 0, 0), thickness=2)
if len(pts_direita) > 1:
cv2.polylines(overlay, [np.array(pts_direita, dtype=np.int32)], isClosed=False, color=(0, 0, 0), thickness=2)
# Opcional: desenhar largura do corredor como linhas horizontais
for (px_esq, py), (px_dir, _) in zip(pts_esquerda, pts_direita):
cv2.line(overlay, (px_esq, py), (px_dir, py), (0, 0, 0), 1)
if len(pts_esquerda) > 1 and len(pts_direita) > 1:
pts = np.array(pts_esquerda + pts_direita[::-1], dtype=np.int32)
# Cria uma imagem temporária (preta) do mesmo tamanho que o overlay
overlay_temp = np.zeros_like(overlay)
# Desenha o polígono na imagem temporária
cv2.fillPoly(overlay_temp, [pts], color=(100, 255, 100))
# Define o nível de transparência (0 = invisível, 1 = totalmente opaco)
alpha = 0.4
# Aplica o blend na área onde o polígono foi desenhado
mask = overlay_temp > 0
overlay[mask] = (overlay[mask] * (1 - alpha) + overlay_temp[mask] * alpha).astype(np.uint8)
# 🔹 Desenha perfil do corredor de segmentacao
if perfil_corredor_seg and True:
#print(f"Pefil Corredor: {perfil_corredor_seg}")
pts_esquerda = []
pts_direita = []
for p in perfil_corredor_seg:
z = p["distancia_m"]
esq = -p["esquerda_m"]
dir = p["direita_m"]
#largura_real = p["largura_m"]
largura_real = 2 * z * np.tan(fov_d / 2) * 1.0
pixels_por_metro_largura = largura / largura_real
#print(f"Distancia: {z} m, Esq: {esq} m, Dir: {dir} m, Largura Real: {largura_real} m, Px/m: {pixels_por_metro_largura}")
py = self._converter_profundidade_para_y(z, grid_conf, h_step)
if py is None:
py = altura
centro = largura // 2
px_esq = int(centro + (esq * pixels_por_metro_largura))
px_dir = int(centro + (dir * pixels_por_metro_largura))
pts_esquerda.append((px_esq, py))
pts_direita.append((px_dir, py))
#print(f"pts_esq: {pts_esquerda}")
#print(f"pts_dir: {pts_direita}")
# Desenha as linhas do corredor (em azul e laranja)
if len(pts_esquerda) > 1:
cv2.polylines(overlay, [np.array(pts_esquerda, dtype=np.int32)], isClosed=False, color=(255, 0, 0), thickness=2)
if len(pts_direita) > 1:
cv2.polylines(overlay, [np.array(pts_direita, dtype=np.int32)], isClosed=False, color=(255, 0, 0), thickness=2)
# Opcional: desenhar largura do corredor como linhas horizontais
for (px_esq, py), (px_dir, _) in zip(pts_esquerda, pts_direita):
cv2.line(overlay, (px_esq, py), (px_dir, py), (0, 0, 0), 1)
if len(pts_esquerda) > 1 and len(pts_direita) > 1:
pts = np.array(pts_esquerda + pts_direita[::-1], dtype=np.int32)
# Cria uma imagem temporária (preta) do mesmo tamanho que o overlay
overlay_temp = np.zeros_like(overlay)
# Desenha o polígono na imagem temporária
cv2.fillPoly(overlay_temp, [pts], color=(100, 100, 255))
# Define o nível de transparência (0 = invisível, 1 = totalmente opaco)
alpha = 0.4
# Aplica o blend na área onde o polígono foi desenhado
mask = overlay_temp > 0
overlay[mask] = (overlay[mask] * (1 - alpha) + overlay_temp[mask] * alpha).astype(np.uint8)
# 🔹 Desenha valores da matriz de confianca
if True:
for i in range(grid_h):
for j in range(grid_w):
celula = grid_conf[i][j]
y0, y1 = i * h_step, (i + 1) * h_step
x0, x1 = j * w_step, (j + 1) * w_step
icl = np.clip(celula.get("indice_caminho_livre", 0.0), 0.0, 1.0)
is_seg = celula.get("indice_seg_chao", -1.0)
ip_prof = celula.get("indice_prof_delta", -1.0)
r = int(255 * (1.0 - icl))
g = int(255 * icl)
cor = (0, g, r)
cv2.rectangle(overlay, (x0, y0), (x1, y1), cor, -1)
pos_x, pos_y = x0 + 2, y0 + 12
font = cv2.FONT_HERSHEY_SIMPLEX
scale = 0.35
thickness = 1
color_texto = (255, 255, 255)
cv2.putText(overlay, f"ICL:{icl:.2f}", (pos_x, pos_y), font, scale, color_texto, thickness)
cv2.putText(overlay, f"ISeg:{is_seg:.2f}", (pos_x, pos_y + 12), font, scale, color_texto, thickness)
cv2.putText(overlay, f"IPrf:{ip_prof:.2f}", (pos_x, pos_y + 24), font, scale, color_texto, thickness)
# 🔹 Perfil do solo desenhado como linha
if perfil_solo and True:
for j in range(1, grid_w):
z1 = perfil_solo[j - 1]
z2 = perfil_solo[j]
if z1 <= 0 or z2 <= 0:
continue
y1 = self._converter_profundidade_para_y(z1, grid_conf, h_step) or 0
y2 = self._converter_profundidade_para_y(z2, grid_conf, h_step) or 0
x1 = (j - 1) * w_step
x2 = j * w_step
cv2.line(overlay, (x1, y1), (x2, y2), (0, 0, 0), 2) # linha preta do perfil
# Linha de referência da média do solo
media_z = np.nanmean(perfil_solo)
y_ref = self._converter_profundidade_para_y(media_z, grid_conf, h_step) or 0
cv2.line(overlay, (0, y_ref), (largura, y_ref), (100, 100, 100), 1)
cv2.putText(overlay, f"Media: {media_z:.2f}m", (10, y_ref - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (100, 100, 100), 1)
# Linha superior da faixa de análise (por qtd de linhas da grid_conf)
altura_grid = len(grid_conf)
faixa_inicio = altura_grid - qtd_linhas_solo # índice da linha mais alta usada
y_faixa_top = faixa_inicio * h_step
cv2.line(overlay, (0, y_faixa_top), (largura, y_faixa_top), (0, 0, 255), 1)
cv2.putText(overlay, f"Topo faixa solo", (10, y_faixa_top - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 0, 255), 1)
# 🔹 Desenha bboxes de anomalias
if bboxes_anomalias and True:
for b in bboxes_anomalias:
x0 = int(b["bbox"][0] * largura)
y0 = int(b["bbox"][1] * altura)
x1 = int(b["bbox"][2] * largura)
y1 = int(b["bbox"][3] * altura)
cv2.rectangle(overlay, (x0, y0), (x1, y1), (0, 0, 0), 2)
cv2.putText(overlay, f"{b['tipo']}, D: {b['distancia_m']} m, LxA: {b['largura_m']}x{b['altura_m']} m", (x0 + 2, y0 + 16), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 2)
# 🔹 Desenha bboxes do radar
if bboxes_radar and True:
escala_x = largura / grid_x
escala_y = altura / grid_z
zoom_x = 1.5 # aumenta 50%
x_centro = 0.5
for b in bboxes_radar:
x0_pct, y0_pct, x1_pct, y1_pct = b["bbox"]
# Convertendo percentuais em pixels diretamente
x0 = int((x0_pct - x_centro) * zoom_x * largura + x_centro * largura)
x1 = int((x1_pct - x_centro) * zoom_x * largura + x_centro * largura)
y0 = int(y0_pct * altura)
y1 = int(y1_pct * altura)
cv2.rectangle(overlay, (x0, y0), (x1, y1), (255, 0, 255), 2)
cv2.putText(
overlay,
f"{b['tipo']}, D: {b['distancia_m']}m, LxA: {b['largura_m']}x{b['altura_m']} m",
(x0 + 2, y0 + 12),
cv2.FONT_HERSHEY_SIMPLEX,
0.4,
(255, 0, 255),
1
)
alpha = 0.4
cv2.addWeighted(overlay, alpha, img_debug, 1 - alpha, 0, img_debug)
# 🔹 Texto da inclinacao (se houver)
if inclinacao is not None:
cv2.putText(img_debug, f"Inclinacao: {inclinacao:.1f} graus", (10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 2)
cv2.imshow("Debug - Grid Integrado", img_debug)
cv2.waitKey(1)
except Exception as e:
self.mostrar_log(f"❌ Erro ao mostrar grid de debug integrado: {e}")
def _converter_profundidade_para_y(self, prof_desejada, grid_conf, h_step):
try:
altura_grid = len(grid_conf)
# Constrói uma lista com a profundidade média por linha (coluna central, por exemplo)
col_central = len(grid_conf[0]) // 2
profundidades_por_linha = [
grid_conf[i][col_central]["prof_ref"] for i in range(altura_grid)
]
for i in range(1, altura_grid):
z_base = profundidades_por_linha[i]
z_topo = profundidades_por_linha[i - 1]
if z_topo is None or z_base is None:
continue
# Verifica se prof_desejada está entre essas duas linhas
if min(z_base, z_topo) <= prof_desejada <= max(z_base, z_topo):
y_base = (i + 1) * h_step
y_topo = i * h_step
# Interpola entre topo e base
t = (prof_desejada - z_base) / (z_topo - z_base + 1e-6)
y_real = int(y_base + t * (y_topo - y_base))
return y_real
return None # Fora do intervalo
except Exception as e:
self.mostrar_log(f"❌ Erro ao converter profundidade para y: {e}")
return None
def _encontrar_item_perfil_corredor(self, perfil_corredor, profundidade_alvo):
try:
if not perfil_corredor:
return None
mais_proximo = min(perfil_corredor, key=lambda item: abs(item["distancia_m"] - profundidade_alvo))
return mais_proximo
except Exception as e:
self.mostrar_log(f"❌ Erro ao encontrar item no perfil do corredor: {e}")
return None
def _gerar_matriz_custo_fundida(self, matriz_conf, largura_robo_m, fov_h): def _gerar_matriz_custo_fundida(self, matriz_conf, largura_robo_m, fov_h):
# Valores podem variar de -1.5 ~ 6.0 # Valores podem variar de -1.5 ~ 6.0
@ -1643,215 +1175,7 @@ class CameraManager:
key = cv2.waitKey(wait) & 0xFF key = cv2.waitKey(wait) & 0xFF
return key, vis return key, vis
def _snap_to_array_u8(self, snap_value, H, W, name="array"):
try:
a = np.asarray(snap_value, dtype=np.uint8)
if a.ndim == 1 and a.size == H*W:
return a.reshape(H, W)
if a.ndim == 2 and a.shape == (H, W):
return a
a = np.array(snap_value, dtype=np.uint8)
if a.shape == (H, W):
return a
except Exception as e:
self.mostrar_log(f"[VW-SNAP] falha convertendo {name}: {e}")
self.mostrar_log(f"[VW-SNAP] shape invalido para {name}: esperado {(H,W)}, veio {np.shape(snap_value)}")
return None
# NEW: pega primeiro campo existente e aplica escala (útil p/ z em m vs mm)
def _snap_pick_numeric(self, snap: dict, names: list[str], H: int, W: int, *, scale: float = 1.0, as_float=True):
import numpy as np, cv2
for nm in names:
if nm in snap and snap[nm] is not None:
try:
arr = np.asarray(snap[nm], dtype=np.float32 if as_float else np.int32)
if arr.ndim == 1 and arr.size == H*W:
arr = arr.reshape(H, W)
elif arr.ndim != 2 or arr.shape != (H, W):
# tenta redimensionar mantendo nearest neighbor (quando fizer sentido)
arr = cv2.resize(arr.astype(np.float32), (W, H), interpolation=cv2.INTER_NEAREST)
if scale != 1.0:
arr = arr * scale
return arr
except Exception as e:
self.mostrar_log(f"[VW-SNAP] erro lendo {nm}: {e}")
return None
# UPDATED: agora lê conf/anom/z/pcts do SNAP e escreve no modo 'full'
def make_visualworker_overlay_from_snap(
self,
frame_bgr,
snap: dict,
*,
alpha_cost=0.45,
draw_grid=True,
draw_cells=True,
text_mode="mini",
draw_legend=True,
nav_overlay_alpha=0.35,
align_near_to_bottom=True
):
vis_fallback = frame_bgr.copy() if frame_bgr is not None else None
try:
Hf, Wf = frame_bgr.shape[:2]
Wc = int(snap.get("grid_w", 0))
Hc = int(snap.get("grid_h", 0))
if Hc <= 0 or Wc <= 0:
self.mostrar_log("[VW-SNAP] grid_w/grid_h ausentes no snap")
return vis_fallback
custo_u8 = self._snap_to_array_u8(snap.get("custo_u8"), Hc, Wc, "custo_u8")
if custo_u8 is None:
return vis_fallback
# NAV: 1=navegavel, 0=bloqueado
nav = None
if snap.get("nav_mask") is not None:
nav = self._snap_to_array_u8(snap.get("nav_mask"), Hc, Wc, "nav_mask")
if nav is not None:
nav = nav.astype(bool)
# orientacao
near_is_bottom = bool(snap.get("fuse", {}).get("near_is_bottom", True))
if align_near_to_bottom and not near_is_bottom:
custo_u8 = np.flipud(custo_u8)
if nav is not None:
nav = np.flipud(nav)
# 1) heatmap (custo_u8 já está 0..255)
up = cv2.resize(custo_u8, (Wf, Hf), interpolation=cv2.INTER_NEAREST)
try:
cmap = cv2.COLORMAP_TURBO
except AttributeError:
cmap = cv2.COLORMAP_JET
heat = cv2.applyColorMap(up, cmap)
out = cv2.addWeighted(heat, alpha_cost, frame_bgr, 1.0 - alpha_cost, 0)
# 2) NAV overlay
if nav is not None:
bad = (~nav).astype(np.uint8) * 255
bad_up = cv2.resize(bad, (Wf, Hf), interpolation=cv2.INTER_NEAREST)
mask = bad_up.astype(bool)
overlay = out.copy()
overlay[mask] = (0, 0, 255)
out = cv2.addWeighted(overlay, nav_overlay_alpha, out, 1.0 - nav_overlay_alpha, 0)
# 3) grade
x_edges = np.linspace(0, Wf, Wc + 1).astype(int)
y_edges = np.linspace(0, Hf, Hc + 1).astype(int)
if draw_grid:
for x in x_edges: cv2.line(out, (x, 0), (x, Hf - 1), (60,60,60), 1, cv2.LINE_AA)
for y in y_edges: cv2.line(out, (0, y), (Wf - 1, y), (60,60,60), 1, cv2.LINE_AA)
# --- NEW: ler campos extras do SNAP ---
# conf/anom em u8 → [0..1]
conf_u8 = snap.get("conf_u8", None)
conf = self._snap_to_array_u8(conf_u8, Hc, Wc, "conf_u8")/255.0 if conf_u8 is not None else None
if conf is not None and align_near_to_bottom and not near_is_bottom: conf = np.flipud(conf)
anom_u8 = snap.get("anom_u8", None)
anom = self._snap_to_array_u8(anom_u8, Hc, Wc, "anom_u8")/255.0 if anom_u8 is not None else None
if anom is not None and align_near_to_bottom and not near_is_bottom: anom = np.flipud(anom)
# profundidade (se existir no snap): aceita *_m ou *_mm
z_med = self._snap_pick_numeric(snap, ["z_med_m", "z_med_mm"], Hc, Wc, scale=1.0) # se mm, troque scale=0.001
z_ref = self._snap_pick_numeric(snap, ["z_ref_m", "z_ref_mm"], Hc, Wc, scale=1.0)
if z_med is not None and align_near_to_bottom and not near_is_bottom: z_med = np.flipud(z_med)
if z_ref is not None and align_near_to_bottom and not near_is_bottom: z_ref = np.flipud(z_ref)
# classes (se existirem em u8 → 0..1)
pct_rua = pct_cana = pct_obs = None
if snap.get("pct_rua_u8") is not None:
pct_rua = self._snap_to_array_u8(snap["pct_rua_u8"], Hc, Wc, "pct_rua_u8")/255.0
if align_near_to_bottom and not near_is_bottom: pct_rua = np.flipud(pct_rua)
if snap.get("pct_cana_u8") is not None:
pct_cana = self._snap_to_array_u8(snap["pct_cana_u8"], Hc, Wc, "pct_cana_u8")/255.0
if align_near_to_bottom and not near_is_bottom: pct_cana = np.flipud(pct_cana)
if snap.get("pct_obs_u8") is not None:
pct_obs = self._snap_to_array_u8(snap["pct_obs_u8"], Hc, Wc, "pct_obs_u8")/255.0
if align_near_to_bottom and not near_is_bottom: pct_obs = np.flipud(pct_obs)
# 4) textos por célula
if draw_cells and text_mode != "off":
for j in range(Hc):
y0, y1 = y_edges[j], y_edges[j + 1]
cy = (y0 + y1) // 2
for i in range(Wc):
x0, x1 = x_edges[i], x_edges[i + 1]
cx = (x0 + x1) // 2
if nav is not None:
color = (60,200,60) if nav[j,i] else (20,20,220)
else:
color = (90,90,90)
cv2.rectangle(out, (x0, y0), (x1-1, y1-1), color, 1, cv2.LINE_AA)
if text_mode == "mini":
txt = f"C{int(custo_u8[j,i])}"
if nav is not None: txt += (" N" if nav[j,i] else " B")
self._put_text_centered(out, txt, cx, cy, font_scale=0.32, thickness=1, color=(255,255,255), outline=True)
elif text_mode == "full":
# linha 1: custo + conf + anom
l1 = f"C{int(custo_u8[j,i])}"
if conf is not None: l1 += f" cf{conf[j,i]:.2f}"
if anom is not None: l1 += f" A{anom[j,i]*100:.0f}"
self._put_text_centered(out, l1, cx, cy-10, font_scale=0.38, thickness=1, color=(255,255,255), outline=True)
# linha 2: profundidade (se houver)
l2 = ""
if z_med is not None and z_ref is not None and not np.isnan(z_med[j,i]):
l2 = f"Z{z_med[j,i]:.2f}/{z_ref[j,i]:.2f}m"
elif z_ref is not None:
l2 = f"Zref {z_ref[j,i]:.2f}m"
if l2:
self._put_text_centered(out, l2, cx, cy+4, font_scale=0.36, thickness=1, color=(255,255,255), outline=True)
# linha 3: percentuais de classe (se houver)
if pct_rua is not None and pct_cana is not None and pct_obs is not None:
l3 = f"R{int(pct_rua[j,i]*100)} C{int(pct_cana[j,i]*100)} O{int(pct_obs[j,i]*100)}"
self._put_text_centered(out, l3, cx, cy+18, font_scale=0.34, thickness=1, color=(230,230,230), outline=True)
# distâncias por linha (row_dist_m) — já estão “no referencial do snap”
row_dist = snap.get("row_dist_m", None)
if row_dist is not None and len(row_dist) == Hc:
dist_draw = row_dist[::-1] if (align_near_to_bottom and not near_is_bottom) else row_dist
for j in range(Hc):
y = (y_edges[j] + y_edges[j + 1]) // 2
s = f"{float(dist_draw[j]):.2f}m"
cv2.putText(out, s, (5, y + 10), cv2.FONT_HERSHEY_SIMPLEX, 0.42, (0,0,0), 2, cv2.LINE_AA)
cv2.putText(out, s, (5, y + 10), cv2.FONT_HERSHEY_SIMPLEX, 0.42, (255,255,255), 1, cv2.LINE_AA)
# corredor central (se existir)
cc = snap.get("fuse", {}).get("central_cols", None)
if cc and len(cc) == 2:
i0, i1 = int(cc[0]), int(cc[1])
if 0 <= i0 <= Wc and 0 <= i1 <= Wc:
cv2.line(out, (x_edges[i0], 0), (x_edges[i0], Hf-1), (255,255,0), 2, cv2.LINE_AA)
cv2.line(out, (x_edges[i1], 0), (x_edges[i1], Hf-1), (255,255,0), 2, cv2.LINE_AA)
if draw_legend:
pad = 8
x0, y0 = pad, pad
cv2.rectangle(out, (x0-4, y0-4), (x0+220, y0+104), (0,0,0), -1)
cv2.putText(out, "VW SNAP overlay", (x0, y0+14), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255,255,255), 1, cv2.LINE_AA)
cv2.putText(out, "C: 0..255 (fused)", (x0, y0+32), cv2.FONT_HERSHEY_SIMPLEX, 0.42, (200,200,200), 1, cv2.LINE_AA)
cv2.putText(out, "cf: conf (0..1), A: %", (x0, y0+50), cv2.FONT_HERSHEY_SIMPLEX, 0.42, (200,200,200), 1, cv2.LINE_AA)
cv2.putText(out, "Z: med/ref (m) se houver", (x0, y0+68), cv2.FONT_HERSHEY_SIMPLEX, 0.42, (200,200,200), 1, cv2.LINE_AA)
cv2.putText(out, "N nav / B bloqueado", (x0, y0+86), cv2.FONT_HERSHEY_SIMPLEX, 0.42, (200,200,200), 1, cv2.LINE_AA)
cv2.putText(out, f"{Wc}x{Hc}", (x0, y0+102), cv2.FONT_HERSHEY_SIMPLEX, 0.42, (160,160,160), 1, cv2.LINE_AA)
return out
except Exception as e:
self.mostrar_log(f"[VW-SNAP] overlay error: {e}")
return vis_fallback
# === cole seu _compute_blockage_metrics aqui (sem mudanças) ===
def _colorize_masks(self, anom_f, custo_f, conf_f, thr_anom_block=0.50, thr_cost_block=0.65, thr_conf_low=0.35): def _colorize_masks(self, anom_f, custo_f, conf_f, thr_anom_block=0.50, thr_cost_block=0.65, thr_conf_low=0.35):
"""Overlay BGR com soma segura (clamped) nas regiões de máscara.""" """Overlay BGR com soma segura (clamped) nas regiões de máscara."""
H, W = anom_f.shape H, W = anom_f.shape
@ -1915,10 +1239,12 @@ class CameraManager:
blocked = bool(metrics.get("blocked", False)) blocked = bool(metrics.get("blocked", False))
reason = metrics.get("reason", "none") or "none" reason = metrics.get("reason", "none") or "none"
d_obs_min = metrics.get("d_obs_min", None) reason_detail = metrics.get("reason_detail", "none") or "none"
d_obs_min = metrics.get("d_obs_true_min_m", None)
j_block = metrics.get("j_block", None) j_block = metrics.get("j_block", None)
cov_cent = metrics.get("coverage", {}).get("central_max", 0.0) cov_cent = metrics.get("coverage", {}).get("central_max", 0.0)
cov_glob = metrics.get("coverage", {}).get("global", 0.0) cov_glob = metrics.get("coverage", {}).get("global", 0.0)
decision = metrics.get("decision", {})
sb = metrics.get("side_bias", {}) or {} sb = metrics.get("side_bias", {}) or {}
side_val = float(sb.get("value", 0.0)) side_val = float(sb.get("value", 0.0))
left_frac = float(sb.get("left_frac", 0.0)) left_frac = float(sb.get("left_frac", 0.0))
@ -2029,21 +1355,15 @@ class CameraManager:
put_text_outlined(vis, f"L:{left_frac:.2f} R:{right_frac:.2f}", (x0, y_bar + 20), put_text_outlined(vis, f"L:{left_frac:.2f} R:{right_frac:.2f}", (x0, y_bar + 20),
font, font_scale, (255,255,255), thick) font, font_scale, (255,255,255), thick)
# --- decisão PARAR / LIVRE ---
v = float(max(0.0, velocidade_media))
dist_freio = max(0.30, (v*v) / max(1e-9, 2.0 * a_max_freio))
dist_necessaria = dist_freio + margem_parada
tem_obs_perto = (d_obs_min is not None) and np.isfinite(d_obs_min) and (d_obs_min <= dist_necessaria)
deve_parar = bool(blocked) or tem_obs_perto
# --- DECISION HUD: PARAR / LIVRE --- # --- DECISION HUD: PARAR / LIVRE ---
# empurra um pouco pra baixo do L/R # empurra um pouco pra baixo do L/R
y0_dec = y_bar + 50 y0_dec = y_bar + 50
dec_txt = "PARAR" if deve_parar else "LIVRE" parada_necessaria = decision.get("parar", False)
dec_col = (0, 0, 255) if deve_parar else (0, 200, 0) dec_txt = "PARAR" if parada_necessaria else "LIVRE"
dec_info = (f"v={v:.2f} m/s d_obs={('-' if d_obs_min is None else f'{d_obs_min:.2f} m')} " dec_col = (0, 0, 255) if parada_necessaria else (0, 200, 0)
f"d_necess={dist_necessaria:.2f} m") dec_info = (f"v={velocidade_media:.3f} m/s d_obs={('-' if d_obs_min is None else f'{d_obs_min:.2f} m')} "
f"d_necess={decision.get('dist_necessaria', 0.0):.2f} m")
# painel por trás para legibilidade # painel por trás para legibilidade
panel_w2 = max(280, bar_w + 100) panel_w2 = max(280, bar_w + 100)
@ -2052,12 +1372,13 @@ class CameraManager:
cv2.addWeighted(overlay2, 0.35, vis, 0.65, 0, vis) cv2.addWeighted(overlay2, 0.35, vis, 0.65, 0, vis)
# linha 1: PARAR / LIVRE (grande) # linha 1: PARAR / LIVRE (grande)
put_text_outlined(vis, f"{dec_txt}", (x0, y0_dec-4), put_text_outlined(vis, f"{dec_txt}", (x0, y0_dec-4), font, 0.80, dec_col, thick)
font, 0.80, dec_col, thick)
# linha 2: detalhes (menor) # linha 2: detalhes (menor)
put_text_outlined(vis, dec_info, (x0, y0_dec+18), put_text_outlined(vis, dec_info, (x0, y0_dec+18), font, 0.58, (255,255,255), thick)
font, 0.58, (255,255,255), thick)
# linha 3: detalhes (menor)
put_text_outlined(vis, reason_detail, (x0, y0_dec+50), font, 0.58, (255,255,255), thick)
legend = [ legend = [
@ -2073,8 +1394,7 @@ class CameraManager:
status_txt = f"BLOCKED: {blocked} ({reason})" status_txt = f"BLOCKED: {blocked} ({reason})"
status_col = (0,0,255) if blocked else (0,255,0) status_col = (0,0,255) if blocked else (0,255,0)
cv2.putText(vis, status_txt, (Wf - 10 - 8*len(status_txt), 24), cv2.putText(vis, status_txt, (Wf - 40 - 8*len(status_txt), 24), cv2.FONT_HERSHEY_SIMPLEX, 0.6, status_col, 2, cv2.LINE_AA)
cv2.FONT_HERSHEY_SIMPLEX, 0.6, status_col, 2, cv2.LINE_AA)
cv2.imshow(win_name, vis) cv2.imshow(win_name, vis)
cv2.waitKey(1) cv2.waitKey(1)

View File

@ -39,7 +39,8 @@ class CostmapFuser:
self.buf_conf = [] self.buf_conf = []
self.buf_anom = [] self.buf_anom = []
self.buf_nav = [] self.buf_nav = []
self.buf_zref = [] # opcional self.buf_zref = []
self.buf_zmed = []
self.buf_ts = [] self.buf_ts = []
self._blk_state = { self._blk_state = {
@ -91,24 +92,268 @@ class CostmapFuser:
return lin[::-1] return lin[::-1]
return lin return lin
def _compute_d_obs_min(self, custo_fused, nav_fused, z_ref_2d=None): def _compute_blockage_metrics(
"""Menor distância livre (m) na janela central; retorna None se livre.""" self,
i0, i1 = self.central_cols custo_f, anom_f, conf_f, nav_f, zmed_f,
blocked = (custo_fused[:, i0:i1 + 1] > self.block_thr) row_dist_m, row_scale_x_m, central_cols,
if nav_fused is not None: robot_width_m=0.84, margin_m=0.12,
blocked |= (nav_fused[:, i0:i1 + 1] == 0) thr_anom_block=0.50, thr_cost_block=0.65, thr_conf_low=0.35,
rho_block_central=0.70, rho_block_global=0.60,
near_is_bottom=True,
# ------ NOVOS (opcionais) ------
use_persistence=False,
velocidade_mps=None, # m/s; se None, decisão não usa distância de frenagem
a_max_freio=0.8, # m/s²
margem_parada=0.25, # m
N_on=2, # frames p/ entrar
N_off=5, # frames p/ sair
blackout_imediato=True
):
H, W = custo_f.shape
c0, c1 = central_cols
c0 = max(0, min(W-1, int(c0)))
c1 = max(0, min(W, int(c1)))
if c1 <= c0:
c0, c1 = W//3, 2*W//3 # fallback
if not blocked.any(): # 1) Máscaras inseguras
return None mask_anom = (anom_f >= thr_anom_block)
mask_cost = (custo_f >= thr_cost_block)
mask_conf = (conf_f < thr_conf_low)
unsafe = mask_anom | mask_cost | mask_conf
row_dists = self._row_distances(z_ref_2d) # 2) Largura em colunas por linha
d_min = None width_need_m = robot_width_m + margin_m
for j in self._rows_near_to_far(): cols_need = np.empty(H, dtype=int)
if blocked[j].any(): for j in range(H):
d = float(row_dists[j]) sx = row_scale_x_m[j] if row_scale_x_m is not None else (width_need_m / max(1, (c1 - c0)))
d_min = d if (d_min is None or d < d_min) else d_min if (sx is None) or (sx <= 1e-6):
return d_min cols_need[j] = (c1 - c0)
else:
ncols = int(np.ceil(width_need_m / sx))
cols_need[j] = max(1, min(W, ncols))
def central_window(j, ncols):
mid = (c0 + c1) // 2
half = ncols // 2
a = max(0, mid - half)
b = min(W, a + ncols)
a = max(0, b - ncols)
return a, b
# 3) Varredura central (cobertura e "existe obstáculo?")
coverage_central = np.zeros(H, np.float32)
exists_unsafe_central = np.zeros(H, np.bool_) # <- NOVO: existe ao menos 1 px inseguro na janela
j_block = None
it = (range(H-1, -1, -1) if near_is_bottom else range(H))
for j in it:
a, b = central_window(j, cols_need[j])
unsafe_row = unsafe[j, a:b]
coverage = unsafe_row.mean() if (b > a) else 1.0
coverage_central[j] = coverage
exists_unsafe_central[j] = bool(unsafe_row.any()) if (b > a) else True
if (j_block is None) and (coverage >= rho_block_central):
j_block = j
# 4) Cobertura global
global_cov = unsafe.mean()
# --- 4.5) Sanitiza zmed_f ---
# copia para não mexer no buffer original
zmed = np.array(zmed_f, dtype=np.float32, copy=True)
# trata inválidos: <=0, nan, inf -> nan
invalid = (~np.isfinite(zmed)) | (zmed <= 0.0)
zmed[invalid] = np.nan
# --- 5) Distâncias usando Z real (zmed_f) ---
d_block_line_m = None if j_block is None else float(row_dist_m[j_block]) # fallback geométrico antigo
d_block_line_m_z = None # NOVO: Z real na linha-bloqueio
# primeira linha (mais próxima) onde há QUALQUER insegurança central (com Z real)
j_obs_true_min = None
d_obs_true_min_m = None # fallback geométrico
d_obs_true_min_m_z = None # NOVO: Z real
# varre do perto -> longe conforme 'near_is_bottom'
it2 = (range(H-1, -1, -1) if near_is_bottom else range(H))
for j in it2:
# janela central nessa linha
a, b = central_window(j, cols_need[j])
if b <= a:
continue
# máscara de insegurança na janela
bad = unsafe[j, a:b]
if np.any(bad):
z_slice = zmed[j, a:b]
z_bad = z_slice[bad]
if z_bad.size:
z_bad_f = z_bad[np.isfinite(z_bad)]
if z_bad_f.size:
z_min = float(np.min(z_bad_f)) # ok, só finitos
if j_obs_true_min is None:
j_obs_true_min = j
d_obs_true_min_m_z = z_min
d_obs_true_min_m = float(row_dist_m[j])
# se esta é a linha-bloqueio (cobertura >= rho), também calcule o Z dessa linha
if (j_block is not None) and (j == j_block):
if np.any(unsafe[j, a:b]):
z_block = zmed[j, a:b][unsafe[j, a:b]]
if z_block.size > 0:
z_bmin = np.nanmin(z_block)
if np.isfinite(z_bmin):
d_block_line_m_z = float(z_bmin)
# 6) Viés lateral
left = unsafe[:, c0:(c0+c1)//2].mean() if (c1-c0) >= 2 else 0.0
right = unsafe[:, (c0+c1)//2:c1].mean() if (c1-c0) >= 2 else 0.0
side_bias_val = float(np.clip((right - left) / max(1e-6, (right + left)), -1.0, 1.0))
# 7) Decisão "raw" (sem persistência) + detalhes
def _fmt_m(x):
return "-" if (x is None or not np.isfinite(x)) else f"{float(x):.2f} m"
conf_mean = float(conf_f.mean())
central_cov_max = float(coverage_central.max())
blocked_raw = False
reason = "none"
reason_detail = "Caminho livre."
if j_block is not None:
# houve uma linha com cobertura central >= rho_block_central
blocked_raw = True
reason = "obstacle"
# qual distância vamos considerar como "mais conservadora"
# (pixel inseguro mais perto vs linha que bloqueia)
def _min_non_none(a, b):
if a is None: return b
if b is None: return a
return a if a <= b else b
d_used = _min_non_none(d_obs_true_min_m_z, d_block_line_m_z)
reason_detail = (
f"Janela central bloqueada (p>={rho_block_central:.2f}). "
f"d*={_fmt_m(d_used)} [linha={_fmt_m(d_block_line_m_z)}; pixel={_fmt_m(d_obs_true_min_m_z)}]; "
f"cobertura_central_max={central_cov_max:.2f}."
)
elif (global_cov >= rho_block_global) and (conf_mean < 0.45):
blocked_raw = True
reason = "blackout"
reason_detail = (
f"Percepcao degradada: cobertura_global={global_cov:.2f}{rho_block_global:.2f} "
f"e confianca_media={conf_mean:.2f}<0.45."
)
elif (central_cov_max > 0.45) and (left > 0.7 or right > 0.7):
# corredor 'estreito': laterais muito ruins, mesmo sem bloquear de fato a janela central
reason = "narrow"
lado = "direita" if right > left else "esquerda"
lado_frac = max(left, right)
reason_detail = (
f"Corredor estreito: lateral {lado} muito fechada (frac={lado_frac:.2f}), "
f"central_max={central_cov_max:.2f}."
)
# 8) Persistência + decisão de parada
decision = {
"parar": False,
"dist_necessaria": None,
"v_max_sugerida_mps": None,
"frames_on": 0,
"frames_off": 0,
"N_on": N_on,
"N_off": N_off
}
blocked_out = blocked_raw
if use_persistence:
v = float(max(0.0, velocidade_mps or 0.0))
dist_freio = max(0.30, (v*v) / max(1e-9, 2.0 * a_max_freio))
dist_necessaria = dist_freio + margem_parada
decision["dist_necessaria"] = float(dist_necessaria)
# usamos o mais conservador entre a linha-bloqueio e o "qualquer-unsafe"
def _min_non_none(a, b):
if (a is None) and (b is None): return None
if a is None: return b
if b is None: return a
return a if a <= b else b
d_for_stop = _min_non_none(d_block_line_m_z, d_obs_true_min_m_z)
stop_now = False
v_max_sug = None
if blocked_raw and reason == "blackout":
stop_now = True if blackout_imediato else False
if blocked_raw and reason == "obstacle":
if (d_for_stop is not None) and np.isfinite(d_for_stop) and (d_for_stop <= dist_necessaria):
stop_now = True
else:
# ainda não precisa parar: sugere v_max segura se tivermos alguma medida de distância
d_ref = _min_non_none(d_block_line_m_z, d_obs_true_min_m_z)
if (d_ref is not None) and np.isfinite(d_ref) and (d_ref > margem_parada):
v_max_sug = float(np.sqrt(max(0.0, 2.0 * a_max_freio * (d_ref - margem_parada))))
# histerese
st = self._blk_state
if stop_now:
st["on"] = min(N_on, st["on"] + 1)
st["off"] = 0
else:
st["off"] = min(N_off, st["off"] + 1)
st["on"] = 0
if (not st["latched"]) and (st["on"] >= N_on):
st["latched"] = True
elif st["latched"] and (st["off"] >= N_off):
st["latched"] = False
decision.update({
"parar": bool(st["latched"]),
"v_max_sugerida_mps": v_max_sug,
"frames_on": int(st["on"]),
"frames_off": int(st["off"])
})
blocked_out = bool(st["latched"])
st["reason"] = reason
# guarda também as duas distâncias pra debug
st["d_block_line_m"] = (None if d_block_line_m_z is None else float(d_block_line_m_z))
st["d_obs_true_min_m"] = (None if d_obs_true_min_m_z is None else float(d_obs_true_min_m_z))
st["last_decision"] = "PARAR" if st["latched"] else "LIVRE"
return {
# mantém o nome antigo, mas esclarece nos campos abaixo:
"d_block_line_m": d_block_line_m_z, # distância da LINHA que bloqueia (cobertura ≥ ρ)
"d_obs_true_min_m": d_obs_true_min_m_z, # menor distância com QUALQUER insegurança central
"blocked": bool(blocked_out), # com persistência se habilitada
"blocked_raw": bool(blocked_raw),
"reason": reason,
"reason_detail": reason_detail,
"coverage": {
"central_max": float(coverage_central.max()),
"global": float(global_cov),
},
"side_bias": {
"value": side_bias_val,
"left_frac": float(left),
"right_frac": float(right),
},
"j_block": (None if j_block is None else int(j_block)),
"j_obs_true_min": (None if j_obs_true_min is None else int(j_obs_true_min)),
"decision": decision
}
def _row_scale_x(self, row_dist_m): def _row_scale_x(self, row_dist_m):
"""metros por célula em X para cada linha, dado FOV_H.""" """metros por célula em X para cada linha, dado FOV_H."""
if self.fov_h_rad is None: if self.fov_h_rad is None:
@ -133,6 +378,9 @@ class CostmapFuser:
zref = grid_dict.get("z_ref", None) zref = grid_dict.get("z_ref", None)
if zref is not None: if zref is not None:
zref = zref.astype(np.float32) zref = zref.astype(np.float32)
zmed = grid_dict.get("z_med", None)
if zmed is not None:
zmed = zmed.astype(np.float32)
# valida shape (H,W) = (grid_h,grid_w) # valida shape (H,W) = (grid_h,grid_w)
H, W = custo.shape H, W = custo.shape
@ -145,11 +393,12 @@ class CostmapFuser:
self.buf_nav.append(nav) self.buf_nav.append(nav)
self.buf_ts.append(ts) self.buf_ts.append(ts)
self.buf_zref.append(zref) self.buf_zref.append(zref)
self.buf_zmed.append(zmed)
# mantém no máximo K # mantém no máximo K
if len(self.buf_custo) > self.K: if len(self.buf_custo) > self.K:
self.buf_custo.pop(0); self.buf_conf.pop(0); self.buf_anom.pop(0) self.buf_custo.pop(0); self.buf_conf.pop(0); self.buf_anom.pop(0)
self.buf_nav.pop(0); self.buf_ts.pop(0); self.buf_zref.pop(0) self.buf_nav.pop(0); self.buf_ts.pop(0); self.buf_zref.pop(0); self.buf_zmed.pop(0)
# empilha # empilha
S_custo = self._stack(self.buf_custo, 0.0) S_custo = self._stack(self.buf_custo, 0.0)
@ -177,7 +426,7 @@ class CostmapFuser:
else: else:
nav_f = np.zeros((self.grid_h, self.grid_w), np.uint8) nav_f = np.zeros((self.grid_h, self.grid_w), np.uint8)
# z_ref fundido (opcional) só p/ d_obs_min # z_ref fundido (opcional)
zref_f = None zref_f = None
if any(z is not None for z in self.buf_zref): if any(z is not None for z in self.buf_zref):
# pega a última não-nula # pega a última não-nula
@ -186,7 +435,14 @@ class CostmapFuser:
zref_f = z zref_f = z
break break
#d_obs_min = self._compute_d_obs_min(custo_f, nav_f, zref_f) # z_med fundido (opcional)
zmed_f = None
if any(z is not None for z in self.buf_zmed):
# pega a última não-nula
for z in reversed(self.buf_zmed):
if z is not None:
zmed_f = z
break
# distâncias por linha (m), usando z_ref se houver; senão, mapeamento linear y_range_m # distâncias por linha (m), usando z_ref se houver; senão, mapeamento linear y_range_m
row_dist = self._row_distances(zref_f).astype(np.float32) # shape (grid_h,) row_dist = self._row_distances(zref_f).astype(np.float32) # shape (grid_h,)
@ -196,11 +452,11 @@ class CostmapFuser:
row_scale_x = self._row_scale_x(row_dist) # (H,) ou None row_scale_x = self._row_scale_x(row_dist) # (H,) ou None
block = self._compute_blockage_metrics( block = self._compute_blockage_metrics(
custo_f, anom_f, conf_f, nav_f, custo_f, anom_f, conf_f, nav_f, zmed_f,
row_dist_m, row_scale_x, self.central_cols, row_dist_m, row_scale_x, self.central_cols,
use_persistence=True, use_persistence=True,
velocidade_mps=velocidade_ms, velocidade_mps=velocidade_ms,
a_max_freio=0.8, margem_parada=0.25, a_max_freio=0.8, margem_parada=0.60,
N_on=2, N_off=5, blackout_imediato=True N_on=2, N_off=5, blackout_imediato=True
) )
@ -231,7 +487,6 @@ class CostmapFuser:
"conf_u8": to_u8_list(conf_f), "conf_u8": to_u8_list(conf_f),
"anom_u8": to_u8_list(anom_f), "anom_u8": to_u8_list(anom_f),
"nav_mask": nav_f.astype(np.uint8).ravel().tolist(), "nav_mask": nav_f.astype(np.uint8).ravel().tolist(),
"d_obs_min": block["d_obs_min"],
"block": block "block": block
} }
@ -242,188 +497,6 @@ class CostmapFuser:
return snap return snap
def _compute_blockage_metrics(
self,
custo_f, anom_f, conf_f, nav_f,
row_dist_m, row_scale_x_m, central_cols,
robot_width_m=0.84, margin_m=0.12,
thr_anom_block=0.50, thr_cost_block=0.65, thr_conf_low=0.35,
rho_block_central=0.70, rho_block_global=0.60,
near_is_bottom=True,
# ------ NOVOS (opcionais) ------
use_persistence=False,
velocidade_mps=None, # m/s; se None, decisão não usa distância de frenagem
a_max_freio=0.8, # m/s²
margem_parada=0.25, # m
N_on=2, # frames p/ entrar
N_off=5, # frames p/ sair
blackout_imediato=True
):
"""
Retorna dict com:
- d_obs_min (m) ou None
- blocked (bool) -> se use_persistence=False: igual ao "raw"; se True: com persistência
- blocked_raw (bool)
- reason ('obstacle','blackout','narrow','none')
- coverage: {'central_max':..., 'global':...}
- side_bias: {'value': -1..+1, 'left_frac':..., 'right_frac':...}
- j_block (índice) ou None
- decision: { 'parar': bool, 'dist_necessaria': float, 'v_max_sugerida_mps': float|None,
'frames_on':int, 'frames_off':int, 'N_on':int, 'N_off':int }
"""
import numpy as np
H, W = custo_f.shape
c0, c1 = central_cols
c0 = max(0, min(W-1, int(c0)))
c1 = max(0, min(W, int(c1)))
if c1 <= c0:
c0, c1 = W//3, 2*W//3 # fallback
# 1) Máscaras inseguras
mask_anom = (anom_f >= thr_anom_block)
mask_cost = (custo_f >= thr_cost_block)
mask_conf = (conf_f < thr_conf_low)
unsafe = mask_anom | mask_cost | mask_conf
# 2) Largura em colunas por linha
width_need_m = robot_width_m + margin_m
cols_need = []
for j in range(H):
sx = row_scale_x_m[j] if row_scale_x_m is not None else (width_need_m / max(1, (c1 - c0)))
if sx is None or sx <= 1e-6:
cols_need.append(c1 - c0)
else:
ncols = int(np.ceil(width_need_m / sx))
cols_need.append(max(1, min(W, ncols)))
cols_need = np.asarray(cols_need, dtype=int)
# 3) Varredura central
def central_window(j, ncols):
mid = (c0 + c1) // 2
half = ncols // 2
a = max(0, mid - half)
b = min(W, a + ncols)
a = max(0, b - ncols)
return a, b
coverage_central = np.zeros(H, np.float32)
j_block = None
it = (range(H-1, -1, -1) if near_is_bottom else range(H))
for j in it:
a, b = central_window(j, cols_need[j])
unsafe_row = unsafe[j, a:b]
coverage = unsafe_row.mean() if (b > a) else 1.0
coverage_central[j] = coverage
if coverage >= rho_block_central:
j_block = j
break
# 4) Cobertura global
global_cov = unsafe.mean()
# 5) Distância do 1º bloqueio
d_obs_min = None if j_block is None else float(row_dist_m[j_block])
# 6) Viés lateral
left = unsafe[:, c0:(c0+c1)//2].mean() if (c1-c0) >= 2 else 0.0
right = unsafe[:, (c0+c1)//2:c1].mean() if (c1-c0) >= 2 else 0.0
side_bias_val = float(np.clip((right - left) / max(1e-6, (right + left)), -1.0, 1.0))
# 7) Decisão "raw" (sem persistência)
blocked_raw = False
reason = "none"
if j_block is not None:
blocked_raw = True
reason = "obstacle"
elif global_cov >= rho_block_global and (conf_f.mean() < 0.45):
blocked_raw = True
reason = "blackout"
elif coverage_central.max() > 0.45 and (left > 0.7 or right > 0.7):
reason = "narrow"
# 8) Persistência / histerese + decisão de parada (opcional)
decision = {
"parar": False,
"dist_necessaria": None,
"v_max_sugerida_mps": None,
"frames_on": 0,
"frames_off": 0,
"N_on": N_on,
"N_off": N_off
}
blocked_out = blocked_raw # default: compatível com antes
if use_persistence:
# calcula distância necessária se tivermos velocidade
v = float(max(0.0, velocidade_mps or 0.0))
dist_freio = max(0.30, (v*v) / max(1e-9, 2.0 * a_max_freio))
dist_necessaria = dist_freio + margem_parada
decision["dist_necessaria"] = float(dist_necessaria)
# regra "stop_now" crua (antes do debounce)
stop_now = False
v_max_sug = None
if blocked_raw and reason == "blackout":
stop_now = True if blackout_imediato else False
if blocked_raw and reason == "obstacle":
if (d_obs_min is not None) and np.isfinite(d_obs_min) and (d_obs_min <= dist_necessaria):
stop_now = True
else:
if (d_obs_min is not None) and np.isfinite(d_obs_min) and (d_obs_min > margem_parada):
v_max_sug = float(np.sqrt(max(0.0, 2.0 * a_max_freio * (d_obs_min - margem_parada))))
# histerese
st = self._blk_state
if stop_now:
st["on"] += 1
st["off"] = 0
else:
st["off"] += 1
st["on"] = 0
if not st["latched"] and st["on"] >= N_on:
st["latched"] = True
elif st["latched"] and st["off"] >= N_off:
st["latched"] = False
# saída persistente
decision.update({
"parar": bool(st["latched"]),
"v_max_sugerida_mps": v_max_sug,
"frames_on": int(st["on"]),
"frames_off": int(st["off"])
})
# quando usamos persistência, o 'blocked' exposto passa a refletir a decisão debounced:
blocked_out = bool(st["latched"])
# guarda debug
st["reason"] = reason
st["dmin"] = (None if d_obs_min is None else float(d_obs_min))
st["last_decision"] = "PARAR" if st["latched"] else "LIVRE"
return {
"d_obs_min": d_obs_min,
"blocked": bool(blocked_out), # <- já com persistência se habilitada
"blocked_raw": bool(blocked_raw), # <- útil pra debug/HUD
"reason": reason,
"coverage": {
"central_max": float(coverage_central.max()),
"global": float(global_cov),
},
"side_bias": {
"value": side_bias_val,
"left_frac": float(left),
"right_frac": float(right),
},
"j_block": (None if j_block is None else int(j_block)),
"decision": decision
}

View File

@ -2,6 +2,7 @@ from enum import IntEnum
import time import time
import cv2 import cv2
import numpy as np import numpy as np
from collections import deque
from shared.utils import encode_image_base64 from shared.utils import encode_image_base64
from shared.enums import StatusCarroMapa from shared.enums import StatusCarroMapa
@ -33,6 +34,19 @@ class SegmentacaoManager:
self.log = None self.log = None
self.dados_visuais = {} self.dados_visuais = {}
self._ema_ang = None
self._ema_lat = None
self._ema_alpha_ang = 0.25 # suavização do ângulo (0..1)
self._ema_alpha_lat = 0.25 # suavização do lateral (0..1)
self._status_hist = deque(maxlen=7) # maioria em 7 frames
self._prev_cx = None
self._has_top_prev = False
self._has_bot_prev = False
self._thr_top_on = 0.03 # 3% para ligar (ajuste fino depois)
self._thr_top_off = 0.02 # 2% para desligar (histerese)
self._thr_bot_on = 0.03
self._thr_bot_off = 0.02
def _segmentar_predictions(self, predictions): def _segmentar_predictions(self, predictions):
try: try:
self.pred_rgb[:] = self.lut[predictions] self.pred_rgb[:] = self.lut[predictions]
@ -135,108 +149,253 @@ class SegmentacaoManager:
return perfil return perfil
def _bool_hysteresis(self, prev: bool, x: float, thr_on: float, thr_off: float) -> bool:
"""
Histerese booleana: se estava False, liga se x > thr_on;
se estava True, desliga se x < thr_off.
"""
if prev:
return x >= thr_off
else:
return x >= thr_on
def _classificar_status_por_cana(self, mask_cana: np.ndarray, mask_main, near_is_bottom: bool = True):
"""
Aplica a tua regra baseada **apenas** na presença de CANA
nas metades da imagem.
"""
H, W = mask_cana.shape
mid = H // 2
if near_is_bottom:
# parte "de baixo" = perto do robô
bottom = mask_cana[mid:, :]
top = mask_cana[:mid, :]
else:
# se tua convenção um dia inverter, estamos prontos
bottom = mask_cana[:mid, :]
top = mask_cana[mid:, :]
area_top = top.size
area_bot = bottom.size
p_top = float(top.sum()) / max(1, area_top)
p_bot = float(bottom.sum()) / max(1, area_bot)
# histerese por banda
has_top = self._bool_hysteresis(self._has_top_prev, p_top, self._thr_top_on, self._thr_top_off)
has_bot = self._bool_hysteresis(self._has_bot_prev, p_bot, self._thr_bot_on, self._thr_bot_off)
self._has_top_prev, self._has_bot_prev = has_top, has_bot
# classificação instantânea pela tua definição
if not has_top and not has_bot:
status_now = StatusCarroMapa.Direcionando
elif has_top and not has_bot:
status_now = StatusCarroMapa.EntrandoRua
elif has_bot and not has_top:
status_now = StatusCarroMapa.SaindoRua
else:
status_now = StatusCarroMapa.CaminhandoRua
# persistência temporal (maioria)
self._status_hist.append(status_now)
status_final = max(set(self._status_hist), key=self._status_hist.count)
# (opcional) se quiser um “gate” extra pra não dizer CaminhandoRua quando a “rua” ocupa quase tudo:
mask_cover = np.mean(mask_main) # fração de pixels True em mask_main
if status_final == StatusCarroMapa.CaminhandoRua and mask_cover > 0.90:
status_final = StatusCarroMapa.Direcionando
return status_now, status_final, p_top, p_bot, has_top, has_bot
def _extrair_corredor_principal(self, mask_classes): def _extrair_corredor_principal(self, mask_classes):
altura, largura = mask_classes.shape H, W = mask_classes.shape
centro_img = largura // 2 cx_img = W // 2
mask_rua = (mask_classes == ClassesSegmentacao.RUA.value).astype(np.uint8)
# optional: fecha buracos pequenos
# kernel = np.ones((3,3), np.uint8)
# mask_rua = cv2.morphologyEx(mask_rua, cv2.MORPH_CLOSE, kernel, iterations=1)
mask_rua = np.uint8(mask_classes == ClassesSegmentacao.RUA.value)
num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(mask_rua, connectivity=8) num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(mask_rua, connectivity=8)
if num_labels <= 1:
return np.zeros_like(mask_rua, dtype=np.uint8)
melhor_blob_idx = -1 best_i, best_score = -1, -1e9
melhor_score = float('-inf')
for i in range(1, num_labels): # ignora fundo # pesos do score
w_area = 1.0
w_center = 1.0
w_bottom = 1.0
w_vertical = 0.5
w_prev = 0.75
prev_cx = self._prev_cx
for i in range(1, num_labels): # 0 = fundo
x, y, w, h, area = stats[i] x, y, w, h, area = stats[i]
centro_blob = x + w // 2 if area < 50: # lixo
dist_centro = abs(centro_blob - centro_img)
score = area - (dist_centro * 2) # prioriza blobs grandes e centrais
if score > melhor_score:
melhor_score = score
melhor_blob_idx = i
return np.uint8(labels == melhor_blob_idx)
def _analisar_corredor_visual(self, predictions):
self.predictions = predictions
ALTURA, LARGURA = self.predictions.shape
centro_x = LARGURA // 2
erro_lateral = None
erro_angular = None
status_corredor = StatusCarroMapa.Parado
mask_corredor_principal = self._extrair_corredor_principal(self.predictions)
scanlines = [int(ALTURA * f) for f in [0.999, 0.85, 0.7, 0.55, 0.4, 0.25, 0.1]]
centros_corredor = []
for y in scanlines:
linha = mask_corredor_principal[y]
mask_bin = np.uint8(linha > 0)
if np.count_nonzero(mask_bin) == 0:
centros_corredor.append((-1, y))
continue continue
# Agora garantido que só tem um blob principal
indices = np.where(mask_bin > 0)[0]
centro_x = int(np.mean(indices))
centros_corredor.append((centro_x, y))
# 2. Calcular erro angular (reta entre os centros) cx = x + w // 2
if len(centros_corredor) >= 2: # normalizações 0..1
# Angular curto area_n = area / float(H * W)
(x1c, y1c), (x2c, y2c) = centros_corredor[0], centros_corredor[1] center_n = 1.0 - min(1.0, abs(cx - cx_img) / (W * 0.5))
erro_angular_curto = np.arctan2(x2c - x1c, y1c - y2c) vertical = h / max(1.0, w) # alongamento vertical
# Angular longo bottom_touch = 1.0 if (y + h >= H - 2) else 0.0
(x1l, y1l), (x2l, y2l) = centros_corredor[0], centros_corredor[-1]
erro_angular_longo = np.arctan2(x2l - x1l, y1l - y2l)
# Peso para suavizar ou escolher dinamicamente
erro_angular = 0.2 * erro_angular_curto + 0.8 * erro_angular_longo
erro_angular = (erro_angular + 180) % 360 - 180
# 3. Calcular erro lateral (deslocamento da base) prev_bias = 0.0
erro_lateral_px = None if prev_cx is not None:
erro_lateral_pct = 0 prev_bias = 1.0 - min(1.0, abs(cx - prev_cx) / (W * 0.5))
largura_corredor_px = None
if len(centros_corredor) > 0:
x_base, y_base = centros_corredor[0]
if x_base != -1:
erro_lateral_px = centro_x - x_base
# Recalcular largura do maior blob na linha base
linha_base = self.predictions[y_base]
mask_bin_base = np.uint8(linha_base == ClassesSegmentacao.RUA.value)
num_labels, _, stats, _ = cv2.connectedComponentsWithStats(mask_bin_base.reshape(1, -1), connectivity=8)
max_area = 0
largura_corredor_px = None
for i in range(1, num_labels):
w = stats[i, cv2.CC_STAT_WIDTH]
area = stats[i, cv2.CC_STAT_AREA]
if area > max_area:
max_area = area
largura_corredor_px = w
if largura_corredor_px and largura_corredor_px > 0:
erro_lateral_pct = (erro_lateral_px / largura_corredor_px) * 100
# 5. Novo status de corredor score = (w_area*area_n +
LIMIAR_CANA = 1000 w_center*center_n +
parte_cima = self.predictions[:int(ALTURA * 0.3), :] w_bottom*bottom_touch +
parte_baixo = self.predictions[int(ALTURA * 0.7):, :] w_vertical*vertical +
w_prev*prev_bias)
cana_cima = np.sum(parte_cima == ClassesSegmentacao.CANA.value) if score > best_score:
cana_baixo = np.sum(parte_baixo == ClassesSegmentacao.CANA.value) best_score = score
cana_total = np.sum(self.predictions == ClassesSegmentacao.CANA.value) best_i = i
if cana_total < 500: self._prev_cx = None
status_corredor = StatusCarroMapa.Direcionando if best_i == -1:
elif cana_baixo > LIMIAR_CANA and cana_cima > LIMIAR_CANA: return np.zeros_like(mask_rua, dtype=np.uint8)
status_corredor = StatusCarroMapa.CaminhandoRua
elif cana_baixo < LIMIAR_CANA and cana_cima > LIMIAR_CANA: # guarda cx do blob vencedor pro próximo frame
status_corredor = StatusCarroMapa.EntrandoRua bx, by, bw, bh, _ = stats[best_i]
elif cana_baixo > LIMIAR_CANA and cana_cima < LIMIAR_CANA: self._prev_cx = bx + bw // 2
status_corredor = StatusCarroMapa.SaindoRua self._prev_y0 = by
return (labels == best_i).astype(np.uint8)
def _get_scanlines_y(self, H, grid_rows_y_px=None, near_is_bottom=True):
if grid_rows_y_px and len(grid_rows_y_px) >= 5:
ys = [int(np.clip(y, 0, H-1)) for y in grid_rows_y_px]
# reordena do "perto" para o "longe"
ys = sorted(ys, reverse=near_is_bottom)
return ys
# fallback por frações
fracs = [0.98, 0.85, 0.70, 0.55, 0.40, 0.25, 0.10]
return [min(H-1, max(0, int(H*f))) for f in fracs]
def _analisar_corredor_visual(self, predictions, grid_rows_y_px=None, near_is_bottom=True):
self.predictions = predictions
H, W = self.predictions.shape
cx_img = W // 2
# 1) máscara do corredor principal
mask_main = self._extrair_corredor_principal(self.predictions).astype(bool)
mask_cana = (self.predictions == ClassesSegmentacao.CANA.value)
# 2) scanlines (grid ou fallback)
ys = self._get_scanlines_y(H, grid_rows_y_px, near_is_bottom)
centros, larguras = [], []
for y in ys:
row = mask_main[y]
idx = np.flatnonzero(row)
if idx.size == 0:
centros.append((None, y))
larguras.append(0)
else:
x0, x1 = idx[0], idx[-1]
cx = (x0 + x1) // 2
w = (x1 - x0 + 1)
centros.append((int(cx), y))
larguras.append(int(w))
# 3) Ângulo do corredor (rad) via ajuste linear x(y)
pts = [(x, y) for (x, y) in centros if x is not None]
ang_rad = None
if len(pts) >= 2:
ys_fit = np.array([p[1] for p in pts], dtype=np.float32)
xs_fit = np.array([p[0] for p in pts], dtype=np.float32)
# pesos: linhas mais próximas ao robô pesam mais
# (primeiros ys na lista são "perto" se near_is_bottom=True)
n = len(ys_fit)
wts = np.linspace(1.0, 2.0, n).astype(np.float32) # simples e eficaz
# polyfit ponderado (equivalente com normal equations)
# x = a*y + b
Wm = np.diag(wts)
Y = ys_fit.reshape(-1,1)
X = np.hstack([Y, np.ones_like(Y)])
# a, b = (X^T W X)^-1 X^T W x
XtW = X.T @ Wm
beta = np.linalg.pinv(XtW @ X) @ (XtW @ xs_fit)
a = float(beta[0])
ang_rad = np.arctan(a)
# wrap correto em radianos
ang_rad = (ang_rad + np.pi) % (2*np.pi) - np.pi
# 4) Erro lateral (% da largura na base)
erro_lateral_pct = 0.0
if centros and centros[0][0] is not None:
x_base, y_base = centros[0]
w_base = max(1, larguras[0])
err_px = cx_img - x_base
erro_lateral_pct = (err_px / w_base) * 100.0
# 5) Suavização (EMA)
if ang_rad is not None:
deg = np.degrees(ang_rad)
if self._ema_ang is None:
self._ema_ang = deg
else:
a = self._ema_alpha_ang
# unwrap simples para evitar saltos de ±180
delta = ((deg - self._ema_ang + 180) % 360) - 180
self._ema_ang = self._ema_ang + a * delta
ang_out = round(self._ema_ang, 3)
else:
ang_out = None
if True: # sempre temos lateral pct numérico
if self._ema_lat is None:
self._ema_lat = erro_lateral_pct
else:
a = self._ema_alpha_lat
self._ema_lat = (1 - a) * self._ema_lat + a * erro_lateral_pct
lat_out = round(float(self._ema_lat), 3)
# 6) Status baseado em presença nas scanlines
# proximidade: usa 2 mais perto e 2 mais longe
near_valid = sum(1 for (x,_) in centros[:2] if x is not None)
far_valid = sum(1 for (x,_) in centros[-2:] if x is not None)
any_valid = sum(1 for (x,_) in centros if x is not None)
# --- STATUS pela regra das metades (CANA) ---
status_now, status_final, p_top, p_bot, has_top, has_bot = self._classificar_status_por_cana(
mask_cana.astype(np.uint8), mask_main, near_is_bottom=near_is_bottom
)
# 7) Persistência (maioria em N frames)
self._status_hist.append(status_now)
status_final = max(set(self._status_hist), key=self._status_hist.count)
self._last_status = status_final
# 8) Confiança
confianca = any_valid / max(1, len(centros))
return { return {
"height": ALTURA, "height": H,
"width": LARGURA, "width": W,
"erro_angular": round(np.degrees(erro_angular), 3) if erro_angular is not None else None, "erro_angular": ang_out,
"erro_lateral_pct": erro_lateral_pct, "erro_lateral_pct": lat_out,
"status_corredor": status_corredor.value, "status_corredor": status_final.value,
"centros_corredor": centros_corredor "centros_corredor": centros,
"larguras_px": larguras,
"confianca": round(float(sum(1 for (x,_) in centros if x is not None) / max(1, len(centros))), 3),
# DEBUG/telemetria úteis
"p_cana_top": round(float(p_top), 3),
"p_cana_bottom": round(float(p_bot), 3),
"has_top": bool(has_top),
"has_bottom": bool(has_bot),
} }
def display_segmentation_debug(self, frame, largura_robo_px): def display_segmentation_debug(self, frame, largura_robo_px):

View File

@ -4,7 +4,7 @@ import threading
import time import time
import cv2 import cv2
from shared.enums import StatusModulo, CameraFrameType, StatusOperacao, WeedWorkerCommandType from shared.enums import StatusModulo, CameraFrameType, StatusOperacao, T_Code, WeedWorkerCommandType
from shared.utils import decode_image_base64, encode_image_base64, fazer_overlay from shared.utils import decode_image_base64, encode_image_base64, fazer_overlay
from camera_worker.camera_oak import CameraOak from camera_worker.camera_oak import CameraOak
from shared.contexto_global_redis import CmdKey, ContextoGlobalRedis, CtxKey from shared.contexto_global_redis import CmdKey, ContextoGlobalRedis, CtxKey
@ -15,7 +15,6 @@ class CameraManager:
def __init__(self, mostrar_log): def __init__(self, mostrar_log):
self.mostrar_log = mostrar_log self.mostrar_log = mostrar_log
self.mx_id = None self.mx_id = None
self.tempo_saude = 10
self.reiniciar_status() self.reiniciar_status()
def reiniciar_status(self): def reiniciar_status(self):
@ -26,7 +25,6 @@ class CameraManager:
self._ultimo_rgb_frame = None self._ultimo_rgb_frame = None
self._ultima_analise = {} self._ultima_analise = {}
self._ts_segmentacao_anterior = 0 self._ts_segmentacao_anterior = 0
self._ultima_saude_ts = 0
def inicializar(self, mx_id): def inicializar(self, mx_id):
if self.iniciando: if self.iniciando:
@ -78,7 +76,7 @@ class CameraManager:
self.camera.atualizar_saude() self.camera.atualizar_saude()
elif self.mx_id is not None: elif self.mx_id is not None:
from camera_worker.manager import definir_saude_camera from camera_worker.manager import definir_saude_camera
definir_saude_camera(self.mx_id, StatusModulo.DESCONECTADO, 0, ["desconectado"], False, {}) definir_saude_camera(self.mx_id, StatusModulo.DESCONECTADO, 0, ["desconectado"], False, {}, disp=T_Code.Cam, conectado=False)
except Exception as e: except Exception as e:
self.mostrar_log(f"[saude] erro: {e}") self.mostrar_log(f"[saude] erro: {e}")
@ -141,19 +139,16 @@ class CameraManager:
def _iniciar_loop_analise_continua(self, freq): def _iniciar_loop_analise_continua(self, freq):
def loop(): def loop():
self._ultima_saude_ts = 0
while True: while True:
if self.camera is None: if self.camera is None:
time.sleep(5) time.sleep(5)
continue continue
t0 = time.time() t0 = time.time()
if (t0 - self._ultima_saude_ts) >= self.tempo_saude:
self._ultima_saude_ts = time.time()
self.atualizar_saude_camera()
try: try:
status = StatusModulo((self.camera.ultima_saude or {}).get("status", StatusModulo.DESCONECTADO.value)) status = StatusModulo((self.camera.ultima_saude or {}).get("status", StatusModulo.DESCONECTADO.value))
if status == StatusModulo.DESCONECTADO: ts_status = (self.camera.ultima_saude or {}).get("timestamp", 0)
if status == StatusModulo.DESCONECTADO and (t0 - ts_status) > 5.0:
self.reiniciar_status() self.reiniciar_status()
continue continue
elif self.operante and status == StatusModulo.OPERANTE and self.camera is not None: elif self.operante and status == StatusModulo.OPERANTE and self.camera is not None: