otimizacoes no modelo de IA para ervas

This commit is contained in:
Diego Freitas 2025-08-08 17:09:17 -03:00
parent 56b268a804
commit 2266ddc84d
63 changed files with 773 additions and 987 deletions

Binary file not shown.

View File

@ -182,7 +182,7 @@ namespace AgroBase.Forms.Operacoes
ReqFrameCam = true;
try
{
var frame = await WeedWorkerService.GetCameraFrame(CameraFrameType.Rgb);
var frame = await WeedWorkerService.GetCameraFrame(CameraFrameType.Debug);
Panel pnl = FuncoesGlobais.FindControlRecursive<Panel>(flwCamerasSolo, "pnlCamSolo_" + Variaveis.OperacaoEmAndamento.DispSen.Dados.CamerasSolo[0].Name);
AtualizarImagemPainel(pnl, frame?.image());
}

View File

@ -3518,10 +3518,12 @@
//
// flwCameras
//
this.flwCameras.AutoScroll = true;
this.flwCameras.Location = new System.Drawing.Point(4, 18);
this.flwCameras.Name = "flwCameras";
this.flwCameras.Size = new System.Drawing.Size(489, 170);
this.flwCameras.Size = new System.Drawing.Size(489, 180);
this.flwCameras.TabIndex = 97;
this.flwCameras.WrapContents = false;
//
// lblATU_LatenciaLoop
//
@ -3559,20 +3561,20 @@
this.chartAtuador.ChartAreas.Add(chartArea4);
legend4.Name = "Legend1";
this.chartAtuador.Legends.Add(legend4);
this.chartAtuador.Location = new System.Drawing.Point(4, 276);
this.chartAtuador.Location = new System.Drawing.Point(4, 286);
this.chartAtuador.Margin = new System.Windows.Forms.Padding(2);
this.chartAtuador.Name = "chartAtuador";
series4.ChartArea = "ChartArea1";
series4.Legend = "Legend1";
series4.Name = "Series1";
this.chartAtuador.Series.Add(series4);
this.chartAtuador.Size = new System.Drawing.Size(489, 241);
this.chartAtuador.Size = new System.Drawing.Size(489, 231);
this.chartAtuador.TabIndex = 95;
this.chartAtuador.Text = "chart1";
//
// pnlAtuadores
//
this.pnlAtuadores.Location = new System.Drawing.Point(4, 193);
this.pnlAtuadores.Location = new System.Drawing.Point(4, 203);
this.pnlAtuadores.Margin = new System.Windows.Forms.Padding(2);
this.pnlAtuadores.Name = "pnlAtuadores";
this.pnlAtuadores.Size = new System.Drawing.Size(489, 79);

View File

@ -26,6 +26,8 @@ namespace AgroBase.Forms.Operacoes
List<List<CameraWorkerItemModel>> LogsCam_Solo = new List<List<CameraWorkerItemModel>>();
List<string> pathImagensSolo = new List<string>();
List<PictureBox> picsCamSolo = new List<PictureBox>();
List<PictureBox> picsCamSoloSeg = new List<PictureBox>();
List<PictureBox> picsCamSoloOverlay = new List<PictureBox>();
List<CameraWorkerItemModel> LogsCam_Caminho = new List<CameraWorkerItemModel>();
string pathImagensCaminho = "";
@ -131,6 +133,8 @@ namespace AgroBase.Forms.Operacoes
pathImagensSolo = new List<string>();
picsCamSolo = new List<PictureBox>();
picsCamSoloSeg = new List<PictureBox>();
picsCamSoloOverlay = new List<PictureBox>();
flwCameras.Controls.Clear();
foreach (string cam in data.cam_solo)
{
@ -144,8 +148,26 @@ namespace AgroBase.Forms.Operacoes
Height = 166,
SizeMode = PictureBoxSizeMode.Zoom
};
PictureBox picSeg = new PictureBox()
{
Name = "picCamSoloSeg_" + cam,
Width = 250,
Height = 166,
SizeMode = PictureBoxSizeMode.Zoom
};
PictureBox picOverlay = new PictureBox()
{
Name = "picCamSoloOverlay_" + cam,
Width = 250,
Height = 166,
SizeMode = PictureBoxSizeMode.Zoom
};
flwCameras.Controls.Add(pic);
flwCameras.Controls.Add(picSeg);
flwCameras.Controls.Add(picOverlay);
picsCamSolo.Add(pic);
picsCamSoloSeg.Add(picSeg);
picsCamSoloOverlay.Add(picOverlay);
}
Mapa = new MapasModel()
@ -1117,6 +1139,10 @@ namespace AgroBase.Forms.Operacoes
{
picsCamSolo[i].Image = bitmapSolo;
}
Bitmap bitmapSoloSeg = CarregarImagemCamera(pathImagensSolo[i], "_segmentacao");
picsCamSoloSeg[i].Image = bitmapSoloSeg;
Bitmap bitmapSoloOverlay = FuncoesGlobais.FazerOverlay(bitmapSolo, bitmapSoloSeg);
picsCamSoloOverlay[i].Image = bitmapSoloOverlay;
}

View File

@ -766,6 +766,61 @@ namespace AgroBase.Models
}
}
public static Bitmap FazerOverlay(Bitmap rgb, Bitmap segmentada, float alpha = 0.35f, bool useNearest = true)
{
// 1) Garantir mesma resolução
Bitmap segSameSize = segmentada;
if (segmentada.Width != rgb.Width || segmentada.Height != rgb.Height)
{
segSameSize = new Bitmap(rgb.Width, rgb.Height, PixelFormat.Format24bppRgb);
using (var g = Graphics.FromImage(segSameSize))
{
g.InterpolationMode = useNearest ? InterpolationMode.NearestNeighbor : InterpolationMode.HighQualityBilinear;
g.PixelOffsetMode = PixelOffsetMode.Half;
g.DrawImage(segmentada, new Rectangle(0, 0, rgb.Width, rgb.Height));
}
}
// 2) Compor overlay (rgb + alpha*segmentada)
var output = new Bitmap(rgb.Width, rgb.Height, PixelFormat.Format24bppRgb);
using (var g = Graphics.FromImage(output))
using (var ia = new ImageAttributes())
{
// fundo (RGB)
g.DrawImage(rgb, 0, 0, rgb.Width, rgb.Height);
// matriz de cor com alpha global
var cm = new ColorMatrix
{
Matrix00 = 1f,
Matrix11 = 1f,
Matrix22 = 1f, // R,G,B inalterados
Matrix33 = alpha, // A (transparência da segmentação)
Matrix44 = 1f
};
ia.SetColorMatrix(cm, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
// overlay = 0.65*rgb + 0.35*seg (se quiser pesar o fundo, desenhe rgb antes, como já fizemos)
g.CompositingMode = CompositingMode.SourceOver;
g.CompositingQuality = CompositingQuality.HighSpeed;
g.InterpolationMode = useNearest ? InterpolationMode.NearestNeighbor : InterpolationMode.HighQualityBilinear;
g.PixelOffsetMode = PixelOffsetMode.Half;
g.DrawImage(
segSameSize,
new Rectangle(0, 0, rgb.Width, rgb.Height),
0, 0, segSameSize.Width, segSameSize.Height,
GraphicsUnit.Pixel,
ia
);
}
if (!ReferenceEquals(segSameSize, segmentada))
segSameSize.Dispose();
return output;
}
public static string ConverterSegundosParaHHmmss(int totalSeconds)
{
TimeSpan timeSpan = TimeSpan.FromSeconds(totalSeconds);

View File

@ -1,12 +1,5 @@
{
"epochs": [ {
"calculation_time": "13396617367014463",
"config_version": 0,
"model_version": "0",
"padded_top_topics_start_index": 0,
"taxonomy_version": 0,
"top_topics_and_observing_domains": [ ]
}, {
"calculation_time": "13397225327956881",
"config_version": 0,
"model_version": "0",
@ -27,7 +20,14 @@
"padded_top_topics_start_index": 0,
"taxonomy_version": 0,
"top_topics_and_observing_domains": [ ]
}, {
"calculation_time": "13399153521058315",
"config_version": 0,
"model_version": "0",
"padded_top_topics_start_index": 0,
"taxonomy_version": 0,
"top_topics_and_observing_domains": [ ]
} ],
"hex_encoded_hmac_key": "40F346D3248C3AFDF2BEE1FE496DBD32F7CED6E5AE98B881ABC421AA7E7B5642",
"next_scheduled_calculation_time": "13399060328666305"
"next_scheduled_calculation_time": "13399758321058597"
}

View File

@ -1,3 +1,3 @@
2025/08/06-10:48:40.356 1270 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Local Storage\leveldb/MANIFEST-000001
2025/08/06-10:48:40.362 1270 Recovering log #3
2025/08/06-10:48:40.365 1270 Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Local Storage\leveldb/000003.log
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

View File

@ -1,3 +1,3 @@
2025/08/06-10:45:42.987 5bbc Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Local Storage\leveldb/MANIFEST-000001
2025/08/06-10:45:42.993 5bbc Recovering log #3
2025/08/06-10:45:42.995 5bbc Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Local Storage\leveldb/000003.log
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

View File

@ -1 +1 @@
{"net":{"http_server_properties":{"servers":[{"alternative_service":[{"advertised_alpns":["h3"],"expiration":"13399048123791313","port":443,"protocol_str":"quic"}],"anonymization":["DAAAAAcAAABmaWxlOi8vAA==",false,0],"network_stats":{"srtt":33724},"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"}}}
{"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"}}}

View File

@ -1 +1 @@
{"sts":[{"expiry":1786019258.180145,"host":"bWGAftl61rqoc0YzqPncsLvQQh/iC2Bdp3ejUeGC83w=","mode":"force-https","sts_include_subdomains":true,"sts_observed":1754483258.180153}],"version":2}
{"sts":[{"expiry":1786217642.613677,"host":"bWGAftl61rqoc0YzqPncsLvQQh/iC2Bdp3ejUeGC83w=","mode":"force-https","sts_include_subdomains":true,"sts_observed":1754681642.613684}],"version":2}

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,3 @@
2025/08/06-14:23:57.026 1270 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Session Storage/MANIFEST-000001
2025/08/06-14:23:57.027 1270 Recovering log #3
2025/08/06-14:23:57.030 1270 Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Session Storage/000003.log
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

View File

@ -1,3 +1,3 @@
2025/08/06-10:48:33.159 5bbc Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Session Storage/MANIFEST-000001
2025/08/06-10:48:33.160 5bbc Recovering log #3
2025/08/06-10:48:33.163 5bbc Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Session Storage/000003.log
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

View File

@ -1,3 +1,3 @@
2025/08/06-10:48:40.290 5f3c Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Site Characteristics Database/MANIFEST-000001
2025/08/06-10:48:40.291 5f3c Recovering log #7
2025/08/06-10:48:40.292 5f3c Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Site Characteristics Database/000007.log
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

View File

@ -1,3 +1,3 @@
2025/08/06-10:45:42.911 3124 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Site Characteristics Database/MANIFEST-000001
2025/08/06-10:45:42.912 3124 Recovering log #7
2025/08/06-10:45:42.912 3124 Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Site Characteristics Database/000007.log
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

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"user_experience_metrics.stability.exited_cleanly":true,"variations_crash_streak":1}
{"user_experience_metrics.stability.exited_cleanly":true,"variations_crash_streak":0}

File diff suppressed because one or more lines are too long

View File

@ -1,221 +1 @@
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"Id": "1",
"Name": "CidadeJardimTerreno2",
"Length": 14.558011415731592,
"Dist1": 14.558011415731592,
"Dist2": 0.0
},
"geometry": {
"id": null,
"type": "LineString",
"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
]
]
}
},
{
"type": "Feature",
"properties": {
"Id": "2",
"Name": "CidadeJardimTerreno2",
"Length": 14.771318274761821,
"Dist1": 14.558011415731592,
"Dist2": 0.0
},
"geometry": {
"id": null,
"type": "LineString",
"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
]
]
}
},
{
"type": "Feature",
"properties": {
"Id": "3",
"Name": "CidadeJardimTerreno2",
"Length": 14.827915524386164,
"Dist1": 14.558011415731592,
"Dist2": 0.0
},
"geometry": {
"id": null,
"type": "LineString",
"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
]
]
}
},
{
"type": "Feature",
"properties": {
"Id": "4",
"Name": "CidadeJardimTerreno2",
"Length": 14.785159385903514,
"Dist1": 14.558011415731592,
"Dist2": 0.0
},
"geometry": {
"id": null,
"type": "LineString",
"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
]
]
}
},
{
"type": "Feature",
"properties": {
"Id": "5",
"Name": "CidadeJardimTerreno2",
"Length": 14.80117692191233,
"Dist1": 14.558011415731592,
"Dist2": 0.0
},
"geometry": {
"id": null,
"type": "LineString",
"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
]
]
}
}
]
}
{"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]]}}]}

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_71c541743f0c8faef84495db1742d718 {
#map_7166a4145b3904abbc4abfe2859cddb9 {
position: relative;
width: 100.0%;
height: 100.0%;
@ -54,14 +54,14 @@
<body>
<div class="folium-map" id="map_71c541743f0c8faef84495db1742d718" ></div>
<div class="folium-map" id="map_7166a4145b3904abbc4abfe2859cddb9" ></div>
</body>
<script>
var map_71c541743f0c8faef84495db1742d718 = L.map(
"map_71c541743f0c8faef84495db1742d718",
var map_7166a4145b3904abbc4abfe2859cddb9 = L.map(
"map_7166a4145b3904abbc4abfe2859cddb9",
{
center: [0.0, 0.0],
crs: L.CRS.EPSG3857,
@ -78,7 +78,7 @@
var tile_layer_44cd6f7e825210da22334b9c0a7d73f9 = L.tileLayer(
var tile_layer_9627b4b85e8fb117b7e79c7fddf9872d = L.tileLayer(
"https://tile.openstreetmap.org/{z}/{x}/{y}.png",
{
"minZoom": 0,
@ -95,7 +95,7 @@
);
tile_layer_44cd6f7e825210da22334b9c0a7d73f9.addTo(map_71c541743f0c8faef84495db1742d718);
tile_layer_9627b4b85e8fb117b7e79c7fddf9872d.addTo(map_7166a4145b3904abbc4abfe2859cddb9);
</script>
@ -116,7 +116,7 @@
}
trajeto_json_add({"features": []});
trajeto_json.addTo(map_71c541743f0c8faef84495db1742d718);
trajeto_json.addTo(map_7166a4145b3904abbc4abfe2859cddb9);
function adicionarGeometria(novaGeometria) {
trajeto_json.addData(novaGeometria);
@ -179,9 +179,9 @@
var marcadorEquipamento = L.marker([0, 0], {
icon: customIcon
}).addTo(map_71c541743f0c8faef84495db1742d718);
}).addTo(map_7166a4145b3904abbc4abfe2859cddb9);
var marcadorBase = L.marker([0, 0], {}).addTo(map_71c541743f0c8faef84495db1742d718);
var marcadorBase = L.marker([0, 0], {}).addTo(map_7166a4145b3904abbc4abfe2859cddb9);
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_71c541743f0c8faef84495db1742d718.setView(novaPosicao, map_71c541743f0c8faef84495db1742d718.getZoom());
map_7166a4145b3904abbc4abfe2859cddb9.setView(novaPosicao, map_7166a4145b3904abbc4abfe2859cddb9.getZoom());
}
}
@ -268,7 +268,7 @@
marcadorDinamico.setRotationAngle(angulo);
adicionarCoordenada("Tj", [novaLongitude, novaLatitude]);
map_71c541743f0c8faef84495db1742d718.setView(novaPosicao, map_71c541743f0c8faef84495db1742d718.getZoom());*/
map_7166a4145b3904abbc4abfe2859cddb9.setView(novaPosicao, map_7166a4145b3904abbc4abfe2859cddb9.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_ca7dc8cbe647335c250a74caa3289064 {
#map_f21522cbe2b071ceb21cf0d5c95f5a21 {
position: relative;
width: 100.0%;
height: 100.0%;
@ -54,16 +54,16 @@
<body>
<div class="folium-map" id="map_ca7dc8cbe647335c250a74caa3289064" ></div>
<div class="folium-map" id="map_f21522cbe2b071ceb21cf0d5c95f5a21" ></div>
</body>
<script>
var map_ca7dc8cbe647335c250a74caa3289064 = L.map(
"map_ca7dc8cbe647335c250a74caa3289064",
var map_f21522cbe2b071ceb21cf0d5c95f5a21 = L.map(
"map_f21522cbe2b071ceb21cf0d5c95f5a21",
{
center: [-22.172636164916668, -47.395186322185666],
center: [0.0, 0.0],
crs: L.CRS.EPSG3857,
...{
"zoom": 12,
@ -78,7 +78,7 @@
var tile_layer_79063dfeb1319c85aa667607a51dd28b = L.tileLayer(
var tile_layer_1ca71298385dd222bb7746161b7dbce5 = L.tileLayer(
"https://tile.openstreetmap.org/{z}/{x}/{y}.png",
{
"minZoom": 0,
@ -95,7 +95,7 @@
);
tile_layer_79063dfeb1319c85aa667607a51dd28b.addTo(map_ca7dc8cbe647335c250a74caa3289064);
tile_layer_1ca71298385dd222bb7746161b7dbce5.addTo(map_f21522cbe2b071ceb21cf0d5c95f5a21);
@ -111,7 +111,7 @@
}*/
});
}
function geo_json_e3dc8721488569c264b865754d696ffb_onEachFeature(feature, layer) {
function geo_json_b0da161aca2ab03761c9445da3d471fe_onEachFeature(feature, layer) {
layer.on({
@ -148,23 +148,23 @@
}*/
});
};
var geo_json_e3dc8721488569c264b865754d696ffb = L.geoJson(null, {
onEachFeature: geo_json_e3dc8721488569c264b865754d696ffb_onEachFeature,
var geo_json_b0da161aca2ab03761c9445da3d471fe = L.geoJson(null, {
onEachFeature: geo_json_b0da161aca2ab03761c9445da3d471fe_onEachFeature,
...{
}
});
function geo_json_e3dc8721488569c264b865754d696ffb_add (data) {
geo_json_e3dc8721488569c264b865754d696ffb
function geo_json_b0da161aca2ab03761c9445da3d471fe_add (data) {
geo_json_b0da161aca2ab03761c9445da3d471fe
.addData(data);
}
geo_json_e3dc8721488569c264b865754d696ffb_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_e3dc8721488569c264b865754d696ffb.setStyle(function(feature) {return feature.properties.style;});
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_e3dc8721488569c264b865754d696ffb.addTo(map_ca7dc8cbe647335c250a74caa3289064);
geo_json_b0da161aca2ab03761c9445da3d471fe.addTo(map_f21522cbe2b071ceb21cf0d5c95f5a21);
</script>
@ -185,7 +185,7 @@
}
trajeto_json_add({"features": []});
trajeto_json.addTo(map_ca7dc8cbe647335c250a74caa3289064);
trajeto_json.addTo(map_f21522cbe2b071ceb21cf0d5c95f5a21);
function adicionarGeometria(novaGeometria) {
trajeto_json.addData(novaGeometria);
@ -243,7 +243,7 @@
}
trajeto_dinamico_json_add({"features": []});
trajeto_dinamico_json.addTo(map_ca7dc8cbe647335c250a74caa3289064);
trajeto_dinamico_json.addTo(map_f21522cbe2b071ceb21cf0d5c95f5a21);
function adicionarGeometriaDinamica(novaGeometria) {
trajeto_dinamico_json.addData(novaGeometria);
@ -296,9 +296,9 @@
var marcadorEquipamento = L.marker([0, 0], {
icon: customIcon
}).addTo(map_ca7dc8cbe647335c250a74caa3289064);
}).addTo(map_f21522cbe2b071ceb21cf0d5c95f5a21);
var marcadorBase = L.marker([0, 0], {}).addTo(map_ca7dc8cbe647335c250a74caa3289064);
var marcadorBase = L.marker([0, 0], {}).addTo(map_f21522cbe2b071ceb21cf0d5c95f5a21);
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_ca7dc8cbe647335c250a74caa3289064.setView(novaPosicao, map_ca7dc8cbe647335c250a74caa3289064.getZoom());
map_f21522cbe2b071ceb21cf0d5c95f5a21.setView(novaPosicao, map_f21522cbe2b071ceb21cf0d5c95f5a21.getZoom());
}
}
@ -397,7 +397,7 @@
function atualizarSelecaoRuas(selecionadas) {
selecionadas = JSON.parse(selecionadas);
RuasSelecionadas = Array.isArray(selecionadas) ? [...selecionadas] : [];
geo_json_e3dc8721488569c264b865754d696ffb.eachLayer(function (layer) {
geo_json_b0da161aca2ab03761c9445da3d471fe.eachLayer(function (layer) {
if (RuasSelecionadas.includes(parseInt(layer.feature.id))) {
layer.setStyle({ color: 'blue' });
} else {

View File

@ -71,7 +71,7 @@ class CameraOak:
try:
self.pipeline = self._criar_pipeline()
self.device = dai.Device(self.pipeline, self.dev_info)
self.q_video = self.device.getOutputQueue(name="video", maxSize=1, blocking=False)
self.q_video = self.device.getOutputQueue(name="rgb", maxSize=1, blocking=False)
if self.tem_depth:
self.q_depth = self.device.getOutputQueue(name="depth", maxSize=1, blocking=False)
@ -139,12 +139,15 @@ class CameraOak:
pipeline = dai.Pipeline()
# RGB
cam = pipeline.create(dai.node.ColorCamera)
cam = pipeline.createColorCamera()
cam.setBoardSocket(dai.CameraBoardSocket.CAM_A)
cam.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1080_P)
cam.setInterleaved(False)
cam.setBoardSocket(dai.CameraBoardSocket.CAM_A)
xout = pipeline.create(dai.node.XLinkOut)
xout.setStreamName("video")
cam.setColorOrder(dai.ColorCameraProperties.ColorOrder.RGB)
cam.setFps(30)
xout = pipeline.createXLinkOut()
xout.setStreamName("rgb")
cam.video.link(xout.input)
self.mostrar_log(f"Pipeline rgb criado")
@ -203,18 +206,18 @@ class CameraOak:
y1 = 1.0 - (ROI_INICIO + ROI_TAMANHO)
y2 = 1.0 - ROI_INICIO
manip = pipeline.create(dai.node.ImageManip)
manip = pipeline.createImageManip()
manip.initialConfig.setCropRect(0.0, y1, 1.0, y2)
manip.initialConfig.setResize(RESOLUCAO[1], RESOLUCAO[0])
manip.initialConfig.setResize(RESOLUCAO[0], RESOLUCAO[1])
manip.initialConfig.setFrameType(dai.RawImgFrame.Type.RGB888p)
manip.initialConfig.setKeepAspectRatio(False)
cam.video.link(manip.inputImage)
cam.preview.link(manip.inputImage)
nn = pipeline.create(dai.node.NeuralNetwork)
nn = pipeline.createNeuralNetwork()
nn.setBlobPath(blob_path)
manip.out.link(nn.input)
xout_nn = pipeline.create(dai.node.XLinkOut)
xout_nn = pipeline.createXLinkOut()
xout_nn.setStreamName("nn")
nn.out.link(xout_nn.input)
@ -291,11 +294,12 @@ class CameraOak:
return None, {"erro": "Segmentação não disponível", "duracao": 0, "frame_valido": False}
start = time.time()
try:
w, h = self.modelo_ia_onboard["ia_resolution"]
in_nn = self.q_nn.get()
out = in_nn.getFirstLayerFp16()
h, w = self.modelo_ia_onboard["ia_resolution"]
out_np = np.array(out, dtype=np.float32).reshape((len(self.classes), h, w))
pred_ids = np.argmax(out_np, axis=0).astype(np.uint8)
out_raw = in_nn.getFirstLayerFp16()
arr16 = np.frombuffer(np.asarray(out_raw, dtype=np.float16), dtype=np.float16)
arr16 = arr16.reshape(len(self.classes), h, w)
pred_ids = arr16.argmax(axis=0).astype(np.uint8, copy=False)
dur = time.time() - start
self.timestamp_ultima_segmentacao = time.time()
return pred_ids, {"erro": None, "duracao": dur, "frame_valido": True}

View File

@ -35,6 +35,40 @@ def decode_image_base64(base64_str):
frame_decodificado = cv2.imdecode(img_array, cv2.IMREAD_COLOR)
return frame_decodificado
def fazer_overlay(rgb_bgr, seg_color, alpha=0.35, out_size=None, seg_is_rgb=False):
"""
rgb_bgr: np.uint8 HxWx3 (BGR)
seg_color: np.uint8 HxWx3 (BGR) ou RGB (defina seg_is_rgb=True)
alpha: peso da segmentação (0..1)
out_size: (W, H) opcional pra forçar dimensão final
"""
# converte seg pra BGR se veio em RGB
if seg_is_rgb:
seg_color = cv2.cvtColor(seg_color, cv2.COLOR_RGB2BGR)
# garante mesmo tamanho (usa INTER_AREA pro RGB e NEAREST pra máscara)
if out_size is not None:
W, H = out_size
else:
H, W = rgb_bgr.shape[:2]
if (rgb_bgr.shape[1], rgb_bgr.shape[0]) != (W, H):
rgb_res = np.empty((H, W, 3), np.uint8)
cv2.resize(rgb_bgr, (W, H), dst=rgb_res, interpolation=cv2.INTER_AREA)
else:
rgb_res = rgb_bgr
if (seg_color.shape[1], seg_color.shape[0]) != (W, H):
seg_res = np.empty((H, W, 3), np.uint8)
cv2.resize(seg_color, (W, H), dst=seg_res, interpolation=cv2.INTER_NEAREST)
else:
seg_res = seg_color
# blend (0.65*RGB + 0.35*SEG por padrão)
out = np.empty_like(rgb_res)
cv2.addWeighted(rgb_res, 1.0 - alpha, seg_res, alpha, 0, dst=out)
return out
def analisar_linhas_por_profundidade(matriz, campo, fov_h):
try:
linhas_info = {}

View File

@ -5,7 +5,7 @@ import time
import cv2
from shared.enums import StatusModulo, CameraFrameType, StatusOperacao, WeedWorkerCommandType
from shared.utils import decode_image_base64, encode_image_base64
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
from weed_worker.weed_detector import WeedDetector
@ -63,7 +63,7 @@ class CameraManager:
self.weed_detector = WeedDetector(self.camera.colormap_rgb, self.camera.classes)
self._iniciar_loop_analise_continua(10.0)
self._iniciar_loop_analise_continua(20.0)
self.iniciando = False
self.atualizar_saude_camera()
@ -114,12 +114,21 @@ class CameraManager:
f = None
t = None
if tipo == CameraFrameType.Rgb:
f, t, _ = self.get_rgb_frame()
f = self._ultimo_rgb_frame
if f is not None:
f = encode_image_base64(f)
t = self.camera.timestamp_ultimo_frame_rgb
elif tipo == CameraFrameType.Segmentacao:
f = self._ultima_analise.get("frame", {}).get("frame")
t = self._ultima_analise.get("timestamp")
elif tipo == CameraFrameType.Debug:
frame_seg = self._ultima_analise.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):
@ -156,9 +165,11 @@ class CameraManager:
predictions, ts, res = self.get_segmentation_predictions()
if ts == self._ts_segmentacao_anterior:
return # já analisado
fps = 1.0 / (ts - self._ts_segmentacao_anterior)
self._ts_segmentacao_anterior = ts
if predictions is not None:
analise_completa = self.detectar_ervas(predictions)
rgb_frame, ts_frame, res_frame = self.get_rgb_frame()
analise_completa = self.detectar_ervas(predictions, rgb_frame)
analise = analise_completa.get("dados_visuais", {})
#self.mostrar_log(f"Deteccoes no radar: {len(analise.get('deteccoes', []))}")
@ -166,6 +177,7 @@ class CameraManager:
ContextoGlobalRedis.atualizar_ctx_dict(
CtxKey.DadosWeedWorker,
ts_analise=ts,
fps_model=fps,
analise=converter_valores_numpy(analise)
)
@ -185,12 +197,12 @@ class CameraManager:
self._ultima_analise = analise_completa.copy()
def detectar_ervas(self, predictions):
def detectar_ervas(self, predictions, rgb_frame):
if self.weed_detector is None:
self.mostrar_log("WeedDetector não inicializado!")
return []
try:
return self.weed_detector.detectar(predictions)
return self.weed_detector.detectar(predictions, rgb_frame)
except Exception as e:
self.mostrar_log(f"Erro na detecção de ervas: {e}")
return []

View File

@ -4,8 +4,10 @@
"frames_histerese": 2,
"min_area_px": 400,
"max_area_frac": 0.2,
"ia_roi_size": 0.2,
"ia_resolution": [384,384],
"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,

View File

@ -42,36 +42,58 @@ _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,
"ia_roi_size": 0.2,
"ia_resolution": [384,384],
"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
}
#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,
"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
}
dadosAtu = ContextoGlobalRedis.get_operacao().get("Atu", {})
contexto = ContextoGlobalRedis.get_contexto()
_CONFIG_CACHE["qtd_bicos"] = dadosAtu.get("qtd_bicos", 4)
@ -79,7 +101,7 @@ def load_config(force_reload=False):
_CONFIG_CACHE["ia_model_path"] = ContextoGlobalRedis.get_equipamento().get("path_ia_model_ervas", "C:/AgroBaseModels/Ervas/model-2_1.blob")
_CONFIG_CACHE["ia_labelmap_path"] = ContextoGlobalRedis.get_equipamento().get("path_ia_labelmap_ervas", "C:/AgroBaseModels/Ervas/model-2_1.txt")
_CONFIG_CACHE["ia_roi_begin"] = dadosAtu.get("percent_vertical_deteccao", 0.3)
_CONFIG_CACHE["faixa_atuacao_bicos"] = dadosAtu.get("percent_vertical_deteccao", 0.3)
return _CONFIG_CACHE
def reload_config():

View File

@ -10,20 +10,29 @@ class ClassesSegmentacao(IntEnum):
CANA = 1
CHAO = 2
class WeedDetector:
def __init__(self, color_map, classes):
from weed_worker.config import load_config
config = load_config()
resolucao = config.get("ia_resolution")
self._reiniciar_deteccoes()
self.color_map = color_map
self.classes = classes
self.resolucao = (resolucao[0], resolucao[1])
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._reiniciar_deteccoes()
self.use_mock = False
self.img_mock = "C:\\ZendionInc\\agrobot_base\\AgroBase\\AgroBase\\bin\\x64\\Debug\\Operacoes\\25_07_2025_14_39_14\\Cam0\\85_rgb.jpeg"
self.resolucao = (resolucao[0], resolucao[1])
self.predictions = None
self.dados_visuais = {}
self._dbg_img_shape = (640, 360)
self._mostrar_debug = False
self._dbg_last_ts = None
self._dbg_fps_ema = None # fps do "ciclo de debug" (pós-segmentação)
@ -41,6 +50,17 @@ class WeedDetector:
self.ervas_identificadas = [dict() for _ in range(qtd_bicos)]
self.ultimo_status_bicos = {i: False for i in range(qtd_bicos)}
self.ervas_registradas_bico = [set() for _ in range(qtd_bicos)]
self.pred_rgb = np.empty((self.resolucao[1], self.resolucao[0], 3), dtype=np.uint8)
self._is_weed = np.zeros(256, dtype=bool)
self._is_weed[int(ClassesSegmentacao.ERVA.value)] = True
self._inv_total = 1.0 / (self.resolucao[1] * self.resolucao[0])
self._erva_frac_global_ema = 0.0
self._ervas_no_radar = False
self._ervas_no_radar_percent = 0.0
self.debug_estat = False
ContextoGlobalRedis.atualizar_ctx_dict(
CtxKey.DadosWeedWorker,
analise__ervas_identificadas=self.ervas_identificadas
@ -48,14 +68,9 @@ class WeedDetector:
def _segmentar_predictions(self, predictions):
try:
# 🔸 Redimensiona para RGB bonitão (overlay, debug ou exportar)
mask_resized = cv2.resize(predictions.astype(np.uint8), self.resolucao, interpolation=cv2.INTER_NEAREST)
self.pred_rgb[:] = self.lut[predictions]
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)
return {
@ -65,14 +80,14 @@ class WeedDetector:
"frame": frame_color
},
"mask_color": mask_color,
"classes": mask_resized
"classes": predictions
}
except Exception as e:
print(f"Erro ao processar predictions: {e}")
return None
def detectar(self, predictions):
def detectar(self, predictions, rgb_frame=None):
try:
# 🔸 Constrói a máscara colorida e outras saídas com base na predictions já pronta
resultado = self._segmentar_predictions(predictions)
@ -80,59 +95,35 @@ class WeedDetector:
print("[Erro] Segmentação vazia ou falhou")
return None
classes_mask = resultado.get("classes")
if classes_mask is None:
if predictions is None:
print("[Erro] Máscara de classes não encontrada no resultado")
return None
# 🔸 Extrai blobs da classe ERVA (classe_id = 0)
#mask_erva = (classes_mask == ClassesSegmentacao.ERVA.value).astype(np.uint8)
#num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(mask_erva, connectivity=8)
#deteccoes = []
#for i in range(1, num_labels): # ignora fundo
# x, y, w, h, area = stats[i]
# deteccoes.append({
# 'id': 0,
# 'x': x,
# 'y': y,
# 'largura': w,
# 'altura': h,
# 'confianca': 1.0,
# 'descricao': self.classes[ClassesSegmentacao.ERVA.value]
# })
# 🔸 Calcula controle de bicos com base na segmentação
#controle_bicos, deteccoes_filtro = self._calcular_atuacao_bicos(deteccoes, classes_mask.shape)
# 🔸 Decisão por máscara (sem bbox)
#controle_bicos, estat, ervas_no_radar = self._atuacao_por_mascara(classes_mask)
#self.frame_idx += 1
from weed_worker.config import load_config
config = load_config()
# vel_norm pode vir do contexto (0..1 da sua Vmax). Se não tiver, manda 0.0
vel_norm = float(config.get("velocidade_robo", 0.0))
ervas_no_radar, estat_erva = self._decidir_ervas_no_radar(classes_mask, config, vel_norm=vel_norm)
controle_bicos, estat_bicos = self._atuacao_por_mascara(classes_mask, config) # sua lógica de CANA por setor
ervas_no_radar, estat_erva = self._decidir_ervas_no_radar(predictions, config, vel_norm=vel_norm)
controle_bicos, estat_bicos = self._atuacao_por_mascara(predictions, config) # sua lógica de CANA por setor
resultado["dados_visuais"] = {
"timestamp": time.time(),
"height": classes_mask.shape[0],
"width": classes_mask.shape[1],
"height": predictions.shape[0],
"width": predictions.shape[1],
"deteccoes": [], # deteccoes_filtro,
"controle": controle_bicos,
"ervas_identificadas": self.ervas_identificadas,
"ervas_no_radar": ervas_no_radar, # <- NOVO sinal
"ervas_no_radar": ervas_no_radar,
"estatisticas": {
"erva": estat_erva,
"bicos": estat_bicos
}
}
self._mostrar_debug_bicos(resultado["mask_color"], [], controle_bicos, ervas_no_radar)
frame = rgb_frame if rgb_frame is not None else resultado["mask_color"]
self._mostrar_debug_bicos(frame, resultado["classes"], [], controle_bicos, config)
return resultado
@ -140,250 +131,218 @@ class WeedDetector:
print(f"Erro ao detectar ervas com predictions: {e}")
return None
def _calcular_iou(self, bbox1, bbox2):
x1, y1, w1, h1 = bbox1
x2, y2, w2, h2 = bbox2
def _atuacao_por_mascara(self, predictions, config):
"""
Decide atuação dos bicos por máscara (sem bbox), otimizado:
- LUT booleana pra "é erva?"
- soma por colunas + binning com np.add.reduceat (sem loop por bico)
- kernel morfológico cacheado
- retorna dict se precisar (debug); senão usa arrays
"""
qtd_bicos = int(config.get("qtd_bicos"))
zona_inicio = float(config.get("faixa_atuacao_bicos"))
zona_altura = float(config.get("area_atuacao_bicos"))
usar_morf = bool(config.get("usar_morfologia", False))
kernel_morf = int(config.get("kernel_morf", 3))
xi1 = max(x1, x2)
yi1 = max(y1, y2)
xi2 = min(x1 + w1, x2 + w2)
yi2 = min(y1 + h1, y2 + h2)
inter_width = max(0, xi2 - xi1)
inter_height = max(0, yi2 - yi1)
inter_area = inter_width * inter_height
# threshold: fração (<1) ou px absolutos (>=1)
thr_cfg = float(config.get("min_frac_erva_por_bico", 0.02))
area1 = w1 * h1
area2 = w2 * h2
union_area = area1 + area2 - inter_area
H, W = predictions.shape[:2]
if union_area == 0:
return 0
return inter_area / union_area
# recorte vertical (banda de atuação)
y_inicio = int((1.0 - zona_inicio) * H)
y_fim = int((1.0 - (zona_inicio + zona_altura)) * H)
y_top = min(y_inicio, y_fim)
y_bot = max(y_inicio, y_fim)
if y_bot <= y_top:
# nada a fazer
zeros = np.zeros(qtd_bicos, dtype=bool)
estat = {"contagem_cana_px_por_bico": np.zeros(qtd_bicos, int),
"contagem_cana_frac_por_bico": np.zeros(qtd_bicos, float),
"faixa": {"y_top": y_top, "y_bot": y_bot}}
self.ultimo_status_bicos = {i: False for i in range(qtd_bicos)}
return {i: False for i in range(qtd_bicos)}, estat
def _track_ervas(self, detections, histerese, velocidade_robo):
band = predictions[y_top:y_bot, :]
# --- LUT booleana (inicialize uma vez no __init__):
# self._is_weed = np.zeros(256, bool); self._is_weed[ClassesSegmentacao.ERVA.value] = True
mask_erva = self._is_weed[band] # bool view HxW da banda
# --- morfologia opcional (kernel cacheado):
if usar_morf and kernel_morf >= 3 and (kernel_morf & 1):
# cacheia por tamanho pra não recriar
if getattr(self, "_morf_cache_k", None) != kernel_morf:
self._morf_cache_k = kernel_morf
self._morf_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (kernel_morf, kernel_morf))
mask_erva = cv2.morphologyEx(mask_erva.astype(np.uint8), cv2.MORPH_OPEN, self._morf_kernel).astype(bool)
# --- soma por colunas e binning por bicos (sem loop):
# soma de 'True' por coluna
col_sums = mask_erva.sum(axis=0).astype(np.int32) # shape (W,)
# bordas dos bicos (inteiros, de 0 a W):
# exato e sem acumulador manual
edges = np.linspace(0, W, qtd_bicos + 1, dtype=np.int32)
# soma por bico: reduceat soma colunas entre edges[i]:edges[i+1]
px_erva_por_bico = np.add.reduceat(col_sums, edges[:-1])
# áreas por bico (altura da banda * largura de cada setor)
alturas = (y_bot - y_top)
larguras = np.diff(edges)
area_por_bico = alturas * larguras
# fração por bico (evita divisão por zero)
with np.errstate(divide="ignore", invalid="ignore"):
frac_por_bico = np.where(area_por_bico > 0, px_erva_por_bico / area_por_bico, 0.0)
# decisão vetorizada
if thr_cfg < 1.0:
ativa_arr = (frac_por_bico >= thr_cfg)
else:
ativa_arr = (px_erva_por_bico >= int(thr_cfg))
# salva status (se você precisa em dict em outro lugar)
atuacao_bicos = {i: bool(ativa_arr[i]) for i in range(qtd_bicos)}
self.ultimo_status_bicos = atuacao_bicos.copy()
estatisticas = {
"contagem_cana_px_por_bico": px_erva_por_bico, # array
"contagem_cana_frac_por_bico": frac_por_bico, # array
"faixa": {"y_top": y_top, "y_bot": y_bot},
"larguras": larguras,
}
return atuacao_bicos, estatisticas
def _decidir_ervas_no_radar(self, predictions, cfg, vel_norm=0.0):
# thresholds base
on_global = float(cfg.get("min_frac_erva_global_on", 0.0020))
off_global = float(cfg.get("min_frac_erva_global_off", 0.0015))
# ajuste por velocidade
k = float(cfg.get("erva_thresh_vel_gain", 0.0))
if k:
adj = 1.0 - k * float(vel_norm)
if adj < 0.5: adj = 0.5
elif adj > 1.0: adj = 1.0
on_global *= adj
else:
adj = 1.0
# fração global (sem ==)
weed_sum = int(self._is_weed[predictions].sum())
frac_global = weed_sum * self._inv_total
# EMA
alpha = float(cfg.get("erva_frac_ema", 0.3))
ema_g = self._erva_frac_global_ema = (1 - alpha) * self._erva_frac_global_ema + alpha * frac_global
# histerese
prev = self._ervas_no_radar
thr = off_global if prev else on_global
ervas_no_radar = ema_g >= thr
self._ervas_no_radar = ervas_no_radar
self._ervas_no_radar_percent = frac_global
if self.debug_estat:
return ervas_no_radar, {
"frac_global": frac_global,
"ema_global": ema_g,
"thr_on_global": on_global,
"thr_off_global": off_global,
"vel_adj": adj,
}
return ervas_no_radar, None
def _mostrar_debug_bicos(self, rgb_frame, classes_mask, detections, atuacao_bicos, config=None):
try:
max_dist_base = 60 # px (parado)
fator_velocidade = 100 # px por m/s (ajuste conforme calibragem real!)
max_dist = max_dist_base + fator_velocidade * velocidade_robo
_ervas_filtradas = []
_ervas_radar = []
#print(f"Deteccoes: {detections}")
for det in detections:
cx = det["x"] + det["largura"] // 2
cy = det["y"] + det["altura"] // 2
class_id = det["id"]
found = False
for erva in self.ervas_ativas_filtradas:
if erva["class_id"] == class_id:
ecx, ecy = erva["centro"]
dist = np.hypot(cx - ecx, cy - ecy)
iou = self._calcular_iou((det["x"], det["y"], det["largura"], det["altura"]), erva["bbox"])
if dist < max_dist or iou > 0.3:
# Atualiza erva
erva["centro"] = (cx, cy)
erva["bbox"] = (det["x"], det["y"], det["largura"], det["altura"])
erva["ultimo_frame"] = self.frame_idx
erva["frames_detectada"] = erva.get("frames_detectada", 0) + 1
_ervas_filtradas.append(erva)
_ervas_radar.append(erva)
found = True
break
if not found:
erva = {
"id": self.next_erva_id,
"centro": (cx, cy),
"bbox": (det["x"], det["y"], det["largura"], det["altura"]),
"class_id": class_id,
"ultimo_frame": self.frame_idx,
"frames_detectada": 1, # Primeira vez detectada
"status": "ativa",
"descricao": det["descricao"],
"confianca": det["confianca"]
}
self.next_erva_id += 1
_ervas_filtradas.append(erva)
_ervas_radar.append(erva)
# Remove ervas sumidas há mais de N frames
self.ervas_ativas_filtradas = [
erva for erva in _ervas_filtradas
if self.frame_idx - erva["ultimo_frame"] < histerese
]
self.ervas_ativas_radar = [
erva for erva in _ervas_radar
if self.frame_idx - erva["ultimo_frame"] < histerese
]
#print(f"Ervas ativas: {self.ervas_ativas}")
return self.ervas_ativas_filtradas, self.ervas_ativas_radar
except Exception as e:
print(f"Erro ao trackear ervas: {e}")
def _calcular_atuacao_bicos(self, detections, frame_shape):
try:
from weed_worker.config import load_config
config = load_config()
qtd_bicos = config.get("qtd_bicos")
zona_inicio = config.get("ia_roi_begin")
zona_altura = config.get("ia_roi_size")
frames_consecutivos = config.get("frames_consecutivos")
histerese = config.get("frames_histerese")
velocidade_robo = config.get("velocidade_robo")
min_area = config.get("min_area_px")
max_area = config.get("max_area_frac")
H, W = frame_shape[:2]
largura_bico = W / qtd_bicos
y_inicio = int((1.0 - zona_inicio) * H)
y_fim = int((1.0 - (zona_inicio + zona_altura)) * H)
atuacao_bicos = {i: False for i in range(qtd_bicos)}
ervas_filtradas, ervas_radar = self._track_ervas(detections, histerese, velocidade_robo)
deteccoes = []
for det in ervas_radar:
if not self._filtro_bbox(det, frame_shape, min_area, max_area):
continue
if det.get("frames_detectada", 0) < frames_consecutivos:
continue
deteccoes.append(det)
x, y, w, h = det["bbox"][0], det["bbox"][1], det["bbox"][2], det["bbox"][3]
y_faixa_top = min(y_inicio, y_fim)
y_faixa_bot = max(y_inicio, y_fim)
y_base = y + h
y_top = y
# Só atua se base da erva está dentro da faixa fina de atuação!
if y_base >= y_faixa_top and y_top <= y_faixa_bot:
x1 = x
x2 = x + w - 1
idx_ini = int(x1 / largura_bico)
idx_fim = int(x2 / largura_bico)
idx_ini = max(0, min(idx_ini, qtd_bicos - 1))
idx_fim = max(0, min(idx_fim, qtd_bicos - 1))
for i in range(idx_ini, idx_fim + 1):
atuacao_bicos[i] = True
self._registrar_erva_identificada(i, det["descricao"], det["id"])
self.ultimo_status_bicos = atuacao_bicos.copy()
return atuacao_bicos, deteccoes
except Exception as e:
print(f"Erro ao calcular atuacaoi dos bicos: {e}")
def _filtro_bbox(self, det, frame_shape, min_area, max_area_frac):
"""Filtra bbox por área mínima e máxima (anti ruído/falsos positivos)"""
try:
H, W = frame_shape[:2]
max_area = max_area_frac * (W * H)
w, h = det["bbox"][2], det["bbox"][3]
area = w * h
if area < min_area or area > max_area:
return False
return True
except Exception as e:
print(f"Erro ao filtrar as bboxes: {e}")
def _registrar_erva_identificada(self, idx_bico, class_erva, id_erva):
if id_erva in self.ervas_registradas_bico[idx_bico]:
return
d = self.ervas_identificadas[idx_bico]
d[class_erva] = d.get(class_erva, 0) + 1
self.ervas_registradas_bico[idx_bico].add(id_erva)
def _mostrar_debug_bicos(self, frame, detections, atuacao_bicos, ervas_no_radar):
try:
# mede FPS do "ciclo de debug" (render + imshow)
dbg_fps = self._fps_update('_dbg_last_ts', '_dbg_fps_ema')
#original = cv2.resize(frame, self.resolucao)
#seg_color = np.zeros_like(original)
#for class_id, color in enumerate(self.color_map):
# seg_color[self.predictions == class_id] = color
#overlay = cv2.addWeighted(original, 0.5, seg_color, 0.5, 0)
#debug_img = frame.copy()
debug_img = cv2.resize(frame.copy(), (1920,1080), interpolation=cv2.INTER_NEAREST)
H, W = debug_img.shape[:2]
if not self._mostrar_debug:
#print(f"FPS {dbg_fps:.2f}")
return
from weed_worker.config import load_config
config = load_config()
qtd_bicos = config.get("qtd_bicos")
zona_inicio = config.get("ia_roi_begin")
faixa_atuacao = config.get("ia_roi_size")
#H, W = overlay.shape[:2]
largura_bico = W / qtd_bicos
# --- cache/config ---
if config is None:
config = self._cached_cfg # já carregado fora do loop, atualize quando mudar
qtd_bicos = int(config.get("qtd_bicos"))
zona_inicio = float(config.get("faixa_atuacao_bicos"))
faixa_atuacao = float(config.get("area_atuacao_bicos"))
# Limites ajustados da faixa de atuação (do topo para baixo!)
# --- resize sem alocar ---
if not hasattr(self, "_dbg_img") or self._dbg_img.shape[:2] != self._dbg_img_shape:
self._dbg_img = np.empty((self._dbg_img_shape[1], self._dbg_img_shape[0], 3), dtype=np.uint8)
self._seg_color = np.empty((self._dbg_img_shape[1], self._dbg_img_shape[0], 3), dtype=np.uint8)
self._layer = np.zeros_like(self._dbg_img)
cv2.resize(rgb_frame, self._dbg_img_shape, dst=self._dbg_img, interpolation=cv2.INTER_AREA)
cm_resized = cv2.resize(classes_mask, self._dbg_img_shape, interpolation=cv2.INTER_NEAREST)
# --- colore segmentação por LUT (BGR) ---
# self.color_lut: (256,3) uint8 BGR (prepare uma vez no __init__)
self._seg_color[:] = self.color_lut[cm_resized]
# --- overlay da segmentação (um addWeighted) ---
cv2.addWeighted(self._seg_color, 0.35, self._dbg_img, 0.65, 0, dst=self._dbg_img)
W, H = self._dbg_img_shape
largura_bico = W / float(qtd_bicos)
# --- faixa de atuação: desenha em layer e blend uma vez ---
y_inicio = int((1.0 - zona_inicio) * H)
y_fim = int((1.0 - (zona_inicio + faixa_atuacao)) * H)
y_fim = int((1.0 - (zona_inicio + faixa_atuacao)) * H)
y0, y1 = min(y_inicio, y_fim), max(y_inicio, y_fim)
#debug_img = overlay.copy()
self._layer.fill(0) # zera layer (sem realocar)
cv2.rectangle(self._layer, (0, y0), (W, y1), (220, 220, 100), thickness=-1)
cv2.addWeighted(self._layer, 0.18, self._dbg_img, 0.82, 0, dst=self._dbg_img)
cv2.rectangle(self._dbg_img, (0, y0), (W, y1), (180, 180, 80), 2)
cv2.putText(self._dbg_img, "Zona de Atuacao", (10, max(0, y0 - 10)), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (180, 180, 80), 2)
# --- bicos: desenha todos os retângulos ativos na layer e blend uma vez ---
self._layer.fill(0)
CORES = [(0,255,0), (255,0,0), (0,255,255), (255,128,0), (255,0,255), (0,128,255), (128,255,0), (0,0,255)]
# Faixa de atuação (transparente)
overlay_tmp = debug_img.copy()
cv2.rectangle(overlay_tmp, (0, y_fim), (W, y_inicio), (220,220,100), -1)
cv2.addWeighted(overlay_tmp, 0.18, debug_img, 0.82, 0, debug_img)
# Borda + texto
cv2.rectangle(debug_img, (0, y_fim), (W, y_inicio), (180,180,80), 2)
cv2.putText(debug_img, "Zona de Atuacao", (10, y_inicio-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (180,180,80), 2)
controle_bicos = ContextoGlobalRedis.get_controle().get("controle_bicos", {})
# Zonas dos bicos
for i in range(qtd_bicos):
x0 = int(i * largura_bico)
x1 = int((i+1) * largura_bico)
x1 = int((i + 1) * largura_bico)
cor = CORES[i % len(CORES)]
if atuacao_bicos.get(i, False):
overlay2 = debug_img.copy()
cv2.rectangle(overlay2, (x0, y_fim), (x1, y_inicio), cor, -1)
cv2.addWeighted(overlay2, 0.15, debug_img, 0.85, 0, debug_img)
cv2.rectangle(debug_img, (x0, y_fim), (x1, y_inicio), cor, 1)
cv2.rectangle(self._layer, (x0, y0), (x1, y1), cor, thickness=-1)
# um blend para todos os bicos ligados
cv2.addWeighted(self._layer, 0.15, self._dbg_img, 0.85, 0, dst=self._dbg_img)
# bordas + texto (rápido, mantém no loop)
for i in range(qtd_bicos):
x0 = int(i * largura_bico)
x1 = int((i + 1) * largura_bico)
cor = CORES[i % len(CORES)]
cv2.rectangle(self._dbg_img, (x0, y0), (x1, y1), cor, 1)
status = "ON" if atuacao_bicos.get(i, False) else "OFF"
status_controle = "ON" if controle_bicos.get(i, False) else "OFF"
cv2.putText(debug_img, f"Bico {i} {status} ({status_controle})", (x0+5, y_inicio+25), cv2.FONT_HERSHEY_SIMPLEX, 0.6, cor, 2)
cv2.putText(self._dbg_img, f"Bico {i} {status}", (x0 + 5, min(H-5, y1 + 20)),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, cor, 2)
escala_x = self.resolucao[0] / frame.shape[1]
escala_y = self.resolucao[1] / frame.shape[0]
# BBoxes
# --- bboxes (escala pro debug HxW) ---
sx = W / float(self.resolucao[0])
sy = H / float(self.resolucao[1])
for det in detections:
x = int(det["bbox"][0] * escala_x)
y = int(det["bbox"][1] * escala_y)
w = int(det["bbox"][2] * escala_x)
h = int(det["bbox"][3] * escala_y)
_id = det.get("id")
class_name = det.get("descricao", "erva")
conf = det.get("confianca", 0)
bbox_cor = (0,0,255)
cv2.rectangle(debug_img, (x, y), (x+w, y+h), bbox_cor, 2)
cv2.putText(debug_img, f"ID: {_id} | {class_name} {conf:.2f}", (x, y-5),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, bbox_cor, 2)
x = int(det["bbox"][0] * sx); y = int(det["bbox"][1] * sy)
w = int(det["bbox"][2] * sx); h = int(det["bbox"][3] * sy)
bbox_cor = (0, 0, 255)
cv2.rectangle(self._dbg_img, (x, y), (x + w, y + h), bbox_cor, 2)
cv2.putText(self._dbg_img, f'ID:{det.get("id")} {det.get("descricao","erva")} {det.get("confianca",0):.2f}',
(x, max(0, y - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, bbox_cor, 2)
# --- HUD de performance ---
cv2.putText(debug_img, f"Ervas no radar: {'Sim' if ervas_no_radar else 'Nao'}", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0,255,0), 2)
cv2.putText(debug_img, f"Dbg FPS: {dbg_fps:.1f}", (10, 60),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0,255,0), 2)
# --- HUD ---
cv2.putText(self._dbg_img, f"Ervas no radar: {'Sim' if self._ervas_no_radar else 'Nao'} ({(self._ervas_no_radar_percent * 100.0):.2f}%)", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0,255,0), 2)
cv2.putText(self._dbg_img, f"Dbg FPS: {dbg_fps:.1f}", (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0,255,0), 2)
cv2.imshow("Debug Weed Worker", cv2.cvtColor(debug_img, cv2.COLOR_RGB2BGR))
# Dica: para nao travar pipeline, mantenha 1ms; evite valores maiores
# já está em BGR
cv2.imshow("Debug Weed Worker", self._dbg_img)
cv2.waitKey(1)
except Exception as e:
print(f"Erro ao mostrar debug: {e}")
def _fps_update(self, last_ts_attr: str, ema_attr: str, alpha: float = 0.2):
"""Atualiza e retorna FPS (EMA) baseado no timestamp anterior salvo em self"""
import time
@ -396,145 +355,3 @@ class WeedDetector:
setattr(self, last_ts_attr, now)
setattr(self, ema_attr, fps_ema)
return fps_ema if fps_ema is not None else 0.0
def _atuacao_por_mascara(self, classes_mask, config):
"""
Decide atuação dos bicos por máscara (sem bbox):
- Para cada bico: ativa se a fração de CANA no setor >= limiar.
- Limiar aceita dois formatos:
< 1.0 => fração (recomendado, independente da resolução)
>= 1 => pixels absolutos (retrocompat)
Retorna: (atuacao_bicos: dict, estatisticas: dict)
"""
qtd_bicos = int(config.get("qtd_bicos"))
zona_inicio = float(config.get("ia_roi_begin"))
zona_altura = float(config.get("ia_roi_size"))
usar_morf = bool(config.get("usar_morfologia", False))
kernel_morf = int(config.get("kernel_morf", 3))
# Limiar “dinâmico”: fração (<1) ou absoluto (>=1)
thr_erva_cfg = config.get("min_frac_erva_por_bico", 0.02) # 2% do setor por padrão
try:
thr_erva_cfg = float(thr_erva_cfg)
except:
thr_erva_cfg = 0.02
H, W = classes_mask.shape[:2]
y_inicio = int((1.0 - zona_inicio) * H)
y_fim = int((1.0 - (zona_inicio + zona_altura)) * H)
y_top = min(y_inicio, y_fim)
y_bot = max(y_inicio, y_fim)
band = classes_mask[y_top:y_bot, :]
# Máscara binária de CANA na banda
mask_erva = (band == ClassesSegmentacao.ERVA.value).astype(np.uint8)
# Anti-ruído opcional
if usar_morf and kernel_morf >= 3 and kernel_morf % 2 == 1:
k = cv2.getStructuringElement(cv2.MORPH_RECT, (kernel_morf, kernel_morf))
mask_erva = cv2.morphologyEx(mask_erva, cv2.MORPH_OPEN, k)
largura_bico = W / float(qtd_bicos)
atuacao_bicos = {i: False for i in range(qtd_bicos)}
cont_erva_px = {}
cont_erva_frac = {}
for i in range(qtd_bicos):
x0 = int(i * largura_bico)
x1 = int((i + 1) * largura_bico)
x0 = max(0, min(W, x0))
x1 = max(0, min(W, x1))
if x1 <= x0:
cont_erva_px[i] = 0
cont_erva_frac[i] = 0.0
continue
region = mask_erva[:, x0:x1]
px_erva = int(region.sum())
area_setor = float(region.size)
frac_erva = (px_erva / area_setor) if area_setor > 0 else 0.0
cont_erva_px[i] = px_erva
cont_erva_frac[i] = frac_erva
# Se threshold <1: comparar por fração; se >=1: comparar por pixels
if thr_erva_cfg < 1.0:
ativa = (frac_erva >= thr_erva_cfg)
else:
ativa = (px_erva >= int(thr_erva_cfg))
atuacao_bicos[i] = bool(ativa)
estatisticas = {
"contagem_cana_px_por_bico": cont_erva_px,
"contagem_cana_frac_por_bico": cont_erva_frac,
"faixa": {"y_top": y_top, "y_bot": y_bot}
}
self.ultimo_status_bicos = atuacao_bicos.copy()
return atuacao_bicos, estatisticas
def _fractions_erva(self, classes_mask):
H, W = classes_mask.shape[:2]
total_px = H * W
# global
frac_global = np.count_nonzero(classes_mask == ClassesSegmentacao.ERVA.value) / float(total_px)
# lookahead (top band p/ antecipar redução)
from weed_worker.config import load_config
cfg = load_config()
top_frac = float(cfg.get("erva_top_band_frac", 0.30)) # 30% do topo
top_h = max(1, int(H * top_frac))
band_top = classes_mask[0:top_h, :]
frac_top = np.count_nonzero(band_top == ClassesSegmentacao.ERVA.value) / float(band_top.size)
return frac_global, frac_top
def _decidir_ervas_no_radar(self, classes_mask, cfg, vel_norm=0.0):
"""
Decide ErvasNoRadar por fração (global e lookahead).
Aplica histerese e opcionalmente ajusta threshold pela velocidade.
"""
# thresholds base (frações)
on_global = float(cfg.get("min_frac_erva_global_on", 0.0020)) # 0,20%
off_global = float(cfg.get("min_frac_erva_global_off", 0.0015)) # 0,15%
on_top = float(cfg.get("min_frac_erva_top_on", 0.0015))
off_top = float(cfg.get("min_frac_erva_top_off", 0.0010))
# ajuste por velocidade (opcional)
k = float(cfg.get("erva_thresh_vel_gain", 0.0)) # 0.0 desliga
adj = max(0.5, min(1.0, 1.0 - k * float(vel_norm))) # clamp [0.5, 1.0]
on_global *= adj; on_top *= adj
# (tipicamente só mexe no ON; OFF pode ficar fixo)
frac_global, frac_top = self._fractions_erva(classes_mask)
# EMA (suavização) opcional
alpha = float(cfg.get("erva_frac_ema", 0.3))
self._erva_frac_global_ema = (1-alpha)*getattr(self, "_erva_frac_global_ema", frac_global) + alpha*frac_global
self._erva_frac_top_ema = (1-alpha)*getattr(self, "_erva_frac_top_ema", frac_top) + alpha*frac_top
# Histerese global
prev = getattr(self, "_ervas_no_radar", False)
hit_global = self._erva_frac_global_ema >= (on_global if not prev else off_global)
hit_top = self._erva_frac_top_ema >= (on_top if not prev else off_top)
ervas_no_radar = bool(hit_global or hit_top)
self._ervas_no_radar = ervas_no_radar
estat = {
"frac_global": frac_global,
"frac_top": frac_top,
"ema_global": self._erva_frac_global_ema,
"ema_top": self._erva_frac_top_ema,
"thr_on_global": on_global,
"thr_off_global": off_global,
"thr_on_top": on_top,
"thr_off_top": off_top,
"vel_adj": adj
}
return ervas_no_radar, estat

File diff suppressed because one or more lines are too long

View File

@ -6,42 +6,54 @@ import numpy as np
MODELO = "oak-1"
pasta_mascaras = os.path.join(MODELO, "dataset", "original", "masks")
# Lista de substituições (em RGB)
substituicoes = [
{"target_rgb": (128, 0, 0), "tolerancia": 10}, # chão
{"target_rgb": (0, 128, 0), "tolerancia": 10}, # erva
{"target_rgb": (0, 0, 128), "tolerancia": 10}, # cana
# 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": (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
]
def dentro_da_tolerancia(pixel, target, tol):
return all(abs(int(pixel[i]) - target[i]) <= tol for i in range(3))
def aplicar_substituicoes(img_bgr):
"""Recebe imagem BGR (OpenCV) e aplica regras RGB com tolerância, de forma vetorizada."""
# Converte uma vez pra RGB só para fazer o match nas cores “humanas”
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
for rule in SUBSTITUICOES:
(tr, tg, tb) = rule["target_rgb"]
tol = int(rule.get("tolerancia", 0))
(rr, rg, rb) = rule["replace_rgb"]
# Faixa inferior/superior com tolerância (clamp 0..255)
lower = np.array([max(tr - tol, 0), max(tg - tol, 0), max(tb - tol, 0)], dtype=np.uint8)
upper = np.array([min(tr + tol, 255), min(tg + tol, 255), min(tb + tol, 255)], dtype=np.uint8)
# Máscara booleana dos pixels a substituir
mask = cv2.inRange(img_rgb, lower, upper) # 255 onde bate
if np.any(mask):
# Cria uma imagem de destino RGB com a cor de replace
replace_rgb = np.zeros_like(img_rgb)
replace_rgb[:] = (rr, rg, rb)
# Faz o blend: onde mask==255, põe replace; onde não, mantém original
img_rgb = np.where(mask[..., None] == 255, replace_rgb, img_rgb)
# Volta pra BGR pro OpenCV salvar
return cv2.cvtColor(img_rgb, cv2.COLOR_RGB2BGR)
def corrigir_mascara(caminho_img):
img = cv2.imread(caminho_img)
if img is None:
print(f"Erro ao carregar: {caminho_img}")
img_bgr = cv2.imread(caminho_img, cv2.IMREAD_COLOR)
if img_bgr is None:
print(f"[ERRO] Não abriu: {caminho_img}")
return
alterado = False
# Percorre pixel por pixel (sim, é necessário se for por range)
for y in range(img.shape[0]):
for x in range(img.shape[1]):
b, g, r = img[y, x] # OpenCV usa BGR
for s in substituicoes:
target_r, target_g, target_b = s["target_rgb"]
tol = s["tolerancia"]
if dentro_da_tolerancia((r, g, b), (target_r, target_g, target_b), tol):
img[y, x] = (target_b, target_g, target_r) # volta pra BGR
alterado = True
break
if alterado:
cv2.imwrite(caminho_img, img)
out_bgr = aplicar_substituicoes(img_bgr)
if not np.array_equal(out_bgr, img_bgr):
cv2.imwrite(caminho_img, out_bgr)
print(f"Ajustado: {os.path.basename(caminho_img)}")
# Roda para todas as máscaras
for nome_arquivo in os.listdir(pasta_mascaras):
if nome_arquivo.lower().endswith(".png"):
caminho = os.path.join(pasta_mascaras, nome_arquivo)
corrigir_mascara(caminho)
if __name__ == "__main__":
for nome in os.listdir(pasta_mascaras):
if nome.lower().endswith((".png", ".jpg", ".jpeg")):
corrigir_mascara(os.path.join(pasta_mascaras, nome))

View File

@ -1,141 +1,109 @@
import os
from torchvision import transforms
from PIL import ImageOps, Image, ImageEnhance, ImageFilter
import torchvision.transforms.functional as TF
from torchvision.transforms.functional import to_pil_image
import torchvision.transforms as T
import cv2
import numpy as np
import json, os, cv2, numpy as np
from PIL import Image
import albumentations as A
# ⚙️ Configurações
MODELO = "oak-1"
with open("config.json", "r") as f:
config = json.load(f)
MODELO = config["camera"]
# Caminhos para os diretórios onde suas imagens e máscaras originais estão armazenadas
# Pastas
dataset_path = os.path.join(MODELO, "dataset", "original", "images")
masks_path = os.path.join(MODELO, "dataset", "original", "masks")
masks_path = os.path.join(MODELO, "dataset", "original", "masks")
aug_img_out = os.path.join(MODELO, "dataset", "augmented", "images")
aug_msk_out = os.path.join(MODELO, "dataset", "augmented", "masks")
os.makedirs(aug_img_out, exist_ok=True)
os.makedirs(aug_msk_out, exist_ok=True)
# Caminhos para os diretórios onde as imagens e máscaras aumentadas serão salvas
augmented_images_path = os.path.join(MODELO, "dataset", "augmented", "images")
augmented_masks_path = os.path.join(MODELO, "dataset", "augmented", "masks")
# Pipeline de augmentations
train_tf = A.Compose([
A.HorizontalFlip(p=0.5),
class ComposeWithSeed(object):
def __init__(self, transforms):
self.transforms = transforms
# Geométricas (aplicam em imagem e máscara)
A.ShiftScaleRotate(
shift_limit=0.05,
scale_limit=0.15,
rotate_limit=8,
border_mode=cv2.BORDER_CONSTANT,
value=(255,255,255),
mask_value=(255,255,255),
interpolation=cv2.INTER_LINEAR,
p=0.5
),
def __call__(self, i, img, mask):
transforms.RandomHorizontalFlip(p=0.5)
apply_mask, t = self.transforms[i]
img = t(img)
if apply_mask:
mask = t(mask)
return img, mask
# Fotométricas (somente imagem)
A.OneOf([
A.RandomBrightnessContrast(0.2, 0.2, p=1),
A.HueSaturationValue(hue_shift_limit=5, sat_shift_limit=25, val_shift_limit=20, p=1),
A.RandomGamma(gamma_limit=(80,120), p=1),
], p=0.7),
def gaussian_blur(img, radius=1):
return img.filter(ImageFilter.GaussianBlur(radius))
A.OneOf([
A.MotionBlur(blur_limit=3, p=1),
A.GaussianBlur(blur_limit=3, p=1),
], p=0.25),
def add_gaussian_noise(img, mean=0, std=10):
arr = np.array(img).astype(np.float32)
noise = np.random.normal(mean, std, arr.shape)
arr_noisy = np.clip(arr + noise, 0, 255).astype(np.uint8)
return Image.fromarray(arr_noisy)
A.OneOf([
A.GaussNoise(var_limit=(5.0, 15.0), p=1),
A.ImageCompression(quality_lower=40, quality_upper=80, p=1),
], p=0.25),
def lighting_more_sun(img):
t = T.ColorJitter(brightness=0.25, contrast=0.25, saturation=0.15, hue=0.02)
return t(img)
A.RandomShadow(p=0.2),
A.RandomSunFlare(p=0.1),
A.ChannelShuffle(p=0.05),
A.CoarseDropout(max_holes=8, max_height=20, max_width=20, p=0.2)
def lighting_less_sun(img):
t = T.ColorJitter(brightness=0.25, contrast=0.25, saturation=0.05, hue=0.02)
img = t(img)
# Inverter o efeito de "mais sol" — clareia reduzindo brilho/contraste
enhancer_b = ImageEnhance.Brightness(img)
img = enhancer_b.enhance(0.75) # < 1.0 escurece
enhancer_c = ImageEnhance.Contrast(img)
img = enhancer_c.enhance(0.85) # < 1.0 reduz contraste
return img
# Resize final (img=LINEAR, mask=NEAREST)
#A.Resize(height=H, width=W, interpolation=cv2.INTER_LINEAR, mask_interpolation=cv2.INTER_NEAREST),
], additional_targets={'mask':'mask'})
def flip_horizontal(img):
return ImageOps.mirror(img)
def load_rgb(path):
# cv2 lê BGR → converte pra RGB (Albumentations usa RGB por padrão)
im = cv2.imread(path, cv2.IMREAD_COLOR)
if im is None:
raise FileNotFoundError(path)
return cv2.cvtColor(im, cv2.COLOR_BGR2RGB)
def flip_vertical(img):
return ImageOps.flip(img) # ou TF.vflip(img)
def save_rgb(path, arr_rgb):
# Salva em RGB mantendo cores corretas
Image.fromarray(arr_rgb).save(path)
def rotate_image(img, angle):
return TF.rotate(img, angle, fill=(255,255,255))
def augment_images_and_masks(n_copies=6):
# Faz pareamento por nome base (sem extensão)
imgs = sorted([f for f in os.listdir(dataset_path) if os.path.isfile(os.path.join(dataset_path,f))])
msks = sorted([f for f in os.listdir(masks_path) if os.path.isfile(os.path.join(masks_path,f))])
def perspective_image(img, magnitude=0.5):
width, height = img.size
# Pontos de origem
points_orig = np.float32([
[0, 0],
[width, 0],
[0, height],
[width, height]
])
# Pontos de destino, deslocados com base na magnitude
points_dest = np.float32([
[int(magnitude * width), int(magnitude * height)],
[int((1 - magnitude) * width), 0],
[0, int((1 - magnitude) * height)],
[width, height]
])
# Calcula a matriz de transformação e aplica a transformação de perspectiva
matrix = cv2.getPerspectiveTransform(points_orig, points_dest)
img_transformed = cv2.warpPerspective(np.array(img), matrix, (width, height), borderValue=(255,255,255))
return Image.fromarray(img_transformed)
# Mapeia máscaras por nome-base
msk_map = {os.path.splitext(m)[0]: m for m in msks}
# Definindo as transformações
transform_list = [
(True, T.Lambda(lambda img: flip_horizontal(img))), # Aplica flip na horizontal
#(True, T.Lambda(lambda img: flip_vertical(img))), # Aplica flip na vertical
#(True, T.Lambda(lambda img: rotate_image(img, 90))), # Rotação de 90 graus
#(True, T.Lambda(lambda img: rotate_image(img, -90))), # Rotação de -90 graus
#(True, T.Lambda(lambda img: perspective_image(img, magnitude=0.2))),
#(True, T.Lambda(lambda img: perspective_image(img, magnitude=0.1))),
(False, T.Lambda(lambda img: lighting_more_sun(img))),
(False, T.Lambda(lambda img: lighting_less_sun(img))),
(False, T.Lambda(lambda img: gaussian_blur(img, radius=1))),
(False, T.Lambda(lambda img: add_gaussian_noise(img, std=8))),
#T.ToTensor(), # Converte as imagens PIL para tensores PyTorch
]
total = 0
for img_file in imgs:
base, ext = os.path.splitext(img_file)
if base not in msk_map:
print(f"[WARN] Máscara não encontrada para {img_file}, pulando.")
continue
# Agora, definimos a transformação composta com a classe personalizada
transform = ComposeWithSeed(transform_list)
img_path = os.path.join(dataset_path, img_file)
msk_path = os.path.join(masks_path, msk_map[base])
# Verifica se os diretórios de destino existem, caso contrário, cria os diretórios
os.makedirs(augmented_images_path, exist_ok=True)
os.makedirs(augmented_masks_path, exist_ok=True)
# Carrega RGB (máscara como RGB também — mantemos as cores exatas)
img = load_rgb(img_path)
msk = load_rgb(msk_path)
# Função para aplicar a transformação e salvar as imagens e máscaras transformadas
def augment_images_and_masks(dataset_path, masks_path, augmented_images_path, augmented_masks_path, transform, num_copies):
# Lista todos os arquivos nos diretórios do dataset de imagens e máscaras
image_files = [f for f in os.listdir(dataset_path) if os.path.isfile(os.path.join(dataset_path, f))]
mask_files = [f for f in os.listdir(masks_path) if os.path.isfile(os.path.join(masks_path, f))]
for i in range(n_copies):
# Aplica aug; máscara recebe só geométricas
aug = train_tf(image=img, mask=msk)
img_aug = aug["image"]
msk_aug = aug["mask"]
for image_file, mask_file in zip(image_files, mask_files):
image_path = os.path.join(dataset_path, image_file)
mask_path = os.path.join(masks_path, mask_file)
image = Image.open(image_path).convert('RGB')
mask = Image.open(mask_path).convert('RGB')
# Salva
out_img = os.path.join(aug_img_out, f"{base}_aug_{i:02d}{ext}")
out_msk = os.path.join(aug_msk_out, f"{base}_aug_{i:02d}{os.path.splitext(msk_map[base])[1]}")
save_rgb(out_img, img_aug)
save_rgb(out_msk, msk_aug)
total += 1
for i in range(num_copies):
# Aplica a transformação de maneira consistente em ambos, imagem e máscara
transformed_image, transformed_mask = transform(i, image, mask)
print(f"Augmentation completed! {total} pares gerados.")
# Salva a imagem e a máscara transformadas
image_save_path = os.path.join(augmented_images_path, f"{os.path.splitext(image_file)[0]}_aug_{i}{os.path.splitext(image_file)[1]}")
mask_save_path = os.path.join(augmented_masks_path, f"{os.path.splitext(mask_file)[0]}_aug_{i}{os.path.splitext(mask_file)[1]}")
#transformed_image_pil = to_pil_image(transformed_image)
transformed_image.save(image_save_path)
#transformed_mask_pil = to_pil_image(transformed_mask)
transformed_mask.save(mask_save_path)
# Chama a função para iniciar o processo de aumento de dados
augment_images_and_masks(dataset_path, masks_path, augmented_images_path, augmented_masks_path, transform, num_copies=len(transform_list))
print("Augmentation completed!")
if __name__ == "__main__":
augment_images_and_masks(n_copies=8)

View File

@ -1,17 +1,19 @@
import json
import os
import cv2
from utils import carregar_labelmap_completo, converter_mask_rgb_para_ids
# ⚙️ Configurações
MODELO = "oak-1"
with open("config.json", "r") as f:
config = json.load(f)
MODELO = config["camera"]
RESOLUCAO = config["resolucao"]
pasta_base = os.path.join(MODELO, "dataset")
fonte_dados = ["original", "augmented"]
labelmap_path = os.path.join(pasta_base, "labelmap.txt")
RESOLUCOES = {
#"512x512": (512, 512),
#"768x768": (768, 768),
"384x384": (384, 384)
f"{RESOLUCAO[0]}x{RESOLUCAO[1]}": (RESOLUCAO[0], RESOLUCAO[1]),
}
# === Início do processamento ===
@ -44,7 +46,7 @@ for fonte in fonte_dados:
# Tenta carregar a máscara RGB (se existir)
if os.path.exists(caminho_mask):
img_mask_rgb = cv2.imread(caminho_mask).convert("RGB")
img_mask_rgb = cv2.cvtColor(cv2.imread(caminho_mask), cv2.COLOR_BGR2RGB)
#img_mask_rgb = cv2.cvtColor(img_mask_rgb, cv2.COLOR_BGR2RGB) # ← CORRIGE isso!
if img_mask_rgb is not None:
mask_ids = converter_mask_rgb_para_ids(img_mask_rgb, cor_para_id, ignore_id)

View File

@ -1,16 +1,19 @@
import json
import os
import shutil
import random
# ⚙️ Configurações
MODELO = "oak-1"
RESOLUCAO = (384, 384)
with open("config.json", "r") as f:
config = json.load(f)
MODELO = config["camera"]
RESOLUCAO = config["resolucao"]
pasta_origem = os.path.join(MODELO, "dataset", f"{RESOLUCAO[0]}x{RESOLUCAO[1]}")
pasta_destino = os.path.join(MODELO, "dataset", "split")
percent_train = 0.7
percent_val = 0.2
percent_test = 0.1
percent_train = 0.9
percent_val = 0.09
percent_test = 0.01
seed = 42
random.seed(seed)

View File

@ -1,3 +1,4 @@
import json
import os
import time
import argparse
@ -10,12 +11,14 @@ from roi_seg_dataset import ROISegDataset
import matplotlib.pyplot as plt
# ⚙️ Configurações
MODELO = "oak-1"
MODEL_NAME = "ervas_full"
RESOLUCAO = (384, 384)
ROI_INICIO = 0.0
ROI_TAMANHO = 1.0
save_path = os.path.join(MODELO, "backup", "fast_scnn", MODEL_NAME)
with open("config.json", "r") as f:
config = json.load(f)
MODELO = config["camera"]
MODEL_NAME = config["model_name"]
RESOLUCAO = config["resolucao"]
ROI_INICIO = config["roi_inicio"]
ROI_TAMANHO = config["roi_tamanho"]
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")
@ -27,12 +30,13 @@ 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[1], RESOLUCAO[0], labelmap_path)
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)
model = FastSCNN(num_classes=len(ds_train.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
@ -52,7 +56,7 @@ def train(args):
# Caso seja apenas um .pth com model.state_dict() direto
model.load_state_dict(checkpoint)
for epoch in range(1, args.epochs + 1):
for epoch in range(start_epoch, args.epochs + 1):
model.train()
total_loss = 0
t0 = time.time()
@ -86,8 +90,9 @@ def train(args):
# Plot da curva de perda
if epoch % 5 == 0 or epoch == args.epochs:
x_epochs = list(range(start_epoch, start_epoch + len(loss_history)))
plt.figure()
plt.plot(range(start_epoch, epoch + 1), loss_history, marker="o", label="Loss de Treinamento")
plt.plot(x_epochs, loss_history, marker="o", label="Loss de Treinamento")
plt.xlabel("Época")
plt.ylabel("Loss")
plt.grid(True)

View File

@ -1,3 +1,4 @@
import json
import os
import time
import cv2
@ -11,15 +12,17 @@ from fast_scnn import FastSCNN
from utils import carregar_labelmap_completo, compute_roi_indices, converter_mask_ids_para_rgb, desenhar_legenda_horizontal, desenhar_legenda_vertical, resize_keep_width
# ⚙️ Configurações
MODELO = "oak-1"
MODEL_NAME = "ervas_full"
RESOLUCAO = (384, 384)
ROI_INICIO = 0.0
ROI_TAMANHO = 1.0
with open("config.json", "r") as f:
config = json.load(f)
MODELO = config["camera"]
MODEL_NAME = config["model_name"]
RESOLUCAO = config["resolucao"]
ROI_INICIO = config["roi_inicio"]
ROI_TAMANHO = config["roi_tamanho"]
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", "fast_scnn", MODEL_NAME, MODEL_NAME + "_best.pth")
model_path = os.path.join(MODELO, "backup", config["modelo"], MODEL_NAME, MODEL_NAME + "_best.pth")
def main():
parser = argparse.ArgumentParser()
@ -65,7 +68,7 @@ def main():
y_fim, y_inicio = compute_roi_indices(H, ROI_INICIO, ROI_TAMANHO)
roi = frame[y_fim:y_inicio, 0:W]
roi_resized = resize_keep_width(roi, RESOLUCAO[1], RESOLUCAO[0])
roi_resized = resize_keep_width(roi, RESOLUCAO[0], RESOLUCAO[1], cv2.INTER_AREA)
roi_norm = roi_resized.astype(np.float32) / 255.0
roi_tensor = torch.from_numpy(roi_norm).permute(2, 0, 1).unsqueeze(0).to(device)
@ -123,8 +126,8 @@ def main():
img_roi = img_rgb[y_fim:y_inicio, 0:W]
mask_roi = mask_gt[y_fim:y_inicio, 0:W]
img_resized = resize_keep_width(img_roi, RESOLUCAO[1], RESOLUCAO[0])
mask_resized = resize_keep_width(mask_roi, RESOLUCAO[1], RESOLUCAO[0])
img_resized = resize_keep_width(img_roi, RESOLUCAO[0], RESOLUCAO[1], cv2.INTER_AREA)
mask_resized = resize_keep_width(mask_roi, RESOLUCAO[0], RESOLUCAO[1], cv2.INTER_NEAREST)
img_norm = img_resized.astype(np.float32) / 255.0
img_tensor = torch.from_numpy(img_norm).permute(2, 0, 1).unsqueeze(0).to(device)

View File

@ -1,17 +1,20 @@
import json
import os
import torch
from fast_scnn import FastSCNN
from utils import carregar_labelmap_completo
# ⚙️ Configurações
MODELO = "oak-1"
MODEL_NAME = "ervas_full"
RESOLUCAO = (384, 384)
model_path = os.path.join(MODELO, "backup", "fast_scnn", MODEL_NAME)
with open("config.json", "r") as f:
config = json.load(f)
MODELO = config["camera"]
MODEL_NAME = config["model_name"]
RESOLUCAO = config["resolucao"]
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"
dummy_input = torch.randn(1, 3, RESOLUCAO[0], RESOLUCAO[1]) # (batch, channels, height, width)
dummy_input = torch.randn(1, 3, RESOLUCAO[1], RESOLUCAO[0]) # (batch, channels, height, width)
_, _, classes, _ = carregar_labelmap_completo(labelmap_path)
NUM_CLASSES = len(classes)
@ -35,7 +38,8 @@ from openvino.tools.mo import convert_model
from openvino.runtime import serialize
ov_model = convert_model(
input_model=os.path.join(model_path, model_name + ".onnx"),
input_shape=[1, 3, RESOLUCAO[0], RESOLUCAO[1]],
input_shape=[1, 3, RESOLUCAO[1], RESOLUCAO[0]],
layout="NCHW",
)
serialize(
ov_model,
@ -50,6 +54,12 @@ blob_path = blobconverter.from_openvino(
bin=os.path.join(model_path, model_name + ".bin"),
data_type="FP16",
shaves=6,
output_dir=model_path
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]",
#"--reverse_input_channels" # pq você treinou em RGB
],
)
print(f"Blob salvo em: {blob_path}")

View File

@ -1,3 +1,4 @@
import json
import os
import depthai as dai
import numpy as np
@ -6,12 +7,14 @@ import time
from utils import converter_mask_ids_para_rgb, carregar_labelmap_completo
# ⚙️ Configurações
MODELO = "oak-1"
MODEL_NAME = "ervas_full"
RESOLUCAO = (384, 384)
with open("config.json", "r") as f:
config = json.load(f)
MODELO = config["camera"]
MODEL_NAME = "ervas_medium" #config["model_name"]
RESOLUCAO = config["resolucao"]
ROI_INICIO = 0.0
ROI_TAMANHO = 1.0
blob_path = os.path.join(MODELO, "backup", "fast_scnn", MODEL_NAME, MODEL_NAME + "_best_openvino_2022.1_6shave.blob")
blob_path = os.path.join(MODELO, "backup", config["modelo"], MODEL_NAME, MODEL_NAME + "_best_openvino_2022.1_6shave.blob")
labelmap_path = os.path.join(MODELO, "dataset", "labelmap.txt")
model_name = MODEL_NAME + "_best"
@ -38,10 +41,10 @@ y1 = 1.0 - (ROI_INICIO + ROI_TAMANHO)
y2 = 1.0 - ROI_INICIO
manip.initialConfig.setCropRect(0.0, y1, 1.0, y2)
manip.initialConfig.setResize(RESOLUCAO[1], RESOLUCAO[0])
manip.initialConfig.setResize(RESOLUCAO[0], RESOLUCAO[1])
manip.initialConfig.setFrameType(dai.RawImgFrame.Type.RGB888p)
cam.preview.link(manip.inputImage)
manip.initialConfig.setKeepAspectRatio(False)
cam.video.link(manip.inputImage)
# Neural network
nn = pipeline.createNeuralNetwork()
@ -51,13 +54,16 @@ manip.out.link(nn.input)
# Saída RGB para overlay (sem redimensionar)
xout_rgb = pipeline.createXLinkOut()
xout_rgb.setStreamName("rgb")
cam.preview.link(xout_rgb.input)
cam.video.link(xout_rgb.input)
# Saída NN
xout_nn = pipeline.createXLinkOut()
xout_nn.setStreamName("nn")
nn.out.link(xout_nn.input)
# E garanta que o video stream é 16:9
#cam.setVideoSize(384, 384) # ou 1280x720
# Rodar pipeline
with dai.Device(pipeline) as device:
rgb_queue = device.getOutputQueue("rgb", maxSize=1, blocking=False)
@ -66,45 +72,51 @@ with dai.Device(pipeline) as device:
print("Rodando inferência na OAK... Pressione 'q' para sair.")
prev_time = time.time()
H, W = RESOLUCAO[1], RESOLUCAO[0]
in_rgb = rgb_queue.get()
frame = in_rgb.getCvFrame()
frame_h, frame_w = frame.shape[:2]
y_start = int(y1 * frame_h)
y_end = int(y2 * frame_h)
roi_h = y_end - y_start
roi_w = frame_w
pred_ids = np.empty((H, W), dtype=np.uint8)
pred_rgb = np.empty((H, W, 3), dtype=np.uint8)
overlay = np.empty((H, W, 3), dtype=np.uint8)
lut = np.zeros((256, 3), dtype=np.uint8)
for i, color in enumerate(colormap_rgb):
lut[i] = color
lut[IGNORE_ID] = (255, 255, 255)
while True:
in_rgb = rgb_queue.get()
in_nn = nn_queue.get()
# RGB frame da câmera
frame = in_rgb.getCvFrame()
# Inferência - saída é um vetor flat [num_classes * H * W]
out = in_nn.getFirstLayerFp16()
out_np = np.array(out, dtype=np.float32).reshape((NUM_CLASSES, RESOLUCAO[1], RESOLUCAO[0]))
out_raw = in_nn.getFirstLayerFp16()
arr16 = np.frombuffer(np.asarray(out_raw, dtype=np.float16), dtype=np.float16)
arr16 = arr16.reshape(NUM_CLASSES, H, W)
pred_ids = arr16.argmax(axis=0).astype(np.uint8, copy=False)
# Pega o índice da classe com maior probabilidade por pixel
pred_ids = np.argmax(out_np, axis=0).astype(np.uint8)
# Converter para RGB bonitão
pred_rgb = converter_mask_ids_para_rgb(pred_ids, colormap_rgb, IGNORE_ID)
roi_h = int((y2 - y1) * frame.shape[0])
roi_w = frame.shape[1]
pred_rgb_resized = cv2.resize(pred_rgb, (roi_w, roi_h), interpolation=cv2.INTER_NEAREST)
y_start = int(y1 * frame.shape[0])
y_end = y_start + roi_h
y_start = max(0, min(frame.shape[0], y_start))
y_end = max(0, min(frame.shape[0], y_end))
overlay = frame.copy()
overlay[y_start:y_end, 0:roi_w] = cv2.addWeighted(
frame[y_start:y_end, 0:roi_w], 0.4, pred_rgb_resized, 0.6, 0
)
pred_rgb[:] = lut[pred_ids]
frame = in_rgb.getCvFrame()
cv2.resize(frame, (W, H), interpolation=cv2.INTER_AREA, dst=overlay)
cv2.addWeighted(overlay, 0.4, pred_rgb, 0.6, 0, dst=overlay)
# FPS
now = time.time()
fps = 1.0 / (now - prev_time)
fps_inst = 1.0 / (now - prev_time)
prev_time = now
cv2.putText(overlay, f"FPS: {fps:.1f}", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)
# Redimensiona para tela cheia (por exemplo 1280x720 ou tela do usuário)
overlay_display = cv2.resize(overlay, (1280, 720))
cv2.imshow("Segmentação - OAK (on-board)", cv2.cvtColor(overlay_display, cv2.COLOR_RGB2BGR))
fps = 0.9 * fps + 0.1 * fps_inst if 'fps' in locals() else fps_inst
cv2.putText(overlay, f"FPS: {fps:.1f}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)
#cv2.imshow("Segmentacao - OAK (on-board)", cv2.cvtColor(overlay, cv2.COLOR_RGB2BGR))
cv2.imshow("Segmentacao - OAK (on-board)", overlay)
if cv2.waitKey(1) & 0xFF == ord('q'):
break

View File

@ -0,0 +1,8 @@
{
"camera": "oak-1",
"modelo": "fast_scnn",
"model_name": "ervas_medium_new",
"resolucao": [512, 288],
"roi_inicio": 0.0,
"roi_tamanho": 1.0
}

Binary file not shown.

View File

@ -44,8 +44,8 @@ class ROISegDataset(Dataset):
img_roi = img_rgb[y_fim:y_inicio, 0:W]
msk_roi = msk_grayscale[y_fim:y_inicio, 0:W]
img_in = resize_keep_width(img_roi, self.input_w, self.min_input_h)
msk_ids = resize_keep_width(msk_roi, self.input_w, self.min_input_h)
img_in = resize_keep_width(img_roi, self.input_w, self.min_input_h, cv2.INTER_AREA)
msk_ids = resize_keep_width(msk_roi, self.input_w, self.min_input_h, cv2.INTER_NEAREST)
valores_validos = list(range(len(self.classes))) + [255]
msk_ids[np.isin(msk_ids, valores_validos, invert=True)] = self.ignore_id # converte inválidos em ignore

View File

@ -37,22 +37,30 @@ def carregar_labelmap_completo(caminho):
return cor_para_id, cores_rgb, id_para_nome, ignore_rgb
def converter_mask_rgb_para_ids(img_rgb, mapa_rgb, ignore_id):
h, w, _ = img_rgb.shape
mask = np.ones((h, w), dtype=np.uint8) * ignore_id # Inicializa como ignore
# Cria um mapa 256^3 para IDs (usa int32 para indexar)
lut = np.full((256**3,), ignore_id, dtype=np.uint8)
for cor, classe_id in mapa_rgb.items():
r, g, b = cor
cond = (img_rgb[:,:,0]==r) & (img_rgb[:,:,1]==g) & (img_rgb[:,:,2]==b)
mask[cond] = classe_id
# Pixels brancos (ou ignore_bgr) continuam como 255
return mask
lut[(r << 16) + (g << 8) + b] = classe_id
def converter_mask_ids_para_rgb(mask_ids: np.ndarray, mapa_rgb: dict, ignore_id: int = 255) -> np.ndarray:
h, w = mask_ids.shape
rgb = np.zeros((h, w, 3), dtype=np.uint8)
for class_id, color in enumerate(mapa_rgb):
rgb[mask_ids == class_id] = color
rgb[mask_ids == ignore_id] = [255, 255, 255]
return rgb
# Converte RGB para índice único
flat_idx = (img_rgb[:,:,0].astype(np.int32) << 16) + \
(img_rgb[:,:,1].astype(np.int32) << 8) + \
img_rgb[:,:,2].astype(np.int32)
# Aplica LUT vetorizada
return lut[flat_idx]
def converter_mask_ids_para_rgb(mask_ids: np.ndarray, colormap_rgb: list, ignore_id: int = 255) -> np.ndarray:
# Criar lookup table (256 cores possíveis)
lut = np.zeros((256, 3), dtype=np.uint8)
for i, color in enumerate(colormap_rgb):
lut[i] = color
lut[ignore_id] = (255, 255, 255)
# Aplicar LUT direto (vetorizado)
return lut[mask_ids]
def desenhar_legenda_vertical(colormap_rgb, classes, largura=200):
"""
@ -103,9 +111,9 @@ def compute_roi_indices(H: int, zona_inicio: float, faixa_atuacao: float):
y_fim = max(0, y_inicio - 1)
return y_fim, y_inicio
def resize_keep_width(img: np.ndarray, new_w: int, min_h: int) -> np.ndarray:
def resize_keep_width(img: np.ndarray, new_w: int, min_h: int, interpolation: int) -> np.ndarray:
h, w = img.shape[:2]
new_h = int(round(new_w * (h / w)))
if min_h is not None and new_h < min_h:
new_h = min_h
return cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_AREA)
return cv2.resize(img, (new_w, new_h), interpolation=interpolation)