ajustes ia sonar

This commit is contained in:
Diego Freitas 2025-08-11 20:11:23 -03:00
parent 2266ddc84d
commit d1e8828e0f
58 changed files with 809 additions and 422 deletions

Binary file not shown.

View File

@ -136,6 +136,7 @@ namespace AgroBase.Forms.Operacoes
picsCamSoloSeg = new List<PictureBox>();
picsCamSoloOverlay = new List<PictureBox>();
flwCameras.Controls.Clear();
LogsCam_Solo = new List<List<CameraWorkerItemModel>>();
foreach (string cam in data.cam_solo)
{
var log = JsonConvert.DeserializeObject<List<CameraWorkerItemModel>>(OperacaoModel.DeserializarDadosOperacao(ofd.FileName, cam)).ToList();

View File

@ -136,8 +136,11 @@ namespace AgroBase.Models
{
// Para gráficos de linha
var _momentos = Momentos.GetRange(startIdx, endIdx - startIdx + 1).ToList();
var _valores = Serie.Valores.GetRange(startIdx, endIdx - startIdx + 1).ToList();
PopularSerieGraficoLinha(chart, Serie.Titulo, _momentos, _valores, Serie.Visivel, Serie.MostrarValor);
if (Serie.Valores.Count() > 0)
{
var _valores = Serie.Valores.GetRange(startIdx, endIdx - startIdx + 1).ToList();
PopularSerieGraficoLinha(chart, Serie.Titulo, _momentos, _valores, Serie.Visivel, Serie.MostrarValor);
}
}
}
}

View File

@ -159,6 +159,7 @@ namespace AgroBase.Services.Operadores
("camera_caminho_id", Variaveis.OperacaoEmAndamento.DispSen?.Dados?.CameraCaminho?.Id ?? ""),
("camera_solo_id", Variaveis.OperacaoEmAndamento.DispSen?.Dados?.CamerasSolo?.FirstOrDefault()?.Id ?? ""),
("path_ia_model_ruas", VersionamentoService.ArquivoModeloStreetDetector.CaminhoCompleto),
("path_ia_labelmap_ruas", VersionamentoService.ArquivoLabelmapStreetDetector.CaminhoCompleto),
("path_ia_model_ervas", VersionamentoService.ArquivoModeloWeedDetector.CaminhoCompleto),
("path_ia_labelmap_ervas", VersionamentoService.ArquivoModeloLabelmapWeedDetector.CaminhoCompleto),
("angulo_roll_max", VariaveisEquipamento.AnguloInclinacaoRollMax),

View File

@ -28,7 +28,17 @@ namespace AgroBase.Services
{
lock (_ArquivoLock)
{
return _ArquivosVersionados.FirstOrDefault(x => x.TipoArquivo == TipoArquivoVersionado.ModeloIA_StreetDetector);
return _ArquivosVersionados.Where(x => x.TipoArquivo == TipoArquivoVersionado.ModeloIA_StreetDetector).FirstOrDefault();
}
}
}
public static VersaoArquivoModel ArquivoLabelmapStreetDetector
{
get
{
lock (_ArquivoLock)
{
return _ArquivosVersionados.Where(x => x.TipoArquivo == TipoArquivoVersionado.ModeloIA_StreetDetector).Skip(1).FirstOrDefault();
}
}
}
@ -38,7 +48,7 @@ namespace AgroBase.Services
{
lock (_ArquivoLock)
{
return _ArquivosVersionados.FirstOrDefault(x => x.TipoArquivo == TipoArquivoVersionado.ModeloIA_WeedDetector);
return _ArquivosVersionados.Where(x => x.TipoArquivo == TipoArquivoVersionado.ModeloIA_WeedDetector).FirstOrDefault();
}
}
}
@ -48,7 +58,7 @@ namespace AgroBase.Services
{
lock (_ArquivoLock)
{
return _ArquivosVersionados.Skip(1).FirstOrDefault(x => x.TipoArquivo == TipoArquivoVersionado.ModeloIA_WeedDetector);
return _ArquivosVersionados.Where(x => x.TipoArquivo == TipoArquivoVersionado.ModeloIA_WeedDetector).Skip(1).FirstOrDefault();
}
}
}

View File

@ -29,5 +29,5 @@
"top_topics_and_observing_domains": [ ]
} ],
"hex_encoded_hmac_key": "40F346D3248C3AFDF2BEE1FE496DBD32F7CED6E5AE98B881ABC421AA7E7B5642",
"next_scheduled_calculation_time": "13399758321058597"
"next_scheduled_calculation_time": "13399758321058748"
}

View File

@ -1,3 +1,3 @@
2025/08/08-17:00:08.334 7670 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Local Storage\leveldb/MANIFEST-000001
2025/08/08-17:00:08.341 7670 Recovering log #3
2025/08/08-17:00:08.345 7670 Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Local Storage\leveldb/000003.log
2025/08/11-17:15:07.450 43b4 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Local Storage\leveldb/MANIFEST-000001
2025/08/11-17:15:07.459 43b4 Recovering log #3
2025/08/11-17:15:07.463 43b4 Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Local Storage\leveldb/000003.log

View File

@ -1,3 +1,3 @@
2025/08/08-16:53:28.084 55f8 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Local Storage\leveldb/MANIFEST-000001
2025/08/08-16:53:28.091 55f8 Recovering log #3
2025/08/08-16:53:28.094 55f8 Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Local Storage\leveldb/000003.log
2025/08/11-16:38:48.533 8fe4 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Local Storage\leveldb/MANIFEST-000001
2025/08/11-16:38:48.540 8fe4 Recovering log #3
2025/08/11-16:38:48.543 8fe4 Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Local Storage\leveldb/000003.log

View File

@ -1 +1 @@
{"net":{"http_server_properties":{"servers":[{"alternative_service":[{"advertised_alpns":["h3"],"expiration":"13399243208733068","port":443,"protocol_str":"quic"}],"anonymization":["DAAAAAcAAABmaWxlOi8vAA==",false,0],"network_stats":{"srtt":10995},"server":"https://tile.openstreetmap.org","supports_spdy":true}],"supports_quic":{"address":"2804:d78:627:be00:194b:9f:4c67:e845","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":"13399499518686465","port":443,"protocol_str":"quic"}],"anonymization":["DAAAAAcAAABmaWxlOi8vAA==",false,0],"network_stats":{"srtt":95712},"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"}}}

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,3 @@
2025/08/08-17:00:46.661 7670 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Session Storage/MANIFEST-000001
2025/08/08-17:00:46.662 7670 Recovering log #3
2025/08/08-17:00:46.666 7670 Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Session Storage/000003.log
2025/08/11-17:17:21.484 43b4 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Session Storage/MANIFEST-000001
2025/08/11-17:17:21.485 43b4 Recovering log #3
2025/08/11-17:17:21.489 43b4 Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Session Storage/000003.log

View File

@ -1,3 +1,3 @@
2025/08/08-16:58:34.324 55f8 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Session Storage/MANIFEST-000001
2025/08/08-16:58:34.326 55f8 Recovering log #3
2025/08/08-16:58:34.329 55f8 Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Session Storage/000003.log
2025/08/11-16:44:31.240 8fe4 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Session Storage/MANIFEST-000001
2025/08/11-16:44:31.242 8fe4 Recovering log #3
2025/08/11-16:44:31.246 8fe4 Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Session Storage/000003.log

View File

@ -1,3 +1,3 @@
2025/08/08-17:00:08.246 a298 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Site Characteristics Database/MANIFEST-000001
2025/08/08-17:00:08.248 a298 Recovering log #7
2025/08/08-17:00:08.249 a298 Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Site Characteristics Database/000007.log
2025/08/11-17:15:07.352 5f14 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Site Characteristics Database/MANIFEST-000001
2025/08/11-17:15:07.354 5f14 Recovering log #7
2025/08/11-17:15:07.354 5f14 Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Site Characteristics Database/000007.log

View File

@ -1,3 +1,3 @@
2025/08/08-16:53:28.002 4c34 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Site Characteristics Database/MANIFEST-000001
2025/08/08-16:53:28.003 4c34 Recovering log #7
2025/08/08-16:53:28.004 4c34 Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Site Characteristics Database/000007.log
2025/08/11-16:38:48.452 418c Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Site Characteristics Database/MANIFEST-000001
2025/08/11-16:38:48.453 418c Recovering log #7
2025/08/11-16:38:48.454 418c Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Site Characteristics Database/000007.log

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -21,14 +21,14 @@
"id": 3,
"Arquivo": "model",
"Diretorio": "C:\\AgroBaseModels\\Ruas\\",
"Extensao": ".onnx",
"Extensao": ".blob",
"Versao": "1_1",
"TipoArquivo": 0,
"ArquivoDownload": "street_detector_model-1_1.onnx",
"ArquivoDownload": "street_detector_model-1_1.blob",
},
{
"id": 4,
"Arquivo": "labelmap",
"Arquivo": "model",
"Diretorio": "C:\\AgroBaseModels\\Ruas\\",
"Extensao": ".txt",
"Versao": "1_1",

View File

@ -1 +1 @@
{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"Id":"1","Name":"08_08_2025_16_42_31_Manual","Length":0.0,"Dist1":0.0,"Dist2":0.0},"geometry":{"id":null,"type":"LineString","coordinates":[[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0]]}}]}
{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"Id":"1","Name":"11_08_2025_16_37_30_Manual","Length":0.0,"Dist1":0.0,"Dist2":0.0},"geometry":{"id":null,"type":"LineString","coordinates":[[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0]]}}]}

View File

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

View File

@ -17,7 +17,7 @@
<meta name="viewport" content="width=device-width,
initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<style>
#map_f21522cbe2b071ceb21cf0d5c95f5a21 {
#map_b86da2c0cf19440a67c00659ca7d57e9 {
position: relative;
width: 100.0%;
height: 100.0%;
@ -54,14 +54,14 @@
<body>
<div class="folium-map" id="map_f21522cbe2b071ceb21cf0d5c95f5a21" ></div>
<div class="folium-map" id="map_b86da2c0cf19440a67c00659ca7d57e9" ></div>
</body>
<script>
var map_f21522cbe2b071ceb21cf0d5c95f5a21 = L.map(
"map_f21522cbe2b071ceb21cf0d5c95f5a21",
var map_b86da2c0cf19440a67c00659ca7d57e9 = L.map(
"map_b86da2c0cf19440a67c00659ca7d57e9",
{
center: [0.0, 0.0],
crs: L.CRS.EPSG3857,
@ -78,7 +78,7 @@
var tile_layer_1ca71298385dd222bb7746161b7dbce5 = L.tileLayer(
var tile_layer_b64ed6ac3009000233329a32f2decf43 = L.tileLayer(
"https://tile.openstreetmap.org/{z}/{x}/{y}.png",
{
"minZoom": 0,
@ -95,7 +95,7 @@
);
tile_layer_1ca71298385dd222bb7746161b7dbce5.addTo(map_f21522cbe2b071ceb21cf0d5c95f5a21);
tile_layer_b64ed6ac3009000233329a32f2decf43.addTo(map_b86da2c0cf19440a67c00659ca7d57e9);
@ -111,7 +111,7 @@
}*/
});
}
function geo_json_b0da161aca2ab03761c9445da3d471fe_onEachFeature(feature, layer) {
function geo_json_92428d205e1566c4dea13c3b7eb4954d_onEachFeature(feature, layer) {
layer.on({
@ -148,23 +148,23 @@
}*/
});
};
var geo_json_b0da161aca2ab03761c9445da3d471fe = L.geoJson(null, {
onEachFeature: geo_json_b0da161aca2ab03761c9445da3d471fe_onEachFeature,
var geo_json_92428d205e1566c4dea13c3b7eb4954d = L.geoJson(null, {
onEachFeature: geo_json_92428d205e1566c4dea13c3b7eb4954d_onEachFeature,
...{
}
});
function geo_json_b0da161aca2ab03761c9445da3d471fe_add (data) {
geo_json_b0da161aca2ab03761c9445da3d471fe
function geo_json_92428d205e1566c4dea13c3b7eb4954d_add (data) {
geo_json_92428d205e1566c4dea13c3b7eb4954d
.addData(data);
}
geo_json_b0da161aca2ab03761c9445da3d471fe_add({"features": [{"geometry": {"coordinates": [[0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0]], "id": null, "type": "LineString"}, "id": 0, "properties": {"Dist1": 0.0, "Dist2": 0.0, "Id": "1", "Length": 0.0, "Name": "08_08_2025_16_42_31_Manual"}, "type": "Feature"}], "type": "FeatureCollection"});
geo_json_b0da161aca2ab03761c9445da3d471fe.setStyle(function(feature) {return feature.properties.style;});
geo_json_92428d205e1566c4dea13c3b7eb4954d_add({"features": [{"geometry": {"coordinates": [[0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0]], "id": null, "type": "LineString"}, "id": 0, "properties": {"Dist1": 0.0, "Dist2": 0.0, "Id": "1", "Length": 0.0, "Name": "11_08_2025_16_37_30_Manual"}, "type": "Feature"}], "type": "FeatureCollection"});
geo_json_92428d205e1566c4dea13c3b7eb4954d.setStyle(function(feature) {return feature.properties.style;});
geo_json_b0da161aca2ab03761c9445da3d471fe.addTo(map_f21522cbe2b071ceb21cf0d5c95f5a21);
geo_json_92428d205e1566c4dea13c3b7eb4954d.addTo(map_b86da2c0cf19440a67c00659ca7d57e9);
</script>
@ -185,7 +185,7 @@
}
trajeto_json_add({"features": []});
trajeto_json.addTo(map_f21522cbe2b071ceb21cf0d5c95f5a21);
trajeto_json.addTo(map_b86da2c0cf19440a67c00659ca7d57e9);
function adicionarGeometria(novaGeometria) {
trajeto_json.addData(novaGeometria);
@ -243,7 +243,7 @@
}
trajeto_dinamico_json_add({"features": []});
trajeto_dinamico_json.addTo(map_f21522cbe2b071ceb21cf0d5c95f5a21);
trajeto_dinamico_json.addTo(map_b86da2c0cf19440a67c00659ca7d57e9);
function adicionarGeometriaDinamica(novaGeometria) {
trajeto_dinamico_json.addData(novaGeometria);
@ -296,9 +296,9 @@
var marcadorEquipamento = L.marker([0, 0], {
icon: customIcon
}).addTo(map_f21522cbe2b071ceb21cf0d5c95f5a21);
}).addTo(map_b86da2c0cf19440a67c00659ca7d57e9);
var marcadorBase = L.marker([0, 0], {}).addTo(map_f21522cbe2b071ceb21cf0d5c95f5a21);
var marcadorBase = L.marker([0, 0], {}).addTo(map_b86da2c0cf19440a67c00659ca7d57e9);
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_f21522cbe2b071ceb21cf0d5c95f5a21.setView(novaPosicao, map_f21522cbe2b071ceb21cf0d5c95f5a21.getZoom());
map_b86da2c0cf19440a67c00659ca7d57e9.setView(novaPosicao, map_b86da2c0cf19440a67c00659ca7d57e9.getZoom());
}
}
@ -397,7 +397,7 @@
function atualizarSelecaoRuas(selecionadas) {
selecionadas = JSON.parse(selecionadas);
RuasSelecionadas = Array.isArray(selecionadas) ? [...selecionadas] : [];
geo_json_b0da161aca2ab03761c9445da3d471fe.eachLayer(function (layer) {
geo_json_92428d205e1566c4dea13c3b7eb4954d.eachLayer(function (layer) {
if (RuasSelecionadas.includes(parseInt(layer.feature.id))) {
layer.setStyle({ color: 'blue' });
} else {

View File

@ -192,36 +192,39 @@ class CameraOak:
self.mostrar_log(f"[WARN] Falha ao montar pipeline imu: {e}")
if self.modelo_ia_onboard is not None:
from shared.utils import carregar_labelmap_completo
try:
from shared.utils import carregar_labelmap_completo
# Carregar mapa de cores
labelmap_path = self.modelo_ia_onboard["ia_labelmap_path"]
self.cor_para_id, self.colormap_rgb, self.classes, self.ignore_rgb = carregar_labelmap_completo(labelmap_path)
# Carregar mapa de cores
labelmap_path = self.modelo_ia_onboard["ia_labelmap_path"]
self.cor_para_id, self.colormap_rgb, self.classes, self.ignore_rgb = carregar_labelmap_completo(labelmap_path)
RESOLUCAO = self.modelo_ia_onboard["ia_resolution"]
ROI_INICIO = self.modelo_ia_onboard["ia_roi_begin"]
ROI_TAMANHO = self.modelo_ia_onboard["ia_roi_size"]
blob_path = self.modelo_ia_onboard["ia_model_path"]
RESOLUCAO = self.modelo_ia_onboard["ia_resolution"]
ROI_INICIO = self.modelo_ia_onboard["ia_roi_begin"]
ROI_TAMANHO = self.modelo_ia_onboard["ia_roi_size"]
blob_path = self.modelo_ia_onboard["ia_model_path"]
y1 = 1.0 - (ROI_INICIO + ROI_TAMANHO)
y2 = 1.0 - ROI_INICIO
y1 = 1.0 - (ROI_INICIO + ROI_TAMANHO)
y2 = 1.0 - ROI_INICIO
manip = pipeline.createImageManip()
manip.initialConfig.setCropRect(0.0, y1, 1.0, y2)
manip.initialConfig.setResize(RESOLUCAO[0], RESOLUCAO[1])
manip.initialConfig.setFrameType(dai.RawImgFrame.Type.RGB888p)
manip.initialConfig.setKeepAspectRatio(False)
cam.video.link(manip.inputImage)
manip = pipeline.createImageManip()
manip.initialConfig.setCropRect(0.0, y1, 1.0, y2)
manip.initialConfig.setResize(RESOLUCAO[0], RESOLUCAO[1])
manip.initialConfig.setFrameType(dai.RawImgFrame.Type.RGB888p)
manip.initialConfig.setKeepAspectRatio(False)
cam.video.link(manip.inputImage)
nn = pipeline.createNeuralNetwork()
nn.setBlobPath(blob_path)
manip.out.link(nn.input)
nn = pipeline.createNeuralNetwork()
nn.setBlobPath(blob_path)
manip.out.link(nn.input)
xout_nn = pipeline.createXLinkOut()
xout_nn.setStreamName("nn")
nn.out.link(xout_nn.input)
xout_nn = pipeline.createXLinkOut()
xout_nn.setStreamName("nn")
nn.out.link(xout_nn.input)
self.mostrar_log("Pipeline de segmentação onboard criado")
self.mostrar_log("Pipeline de segmentação onboard criado")
except Exception as e:
self.mostrar_log(f"[WARN] Falha ao montar pipeline IA Onboard: {e}")
return pipeline

View File

@ -12,16 +12,17 @@ from visual_worker.processamento.analise_solo import AnaliseSoloManager
from visual_worker.processamento.analise_anomalias import AnaliseAnomaliasManager
from visual_worker.processamento.radar_top_down import Radar2DManager
from visual_worker.processamento.segmentacao_semantica import ClassesSegmentacao, SegmentacaoManager
from shared.enums import ManagerWorkerCommandType, ModoOperacao, StatusModulo, T_Code, CameraFrameType
from shared.utils import analisar_linhas_por_profundidade, decode_image_base64, encode_image_base64
from shared.enums import StatusModulo, T_Code, CameraFrameType
from shared.utils import analisar_linhas_por_profundidade, decode_image_base64, encode_image_base64, fazer_overlay
from shared.gps_handler import GPSHandler
from camera_worker.camera_oak import CameraOak
from shared.contexto_global_redis import CmdKey, ContextoGlobalRedis, CtxKey
from shared.contexto_global_redis import ContextoGlobalRedis, CtxKey
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):
@ -34,6 +35,11 @@ class CameraManager:
self._ultima_analise_segmentacao = {}
self._ultima_analise_matriz_confianca = {}
self._ultima_analise_matriz_custo = {}
self._ultimo_rgb_frame = None
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:
@ -51,8 +57,11 @@ class CameraManager:
self.mx_id = mx_id
from visual_worker.config import load_config
camera_config = load_config()
try:
nova = CameraOak(self.mostrar_log, mx_id)
nova = CameraOak(self.mostrar_log, mx_id, modelo_ia_onboard=camera_config)
if nova.iniciado:
self.camera = nova
except Exception as e:
@ -63,14 +72,13 @@ class CameraManager:
self.mostrar_log(f"❌ Camera com ID {mx_id} não iniciada.")
else:
self.mostrar_log(f"📷 Camera visual selecionada: {self.camera.modelo} - {self.camera.mx_id}")
self.grid_ref = None
self.grid_ref_shape = (20, 20)
self.grid_ref_shape = (15, 10)
self.grid_ref = self._gerar_grid_referencia_geometrico()
self.depth_referencia = None
self.setores_referencia = None
self.anomalias_manager = AnaliseAnomaliasManager()
self.solo_manager = AnaliseSoloManager()
arquivo_modelo = ContextoGlobalRedis.get_equipamento().get("path_ia_model_ruas")
self.segmentacao_manager = SegmentacaoManager(arquivo_modelo)
self.segmentacao_manager = SegmentacaoManager(self.camera.colormap_rgb, self.camera.classes)
self.radar_manager = Radar2DManager()
self.operante = True
self._timestamp_analise = None
@ -94,16 +102,32 @@ class CameraManager:
self._analisando_matriz_confianca = False
self._analisando_segmentacao = False
self._iniciar_loop_analise_continua(4.0)
self._iniciar_loop_analise_continua(15.0)
self.iniciando = False
self.atualizar_saude_camera()
def _gerar_grid_referencia_geometrico(self, angulo_inclinacao_graus=26, altura_camera_m=0.74):
grid_h = self.grid_ref_shape[0]
def dist_grid_calibrado(grid_h, i, fov, incl, altura):
alpha_v = ((i + 0.5) / grid_h - 0.5) * np.radians(fov)
gamma = np.radians(incl) + alpha_v
d = (altura / np.tan(gamma)) * 1000.0
return d
d = np.array([dist_grid_calibrado(grid_h, i, -43.28, 28.91, 0.74) for i in range(grid_h)], dtype=np.float32)
d = d[::-1] # ordena de baixo->cima como você queria
return d # <-- ndarray, não list
def atualizar_saude_camera(self):
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, {})
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, {})
except Exception as e:
self.mostrar_log(f"[saude] erro: {e}")
def get_rgb_frame(self):
if self.camera is None:
@ -130,6 +154,7 @@ class CameraManager:
try:
frame, res = self.camera.requisitar_frame_depth()
if frame is not None:
self._ultimo_depth_frame = frame
return frame, self.camera.timestamp_ultimo_frame_depth, res
elif "X_LINK_ERROR" in res["erro"]:
self.reiniciar_status()
@ -146,6 +171,24 @@ class CameraManager:
return gerar_heatmap(frame, self.camera.parametros["distancia_maxima"]), timestamp, res
return None, None, None
def get_segmentation_predictions(self):
if self.camera is None:
return None, None, None
try:
predictions, res = self.camera.requisitar_segmentacao()
if predictions is not None:
self._ultimo_predictions = predictions
return predictions, self.camera.timestamp_ultima_segmentacao, res
elif "X_LINK_ERROR" in res["erro"]:
self.reiniciar_status()
except Exception as e:
self.mostrar_log("Erro ao requisitar predictions:", e)
if "X_LINK_ERROR" in str(e):
self.reiniciar_status()
return None, None, None
def get_select_frame(self, tipo: CameraFrameType):
f = None
t = None
@ -163,20 +206,31 @@ class CameraManager:
elif tipo == CameraFrameType.Segmentacao:
f = self._ultima_analise_segmentacao.get("frame", {}).get("frame")
t = self._ultima_analise_segmentacao.get("timestamp")
elif tipo == CameraFrameType.Debug:
frame_seg = self._ultima_analise_segmentacao.get("mask_color")
frame_rgb = self._ultimo_rgb_frame
if frame_seg is not None and frame_rgb is not None:
f = fazer_overlay(frame_rgb, frame_seg, alpha=0.35, out_size=(640, 360), seg_is_rgb=False)
if f is not None:
f = encode_image_base64(f)
t = self.camera.timestamp_ultimo_frame_rgb
return f, t
def _iniciar_loop_analise_continua(self, freq):
def loop():
ultima_atualizacao_saude = 0
ultima_atualizacao = 0
self._ultima_saude_ts = 0
while True:
if self.camera is None:
time.sleep(5)
continue
t0 = time.time()
if (t0 - ultima_atualizacao_saude) >= 5:
self.camera.atualizar_saude()
ultima_atualizacao_saude = 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:
@ -197,11 +251,35 @@ class CameraManager:
except Exception as e:
self.mostrar_log(f"Erro no loop de analise continua: {e}")
finally:
latencia = time.time() - t0
time.sleep(max(0, (1.0 / freq) - latencia))
latencia, fps, _freq = self._calcular_performance(t0, time.time(), {"ultima_chamada": ultima_atualizacao})
novo_delay = max(0, (1.0 / freq) - latencia)
#self.mostrar_log(
# self._log_performance("Loop", { "freq": _freq, "fps": fps, "latencia": latencia }) +
# self._log_performance("Radar", self._ultima_analise_radar) +
# self._log_performance("Segmentacao", self._ultima_analise_segmentacao) +
# self._log_performance("Matriz Confianca", self._ultima_analise_matriz_confianca) +
# self._log_performance("Anomalias", self._ultima_analise_anomalias) +
# self._log_performance("Solo", self._ultima_analise_solo) +
# self._log_performance("Matriz Custo", self._ultima_analise_matriz_custo)
#)
ultima_atualizacao = t0
time.sleep(novo_delay)
threading.Thread(target=loop, daemon=True).start()
def _calcular_performance(self, t0, t1, analise):
latencia = t1 - t0
freq = 1.0 / max(latencia, 1e-6)
fps = 1.0 / max((t0 - analise.get("ultima_chamada", t0)), 1e-6)
analise["latencia"] = latencia
analise["fps"] = fps
analise["freq"] = freq
analise["ultima_chamada"] = t0
return latencia, fps, freq
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
@ -231,104 +309,101 @@ class CameraManager:
def _realizar_analises(self):
if self._depth_frame_necessario and self.camera.tem_depth:
try:
# 🔸 Captura frame
depth_frame_np, depth_timestamp, depth_res = self.get_depth_frame()
except Exception as e:
self.mostrar_log(f"❌ Erro ao capturar frame: {e}")
return
executor = self._pool
tarefas = []
parametros_camera = self.camera.parametros
fov_h = parametros_camera["fov_h"]
distancia_max_m = parametros_camera["distancia_maxima"] / 1000.0
percentual_solo = parametros_camera["percentual_altura_solo"] / 100.0
limiar_delta = calcular_threshold_anomalias()
limiar_conf: float = 0.4
largura_min: float = 0.15
altura_min: float = 0.15
depth_frame_np, depth_timestamp, depth_res = self.get_depth_frame()
with ThreadPoolExecutor(max_workers=6) as executor:
tarefas = []
tarefas.append(executor.submit(self._analise_radar, depth_frame_np, fov_h, distancia_max_m))
#tarefas.append(executor.submit(self._analise_radar, depth_frame_np, fov_h, distancia_max_m))
if True or not self._nova_segmentacao_disponivel:
if not self._analisando_segmentacao:
tarefas.append(executor.submit(self._analise_segmentacao))
if True or not self._nova_segmentacao_disponivel:
if not self._analisando_segmentacao:
tarefas.append(executor.submit(self._analise_segmentacao))
if self._nova_segmentacao_disponivel:
if not self._analisando_matriz_confianca:
self._nova_segmentacao_disponivel = False
tarefas.append(executor.submit(self._analise_matriz_confianca, depth_frame_np, distancia_max_m, fov_h))
if False and self._nova_segmentacao_disponivel:
if not self._analisando_matriz_confianca:
self._nova_segmentacao_disponivel = False
tarefas.append(executor.submit(self._analise_matriz_confianca, depth_frame_np, distancia_max_m, fov_h))
if self._nova_grid_conf_disponivel:
limiar_conf: float = 0.4
matriz_conf = self._ultima_analise_matriz_confianca["matriz"]
if not self._analisando_anomalias:
self._nova_grid_conf_disponivel = False
limiar_delta = calcular_threshold_anomalias()
largura_min: float = 0.15
altura_min: float = 0.15
tarefas.append(executor.submit(self._analise_anomalias, matriz_conf, limiar_delta, limiar_conf, distancia_max_m, largura_min, altura_min))
if not self._analisando_solo:
self._nova_grid_conf_disponivel = False
tarefas.append(executor.submit(self._analise_solo, matriz_conf, percentual_solo, limiar_conf, fov_h))
if not self._analisando_matriz_custo:
self._nova_grid_conf_disponivel = False
tarefas.append(executor.submit(self._analise_matriz_custo, matriz_conf, fov_h))
if False and self._nova_grid_conf_disponivel:
matriz_conf = self._ultima_analise_matriz_confianca["matriz"]
if not self._analisando_anomalias:
self._nova_grid_conf_disponivel = False
tarefas.append(executor.submit(self._analise_anomalias, matriz_conf, limiar_delta, limiar_conf, distancia_max_m, largura_min, altura_min))
if not self._analisando_solo:
self._nova_grid_conf_disponivel = False
tarefas.append(executor.submit(self._analise_solo, matriz_conf, percentual_solo, limiar_conf, fov_h))
if not self._analisando_matriz_custo:
self._nova_grid_conf_disponivel = False
tarefas.append(executor.submit(self._analise_matriz_custo, matriz_conf, fov_h))
#for t in tarefas:
# t.result() # Espera cada uma terminar
def _analise_segmentacao(self):
if self._analisando_segmentacao:
return
self._analisando_segmentacao = True
t0 = time.time()
try:
rgb_frame, rgb_timestamp, res_frame = self.get_rgb_frame()
if rgb_frame is None or rgb_frame.size == 0:
self._analisando_segmentacao = False
return
analise_segmentacao, log = self.segmentacao_manager.segmentar(rgb_frame)
if analise_segmentacao == None:
self.mostrar_log(log)
t1 = time.time()
analise_segmentacao["latencia"] = t1 - t0
self._ultima_analise_segmentacao = analise_segmentacao
ContextoGlobalRedis.atualizar_ctx_dict(
CtxKey.DadosVisualWorker,
ts_analise=t1,
segmentacao=converter_valores_numpy(self._ultima_analise_segmentacao["dados_visuais"])
)
self._nova_segmentacao_disponivel = True
t0 = time.time()
predictions, ts, res = self.get_segmentation_predictions()
if ts == self._ts_segmentacao_anterior:
return # já analisado
self._ts_segmentacao_anterior = ts
if predictions is not None:
rgb_frame, _ts, res = self.get_rgb_frame()
analise_segmentacao, log = self.segmentacao_manager.segmentar(predictions)
t1 = time.time()
if analise_segmentacao == None:
self.mostrar_log(log)
analise_segmentacao["ultima_chamada"] = self._ultima_analise_segmentacao.get("ultima_chamada", t0)
self._calcular_performance(t0, t1, analise_segmentacao)
self._ultima_analise_segmentacao = analise_segmentacao
ContextoGlobalRedis.atualizar_ctx_dict(
CtxKey.DadosVisualWorker,
ts_analise=t1,
segmentacao=converter_valores_numpy(self._ultima_analise_segmentacao["dados_visuais"])
)
self._nova_segmentacao_disponivel = True
# Enviar comando para atualizar os dados de controle sempre que um novo dado de segmentacao seja processado e a operacao seja do tipo MapeamentoVisual
#op_modo = ContextoGlobalRedis.get_operacao().get("modo", ModoOperacao.NaoDefinido.value)
#movimento_automatico = ContextoGlobalRedis.get_controle().get("movimento_automatico", False)
#if ModoOperacao(op_modo) == ModoOperacao.MapeamentoVisual and movimento_automatico and analise_segmentacao is not None:
# ContextoGlobalRedis.publicar_comando(CmdKey.ManagerWorkerRx, { "cmd": ManagerWorkerCommandType.AtualizarDadosControle.value })
# Enviar comando para atualizar os dados de controle sempre que um novo dado de segmentacao seja processado e a operacao seja do tipo MapeamentoVisual
#op_modo = ContextoGlobalRedis.get_operacao().get("modo", ModoOperacao.NaoDefinido.value)
#movimento_automatico = ContextoGlobalRedis.get_controle().get("movimento_automatico", False)
#if ModoOperacao(op_modo) == ModoOperacao.MapeamentoVisual and movimento_automatico and analise_segmentacao is not None:
# ContextoGlobalRedis.publicar_comando(CmdKey.ManagerWorkerRx, { "cmd": ManagerWorkerCommandType.AtualizarDadosControle.value })
except Exception as e:
self.mostrar_log(f"❌ Erro na segmentacao semantica: {e}")
finally:
self._analisando_segmentacao = False
#self.mostrar_log("Segmentacao concluida")
#self.mostrar_log(f"Segmentacao concluida em {self._ultima_analise_segmentacao['latencia']:.4f} s, a {fps:.4f} FPS")
def _analise_matriz_confianca(self, depth_frame_np, dist_max, fov_h):
if self._analisando_matriz_confianca:
return
self._analisando_matriz_confianca = True
t0 = time.time()
try:
if depth_frame_np is None or depth_frame_np.size == 0:
return
segmentacao = self._ultima_analise_segmentacao.get("classes", [])
segmentacao = self._ultima_analise_segmentacao.get("classes")
if segmentacao is None: return
#segmentacao_vis = self._ultima_analise_segmentacao.get("mask_color", None)
if depth_frame_np is None or segmentacao is None:
self.mostrar_log("❌ Depth frame ou segmentação inválidos para gerar matriz de confiança.")
else:
t0 = time.time()
grid_conf = self._gerar_grid_confianca(depth_frame_np, segmentacao, dist_max)
t1 = time.time()
grid_conf["latencia"] = t1 - t0
grid_conf["ultima_chamada"] = self._ultima_analise_matriz_confianca.get("ultima_chamada", t0)
self._calcular_performance(t0, t1, grid_conf)
self._ultima_analise_matriz_confianca = grid_conf
matriz = self._ultima_analise_matriz_confianca["matriz"]
self._ultima_analise_segmentacao["corredor_perfil"] = self.segmentacao_manager.calcular_perfil_corredor(matriz, fov_h)
@ -339,7 +414,7 @@ class CameraManager:
perfil_corredor__segmentacao=converter_valores_numpy(self._ultima_analise_segmentacao.get("corredor_perfil", []))
)
self._nova_grid_conf_disponivel = True
#self._mostrar_debug_grid_confianca(rgb_frame, grid_conf, True, segmentacao_vis)
#self._mostrar_debug_grid_confianca(self._ultimo_rgb_frame, grid_conf["matriz"], True, self._ultima_analise_segmentacao["mask_color"])
except Exception as e:
self.mostrar_log(f"❌ Erro na geracao da matriz de confianca: {e}")
finally:
@ -351,13 +426,14 @@ class CameraManager:
if self._analisando_anomalias:
return
self._analisando_anomalias = True
t0 = time.time()
try:
t0 = time.time()
#analise_anomalias = self.anomalias_manager.detectar_anomalias(depth_frame, self.depth_referencia, limiar, distancia_max, largura_min, altura_min, parametros_camera)
#analise_anomalias = self.anomalias_manager.analisar_anomalias(depth_frame_np, self.depth_referencia, limiar, distancia_max_m, largura_min, altura_min)
analise_anomalias = self.anomalias_manager.analisar_anomalias_grid(grid_conf, limiar_delta, limiar_conf, (640, 480), dist_max, largura_min, altura_min)
t1 = time.time()
analise_anomalias["latencia"] = t1 - t0
analise_anomalias["ultima_chamada"] = self._ultima_analise_anomalias.get("ultima_chamada", t0)
self._calcular_performance(t0, t1, analise_anomalias)
self._ultima_analise_anomalias = analise_anomalias
ContextoGlobalRedis.atualizar_ctx_dict(
CtxKey.DadosVisualWorker,
@ -374,11 +450,12 @@ class CameraManager:
if self._analisando_solo:
return
self._analisando_solo = True
t0 = time.time()
try:
t0 = time.time()
analise_solo = self.solo_manager.analisar_solo(grid_conf, percentual_solo, limiar_conf, fov_h)
t1 = time.time()
analise_solo["latencia"] = t1 - t0
analise_solo["ultima_chamada"] = self._ultima_analise_solo.get("ultima_chamada", t0)
self._calcular_performance(t0, t1, analise_solo)
self._ultima_analise_solo = analise_solo
ContextoGlobalRedis.atualizar_ctx_dict(
CtxKey.DadosVisualWorker,
@ -395,14 +472,15 @@ class CameraManager:
if self._analisando_radar:
return
self._analisando_radar = True
t0 = time.time()
try:
if depth_frame_np is None or depth_frame_np.size == 0:
return
t0 = time.time()
depth_frame = cp.asarray(depth_frame_np)
analise_radar = self.radar_manager.analisar_radar_2d(depth_frame, fov_h, dist_max)
t1 = time.time()
analise_radar["latencia"] = t1 - t0
analise_radar["ultima_chamada"] = self._ultima_analise_radar.get("ultima_chamada", t0)
self._calcular_performance(t0, t1, analise_radar)
self._ultima_analise_radar = analise_radar
ContextoGlobalRedis.atualizar_ctx_dict(
CtxKey.DadosVisualWorker,
@ -421,12 +499,13 @@ class CameraManager:
if self._analisando_matriz_custo:
return
self._analisando_matriz_custo = True
t0 = time.time()
try:
t0 = time.time()
largura_robo = ContextoGlobalRedis.get(CtxKey.DadosEquipamento, {}).get("largura", 0.85)
matriz_custo = self._gerar_matriz_custo_fundida(grid_conf, largura_robo, fov_h)
t1 = time.time()
matriz_custo["latencia"] = t1 - t0
matriz_custo["ultima_chamada"] = self._ultima_analise_matriz_custo.get("ultima_chamada", t0)
self._calcular_performance(t0, t1, matriz_custo)
self._ultima_analise_matriz_custo = matriz_custo
matriz = matriz_custo["matriz"]
ContextoGlobalRedis.atualizar_ctx_dict(
@ -493,81 +572,133 @@ class CameraManager:
return caminho_analise
return None
def _gerar_grid_confianca(self, depth_frame, segmentacao_frame, limiar_prof):
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:
grid_h, grid_w = self.grid_ref_shape
altura, largura = depth_frame.shape
# --- força ndarray ---
depth = self._as_ndarray(depth_frame) # mm, 2D
seg = self._as_ndarray(segmentacao_frame) # IDs, 2D (NEAREST)
h_step = altura // grid_h
w_step = largura // grid_w
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)
grid_resultado = [[{} for _ in range(grid_w)] for _ in range(grid_h)]
H, W = depth.shape
gh_cfg, gw_cfg = self.grid_ref_shape # alvo "ideal" (ex.: 20x20)
for i in range(grid_h):
prof_ref = self.grid_ref[i]
for j in range(grid_w):
y0, y1 = i * h_step, (i + 1) * h_step
x0, x1 = j * w_step, (j + 1) * w_step
# 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)
cel_depth = depth_frame[y0:y1, x0:x1]
cel_seg = segmentacao_frame[y0:y1, x0:x1]
total_pix = cel_depth.size
# 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])
# Frequência das classes
valores, contagens = np.unique(cel_seg, return_counts=True)
freq_classes = {int(v): int(c) / total_pix for v, c in zip(valores, contagens)}
# ---------- reshape em blocos ----------
depth_b = depth.reshape(gh_eff, h, gw_eff, w)
seg_b = seg.reshape(gh_eff, h, gw_eff, w)
# Índice de Segmentação (presença de chão)
IS = freq_classes.get(ClassesSegmentacao.RUA.value, 0.0)
# ---------- métricas vetorizadas ----------
valid = (depth_b > 0)
ignore_id = getattr(self, "ignore_id", 255)
not_ign = (seg_b != ignore_id)
# Índice de Profundidade
celula_valida = cel_depth[cel_depth > 0.0]
if celula_valida.size == 0:
#print(f"⚠️ Célula ({i}, {j}) sem profundidade válida")
prof_median = -99999.0
delta = -99999.0
IP = 0.0
conf_profundidade = 0.0
else:
prof_median = np.median(celula_valida)
delta = prof_median - prof_ref
IP = 1.0 - min(abs(delta) / (limiar_prof * 1000.0), 1.0)
conf_profundidade = celula_valida.size / total_pix
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
# Índice Caminho Livre
PESO_SEG, PESO_PROF = 0.6, 0.4
ICL = PESO_SEG * IS + PESO_PROF * IP
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))
num_mapeados = total_pix - np.count_nonzero(cel_seg == 0)
conf_segmentacao = num_mapeados / total_pix
# 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)
indice_confiabilidade = (conf_profundidade + conf_segmentacao) / 2.0
# 🔒 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
grid_resultado[i][j] = {
"linha": i,
"coluna": j,
"pix_x": x1 - x0,
"pix_y": y1 - y0,
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": prof_ref / 1000.0,
"prof_median": prof_median / 1000.0,
"prof_delta": delta / 1000.0,
"indice_seg_chao": IS,
"indice_prof_delta": IP,
"indice_caminho_livre": ICL,
"conf_profundidade": conf_profundidade,
"conf_segmentacao": conf_segmentacao,
"indice_confiabilidade": indice_confiabilidade
"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]),
}
return {
"matriz": grid_resultado
}
# (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 {
"matriz": [[]]
}
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:
@ -575,7 +706,7 @@ class CameraManager:
return
img_debug = rgb_frame.copy()
img_debug = cv2.resize(img_debug, (1600, 900))
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:
@ -979,11 +1110,13 @@ class CameraManager:
matriz_custo = self._aplicar_bonus_caminho(matriz_custo, pontos_plotar, largura_robo_m)
return {
"timestamp": time.time(),
"matriz": matriz_custo
}
except Exception as e:
self.mostrar_log(f"❌ Erro ao gerar matriz de custo: {e}")
return {
"timestamp": time.time(),
"matriz": [[]]
}
@ -1018,7 +1151,7 @@ class CameraManager:
if not exibir_debug:
return
img_debug = cv2.resize(rgb_frame.copy(), (1600, 900))
img_debug = cv2.resize(rgb_frame.copy(), (1280, 720))
overlay = img_debug.copy()
altura, largura, _ = img_debug.shape

View File

@ -1,6 +1,9 @@
import os
import threading
import time
from visual_worker.camera_manager import CameraManager
from shared.contexto_global_redis import ContextoGlobalRedis
module_id = "visual"
topico_tx = f"operador/{module_id}/tx"
@ -28,3 +31,57 @@ def iniciar_camera_manager(mx_id):
manager.inicializar(mx_id=mx_id)
if manager.camera is not None and manager.operante:
mostrar_log(f"✅ Camera manager iniciado, com MX_ID: {mx_id}")
_CONFIG_PATH = os.path.join(os.path.dirname(__file__), "config.json")
_CONFIG_CACHE = None
_CONFIG_MTIME = None
_CONFIG_LOCK = threading.Lock()
def load_config(force_reload=False):
global _CONFIG_CACHE, _CONFIG_MTIME
with _CONFIG_LOCK:
#try:
# mtime = os.path.getmtime(_CONFIG_PATH)
# if force_reload or _CONFIG_CACHE is None or mtime != _CONFIG_MTIME:
# with open(_CONFIG_PATH, "r", encoding="utf-8") as f:
# _CONFIG_CACHE = json.load(f)
# _CONFIG_MTIME = mtime
#except Exception as e:
# mostrar_log(f"Erro ao ler config: {e}")
# if _CONFIG_CACHE is None:
# # Valores default se der ruim no primeiro load
# _CONFIG_CACHE = {
# "debug_visual": True,
# "frames_consecutivos": 3,
# "frames_histerese": 2,
# "min_area_px": 400,
# "max_area_frac": 0.2,
# "area_atuacao_bicos": 0.1,
# "ia_roi_begin": 0.0,
# "ia_roi_size": 1.0,
# "ia_resolution": [512,288],
# "erva_top_band_frac": 0.30,
# "erva_frac_ema": 0.3,
# "erva_thresh_vel_gain": 0.4,
# "min_frac_erva_global_on": 0.0020,
# "min_frac_erva_global_off": 0.0015,
# "min_frac_erva_top_on": 0.0015,
# "min_frac_erva_top_off": 0.0010,
# "min_frac_erva_por_bico": 0.02,
# "usar_morfologia": True,
# "kernel_morf": 3
# }
_CONFIG_CACHE = {
"debug_visual": True,
"ia_roi_begin": 0.0,
"ia_roi_size": 1.0,
"ia_resolution": [512,288]
}
_CONFIG_CACHE["ia_model_path"] = ContextoGlobalRedis.get_equipamento().get("path_ia_model_ruas", "C:/AgroBaseModels/Ruas/model-1_1.blob")
_CONFIG_CACHE["ia_labelmap_path"] = ContextoGlobalRedis.get_equipamento().get("path_ia_labelmap_ruas", "C:/AgroBaseModels/Ruas/model-1_1.txt")
return _CONFIG_CACHE
def reload_config():
return load_config(force_reload=True)

View File

@ -59,30 +59,37 @@ class Radar2DManager:
return resultado
def gerar_radar_topdown(self, depth_frame, fov_horizontal, min_dist, max_dist):
def gerar_radar_topdown(self, depth_frame, fov_horizontal, min_dist_m, max_dist_m):
"""
depth_frame: CuPy array (mm), 2D
fov_horizontal: em radianos
min_dist_m / max_dist_m: metros
"""
try:
height, width = depth_frame.shape
d = cp.asarray(depth_frame, dtype=cp.float32) # mm
H, W = d.shape
# 🔥 Índices de pixels na horizontal (normalizado de -0.5 a +0.5)
indices_x = cp.linspace(-0.5, 0.5, width)
theta_x = indices_x * fov_horizontal
# Cacheia tan(theta_x) por largura+FOV para evitar recomputar a cada frame
key = (W, float(fov_horizontal))
if getattr(self, "_tan_cache_key", None) != key:
theta_x = (cp.linspace(-0.5, 0.5, W, dtype=cp.float32) * fov_horizontal)[cp.newaxis, :] # (1, W)
self._tan_theta_x = cp.tan(theta_x) # (1, W)
self._tan_cache_key = key
# 🔥 Matriz com ângulo lateral para cada coluna
theta_matrix = cp.tile(theta_x, (height, 1))
# Depth em metros
Z = d * 0.001 # m
# 🔥 Converte depth de mm para metros
Z = depth_frame / 1000.0
# Filtro válido (agora em metros)
mask = (Z > 0) & (Z >= min_dist_m) & (Z <= max_dist_m)
Z = cp.where(mask, Z, cp.nan)
# 🔥 Calcula X no plano
X = Z * cp.tan(theta_matrix)
# X por broadcasting, sem tile
X = Z * self._tan_theta_x # (H,W) * (1,W) -> (H,W)
# 🔥 Filtro de distância válida
mask = (Z >= min_dist) & (Z <= max_dist) & (Z > 0)
# Compacta removendo NaNs sem copiar desnecessariamente
m = cp.isfinite(Z)
return X[m].ravel(), Z[m].ravel()
X = X[mask]
Z = Z[mask]
return X.flatten(), Z.flatten()
except Exception as e:
print(f"Erro ao gerar radar topdown: {e}")

View File

@ -2,7 +2,6 @@ from enum import IntEnum
import time
import cv2
import numpy as np
import onnxruntime as ort
from shared.utils import encode_image_base64
from shared.enums import StatusCarroMapa
@ -14,127 +13,63 @@ class ClassesSegmentacao(IntEnum):
class SegmentacaoManager:
def __init__(self, path_model: str):
self.path_modelo = path_model
self.path_classes = path_model.replace("onnx", "txt")
self.modelo = None
self.input_name = None
self.output_name = None
self.classes = {}
self.color_map = []
self.carregado = False
self.use_mock = False
self.img_mock = "C:\\ZendionInc\\agrobot_base\\AgroBase\\AgroBase\\bin\\x64\\Debug\\Operacoes\\28_07_2025_14_47_15\\Snr\\137_rgb.jpeg"
self.resolucao = (512, 512)
def __init__(self, color_map, classes):
from visual_worker.config import load_config
config = load_config()
resolucao = config.get("ia_resolution")
self.color_map = color_map
self.classes = classes
self.resolucao = (resolucao[0], resolucao[1])
self.pred_rgb = np.empty((self.resolucao[1], self.resolucao[0], 3), dtype=np.uint8)
self.color_lut = np.array(self.color_map, np.uint8)
self.lut = np.zeros((256, 3), dtype=np.uint8)
for i, color in enumerate(color_map):
#self.lut[i] = color
self.lut[i] = (color[2], color[1], color[0]) # converte pra (B, G, R)
IGNORE_ID = 255
self.lut[IGNORE_ID] = (255, 255, 255)
self.predictions = None
self.log = None
self.dados_visuais = {}
self._carregar_modelo()
def _carregar_labelmap_completo(self, caminho):
cor_para_id = {}
id_para_nome = {}
cores_bgr = []
with open(caminho, 'r') as arquivo:
idx = 0
for linha in arquivo:
if linha.startswith("#") or not linha.strip():
continue
partes = linha.strip().split(':')
if len(partes) >= 2:
nome_classe, cor_rgb_str = partes[0], partes[1]
r, g, b = map(int, cor_rgb_str.split(','))
cor_bgr = (b, g, r) # Corrige para BGR
if nome_classe.lower() == "ignore":
ignore_bgr = cor_bgr
continue # NÃO adiciona ignore no LUT de classes
cor_para_id[cor_bgr] = idx
cores_bgr.append(cor_bgr)
id_para_nome[idx] = nome_classe
idx += 1
return cor_para_id, cores_bgr, id_para_nome, ignore_bgr
def _carregar_modelo(self):
def _segmentar_predictions(self, predictions):
try:
self.modelo = ort.InferenceSession(
self.path_modelo,
providers=["CUDAExecutionProvider", "CPUExecutionProvider"]
)
self.input_name = self.modelo.get_inputs()[0].name
self.output_name = self.modelo.get_outputs()[0].name
self.pred_rgb[:] = self.lut[predictions]
# Carrega as classes e cores
cor_para_id, self.color_map, self.classes, ignore_bgr = self._carregar_labelmap_completo(self.path_classes)
self.carregado = True
self.log = f"✅ Modelo de segmentação carregado com sucesso. Classes: {self.classes}, Color Map: {self.color_map}"
print(self.log)
return True
except Exception as e:
self.log = f"❌ Erro ao carregar modelo: {e}"
self.carregado = False
return False
def segmentar(self, rgb_frame):
if not self.carregado:
if not self._carregar_modelo():
return None, self.log
# 🔧 MOCK PARA TESTE: sobrescreve o frame com imagem local
if self.use_mock:
img_mock = cv2.imread(self.img_mock)
if img_mock is None:
self.log = "❌ Imagem de teste não encontrada!"
return None, self.log
# Redimensiona o mock para o mesmo shape do RGB real (ex: da câmera ou do sistema)
rgb_frame = cv2.resize(img_mock, (rgb_frame.shape[1], rgb_frame.shape[0]))
try:
# 🔸 Preprocessamento
mean = np.array([0.485, 0.456, 0.406], dtype=np.float32)
std = np.array([0.229, 0.224, 0.225], dtype=np.float32)
img_resized = cv2.resize(rgb_frame, self.resolucao).astype(np.float32) / 255.0
img_resized = (img_resized - mean) / std
input_blob = img_resized.transpose(2, 0, 1)
input_blob = np.expand_dims(input_blob, axis=0).astype(np.float32)
# 🔸 Inferência
# Executa a inferência
outputs = self.modelo.run(None, {self.input_name: input_blob})
prediction = outputs[0] # shape: (1, num_classes, H, W)
# Seleciona a classe com maior score
prediction = prediction.squeeze(0).argmax(axis=0)
self.dados_visuais = self._analisar_corredor_visual(prediction)
# 🔸 Redimensiona máscara para o tamanho original da imagem
mask_resized = cv2.resize(prediction.astype(np.uint8), (rgb_frame.shape[1], rgb_frame.shape[0]), interpolation=cv2.INTER_NEAREST)
# 🔸 Cria máscara colorida com LUT vetorizada
lut = np.zeros((256, 3), dtype=np.uint8)
for i, color in enumerate(self.color_map):
lut[i] = color
mask_color = lut[mask_resized]
mask_color = self.pred_rgb
frame_color = encode_image_base64(mask_color)
#perfil_corredor = self._calcular_perfil_corredor(mask_resized, depth_frame, fov_h, 640, 24)
return {
"timestamp": time.time(),
"frame": {
"timestamp": time.time(),
"frame": frame_color
},
"mask_color": mask_color,
"classes": mask_resized,
"dados_visuais": self.dados_visuais
}, self.log
"mask_color": mask_color,
"classes": predictions
}
except Exception as e:
print(f"Erro ao processar predictions: {e}")
return None
def segmentar(self, predictions):
try:
# 🔸 Constrói a máscara colorida e outras saídas com base na predictions já pronta
resultado = self._segmentar_predictions(predictions)
if resultado is None:
print("[Erro] Segmentação vazia ou falhou")
return None
if predictions is None:
print("[Erro] Máscara de classes não encontrada no resultado")
return None
self.dados_visuais = self._analisar_corredor_visual(predictions)
resultado["dados_visuais"] = self.dados_visuais
return resultado, self.log
except Exception as e:
self.log = f"❌ Erro na segmentação: {e}"

View File

@ -15,6 +15,7 @@ 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):
@ -22,8 +23,10 @@ class CameraManager:
self.operante = False
self.iniciando = False
self.weed_detector = None
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:
@ -68,11 +71,16 @@ class CameraManager:
self.atualizar_saude_camera()
def atualizar_saude_camera(self):
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, {})
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, {})
except Exception as e:
self.mostrar_log(f"[saude] erro: {e}")
def get_rgb_frame(self):
if self.camera is None:
@ -133,16 +141,16 @@ class CameraManager:
def _iniciar_loop_analise_continua(self, freq):
def loop():
ultima_atualizacao_saude = 0
self._ultima_saude_ts = 0
while True:
if self.camera is None:
time.sleep(5)
continue
t0 = time.time()
if (t0 - ultima_atualizacao_saude) >= 5:
self.camera.atualizar_saude()
ultima_atualizacao_saude = 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:

View File

@ -9,7 +9,7 @@ pasta_mascaras = os.path.join(MODELO, "dataset", "original", "masks")
# Regras de substituição (cores em RGB)
# Exemplo: trocar (255, 0, 0) por branco (255,255,255) com tolerância 10
SUBSTITUICOES = [
#{"target_rgb": (255, 0, 0), "tolerancia": 10, "replace_rgb": (255, 255, 255)}, # vermelho -> branco
#{"target_rgb": (255, 255, 255), "tolerancia": 10, "replace_rgb": (128, 0, 0)}, # branco -> vermelho
{"target_rgb": (128, 0, 0), "tolerancia": 50, "replace_rgb": (128, 0, 0)}, # chao
{"target_rgb": (0, 128, 0), "tolerancia": 50, "replace_rgb": (0, 128, 0)}, # erva
{"target_rgb": (0, 0, 128), "tolerancia": 50, "replace_rgb": (0, 0, 128)}, # cana

View File

@ -1,4 +1,4 @@
import json, os, cv2, numpy as np
import json, os, cv2
from PIL import Image
import albumentations as A

View File

@ -18,89 +18,290 @@ MODEL_NAME = config["model_name"]
RESOLUCAO = config["resolucao"]
ROI_INICIO = config["roi_inicio"]
ROI_TAMANHO = config["roi_tamanho"]
MAIN_CLASS_NAME = config["main_class_name"]
save_path = os.path.join(MODELO, "backup", config["modelo"], MODEL_NAME)
dataset_path = os.path.join(MODELO, "dataset")
split_folder = "train"
labelmap_path = os.path.join(dataset_path, "labelmap.txt")
batch_size = 8
num_workers = 4
# ---- Helpers de métricas ----
@torch.no_grad()
def confmat_update(confmat, pred, target, num_classes, ignore_index=None):
# pred, target: (B,H,W)
if ignore_index is not None:
mask = target != ignore_index
target = target[mask]
pred = pred[mask]
k = (target * num_classes + pred).to(torch.int64)
binc = torch.bincount(k, minlength=num_classes**2)
confmat += binc.reshape(num_classes, num_classes)
return confmat
def metrics_from_confmat(confmat, main_class_id=None):
# confmat: CxC
cm = confmat.float()
tp = torch.diag(cm)
fp = cm.sum(0) - tp
fn = cm.sum(1) - tp
denom_iou = tp + fp + fn + 1e-7
iou_per_class = tp / denom_iou
miou = iou_per_class.mean().item()
pix_acc = tp.sum() / (cm.sum() + 1e-7)
main_class_metrics = None
if main_class_id is not None and 0 <= main_class_id < cm.shape[0]:
p = tp[main_class_id] / (tp[main_class_id] + fp[main_class_id] + 1e-7)
r = tp[main_class_id] / (tp[main_class_id] + fn[main_class_id] + 1e-7)
f1 = 2 * p * r / (p + r + 1e-7)
main_class_metrics = {
"precision": p.item(),
"recall": r.item(),
"f1": f1.item(),
"iou": iou_per_class[main_class_id].item(),
}
return {
"miou": miou,
"pixel_acc": pix_acc.item(),
"iou_per_class": iou_per_class.cpu().tolist(),
"main_class": main_class_metrics
}
def train(args):
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Device: {device}")
ds_train = ROISegDataset(os.path.join(dataset_path, "split", split_folder), save_path, ROI_INICIO, ROI_TAMANHO, RESOLUCAO[0], RESOLUCAO[1], labelmap_path)
dl_train = DataLoader(ds_train, batch_size=batch_size, shuffle=True, num_workers=num_workers, pin_memory=True)
# --- Dataset ---
ds_train = ROISegDataset(
os.path.join(dataset_path, "split", "train"),
save_path, ROI_INICIO, ROI_TAMANHO,
RESOLUCAO[0], RESOLUCAO[1], labelmap_path
)
ds_val = ROISegDataset(
os.path.join(dataset_path, "split", "val"),
save_path, ROI_INICIO, ROI_TAMANHO,
RESOLUCAO[0], RESOLUCAO[1], labelmap_path
)
model = FastSCNN(num_classes=len(ds_train.classes)).to(device)
dl_train = DataLoader(ds_train, batch_size=batch_size, shuffle=True, num_workers=num_workers, pin_memory=True)
dl_val = DataLoader(ds_val, batch_size=batch_size, shuffle=False, num_workers=num_workers, pin_memory=True)
# Detecta automaticamente o ID da classe ERVA
main_class_id = None
try:
if hasattr(ds_train, "classes") and isinstance(ds_train.classes, dict):
for k, v in ds_train.classes.items():
if isinstance(v, str) and MAIN_CLASS_NAME in v.lower():
main_class_id = k
break
elif isinstance(ds_train.classes, (list, tuple)):
main_class_id = next((i for i, c in enumerate(ds_train.classes) if isinstance(c, str) and MAIN_CLASS_NAME in c.lower()), None)
if main_class_id is not None:
print(f"🌿 Classe PRIMARIA detectada: id={main_class_id}, nome='{ds_train.classes[main_class_id]}'")
else:
print("⚠️ Classe PRIMARIA não encontrada; métricas específicas da classe primaria serão puladas.")
except Exception as e:
print(f"⚠️ Erro ao detectar classe PRIMARIA: {e}")
num_classes = len(ds_train.classes)
# --- Modelo / Otimizador / Schedulers ---
model = FastSCNN(num_classes=num_classes).to(device)
criterion = nn.CrossEntropyLoss(ignore_index=ds_train.ignore_id)
optimizer = optim.AdamW(model.parameters(), lr=args.lr, weight_decay=1e-4)
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=args.epochs)
scaler = torch.cuda.amp.GradScaler(enabled=args.amp)
start_epoch = 1
best_loss = 1e9
loss_history = []
# Scheduler inteligente: começa em Cosine, muda pra Plateau se travar
min_lr = getattr(args, "min_lr", 1e-6)
plateau_factor = getattr(args, "plateau_factor", 0.5)
plateau_patience = getattr(args, "plateau_patience", 6) # épocas sem melhora antes de trocar
plateau_cooldown = getattr(args, "plateau_cooldown", 1)
cosine = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=args.epochs, eta_min=min_lr)
plateau = torch.optim.lr_scheduler.ReduceLROnPlateau(
optimizer, mode="min", factor=plateau_factor,
patience=plateau_patience, cooldown=plateau_cooldown,
min_lr=min_lr, verbose=True
)
active_sched = "cosine"
scaler = torch.cuda.amp.GradScaler(enabled=args.amp)
start_epoch = 1
best_val_loss = float("inf")
best_main_class_f1 = -1.0
train_loss_history, val_loss_history, lr_history = [], [], []
f1_history, miou_history = [], []
# --- no topo (config) ---
patience_loss = 12 # ligeiramente > plateau_patience + 2
patience_f1 = 6 # deixa o F1 respirar
delta_f1_min = 0.0015 # ignora ruído
grace_after_switch = 4 # épocas de graça após mudar pro Plateau
no_imp_loss = 0
no_imp_f1 = 0
epochs_since_switch = 0
active_sched = "cosine" # como já está
# --- Checkpoint ---
if args.checkpoint and os.path.exists(args.checkpoint):
print(f"🔁 Carregando modelo salvo: {args.checkpoint}")
checkpoint = torch.load(args.checkpoint, map_location=device)
if "model" in checkpoint:
model.load_state_dict(checkpoint["model"])
optimizer.load_state_dict(checkpoint["optimizer"])
scaler.load_state_dict(checkpoint["scaler"])
start_epoch = checkpoint.get("epoch", 1) + 1
best_loss = checkpoint.get("best_loss", 1e9)
best_val_loss = checkpoint.get("best_val_loss", float("inf"))
else:
# Caso seja apenas um .pth com model.state_dict() direto
model.load_state_dict(checkpoint)
# --- Loop de treino ---
for epoch in range(start_epoch, args.epochs + 1):
model.train()
total_loss = 0
t0 = time.time()
# ----- Treino -----
model.train()
running_train_loss = 0
for x, y in dl_train:
x, y = x.to(device), y.to(device)
optimizer.zero_grad()
optimizer.zero_grad(set_to_none=True)
with torch.cuda.amp.autocast(enabled=args.amp):
logits = model(x)
loss = criterion(logits, y)
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
total_loss += loss.item() * x.size(0)
running_train_loss += loss.item() * x.size(0)
avg_loss = total_loss / len(ds_train)
loss_history.append(avg_loss)
print(f"[{epoch}/{args.epochs}] loss={avg_loss:.4f} time={time.time()-t0:.1f}s")
avg_train_loss = running_train_loss / len(ds_train)
train_loss_history.append(avg_train_loss)
if avg_loss < best_loss:
best_loss = avg_loss
# ----- Validação + métricas -----
model.eval()
running_val_loss = 0
confmat = torch.zeros((num_classes, num_classes), dtype=torch.int64, device=device)
with torch.no_grad():
for x, y in dl_val:
x, y = x.to(device), y.to(device)
with torch.cuda.amp.autocast(enabled=args.amp):
logits = model(x)
loss = criterion(logits, y)
running_val_loss += loss.item() * x.size(0)
pred = logits.argmax(1)
confmat = confmat_update(confmat, pred, y, num_classes, ignore_index=ds_train.ignore_id)
avg_val_loss = running_val_loss / len(ds_val)
val_loss_history.append(avg_val_loss)
m = metrics_from_confmat(confmat, main_class_id=main_class_id)
miou_history.append(m["miou"])
main_class_f1 = m["main_class"]["f1"] if (m["main_class"] is not None) else None
if main_class_f1 is not None:
f1_history.append(main_class_f1)
cur_lr = optimizer.param_groups[0]["lr"]
lr_history.append(cur_lr)
elapsed = time.time() - t0
msg = (f"[{epoch}/{args.epochs}] "
f"train_loss={avg_train_loss:.4f} "
f"val_loss={avg_val_loss:.4f} "
f"mIoU={m['miou']:.4f} "
f"pixAcc={m['pixel_acc']:.4f} "
f"lr={cur_lr:.2e} "
f"time={elapsed:.1f}s")
if main_class_f1 is not None:
msg += f" | {MAIN_CLASS_NAME}: F1={main_class_f1:.4f} IoU={m['main_class']['iou']:.4f}"
print(msg)
# ----- Tracking de melhora por LOSS -----
improved_loss = avg_val_loss < best_val_loss - 1e-6
if improved_loss:
best_val_loss = avg_val_loss
no_imp_loss = 0
# checkpoint por loss
torch.save(model.state_dict(), os.path.join(save_path, f"{MODEL_NAME}_best.pth"))
torch.save({
"model": model.state_dict(),
"optimizer": optimizer.state_dict(),
"scaler": scaler.state_dict(),
"epoch": epoch,
"best_loss": best_loss
"best_val_loss": best_val_loss
}, os.path.join(save_path, f"{MODEL_NAME}_best_checkpoint.pth"))
print("✅ Novo melhor modelo salvo!")
print("✅ Novo melhor modelo salvo (val_loss).")
else:
no_imp_loss += 1
# Plot da curva de perda
# ----- Tracking + checkpoint por F1 da classe principal -----
if main_class_f1 is not None:
if main_class_f1 > best_main_class_f1 + delta_f1_min:
best_main_class_f1 = main_class_f1
no_imp_f1 = 0
torch.save(model.state_dict(), os.path.join(save_path, f"{MODEL_NAME}_best_f1_{MAIN_CLASS_NAME}.pth"))
print(f"🌿💾 Checkpoint salvo (melhor F1 da {MAIN_CLASS_NAME}).")
else:
no_imp_f1 += 1
else:
# se não houver F1 (ex: id não definido), ignora o critério
no_imp_f1 = 0
# ----- Scheduler inteligente -----
if active_sched == "cosine":
# se travar por plateau_patience, troca pra ReduceLROnPlateau
if no_imp_loss >= plateau_patience:
active_sched = "plateau"
print("🔁 Mudando scheduler: Cosine → ReduceLROnPlateau (platô detectado).")
# resets ao trocar
no_imp_loss = 0
no_imp_f1 = 0
epochs_since_switch = 0
plateau.step(avg_val_loss) # primeiro passo do plateau
# (opcional) “adiantar” a queda do LR:
for g in optimizer.param_groups:
g['lr'] = max(g['lr'] * plateau_factor, min_lr)
else:
cosine.step()
else:
plateau.step(avg_val_loss)
epochs_since_switch += 1
# ----- Log de estagnação -----
print(f"⏳ Sem melhora — loss: {no_imp_loss}/{patience_loss}, {MAIN_CLASS_NAME}: {no_imp_f1}/{patience_f1}")
# ----- Early stopping bi-critério (com 'graça' após switch) -----
if (no_imp_loss >= patience_loss and
(main_class_f1 is None or no_imp_f1 >= patience_f1) and
(active_sched == "cosine" or epochs_since_switch >= grace_after_switch)):
print("⏹ Early stopping: loss e F1 sem melhora (com período de graça respeitado).")
break
# ----- Plots periódicos -----
if epoch % 5 == 0 or epoch == args.epochs:
x_epochs = list(range(start_epoch, start_epoch + len(loss_history)))
x_epochs = list(range(start_epoch, start_epoch + len(train_loss_history)))
# Loss
plt.figure()
plt.plot(x_epochs, loss_history, marker="o", label="Loss de Treinamento")
plt.xlabel("Época")
plt.ylabel("Loss")
plt.grid(True)
plt.legend()
plt.title("Curva de Loss")
plt.plot(x_epochs, train_loss_history, marker="o", label="Train Loss")
plt.plot(x_epochs, val_loss_history, marker="s", label="Val Loss")
plt.xlabel("Época"); plt.ylabel("Loss"); plt.grid(True); plt.legend(); plt.title("Curva de Loss")
plt.tight_layout()
plt.savefig(os.path.join(save_path, "loss_curve.png"))
plt.close()
plt.savefig(os.path.join(save_path, "loss_curve.png")); plt.close()
# LR
plt.figure()
plt.plot(x_epochs, lr_history, marker=".")
plt.xlabel("Época"); plt.ylabel("LR"); plt.grid(True); plt.title("Learning Rate")
plt.tight_layout()
plt.savefig(os.path.join(save_path, "lr_curve.png")); plt.close()
# mIoU e F1(erva)
plt.figure()
plt.plot(x_epochs, miou_history, marker="^", label="mIoU")
if len(f1_history) == len(miou_history):
plt.plot(x_epochs, f1_history, marker="*", label=f"F1 {MAIN_CLASS_NAME}")
plt.xlabel("Época"); plt.ylabel("Score"); plt.grid(True); plt.legend(); plt.title(f"mIoU / F1({MAIN_CLASS_NAME})")
plt.tight_layout()
plt.savefig(os.path.join(save_path, "metrics_curve.png")); plt.close()
def parse_args():
ap = argparse.ArgumentParser()

View File

@ -19,10 +19,12 @@ MODEL_NAME = config["model_name"]
RESOLUCAO = config["resolucao"]
ROI_INICIO = config["roi_inicio"]
ROI_TAMANHO = config["roi_tamanho"]
MAIN_CLASS_NAME = config["main_class_name"]
use_main_class = config["use_main_class"]
dataset_path = os.path.join(MODELO, "dataset")
split_folder = "test"
labelmap_path = os.path.join(dataset_path, "labelmap.txt")
model_path = os.path.join(MODELO, "backup", config["modelo"], MODEL_NAME, MODEL_NAME + "_best.pth")
model_path = os.path.join(MODELO, "backup", config["modelo"], MODEL_NAME, f"{MODEL_NAME}_best{f'_f1_{MAIN_CLASS_NAME}' if use_main_class else ''}.pth")
def main():
parser = argparse.ArgumentParser()

View File

@ -1,7 +1,7 @@
import json
import os
import torch
from fast_scnn import FastSCNN
from fast_scnn import FastSCNN, FastSCNNWithNorm
from utils import carregar_labelmap_completo
# ⚙️ Configurações
@ -10,21 +10,23 @@ with open("config.json", "r") as f:
MODELO = config["camera"]
MODEL_NAME = config["model_name"]
RESOLUCAO = config["resolucao"]
MAIN_CLASS_NAME = config["main_class_name"]
use_main_class = config["use_main_class"]
model_path = os.path.join(MODELO, "backup", config["modelo"], MODEL_NAME)
labelmap_path = os.path.join(MODELO, "dataset", "labelmap.txt")
model_name = MODEL_NAME + "_best"
model_name = f"{MODEL_NAME}_best{f'_f1_{MAIN_CLASS_NAME}' if use_main_class else ''}"
dummy_input = torch.randn(1, 3, RESOLUCAO[1], RESOLUCAO[0]) # (batch, channels, height, width)
_, _, classes, _ = carregar_labelmap_completo(labelmap_path)
NUM_CLASSES = len(classes)
model = FastSCNN(num_classes=NUM_CLASSES) # ajuste num_classes conforme seu labelmap
model.load_state_dict(torch.load(os.path.join(model_path, model_name + ".pth")))
model.eval()
base = FastSCNNWithNorm(num_classes=NUM_CLASSES, to_rgb=True) # ajuste num_classes conforme seu labelmap
base.backbone.load_state_dict(torch.load(os.path.join(model_path, f"{MODEL_NAME}_best.pth"), map_location="cpu"))
base.eval()
torch.onnx.export(
model,
base,
dummy_input,
os.path.join(model_path, model_name + ".onnx"),
input_names=["input"],
@ -55,11 +57,11 @@ blob_path = blobconverter.from_openvino(
data_type="FP16",
shaves=6,
output_dir=model_path,
compile_params=[
"-ip U8", # entrada em bytes; compila a conversão interna p/ FP16
"--mean_values=[123.675,116.28,103.53]",
"--scale_values=[58.395,57.12,57.375]",
#compile_params=[
# "-ip U8", # entrada em bytes; compila a conversão interna p/ FP16
#"--mean_values=[123.675,116.28,103.53]",
#"--scale_values=[58.395,57.12,57.375]",
#"--reverse_input_channels" # pq você treinou em RGB
],
#],
)
print(f"Blob salvo em: {blob_path}")

View File

@ -10,13 +10,14 @@ from utils import converter_mask_ids_para_rgb, carregar_labelmap_completo
with open("config.json", "r") as f:
config = json.load(f)
MODELO = config["camera"]
MODEL_NAME = "ervas_medium" #config["model_name"]
MODEL_NAME = config["model_name"]
RESOLUCAO = config["resolucao"]
ROI_INICIO = 0.0
ROI_TAMANHO = 1.0
blob_path = os.path.join(MODELO, "backup", config["modelo"], MODEL_NAME, MODEL_NAME + "_best_openvino_2022.1_6shave.blob")
MAIN_CLASS_NAME = config["main_class_name"]
use_main_class = config["use_main_class"]
blob_path = os.path.join(MODELO, "backup", config["modelo"], MODEL_NAME, f"{MODEL_NAME}_best{f'_f1_{MAIN_CLASS_NAME}' if use_main_class else ''}_openvino_2022.1_6shave.blob")
labelmap_path = os.path.join(MODELO, "dataset", "labelmap.txt")
model_name = MODEL_NAME + "_best"
# Carregar mapa de cores
_, colormap_rgb, classes, ignore_rgb = carregar_labelmap_completo(labelmap_path)

View File

@ -2,6 +2,8 @@
"camera": "oak-1",
"modelo": "fast_scnn",
"model_name": "ervas_medium_new",
"main_class_name": "erva",
"use_main_class": true,
"resolucao": [512, 288],
"roi_inicio": 0.0,
"roi_tamanho": 1.0

View File

@ -1,5 +1,26 @@
import torch
import torch.nn as nn
class FastSCNNWithNorm(nn.Module):
def __init__(self, num_classes, mean=(123.675,116.28,103.53), std=(58.395,57.12,57.375), to_rgb=True):
super().__init__()
self.backbone = FastSCNN(num_classes=num_classes)
# registra constantes como buffers (vão pro ONNX)
m = torch.tensor(mean).view(1,3,1,1)
s = torch.tensor(std).view(1,3,1,1)
self.register_buffer("mean", m, persistent=False)
self.register_buffer("std", s, persistent=False)
self.to_rgb = to_rgb # se tua câmera entregar BGR, podemos inverter canais aqui
def forward(self, x):
# x chega como FP16 (convertido pelo NN node), mas garantimos float32 pra estabilidade das consts
x = x.float()
if self.to_rgb:
# se a ColorCamera estiver em BGR, inverte canais aqui (BGR->RGB)
x = x[:, [2,1,0], :, :]
x = (x - self.mean) / self.std
return self.backbone(x)
class FastSCNN(nn.Module):
def __init__(self, num_classes):
super().__init__()

Binary file not shown.