ajustes nas regras do visual worker
This commit is contained in:
parent
fc68629218
commit
c43e1daab8
Binary file not shown.
Binary file not shown.
|
|
@ -197,11 +197,11 @@ namespace AgroBase.Forms.Operacoes
|
|||
ReqFrameSnr = true;
|
||||
try
|
||||
{
|
||||
var frameRgb = await VisualWorkerService.GetCameraFrame(CameraFrameType.Rgb);
|
||||
var frameRgb = await VisualWorkerService.GetCameraFrame(CameraFrameType.Debug);
|
||||
AtualizarImagemPainel(pnlCameraRua, frameRgb?.image());
|
||||
|
||||
var frameSeg = await VisualWorkerService.GetCameraFrame(CameraFrameType.Segmentacao);
|
||||
AtualizarImagemPainel(pnlDeteccoesRua, frameSeg?.image());
|
||||
//var frameSeg = await VisualWorkerService.GetCameraFrame(CameraFrameType.Segmentacao);
|
||||
//AtualizarImagemPainel(pnlDeteccoesRua, frameSeg?.image());
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ namespace AgroBase.Forms
|
|||
{
|
||||
//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 = analise.PlotarAnalise(frame);
|
||||
|
||||
PreencherGridAnomalias(analise.anomalias ?? new List<VisualWorkerMessageAnaliseBBoxModel>());
|
||||
//PreencherGridAnomalias(analise.anomalias ?? 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)
|
||||
|
|
|
|||
|
|
@ -60,10 +60,8 @@ namespace AgroBase.Forms
|
|||
private async Task tmrLeitura_Tick()
|
||||
{
|
||||
var Imu = Variaveis.OperacaoEmAndamento.Sensoriamento.IMU;
|
||||
AnguloX = (Imu.InclinacaoFrontal + 180) % 360;
|
||||
if (AnguloX > 180) AnguloX -= 360;
|
||||
AnguloY = (Imu.InclinacaoLateral + 180) % 360;
|
||||
if (AnguloY > 180) AnguloY -= 360;
|
||||
AnguloX = -Imu.InclinacaoFrontal;
|
||||
AnguloY = -Imu.InclinacaoLateral;
|
||||
AnguloZ = -Imu.Rotacao;
|
||||
OrientacaoMagnetica = Imu.RotacaoCorrigida;
|
||||
|
||||
|
|
@ -227,7 +225,12 @@ namespace AgroBase.Forms
|
|||
picBussola.Invalidate();
|
||||
//glControl.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");
|
||||
txtY.Text = AnguloY.ToString("0.00");
|
||||
|
|
|
|||
|
|
@ -175,30 +175,33 @@ namespace AgroBase.Models.Operadores
|
|||
public class VisualWorkerMessageAnaliseModel
|
||||
{
|
||||
public DateTime timestamp { get; set; }
|
||||
public List<VisualWorkerMessageAnaliseBBoxModel> anomalias { get; set; }
|
||||
public List<VisualWorkerMessageAnaliseBBoxModel> sombras { get; set; }
|
||||
public VisualWorkerMessageAnaliseSoloModel solo { get; set; }
|
||||
public List<VisualWorkerMessageRadar2DPerfilModel> corredor_prof { get; set; }
|
||||
public List<VisualWorkerMessageRadar2DPerfilModel> corredor_seg { get; set; }
|
||||
//public List<VisualWorkerMessageAnaliseBBoxModel> anomalias { get; set; }
|
||||
//public List<VisualWorkerMessageAnaliseBBoxModel> sombras { get; set; }
|
||||
//public VisualWorkerMessageAnaliseSoloModel solo { get; set; }
|
||||
//public List<VisualWorkerMessageRadar2DPerfilModel> corredor_prof { get; set; }
|
||||
//public List<VisualWorkerMessageRadar2DPerfilModel> corredor_seg { get; set; }
|
||||
public VisualWorkerMessageSegmentacaoSemanticaModel segmentacao { get; set; }
|
||||
public VisualWorkerMessageMatrizConfiancaModel matriz_confianca { get; set; }
|
||||
|
||||
public VisualWorkerMessageAnaliseModel Clone()
|
||||
{
|
||||
return new VisualWorkerMessageAnaliseModel()
|
||||
{
|
||||
timestamp = timestamp,
|
||||
anomalias = new List<VisualWorkerMessageAnaliseBBoxModel>(anomalias ?? new List<VisualWorkerMessageAnaliseBBoxModel>()),
|
||||
sombras = new List<VisualWorkerMessageAnaliseBBoxModel>(sombras ?? new List<VisualWorkerMessageAnaliseBBoxModel>()),
|
||||
solo = solo?.Clone(),
|
||||
corredor_prof = new List<VisualWorkerMessageRadar2DPerfilModel>(corredor_prof ?? new List<VisualWorkerMessageRadar2DPerfilModel>()),
|
||||
corredor_seg = new List<VisualWorkerMessageRadar2DPerfilModel>(corredor_seg ?? new List<VisualWorkerMessageRadar2DPerfilModel>()),
|
||||
//anomalias = new List<VisualWorkerMessageAnaliseBBoxModel>(anomalias ?? new List<VisualWorkerMessageAnaliseBBoxModel>()),
|
||||
//sombras = new List<VisualWorkerMessageAnaliseBBoxModel>(sombras ?? new List<VisualWorkerMessageAnaliseBBoxModel>()),
|
||||
//solo = solo?.Clone(),
|
||||
//corredor_prof = new List<VisualWorkerMessageRadar2DPerfilModel>(corredor_prof ?? new List<VisualWorkerMessageRadar2DPerfilModel>()),
|
||||
//corredor_seg = new List<VisualWorkerMessageRadar2DPerfilModel>(corredor_seg ?? new List<VisualWorkerMessageRadar2DPerfilModel>()),
|
||||
segmentacao = segmentacao?.Clone() ?? new VisualWorkerMessageSegmentacaoSemanticaModel(),
|
||||
matriz_confianca = matriz_confianca?.Clone() ?? new VisualWorkerMessageMatrizConfiancaModel(),
|
||||
};
|
||||
}
|
||||
|
||||
public Bitmap PlotarAnalise(OAKCameraFrameModel frame)
|
||||
{
|
||||
Bitmap img = frame?.image();
|
||||
return null;
|
||||
/*Bitmap img = frame?.image();
|
||||
if (img == null) return null;
|
||||
|
||||
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
|
||||
{
|
||||
ScriptCarregado = 1,
|
||||
|
|
|
|||
|
|
@ -102,13 +102,18 @@ namespace AgroBase.Services.Operadores
|
|||
|
||||
if (cameras.Any())
|
||||
{
|
||||
CamerasConectadas.AddRange(cameras.Values.Select(x => new OAKCameraModel()
|
||||
foreach (var cam in cameras)
|
||||
{
|
||||
Dispositivo = x.dispositivo,
|
||||
Version = x.versao,
|
||||
Name = x.modelo,
|
||||
Id = x.mx_id,
|
||||
}).ToList());
|
||||
var camera_str = RedisService.Get(RedisService.CamKey(cam.Key));
|
||||
var camera = JsonConvert.DeserializeObject<CameraWorkerItemModel>(camera_str);
|
||||
CamerasConectadas.Add(new OAKCameraModel()
|
||||
{
|
||||
Dispositivo = camera.dispositivo,
|
||||
Version = camera.versao,
|
||||
Name = camera.modelo,
|
||||
Id = camera.mx_id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return CamerasConectadas;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
using AgroBase.Models;
|
||||
using AgroBase.Models.Operadores;
|
||||
using Microsoft.DirectX.DirectInput;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
|
@ -16,6 +18,8 @@ namespace AgroBase.Services.Operadores
|
|||
|
||||
private bool DebugMode = true;
|
||||
|
||||
static long _lastTsVW = -1;
|
||||
|
||||
public void MostrarLog(string message, int limite = 500)
|
||||
{
|
||||
if (DebugMode)
|
||||
|
|
@ -138,45 +142,67 @@ namespace AgroBase.Services.Operadores
|
|||
|
||||
public static void AtualizarAnalise()
|
||||
{
|
||||
// IMU (simples e direto)
|
||||
string imuJson = RedisService.Get(RedisService.ModKey(Enums.T_Code.Imu));
|
||||
if (!string.IsNullOrEmpty(imuJson))
|
||||
if (!string.IsNullOrWhiteSpace(imuJson))
|
||||
{
|
||||
try
|
||||
{
|
||||
DadosLeitura.Imu = JsonConvert.DeserializeObject<VisualWorkerMessageIMUModel>(imuJson);
|
||||
}
|
||||
catch { /* opcional: logar erro */ }
|
||||
}
|
||||
|
||||
// VisualWorker
|
||||
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());
|
||||
|
||||
//var matrizes = JsonConvert.DeserializeObject<Dictionary<string, object>>(dados["matrizes"].ToString());
|
||||
//var confianca = JsonConvert.DeserializeObject<List<List<double>>>(JsonConvert.SerializeObject(matrizes["confianca"]));
|
||||
//var custo = JsonConvert.DeserializeObject<List<List<double>>>(JsonConvert.SerializeObject(matrizes["custo"]));
|
||||
|
||||
var anomalias = JsonConvert.DeserializeObject<Dictionary<string, object>>(dados["anomalias"].ToString());
|
||||
var deteccoes = JsonConvert.DeserializeObject<List<VisualWorkerMessageAnaliseBBoxModel>>(JsonConvert.SerializeObject(anomalias["deteccoes"]));
|
||||
var sombras = JsonConvert.DeserializeObject<List<VisualWorkerMessageAnaliseBBoxModel>>(JsonConvert.SerializeObject(anomalias["sombras"]));
|
||||
|
||||
var corredor = JsonConvert.DeserializeObject<Dictionary<string, object>>(dados["perfil_corredor"].ToString());
|
||||
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"]));
|
||||
|
||||
var segmentacao = JsonConvert.DeserializeObject<VisualWorkerMessageSegmentacaoSemanticaModel>(JsonConvert.SerializeObject(dados["segmentacao"]));
|
||||
|
||||
DadosLeitura.Analises = new VisualWorkerMessageAnaliseModel()
|
||||
root = JObject.Parse(json);
|
||||
}
|
||||
catch
|
||||
{
|
||||
timestamp = timestamp,
|
||||
solo = solo,
|
||||
anomalias = deteccoes,
|
||||
sombras = sombras,
|
||||
corredor_prof = profundidade,
|
||||
corredor_seg = corredor_seg,
|
||||
segmentacao = segmentacao
|
||||
};
|
||||
return; // payload inválido
|
||||
}
|
||||
|
||||
// ts_analise (Unix)
|
||||
long tsUnix = root["ts_analise"]?.Value<long>() ?? 0;
|
||||
if (tsUnix != 0)
|
||||
{
|
||||
// atualiza o DateTime sempre que vier um ts válido
|
||||
DadosLeitura.Analises.timestamp = FuncoesGlobais.UnixToDateTime(tsUnix.ToString());
|
||||
}
|
||||
|
||||
// Se o timestamp não mudou, evita desserializar objetos grandes
|
||||
if (tsUnix != 0 && tsUnix == _lastTsVW)
|
||||
return;
|
||||
|
||||
_lastTsVW = tsUnix;
|
||||
|
||||
// segmentação (sem re-serializar o token)
|
||||
var segTok = root["segmentacao"];
|
||||
if (segTok != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var seg = segTok.ToObject<VisualWorkerMessageSegmentacaoSemanticaModel>();
|
||||
if (seg != null) DadosLeitura.Analises.segmentacao = seg;
|
||||
}
|
||||
catch { /* opcional: log */ }
|
||||
}
|
||||
|
||||
// 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(
|
||||
CtxKey.DadosWeedWorker,
|
||||
("analise.anomalias", new Dictionary<string, object>()),
|
||||
("analise.perfil_corredor", new Dictionary<string, object>()),
|
||||
("analise.solo", new VisualWorkerMessageAnaliseSoloModel()),
|
||||
("analise.segmentacao", new VisualWorkerMessageSegmentacaoSemanticaModel())
|
||||
("analise.segmentacao", new VisualWorkerMessageSegmentacaoSemanticaModel()),
|
||||
("analise.matriz_confianca", new VisualWorkerMessageMatrizConfiancaModel())
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using static AgroBase.Models.Operadores.OperadoresModels;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace AgroBase.Services.Operadores
|
||||
{
|
||||
|
|
@ -15,6 +16,7 @@ namespace AgroBase.Services.Operadores
|
|||
public SaudeWorkerModel Saude { get; set; } = new SaudeWorkerModel();
|
||||
|
||||
private bool DebugMode = true;
|
||||
static long _lastTs = -1;
|
||||
|
||||
public void MostrarLog(string message, int limite = 500)
|
||||
{
|
||||
|
|
@ -164,53 +166,67 @@ namespace AgroBase.Services.Operadores
|
|||
public static void AtualizarAnalise()
|
||||
{
|
||||
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) se não mudou, pule deserializações pesadas
|
||||
if (tsUnix == _lastTs) goto AtualizaBicos;
|
||||
_lastTs = tsUnix;
|
||||
|
||||
// 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
|
||||
// 3) deteccoes (se precisar manter o tipo forte)
|
||||
var detToken = analise["deteccoes"];
|
||||
if (detToken != null)
|
||||
{
|
||||
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
|
||||
};
|
||||
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 (ervas.Count < bico.Posicao || bico.Posicao <= 0)
|
||||
atuacoes = 0;
|
||||
else
|
||||
if (bico.Posicao > 0 && bico.Posicao <= ervas.Count)
|
||||
atuacoes = ervas[bico.Posicao - 1]?.Sum(x => x.Value) ?? 0;
|
||||
bico.Atuacoes = atuacoes;
|
||||
});
|
||||
|
||||
DadosLeitura.Analise = analiseObj;
|
||||
}
|
||||
}
|
||||
|
||||
public static void ReiniciarLeituraAnalise()
|
||||
|
|
|
|||
|
|
@ -26,6 +26,11 @@ namespace AgroBase.Services
|
|||
return CtxKey.DadosModulo + dispositivo.ToString();
|
||||
}
|
||||
|
||||
public static string CamKey(string mx_id)
|
||||
{
|
||||
return CtxKey.DadosCameras + mx_id;
|
||||
}
|
||||
|
||||
public static string Get(string chave)
|
||||
{
|
||||
return _db.StringGet(chave);
|
||||
|
|
@ -180,7 +185,7 @@ namespace AgroBase.Services
|
|||
|
||||
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 DadosOperacao = "ctx:dados_operacao";
|
||||
public const string DadosContexto = "ctx:dados_contexto";
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -1,12 +1,5 @@
|
|||
{
|
||||
"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",
|
||||
"config_version": 0,
|
||||
"model_version": "0",
|
||||
|
|
@ -29,5 +22,5 @@
|
|||
"top_topics_and_observing_domains": [ ]
|
||||
} ],
|
||||
"hex_encoded_hmac_key": "40F346D3248C3AFDF2BEE1FE496DBD32F7CED6E5AE98B881ABC421AA7E7B5642",
|
||||
"next_scheduled_calculation_time": "13399758321059135"
|
||||
"next_scheduled_calculation_time": "13399758321059321"
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -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/14-08:05:35.853 19d8 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.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/15-13:16:10.790 5274 Recovering log #3
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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/13-17:43:50.179 2850 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.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/15-10:42:52.380 713c Recovering log #3
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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"}}}
|
||||
|
|
@ -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
|
|
@ -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/14-08:08:01.413 19d8 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.455 5274 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.456 5274 Recovering log #3
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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/13-17:45:26.420 2850 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.150 713c 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.151 713c Recovering log #3
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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/14-08:05:35.766 9414 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.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/15-13:16:10.703 6a74 Recovering log #7
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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/13-17:43:50.082 675c 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.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/15-10:42:52.306 16a4 Recovering log #7
|
||||
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
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
|
|
@ -17,7 +17,7 @@
|
|||
<meta name="viewport" content="width=device-width,
|
||||
initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<style>
|
||||
#map_7792e3f9a50d24693529ab60471511c5 {
|
||||
#map_15b2c6f7832115c94dd9daa84035059e {
|
||||
position: relative;
|
||||
width: 100.0%;
|
||||
height: 100.0%;
|
||||
|
|
@ -54,14 +54,14 @@
|
|||
<body>
|
||||
|
||||
|
||||
<div class="folium-map" id="map_7792e3f9a50d24693529ab60471511c5" ></div>
|
||||
<div class="folium-map" id="map_15b2c6f7832115c94dd9daa84035059e" ></div>
|
||||
|
||||
</body>
|
||||
<script>
|
||||
|
||||
|
||||
var map_7792e3f9a50d24693529ab60471511c5 = L.map(
|
||||
"map_7792e3f9a50d24693529ab60471511c5",
|
||||
var map_15b2c6f7832115c94dd9daa84035059e = L.map(
|
||||
"map_15b2c6f7832115c94dd9daa84035059e",
|
||||
{
|
||||
center: [0.0, 0.0],
|
||||
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",
|
||||
{
|
||||
"minZoom": 0,
|
||||
|
|
@ -95,7 +95,7 @@
|
|||
);
|
||||
|
||||
|
||||
tile_layer_a04e5e8300b6eacc885e71a463b29158.addTo(map_7792e3f9a50d24693529ab60471511c5);
|
||||
tile_layer_76df0441e10db36359cf485a7152ff2d.addTo(map_15b2c6f7832115c94dd9daa84035059e);
|
||||
|
||||
</script>
|
||||
|
||||
|
|
@ -116,7 +116,7 @@
|
|||
}
|
||||
trajeto_json_add({"features": []});
|
||||
|
||||
trajeto_json.addTo(map_7792e3f9a50d24693529ab60471511c5);
|
||||
trajeto_json.addTo(map_15b2c6f7832115c94dd9daa84035059e);
|
||||
|
||||
function adicionarGeometria(novaGeometria) {
|
||||
trajeto_json.addData(novaGeometria);
|
||||
|
|
@ -179,9 +179,9 @@
|
|||
|
||||
var marcadorEquipamento = L.marker([0, 0], {
|
||||
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(
|
||||
{"extraClasses": "fa-rotate-0", "icon": "info-sign", "iconColor": "white", "markerColor": "red", "prefix": "glyphicon"}
|
||||
);
|
||||
|
|
@ -246,7 +246,7 @@
|
|||
}
|
||||
|
||||
if (foco) {
|
||||
map_7792e3f9a50d24693529ab60471511c5.setView(novaPosicao, map_7792e3f9a50d24693529ab60471511c5.getZoom());
|
||||
map_15b2c6f7832115c94dd9daa84035059e.setView(novaPosicao, map_15b2c6f7832115c94dd9daa84035059e.getZoom());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -268,7 +268,7 @@
|
|||
marcadorDinamico.setRotationAngle(angulo);
|
||||
|
||||
adicionarCoordenada("Tj", [novaLongitude, novaLatitude]);
|
||||
map_7792e3f9a50d24693529ab60471511c5.setView(novaPosicao, map_7792e3f9a50d24693529ab60471511c5.getZoom());*/
|
||||
map_15b2c6f7832115c94dd9daa84035059e.setView(novaPosicao, map_15b2c6f7832115c94dd9daa84035059e.getZoom());*/
|
||||
});
|
||||
|
||||
function calcularOrientacao(P1latitude, P1longitude, P2latitude, P2longitude) {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
<meta name="viewport" content="width=device-width,
|
||||
initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<style>
|
||||
#map_fbec21a667da83a7a2d972cb198fec24 {
|
||||
#map_23f8e35f17470dacda23acd5395a7fac {
|
||||
position: relative;
|
||||
width: 100.0%;
|
||||
height: 100.0%;
|
||||
|
|
@ -54,14 +54,14 @@
|
|||
<body>
|
||||
|
||||
|
||||
<div class="folium-map" id="map_fbec21a667da83a7a2d972cb198fec24" ></div>
|
||||
<div class="folium-map" id="map_23f8e35f17470dacda23acd5395a7fac" ></div>
|
||||
|
||||
</body>
|
||||
<script>
|
||||
|
||||
|
||||
var map_fbec21a667da83a7a2d972cb198fec24 = L.map(
|
||||
"map_fbec21a667da83a7a2d972cb198fec24",
|
||||
var map_23f8e35f17470dacda23acd5395a7fac = L.map(
|
||||
"map_23f8e35f17470dacda23acd5395a7fac",
|
||||
{
|
||||
center: [-22.172636164916668, -47.395186322185666],
|
||||
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",
|
||||
{
|
||||
"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({
|
||||
|
||||
|
|
@ -148,23 +148,23 @@
|
|||
}*/
|
||||
});
|
||||
};
|
||||
var geo_json_2af9830ef68420da295e9fbbf418b0d8 = L.geoJson(null, {
|
||||
onEachFeature: geo_json_2af9830ef68420da295e9fbbf418b0d8_onEachFeature,
|
||||
var geo_json_1f8972074523b6427449f533ba057bf8 = L.geoJson(null, {
|
||||
onEachFeature: geo_json_1f8972074523b6427449f533ba057bf8_onEachFeature,
|
||||
|
||||
...{
|
||||
}
|
||||
});
|
||||
|
||||
function geo_json_2af9830ef68420da295e9fbbf418b0d8_add (data) {
|
||||
geo_json_2af9830ef68420da295e9fbbf418b0d8
|
||||
function geo_json_1f8972074523b6427449f533ba057bf8_add (data) {
|
||||
geo_json_1f8972074523b6427449f533ba057bf8
|
||||
.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_2af9830ef68420da295e9fbbf418b0d8.setStyle(function(feature) {return feature.properties.style;});
|
||||
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_1f8972074523b6427449f533ba057bf8.setStyle(function(feature) {return feature.properties.style;});
|
||||
|
||||
|
||||
|
||||
geo_json_2af9830ef68420da295e9fbbf418b0d8.addTo(map_fbec21a667da83a7a2d972cb198fec24);
|
||||
geo_json_1f8972074523b6427449f533ba057bf8.addTo(map_23f8e35f17470dacda23acd5395a7fac);
|
||||
|
||||
</script>
|
||||
|
||||
|
|
@ -185,7 +185,7 @@
|
|||
}
|
||||
trajeto_json_add({"features": []});
|
||||
|
||||
trajeto_json.addTo(map_fbec21a667da83a7a2d972cb198fec24);
|
||||
trajeto_json.addTo(map_23f8e35f17470dacda23acd5395a7fac);
|
||||
|
||||
function adicionarGeometria(novaGeometria) {
|
||||
trajeto_json.addData(novaGeometria);
|
||||
|
|
@ -243,7 +243,7 @@
|
|||
}
|
||||
trajeto_dinamico_json_add({"features": []});
|
||||
|
||||
trajeto_dinamico_json.addTo(map_fbec21a667da83a7a2d972cb198fec24);
|
||||
trajeto_dinamico_json.addTo(map_23f8e35f17470dacda23acd5395a7fac);
|
||||
|
||||
function adicionarGeometriaDinamica(novaGeometria) {
|
||||
trajeto_dinamico_json.addData(novaGeometria);
|
||||
|
|
@ -296,9 +296,9 @@
|
|||
|
||||
var marcadorEquipamento = L.marker([0, 0], {
|
||||
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(
|
||||
{"extraClasses": "fa-rotate-0", "icon": "info-sign", "iconColor": "white", "markerColor": "red", "prefix": "glyphicon"}
|
||||
);
|
||||
|
|
@ -380,7 +380,7 @@
|
|||
}
|
||||
|
||||
if (foco) {
|
||||
map_fbec21a667da83a7a2d972cb198fec24.setView(novaPosicao, map_fbec21a667da83a7a2d972cb198fec24.getZoom());
|
||||
map_23f8e35f17470dacda23acd5395a7fac.setView(novaPosicao, map_23f8e35f17470dacda23acd5395a7fac.getZoom());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -397,7 +397,7 @@
|
|||
function atualizarSelecaoRuas(selecionadas) {
|
||||
selecionadas = JSON.parse(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))) {
|
||||
layer.setStyle({ color: 'blue' });
|
||||
} else {
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -23,7 +23,7 @@ class CameraOak:
|
|||
#self.dev_info = dai.DeviceInfo(mx_id)
|
||||
self.mx_id = self.dev_info.getMxId()
|
||||
self.versao = self.dev_info.name
|
||||
self.dispositivo = T_Code.Vzo.value
|
||||
self.dispositivo = T_Code.Vzo
|
||||
self.modelo = "Desconhecido"
|
||||
self.ultima_saude = {}
|
||||
self.timestamp_ultimo_frame_rgb = None
|
||||
|
|
@ -34,8 +34,8 @@ class CameraOak:
|
|||
self.rodando = False
|
||||
self.iniciado = False
|
||||
|
||||
_cameras = ContextoGlobalRedis.get_cameras()
|
||||
_camera = _cameras.get(self.mx_id, {})
|
||||
_camera = ContextoGlobalRedis.get_camera(self.mx_id) or {}
|
||||
_camera["mx_id"] = self.mx_id
|
||||
_camera["versao"] = self.versao
|
||||
_camera["iniciando"] = True
|
||||
_camera["iniciado"] = False
|
||||
|
|
@ -49,21 +49,22 @@ class CameraOak:
|
|||
|
||||
if dai.CameraBoardSocket.LEFT in sensores and dai.CameraBoardSocket.RIGHT in sensores:
|
||||
self.modelo = "OAK-D Lite"
|
||||
self.dispositivo = T_Code.Snr.value
|
||||
self.dispositivo = T_Code.Snr
|
||||
self.tem_depth = True
|
||||
self.tem_imu = True
|
||||
elif dai.CameraBoardSocket.CAM_A in sensores:
|
||||
self.modelo = "OAK-1 Lite W"
|
||||
self.dispositivo = T_Code.Cam.value
|
||||
self.dispositivo = T_Code.Cam
|
||||
self.tem_depth = False
|
||||
else:
|
||||
self.dispositivo = T_Code.Vzo
|
||||
self.mostrar_log(f"[WARN] Sensores desconhecidos: {sensores}")
|
||||
except Exception as e:
|
||||
pass
|
||||
self.mostrar_log(f"Falha ao detectar sensores: {e}")
|
||||
|
||||
_camera["modelo"] = self.modelo
|
||||
_camera["dispositivo"] = self.dispositivo
|
||||
_camera["dispositivo"] = self.dispositivo.value
|
||||
_camera["tem_depht"] = self.tem_depth
|
||||
_camera["tem_imu"] = self.tem_imu
|
||||
|
||||
|
|
@ -124,8 +125,11 @@ class CameraOak:
|
|||
}
|
||||
_camera["parametros"] = self.parametros
|
||||
|
||||
self.ultima_saude["timestamp"] = time.time()
|
||||
|
||||
self.iniciado = True
|
||||
_camera["iniciado"] = True
|
||||
_camera["iniciado_em"] = time.time()
|
||||
|
||||
except Exception as e:
|
||||
self.mostrar_log(f"Erro ao iniciar camera: {e}")
|
||||
|
|
@ -133,7 +137,7 @@ class CameraOak:
|
|||
|
||||
_camera["iniciando"] = False
|
||||
|
||||
ContextoGlobalRedis.set(CtxKey.DadosCameras, _cameras)
|
||||
ContextoGlobalRedis.set(ContextoGlobalRedis.CamKey(self.mx_id), _camera)
|
||||
|
||||
def _criar_pipeline(self):
|
||||
pipeline = dai.Pipeline()
|
||||
|
|
@ -311,14 +315,12 @@ class CameraOak:
|
|||
return None, {"erro": str(e), "duracao": dur, "frame_valido": False}
|
||||
|
||||
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:
|
||||
self.imu.atualizar_saude()
|
||||
|
||||
_cameras = ContextoGlobalRedis.get_cameras()
|
||||
|
||||
conectado = _cameras.get(self.mx_id) is not None
|
||||
conectado = ContextoGlobalRedis.get_cameras().get(self.mx_id) is not None
|
||||
|
||||
saude = 50
|
||||
motivos = []
|
||||
|
|
@ -412,7 +414,10 @@ class CameraOak:
|
|||
elif saude < 80:
|
||||
status = StatusModulo.ALERTA
|
||||
|
||||
agora = time.time()
|
||||
|
||||
saude_geral = {
|
||||
"timestamp": agora,
|
||||
"conectado": conectado,
|
||||
"status": status.value,
|
||||
"saude": saude,
|
||||
|
|
@ -422,14 +427,13 @@ class CameraOak:
|
|||
|
||||
self.ultima_saude = saude_geral
|
||||
|
||||
agora = time.time()
|
||||
timeout = 2.0
|
||||
ts_depth = self.timestamp_ultimo_frame_depth or 0
|
||||
ts_rgb = self.timestamp_ultimo_frame_rgb or 0
|
||||
self.rodando = ((agora - ts_depth) <= timeout or (agora - ts_rgb) <= timeout)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -27,22 +27,15 @@ class CameraManager:
|
|||
cameras_mapeadas[mx_id] = {
|
||||
"timestamp": self._ultimo_scan,
|
||||
"mx_id": mx_id,
|
||||
"iniciado": False,
|
||||
"dispositivo": T_Code.Vzo.value
|
||||
}
|
||||
else:
|
||||
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)
|
||||
|
||||
for cam in cameras_mapeadas.values():
|
||||
for cam_id in cameras_mapeadas.keys():
|
||||
cam = ContextoGlobalRedis.get_camera(cam_id)
|
||||
if cam is not None:
|
||||
if cam.get("dispositivo", T_Code.Vzo.value) == T_Code.Snr.value:
|
||||
ContextoGlobalRedis.publicar_comando(CmdKey.VisualWorkerRx, { "cmd": VisualWorkerCommandType.AtualizarSaudeCamera.value } )
|
||||
elif cam.get("dispositivo", T_Code.Vzo.value) == T_Code.Cam.value:
|
||||
|
|
@ -56,26 +49,30 @@ class CameraManager:
|
|||
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}")
|
||||
_cameras = ContextoGlobalRedis.get_cameras()
|
||||
conectado = _cameras.get(mx_id) is not None
|
||||
_camera = ContextoGlobalRedis.get_camera(mx_id)
|
||||
#print(_cameras)
|
||||
saude_geral = {
|
||||
"timestamp": time.time() if ts is None else ts,
|
||||
"conectado": conectado,
|
||||
"status": status.value,
|
||||
"saude": saude,
|
||||
"motivos": motivos,
|
||||
"saude_idividual": []
|
||||
}
|
||||
if not conectado:
|
||||
_cameras[mx_id] = {}
|
||||
_cameras[mx_id]["saude"] = saude_geral
|
||||
_cameras[mx_id]["rodando"] = rodando
|
||||
_cameras[mx_id]["performance"] = performance
|
||||
disp = T_Code(_cameras[mx_id].get("dispositivo", T_Code.Vzo.value))
|
||||
#if not conectado:
|
||||
# _cameras[mx_id] = {}
|
||||
_camera["saude"] = saude_geral
|
||||
_camera["rodando"] = rodando
|
||||
_camera["performance"] = performance
|
||||
if disp is not None:
|
||||
_camera["dispositivo"] = disp.value
|
||||
disp = T_Code(_camera.get("dispositivo", T_Code.Vzo.value))
|
||||
if disp != T_Code.Vzo:
|
||||
ContextoGlobalRedis.atualizar_ctx_dict(
|
||||
ContextoGlobalRedis.ModKey(disp),
|
||||
saude=saude_geral
|
||||
)
|
||||
ContextoGlobalRedis.set(CtxKey.DadosCameras, _cameras)
|
||||
ContextoGlobalRedis.set(ContextoGlobalRedis.CamKey(mx_id), _camera)
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -12,6 +12,7 @@ def main():
|
|||
from health_worker.modulos.movimentacao import ModuloMovimentacao
|
||||
from health_worker.modulos.sensoriamento import ModuloSensoriamento
|
||||
from health_worker.modulos.atuador import ModuloAtuador
|
||||
from health_worker.modulos.imu import IMUCamera
|
||||
from health_worker.config import mostrar_log
|
||||
from shared.contexto_global_redis import ContextoGlobalRedis, CmdKey, CtxKey
|
||||
|
||||
|
|
@ -20,7 +21,8 @@ def main():
|
|||
T_Code.Dir: ModuloDirecional(),
|
||||
T_Code.Mov: ModuloMovimentacao(),
|
||||
T_Code.Sen: ModuloSensoriamento(),
|
||||
T_Code.Atu: ModuloAtuador()
|
||||
T_Code.Atu: ModuloAtuador(),
|
||||
T_Code.Imu: IMUCamera()
|
||||
}
|
||||
|
||||
def loop_ativo():
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -8,8 +8,11 @@ from shared.enums import StatusModulo, T_Code
|
|||
from health_worker.modulos.base import 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
|
||||
|
||||
if queue is None: return
|
||||
|
||||
self.imu_queue = queue
|
||||
self.filtro_imu = Madgwick(beta=0.8, frequency=freq)
|
||||
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -1,6 +1,5 @@
|
|||
import time
|
||||
|
||||
import numpy as np
|
||||
from shared.enums import ModoOperacao, StatusCarroMapa, StatusModulo, StatusOperacao, T_Code, TipoMovimentoDirecional, TiposControladorDirecional
|
||||
from shared.contexto_global_redis import ContextoGlobalRedis, CtxKey
|
||||
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_operante = (_snr.get("saude", {}).get("status", StatusModulo.DESCONECTADO.value)) == StatusModulo.OPERANTE.value
|
||||
_dados_vw = ContextoGlobalRedis.get(CtxKey.DadosVisualWorker, {})
|
||||
_snapshot_vw = _dados_vw.get("matrizes", {}).get("confianca")
|
||||
_snapshot_vw = _dados_vw.get("matriz_confianca")
|
||||
custo = None
|
||||
nav = None
|
||||
dists = None
|
||||
escalas = None
|
||||
d_obs_min = None
|
||||
block = None
|
||||
if not _snapshot_vw:
|
||||
visual_worker_atualizado = False
|
||||
|
|
@ -36,7 +34,6 @@ def definir_comando(pid: PIDAdaptativo):
|
|||
custo, conf, anom, nav = unpack_snapshot(_snapshot_vw)
|
||||
dists = _snapshot_vw.get("row_dist_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)
|
||||
|
||||
_gps = ContextoGlobalRedis.get_modulo(T_Code.Gps)
|
||||
|
|
@ -86,7 +83,6 @@ def definir_comando(pid: PIDAdaptativo):
|
|||
"Navegavel": nav,
|
||||
"DistanciasRef": dists,
|
||||
"EscalasX": escalas,
|
||||
"DistanciaObsMin": d_obs_min,
|
||||
"Block": block
|
||||
},
|
||||
"Camera": {
|
||||
|
|
@ -97,7 +93,7 @@ def definir_comando(pid: PIDAdaptativo):
|
|||
},
|
||||
"RegrasAtivas": {
|
||||
"matriz_custo": False,
|
||||
"deteccao_obstaculos": True,
|
||||
"deteccao_obstaculos": False,
|
||||
},
|
||||
"Equipamento": {
|
||||
"largura": _equipamento.get("largura", 0.85),
|
||||
|
|
@ -201,37 +197,16 @@ def _regras_taticas(contexto):
|
|||
|
||||
# >>> NOVO: gate de bloqueio/parede com block + d_obs_min <<<
|
||||
if vw_operante and contexto.get("RegrasAtivas", {}).get("deteccao_obstaculos", False):
|
||||
block = dados_vw.get("MatrizCusto", {}).get("Block", None)
|
||||
d_obs_min = dados_vw.get("MatrizCusto", {}).get("DistanciaObsMin", None)
|
||||
block = dados_vw.get("MatrizCusto", {}).get("Block", {})
|
||||
reason = block.get("reason", "none")
|
||||
decision = block.get("decision", {})
|
||||
|
||||
#print(block)
|
||||
|
||||
# parâmetros de parada (ajuste se quiser puxar do config)
|
||||
a_max_freio = 0.8 # m/s²
|
||||
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.")
|
||||
if decision.get("parar", False):
|
||||
mostrar_log(f"🟥 Parada necessaria por {reason}.")
|
||||
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
|
||||
if isinstance(block, dict) and reason == "narrow":
|
||||
sb = (block.get("side_bias") or {}).get("value", 0.0)
|
||||
|
|
|
|||
|
|
@ -251,81 +251,83 @@ class ControladorMPC:
|
|||
except Exception as e:
|
||||
mostrar_log(f"Erro ao consultar proximo ponto nao visitado: {e}")
|
||||
|
||||
def _corrigir_pontos_visitados(
|
||||
self, x, y, pontos_visitados: np.ndarray, idx_atual: int = -1,
|
||||
limite_max_avanço: float = 5.0 # em METROS
|
||||
):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
def _corrigir_pontos_visitados(self, x, y, pontos_visitados: np.ndarray, idx_atual: int = -1,
|
||||
limite_max_avanco: float = 5.0, # em METROS
|
||||
limite_max_pontos: int = 10):
|
||||
try:
|
||||
# 1) caminho em arrays (pré-calcule isso uma vez no __init__ ou quando pontos mudarem)
|
||||
# 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)
|
||||
|
||||
p_xy, p_mg, p_s = self._p_xy, self._p_margem, self._p_s
|
||||
N = p_xy.shape[0]
|
||||
if N == 0:
|
||||
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:
|
||||
pontos_visitados[:idx_atual+1] = True
|
||||
pontos_visitados[:idx_atual] = True # <<< EXCLUSIVO (antes era :idx_atual+1)
|
||||
return idx_atual
|
||||
|
||||
# 3) comece da "fronteira" atual (primeiro False)
|
||||
# se já tiver tudo visitado, devolve último índice
|
||||
if pontos_visitados.all():
|
||||
return N - 1
|
||||
|
||||
# próximo não visitado (rápido)
|
||||
start_idx = int(np.argmax(~pontos_visitados))
|
||||
|
||||
# 4) limitar varredura por distância acumulada (em METROS)
|
||||
# queremos procurar até "start_idx" + Δs (metros), convertendo via s_acum
|
||||
# janela por metros + clamp por nº de pontos (compatível com o antigo)
|
||||
s_start = p_s[start_idx]
|
||||
s_lim = s_start + float(limite_max_avanço)
|
||||
|
||||
# encontre o último índice com s <= s_lim (searchsorted é O(log N))
|
||||
# atenção: searchsorted trabalha com array crescente
|
||||
s_lim = s_start + float(limite_max_avanco)
|
||||
end_idx = int(np.searchsorted(p_s, s_lim, side="right") - 1)
|
||||
if 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 + 150)
|
||||
end_idx = min(end_idx, start_idx + int(limite_max_pontos))
|
||||
|
||||
# 5) checagem vetorizada na janela [start_idx:end_idx] (inclusivo)
|
||||
sl = slice(start_idx, end_idx + 1)
|
||||
dx = x - p_xy[sl, 0]
|
||||
dy = y - p_xy[sl, 1]
|
||||
dist2 = dx*dx + dy*dy
|
||||
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):
|
||||
# pega o primeiro índice da janela que bateu
|
||||
off = int(np.argmax(hits)) # primeiro True
|
||||
off = int(np.argmax(hits))
|
||||
idx = start_idx + off
|
||||
# marca até ele (inclusive)
|
||||
pontos_visitados[:idx+1] = True
|
||||
return idx
|
||||
|
||||
# 6) se ninguém bateu dentro da janela, mantém visitados como estão e retorna o próximo não visitado
|
||||
# (isso replica seu comportamento original)
|
||||
prox = int(np.argmax(~pontos_visitados))
|
||||
return prox
|
||||
# --- (3) Fallback robusto para “próximo não visitado” ---
|
||||
nv = np.flatnonzero(~pontos_visitados)
|
||||
return int(nv[0]) if nv.size else (N - 1)
|
||||
|
||||
except Exception as e:
|
||||
mostrar_log(f"Erro ao corrigir pontos visitados: {e}")
|
||||
# fallback conservador
|
||||
try:
|
||||
prox = int(np.argmax(~pontos_visitados))
|
||||
return prox
|
||||
nv = np.flatnonzero(~pontos_visitados)
|
||||
return int(nv[0]) if nv.size else (N - 1)
|
||||
except Exception:
|
||||
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):
|
||||
try:
|
||||
peso_erro_pos = 1.2 # 0
|
||||
|
|
@ -379,7 +381,7 @@ class ControladorMPC:
|
|||
comando = self._processar_mpc_receding(contexto, comando_anterior)
|
||||
|
||||
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")
|
||||
return comando
|
||||
|
||||
|
|
@ -444,11 +446,11 @@ class ControladorMPC:
|
|||
for passo in range(max_simulacoes):
|
||||
if ms_left() <= 0:
|
||||
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]
|
||||
|
||||
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}")
|
||||
|
||||
|
|
@ -461,7 +463,7 @@ class ControladorMPC:
|
|||
"custo": 0.0,
|
||||
"comandos": [(tipo_anterior, angulo_anterior)],
|
||||
"trajetoria": [],
|
||||
"visitados": self.visitados_execucao.copy(),
|
||||
"visitados": pts_visitados,
|
||||
"inicial": True
|
||||
}]
|
||||
|
||||
|
|
@ -541,7 +543,7 @@ class ControladorMPC:
|
|||
|
||||
t_c_ini = now()
|
||||
try:
|
||||
custo, sim, valido = self._simular_passo(
|
||||
custo, sim, valido, visitados_sim = self._simular_passo(
|
||||
x_atual, y_atual, theta_atual,
|
||||
tipo_k, ang_k,
|
||||
custos_candidatos,
|
||||
|
|
@ -555,7 +557,7 @@ class ControladorMPC:
|
|||
"custo": candidato["custo"] + float(custo),
|
||||
"comandos": candidato["comandos"] + [(tipo_k, ang_k)],
|
||||
"trajetoria": candidato["trajetoria"] + sim,
|
||||
"visitados": visitados.copy(), # copy apenas ao guardar
|
||||
"visitados": visitados_sim,
|
||||
"inicial": False
|
||||
}
|
||||
novos_candidatos.append(novo)
|
||||
|
|
@ -1475,10 +1477,10 @@ class ControladorMPC:
|
|||
except Exception as 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:
|
||||
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):
|
||||
distancia_m = velocidade * self.dt
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -9,7 +9,7 @@ from shared.enums import ManagerWorkerCommandType, ModoOperacao, StatusModulo, S
|
|||
from manager_worker.modulos.mpc import inicializar as iniciar_mpc
|
||||
|
||||
class CtxKey(str, Enum):
|
||||
DadosCameras = "ctx:dados_cameras"
|
||||
DadosCameras = "ctx:dados_cameras_"
|
||||
DadosModulo = "ctx:dados_modulo_"
|
||||
DadosOperacao = "ctx:dados_operacao"
|
||||
DadosContexto = "ctx:dados_contexto"
|
||||
|
|
@ -154,10 +154,19 @@ class ContextoGlobalRedis:
|
|||
def get_controle(cls):
|
||||
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
|
||||
def get_cameras(cls):
|
||||
return cls.get(CtxKey.DadosCameras, {})
|
||||
|
||||
@classmethod
|
||||
def get_camera(cls, mx_id: str):
|
||||
return cls.get(cls.CamKey(mx_id), None)
|
||||
|
||||
@classmethod
|
||||
def get_equipamento(cls):
|
||||
return cls.get(CtxKey.DadosEquipamento, {})
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -23,7 +23,6 @@ class CameraManager:
|
|||
def __init__(self, mostrar_log):
|
||||
self.mostrar_log = mostrar_log
|
||||
self.mx_id = None
|
||||
self.tempo_saude = 10
|
||||
self.reiniciar_status()
|
||||
|
||||
def reiniciar_status(self):
|
||||
|
|
@ -40,7 +39,6 @@ class CameraManager:
|
|||
self._ultimo_depth_frame = None
|
||||
self._ts_segmentacao_anterior = 0
|
||||
self._pool = ThreadPoolExecutor(max_workers=6)
|
||||
self._ultima_saude_ts = 0
|
||||
|
||||
def inicializar(self, mx_id):
|
||||
if self.iniciando:
|
||||
|
|
@ -121,14 +119,13 @@ class CameraManager:
|
|||
return d # <-- ndarray, não list
|
||||
|
||||
def atualizar_saude_camera(self):
|
||||
self._ultima_saude_ts = time.time()
|
||||
#self.mostrar_log("Atualizando saude da camera...")
|
||||
try:
|
||||
if self.camera is not None:
|
||||
self.camera.atualizar_saude()
|
||||
elif self.mx_id is not None:
|
||||
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:
|
||||
self.mostrar_log(f"[saude] erro: {e}")
|
||||
|
||||
|
|
@ -224,19 +221,16 @@ class CameraManager:
|
|||
def _iniciar_loop_analise_continua(self, freq):
|
||||
def loop():
|
||||
ultima_atualizacao = 0
|
||||
self._ultima_saude_ts = 0
|
||||
while True:
|
||||
if self.camera is None:
|
||||
time.sleep(5)
|
||||
continue
|
||||
|
||||
t0 = time.time()
|
||||
if (t0 - self._ultima_saude_ts) >= self.tempo_saude:
|
||||
self._ultima_saude_ts = time.time()
|
||||
self.atualizar_saude_camera()
|
||||
try:
|
||||
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:
|
||||
self.camera.imu.parar()
|
||||
self.reiniciar_status()
|
||||
|
|
@ -276,32 +270,6 @@ class CameraManager:
|
|||
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; "
|
||||
|
||||
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):
|
||||
|
|
@ -316,6 +284,7 @@ class CameraManager:
|
|||
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_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):
|
||||
executor = self._pool
|
||||
|
|
@ -417,14 +386,14 @@ class CameraManager:
|
|||
ContextoGlobalRedis.atualizar_ctx_dict(
|
||||
CtxKey.DadosVisualWorker,
|
||||
ts_analise=time.time(),
|
||||
matrizes__confianca=snapshot
|
||||
matriz_confianca=snapshot
|
||||
#perfil_corredor__segmentacao=converter_valores_numpy(self._ultima_analise_segmentacao.get("corredor_perfil", []))
|
||||
)
|
||||
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"])
|
||||
#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:
|
||||
self.mostrar_log(f"❌ Erro na geracao da matriz de confianca: {e}")
|
||||
finally:
|
||||
|
|
@ -529,6 +498,7 @@ class CameraManager:
|
|||
self._analisando_matriz_custo = False
|
||||
#self.mostrar_log("Matriz de custo concluida")
|
||||
|
||||
|
||||
def salvar_frames(self, tipos: list, nome: str, pasta="frames_salvos"):
|
||||
if not self.operante:
|
||||
return []
|
||||
|
|
@ -582,444 +552,6 @@ class CameraManager:
|
|||
return caminho_analise
|
||||
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):
|
||||
# Valores podem variar de -1.5 ~ 6.0
|
||||
|
|
@ -1644,214 +1176,6 @@ class CameraManager:
|
|||
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):
|
||||
"""Overlay BGR com soma segura (clamped) nas regiões de máscara."""
|
||||
H, W = anom_f.shape
|
||||
|
|
@ -1915,10 +1239,12 @@ class CameraManager:
|
|||
|
||||
blocked = bool(metrics.get("blocked", False))
|
||||
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)
|
||||
cov_cent = metrics.get("coverage", {}).get("central_max", 0.0)
|
||||
cov_glob = metrics.get("coverage", {}).get("global", 0.0)
|
||||
decision = metrics.get("decision", {})
|
||||
sb = metrics.get("side_bias", {}) or {}
|
||||
side_val = float(sb.get("value", 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),
|
||||
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 ---
|
||||
# empurra um pouco pra baixo do L/R
|
||||
y0_dec = y_bar + 50
|
||||
|
||||
dec_txt = "PARAR" if deve_parar else "LIVRE"
|
||||
dec_col = (0, 0, 255) if deve_parar else (0, 200, 0)
|
||||
dec_info = (f"v={v:.2f} m/s d_obs={('-' if d_obs_min is None else f'{d_obs_min:.2f} m')} "
|
||||
f"d_necess={dist_necessaria:.2f} m")
|
||||
parada_necessaria = decision.get("parar", False)
|
||||
dec_txt = "PARAR" if parada_necessaria else "LIVRE"
|
||||
dec_col = (0, 0, 255) if parada_necessaria else (0, 200, 0)
|
||||
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
|
||||
panel_w2 = max(280, bar_w + 100)
|
||||
|
|
@ -2052,12 +1372,13 @@ class CameraManager:
|
|||
cv2.addWeighted(overlay2, 0.35, vis, 0.65, 0, vis)
|
||||
|
||||
# linha 1: PARAR / LIVRE (grande)
|
||||
put_text_outlined(vis, f"{dec_txt}", (x0, y0_dec-4),
|
||||
font, 0.80, dec_col, thick)
|
||||
put_text_outlined(vis, f"{dec_txt}", (x0, y0_dec-4), font, 0.80, dec_col, thick)
|
||||
|
||||
# linha 2: detalhes (menor)
|
||||
put_text_outlined(vis, dec_info, (x0, y0_dec+18),
|
||||
font, 0.58, (255,255,255), thick)
|
||||
put_text_outlined(vis, dec_info, (x0, y0_dec+18), 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 = [
|
||||
|
|
@ -2073,8 +1394,7 @@ class CameraManager:
|
|||
|
||||
status_txt = f"BLOCKED: {blocked} ({reason})"
|
||||
status_col = (0,0,255) if blocked else (0,255,0)
|
||||
cv2.putText(vis, status_txt, (Wf - 10 - 8*len(status_txt), 24),
|
||||
cv2.FONT_HERSHEY_SIMPLEX, 0.6, status_col, 2, cv2.LINE_AA)
|
||||
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.imshow(win_name, vis)
|
||||
cv2.waitKey(1)
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -39,7 +39,8 @@ class CostmapFuser:
|
|||
self.buf_conf = []
|
||||
self.buf_anom = []
|
||||
self.buf_nav = []
|
||||
self.buf_zref = [] # opcional
|
||||
self.buf_zref = []
|
||||
self.buf_zmed = []
|
||||
self.buf_ts = []
|
||||
|
||||
self._blk_state = {
|
||||
|
|
@ -91,23 +92,267 @@ class CostmapFuser:
|
|||
return lin[::-1]
|
||||
return lin
|
||||
|
||||
def _compute_d_obs_min(self, custo_fused, nav_fused, z_ref_2d=None):
|
||||
"""Menor distância livre (m) na janela central; retorna None se livre."""
|
||||
i0, i1 = self.central_cols
|
||||
blocked = (custo_fused[:, i0:i1 + 1] > self.block_thr)
|
||||
if nav_fused is not None:
|
||||
blocked |= (nav_fused[:, i0:i1 + 1] == 0)
|
||||
def _compute_blockage_metrics(
|
||||
self,
|
||||
custo_f, anom_f, conf_f, nav_f, zmed_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
|
||||
):
|
||||
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():
|
||||
return None
|
||||
# 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
|
||||
|
||||
row_dists = self._row_distances(z_ref_2d)
|
||||
d_min = None
|
||||
for j in self._rows_near_to_far():
|
||||
if blocked[j].any():
|
||||
d = float(row_dists[j])
|
||||
d_min = d if (d_min is None or d < d_min) else d_min
|
||||
return d_min
|
||||
# 2) Largura em colunas por linha
|
||||
width_need_m = robot_width_m + margin_m
|
||||
cols_need = np.empty(H, dtype=int)
|
||||
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[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):
|
||||
"""metros por célula em X para cada linha, dado FOV_H."""
|
||||
|
|
@ -133,6 +378,9 @@ class CostmapFuser:
|
|||
zref = grid_dict.get("z_ref", None)
|
||||
if zref is not None:
|
||||
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)
|
||||
H, W = custo.shape
|
||||
|
|
@ -145,11 +393,12 @@ class CostmapFuser:
|
|||
self.buf_nav.append(nav)
|
||||
self.buf_ts.append(ts)
|
||||
self.buf_zref.append(zref)
|
||||
self.buf_zmed.append(zmed)
|
||||
|
||||
# mantém no máximo 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_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
|
||||
S_custo = self._stack(self.buf_custo, 0.0)
|
||||
|
|
@ -177,7 +426,7 @@ class CostmapFuser:
|
|||
else:
|
||||
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
|
||||
if any(z is not None for z in self.buf_zref):
|
||||
# pega a última não-nula
|
||||
|
|
@ -186,7 +435,14 @@ class CostmapFuser:
|
|||
zref_f = z
|
||||
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
|
||||
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
|
||||
|
||||
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,
|
||||
use_persistence=True,
|
||||
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
|
||||
)
|
||||
|
||||
|
|
@ -231,7 +487,6 @@ class CostmapFuser:
|
|||
"conf_u8": to_u8_list(conf_f),
|
||||
"anom_u8": to_u8_list(anom_f),
|
||||
"nav_mask": nav_f.astype(np.uint8).ravel().tolist(),
|
||||
"d_obs_min": block["d_obs_min"],
|
||||
"block": block
|
||||
}
|
||||
|
||||
|
|
@ -242,188 +497,6 @@ class CostmapFuser:
|
|||
|
||||
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: já 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
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ from enum import IntEnum
|
|||
import time
|
||||
import cv2
|
||||
import numpy as np
|
||||
from collections import deque
|
||||
|
||||
from shared.utils import encode_image_base64
|
||||
from shared.enums import StatusCarroMapa
|
||||
|
|
@ -33,6 +34,19 @@ class SegmentacaoManager:
|
|||
self.log = None
|
||||
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):
|
||||
try:
|
||||
self.pred_rgb[:] = self.lut[predictions]
|
||||
|
|
@ -135,108 +149,253 @@ class SegmentacaoManager:
|
|||
return perfil
|
||||
|
||||
|
||||
|
||||
def _bool_hysteresis(self, prev: bool, x: float, thr_on: float, thr_off: float) -> bool:
|
||||
"""
|
||||
Histerese booleana: se estava False, liga só se x > thr_on;
|
||||
se estava True, só 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):
|
||||
altura, largura = mask_classes.shape
|
||||
centro_img = largura // 2
|
||||
H, W = mask_classes.shape
|
||||
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)
|
||||
if num_labels <= 1:
|
||||
return np.zeros_like(mask_rua, dtype=np.uint8)
|
||||
|
||||
melhor_blob_idx = -1
|
||||
melhor_score = float('-inf')
|
||||
for i in range(1, num_labels): # ignora fundo
|
||||
best_i, best_score = -1, -1e9
|
||||
|
||||
# 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]
|
||||
centro_blob = x + w // 2
|
||||
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))
|
||||
if area < 50: # lixo
|
||||
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)
|
||||
if len(centros_corredor) >= 2:
|
||||
# Angular curto
|
||||
(x1c, y1c), (x2c, y2c) = centros_corredor[0], centros_corredor[1]
|
||||
erro_angular_curto = np.arctan2(x2c - x1c, y1c - y2c)
|
||||
# Angular longo
|
||||
(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
|
||||
cx = x + w // 2
|
||||
# normalizações 0..1
|
||||
area_n = area / float(H * W)
|
||||
center_n = 1.0 - min(1.0, abs(cx - cx_img) / (W * 0.5))
|
||||
vertical = h / max(1.0, w) # alongamento vertical
|
||||
bottom_touch = 1.0 if (y + h >= H - 2) else 0.0
|
||||
|
||||
# 3. Calcular erro lateral (deslocamento da base)
|
||||
erro_lateral_px = None
|
||||
erro_lateral_pct = 0
|
||||
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
|
||||
prev_bias = 0.0
|
||||
if prev_cx is not None:
|
||||
prev_bias = 1.0 - min(1.0, abs(cx - prev_cx) / (W * 0.5))
|
||||
|
||||
# 5. Novo status de corredor
|
||||
LIMIAR_CANA = 1000
|
||||
parte_cima = self.predictions[:int(ALTURA * 0.3), :]
|
||||
parte_baixo = self.predictions[int(ALTURA * 0.7):, :]
|
||||
score = (w_area*area_n +
|
||||
w_center*center_n +
|
||||
w_bottom*bottom_touch +
|
||||
w_vertical*vertical +
|
||||
w_prev*prev_bias)
|
||||
|
||||
cana_cima = np.sum(parte_cima == ClassesSegmentacao.CANA.value)
|
||||
cana_baixo = np.sum(parte_baixo == ClassesSegmentacao.CANA.value)
|
||||
cana_total = np.sum(self.predictions == ClassesSegmentacao.CANA.value)
|
||||
if score > best_score:
|
||||
best_score = score
|
||||
best_i = i
|
||||
|
||||
if cana_total < 500:
|
||||
status_corredor = StatusCarroMapa.Direcionando
|
||||
elif cana_baixo > LIMIAR_CANA and cana_cima > LIMIAR_CANA:
|
||||
status_corredor = StatusCarroMapa.CaminhandoRua
|
||||
elif cana_baixo < LIMIAR_CANA and cana_cima > LIMIAR_CANA:
|
||||
status_corredor = StatusCarroMapa.EntrandoRua
|
||||
elif cana_baixo > LIMIAR_CANA and cana_cima < LIMIAR_CANA:
|
||||
status_corredor = StatusCarroMapa.SaindoRua
|
||||
self._prev_cx = None
|
||||
if best_i == -1:
|
||||
return np.zeros_like(mask_rua, dtype=np.uint8)
|
||||
|
||||
# guarda cx do blob vencedor pro próximo frame
|
||||
bx, by, bw, bh, _ = stats[best_i]
|
||||
self._prev_cx = bx + bw // 2
|
||||
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 {
|
||||
"height": ALTURA,
|
||||
"width": LARGURA,
|
||||
"erro_angular": round(np.degrees(erro_angular), 3) if erro_angular is not None else None,
|
||||
"erro_lateral_pct": erro_lateral_pct,
|
||||
"status_corredor": status_corredor.value,
|
||||
"centros_corredor": centros_corredor
|
||||
"height": H,
|
||||
"width": W,
|
||||
"erro_angular": ang_out,
|
||||
"erro_lateral_pct": lat_out,
|
||||
"status_corredor": status_final.value,
|
||||
"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):
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -4,7 +4,7 @@ import threading
|
|||
import time
|
||||
|
||||
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 camera_worker.camera_oak import CameraOak
|
||||
from shared.contexto_global_redis import CmdKey, ContextoGlobalRedis, CtxKey
|
||||
|
|
@ -15,7 +15,6 @@ class CameraManager:
|
|||
def __init__(self, mostrar_log):
|
||||
self.mostrar_log = mostrar_log
|
||||
self.mx_id = None
|
||||
self.tempo_saude = 10
|
||||
self.reiniciar_status()
|
||||
|
||||
def reiniciar_status(self):
|
||||
|
|
@ -26,7 +25,6 @@ class CameraManager:
|
|||
self._ultimo_rgb_frame = None
|
||||
self._ultima_analise = {}
|
||||
self._ts_segmentacao_anterior = 0
|
||||
self._ultima_saude_ts = 0
|
||||
|
||||
def inicializar(self, mx_id):
|
||||
if self.iniciando:
|
||||
|
|
@ -78,7 +76,7 @@ class CameraManager:
|
|||
self.camera.atualizar_saude()
|
||||
elif self.mx_id is not None:
|
||||
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:
|
||||
self.mostrar_log(f"[saude] erro: {e}")
|
||||
|
||||
|
|
@ -141,19 +139,16 @@ class CameraManager:
|
|||
|
||||
def _iniciar_loop_analise_continua(self, freq):
|
||||
def loop():
|
||||
self._ultima_saude_ts = 0
|
||||
while True:
|
||||
if self.camera is None:
|
||||
time.sleep(5)
|
||||
continue
|
||||
|
||||
t0 = time.time()
|
||||
if (t0 - self._ultima_saude_ts) >= self.tempo_saude:
|
||||
self._ultima_saude_ts = time.time()
|
||||
self.atualizar_saude_camera()
|
||||
try:
|
||||
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()
|
||||
continue
|
||||
elif self.operante and status == StatusModulo.OPERANTE and self.camera is not None:
|
||||
|
|
|
|||
Loading…
Reference in New Issue