adicionado cameras OAK

This commit is contained in:
Diego Freitas 2025-03-05 07:34:43 -03:00
parent c1d9b2fb30
commit d95257ff2a
39 changed files with 2594 additions and 298 deletions

Binary file not shown.

View File

@ -133,7 +133,7 @@ namespace AgroBase.Forms
ArquivoVersao.Versao,
},
CameraSolo.VideoPorta,
CameraSolo.VideoUrl,
new List<string>() { CameraSolo.VideoUrl },
CameraSolo.MqttTopico,
async (Mensagem) =>
{
@ -144,7 +144,7 @@ namespace AgroBase.Forms
if (CameraSolo.browser == null)
{
CameraSolo.Iniciada = true;
CameraSolo.browser = new ChromiumWebBrowser(CameraSolo.camera.URLcamera);
CameraSolo.browser = new ChromiumWebBrowser(CameraSolo.camera.URLcamera[0]);
CameraSolo.browser.Dock = DockStyle.Fill;
CameraSolo.panel.Invoke(new Action(() =>

View File

@ -105,7 +105,7 @@ namespace AgroBase.Forms.IHM
private void frmAjustes_FormClosing(object sender, FormClosingEventArgs e)
{
Variaveis.OperacaoEmAndamento.AtualizaListaCameras(pnlCaminho, Variaveis.OperacaoEmAndamento.CamerasSolo.Select(x => x.panel).ToList(), false);
Variaveis.OperacaoEmAndamento.AtualizaListaCameras(new List<Panel>() { pnlCaminho }, Variaveis.OperacaoEmAndamento.CamerasSolo.Select(x => x.panel).ToList(), false);
}
#region COMUNICACAO
@ -213,7 +213,7 @@ namespace AgroBase.Forms.IHM
private void IniciarCameras()
{
Variaveis.OperacaoEmAndamento.AtualizaListaCameras(pnlCaminho, Variaveis.OperacaoEmAndamento.CamerasSolo.Select(x => x.panel).ToList(), true);
Variaveis.OperacaoEmAndamento.AtualizaListaCameras(new List<Panel>() { pnlCaminho }, Variaveis.OperacaoEmAndamento.CamerasSolo.Select(x => x.panel).ToList(), true);
}
@ -225,8 +225,8 @@ namespace AgroBase.Forms.IHM
if (CameraCaminho != null)
{
CameraCaminho.combo = cmb;
CameraCaminho.panel = pnlCaminho;
CameraCaminho.panel.Tag = cmb.Text;
CameraCaminho.panelRgb = pnlCaminho;
CameraCaminho.panelRgb.Tag = cmb.Text;
}
if (CameraCaminho.combo.SelectedIndex > 0 && (Variaveis.OperacaoEmAndamento.CamerasSolo.Any(x => x.combo.SelectedIndex == CameraCaminho.combo.SelectedIndex)))

View File

@ -86,7 +86,7 @@ namespace AgroBase.Forms.IHM
public async Task AtualizarRuasSelecionadas(List<string> ruasSelecionadas)
{
Variaveis.OperacaoEmAndamento.AtualizaListaCameras(pnlCameraCaminho, new List<Panel>(){ pnlCameraSoloE, pnlCameraSoloD }, true);
Variaveis.OperacaoEmAndamento.AtualizaListaCameras(new List<Panel>() { pnlCameraCaminho }, new List<Panel>(){ pnlCameraSoloE, pnlCameraSoloD }, true);
await Task.Delay(1000);
GPSService.AtualizarRuasSelecionadas(ruasSelecionadas);
@ -290,7 +290,7 @@ namespace AgroBase.Forms.IHM
freioStartPosition = new Point(pnlFreio.Width / 2 - 20, 10);
freioPosition = freioStartPosition;
Variaveis.OperacaoEmAndamento.AtualizaListaCameras(pnlCameraCaminho, new List<Panel>() { pnlCameraSoloE, pnlCameraSoloD }, true);
Variaveis.OperacaoEmAndamento.AtualizaListaCameras(new List<Panel>() { pnlCameraCaminho }, new List<Panel>() { pnlCameraSoloE, pnlCameraSoloD }, true);
IniciarOperacao();
}

View File

@ -237,7 +237,7 @@ namespace AgroBase.Forms.Movimentacao
{
if (chromiumWebBrowser == null)
{
chromiumWebBrowser = new ChromiumWebBrowser(cameraService.URLcamera);
chromiumWebBrowser = new ChromiumWebBrowser(cameraService.URLcamera[0]);
this.pnlCamera.Controls.Add(chromiumWebBrowser);
chromiumWebBrowser.Dock = DockStyle.Fill;
}

View File

@ -56,7 +56,6 @@
this.pnlDeteccoesRua = new System.Windows.Forms.Panel();
this.chbSonarAtivado = new System.Windows.Forms.CheckBox();
this.chbMostrarGrade = new System.Windows.Forms.CheckBox();
this.picSonar = new System.Windows.Forms.PictureBox();
this.chbSonarDesvioAutomatico = new System.Windows.Forms.CheckBox();
this.tabPulverizador = new System.Windows.Forms.TabPage();
this.lblPressaoSP = new System.Windows.Forms.Label();
@ -89,8 +88,6 @@
this.tabInformacoes.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.gridDados)).BeginInit();
this.tabDeteccoes.SuspendLayout();
this.pnlDeteccoesRua.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.picSonar)).BeginInit();
this.tabPulverizador.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.nudPressaoSP)).BeginInit();
this.pnlDir_EF.SuspendLayout();
@ -336,7 +333,10 @@
//
// tabDeteccoes
//
this.tabDeteccoes.Controls.Add(this.chbSonarAtivado);
this.tabDeteccoes.Controls.Add(this.chbMostrarGrade);
this.tabDeteccoes.Controls.Add(this.pnlDeteccoesRua);
this.tabDeteccoes.Controls.Add(this.chbSonarDesvioAutomatico);
this.tabDeteccoes.Location = new System.Drawing.Point(4, 22);
this.tabDeteccoes.Margin = new System.Windows.Forms.Padding(2);
this.tabDeteccoes.Name = "tabDeteccoes";
@ -348,21 +348,16 @@
//
// pnlDeteccoesRua
//
this.pnlDeteccoesRua.Controls.Add(this.chbSonarAtivado);
this.pnlDeteccoesRua.Controls.Add(this.chbMostrarGrade);
this.pnlDeteccoesRua.Controls.Add(this.picSonar);
this.pnlDeteccoesRua.Controls.Add(this.chbSonarDesvioAutomatico);
this.pnlDeteccoesRua.Dock = System.Windows.Forms.DockStyle.Fill;
this.pnlDeteccoesRua.Location = new System.Drawing.Point(2, 2);
this.pnlDeteccoesRua.Location = new System.Drawing.Point(13, 13);
this.pnlDeteccoesRua.Margin = new System.Windows.Forms.Padding(2);
this.pnlDeteccoesRua.Name = "pnlDeteccoesRua";
this.pnlDeteccoesRua.Size = new System.Drawing.Size(459, 284);
this.pnlDeteccoesRua.Size = new System.Drawing.Size(434, 242);
this.pnlDeteccoesRua.TabIndex = 0;
//
// chbSonarAtivado
//
this.chbSonarAtivado.AutoSize = true;
this.chbSonarAtivado.Location = new System.Drawing.Point(13, 265);
this.chbSonarAtivado.Location = new System.Drawing.Point(13, 267);
this.chbSonarAtivado.Margin = new System.Windows.Forms.Padding(2);
this.chbSonarAtivado.Name = "chbSonarAtivado";
this.chbSonarAtivado.Size = new System.Drawing.Size(93, 17);
@ -374,7 +369,7 @@
// chbMostrarGrade
//
this.chbMostrarGrade.AutoSize = true;
this.chbMostrarGrade.Location = new System.Drawing.Point(141, 265);
this.chbMostrarGrade.Location = new System.Drawing.Point(141, 267);
this.chbMostrarGrade.Margin = new System.Windows.Forms.Padding(2);
this.chbMostrarGrade.Name = "chbMostrarGrade";
this.chbMostrarGrade.Size = new System.Drawing.Size(93, 17);
@ -383,20 +378,10 @@
this.chbMostrarGrade.UseVisualStyleBackColor = true;
this.chbMostrarGrade.CheckedChanged += new System.EventHandler(this.chbMostrarGrade_CheckedChanged);
//
// picSonar
//
this.picSonar.Location = new System.Drawing.Point(13, 3);
this.picSonar.Margin = new System.Windows.Forms.Padding(2);
this.picSonar.Name = "picSonar";
this.picSonar.Size = new System.Drawing.Size(434, 260);
this.picSonar.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom;
this.picSonar.TabIndex = 0;
this.picSonar.TabStop = false;
//
// chbSonarDesvioAutomatico
//
this.chbSonarDesvioAutomatico.AutoSize = true;
this.chbSonarDesvioAutomatico.Location = new System.Drawing.Point(264, 265);
this.chbSonarDesvioAutomatico.Location = new System.Drawing.Point(264, 267);
this.chbSonarDesvioAutomatico.Margin = new System.Windows.Forms.Padding(2);
this.chbSonarDesvioAutomatico.Name = "chbSonarDesvioAutomatico";
this.chbSonarDesvioAutomatico.Size = new System.Drawing.Size(183, 17);
@ -720,9 +705,7 @@
this.tabInformacoes.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.gridDados)).EndInit();
this.tabDeteccoes.ResumeLayout(false);
this.pnlDeteccoesRua.ResumeLayout(false);
this.pnlDeteccoesRua.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.picSonar)).EndInit();
this.tabDeteccoes.PerformLayout();
this.tabPulverizador.ResumeLayout(false);
this.tabPulverizador.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.nudPressaoSP)).EndInit();
@ -774,7 +757,6 @@
private System.Windows.Forms.Label lblGPSaltitude;
private System.Windows.Forms.Label lblGPSvelocidade;
private System.Windows.Forms.Label lblGPSprecisao;
private System.Windows.Forms.PictureBox picSonar;
private System.Windows.Forms.CheckBox chbSonarDesvioAutomatico;
private System.Windows.Forms.Label lblTensao;
private System.Windows.Forms.Label lblCorrente;

View File

@ -54,7 +54,7 @@ namespace AgroBase.Forms.Operacoes
flwCamerasSolo.Controls.Add(pnlCam);
}
Variaveis.OperacaoEmAndamento.AtualizaListaCameras(pnlCameraRua, flwCamerasSolo.Controls.OfType<Panel>().ToList(), true);
Variaveis.OperacaoEmAndamento.AtualizaListaCameras(new List<Panel>() { pnlCameraRua, pnlDeteccoesRua }, flwCamerasSolo.Controls.OfType<Panel>().ToList(), true);
chbPulverizador.Checked = Variaveis.OperacaoEmAndamento.Controle.PulverizadorAutomatico;
chbMostrarGrade.Checked = KinectService.MostrarGrade;
@ -65,7 +65,6 @@ namespace AgroBase.Forms.Operacoes
InicializarMapa();
PopularBicos();
Variaveis.OperacaoEmAndamento.chartGraficos = chartGraficos;
KinectService.picRGB = picSonar;
tmrLeitura = new AsyncTaskTimerModel("tmrLeitura", tmrLeitura_Tick, 500, this);
tmrLeitura.Start();

View File

@ -41,7 +41,7 @@ namespace AgroBase.Forms.Operacoes
tmrLeituras.Start();
AtualizarDispositivos();
Variaveis.OperacaoEmAndamento.AtualizaListaCameras(pnlCameraCaminho, new List<Panel>() { pnlCameraSoloE, pnlCameraSoloD }, true);
Variaveis.OperacaoEmAndamento.AtualizaListaCameras(new List<Panel>() { pnlCameraCaminho }, new List<Panel>() { pnlCameraSoloE, pnlCameraSoloD }, true);
}
private void frmOperacaoMapaGPS_FormClosing(object sender, FormClosingEventArgs e)

View File

@ -26,7 +26,7 @@ namespace AgroBase.Forms.Operacoes
List<PzmSensoriamentoLogModel> LogsPzem = new List<PzmSensoriamentoLogModel>();
List<WitSensoriamentoLogModel> LogsWt901C = new List<WitSensoriamentoLogModel>();
List<UltrasonicA05Model> LogsA05 = new List<UltrasonicA05Model>();
List<KinectModel> LogsKinect = new List<KinectModel>();
//List<KinectModel> LogsKinect = new List<KinectModel>();
List<List<CamSoloSensoriamentoLogModel>> LogsCam_Solo = new List<List<CamSoloSensoriamentoLogModel>>();
List<string> pathImagensSolo = new List<string>();
List<PictureBox> picsCamSolo = new List<PictureBox>();
@ -137,7 +137,7 @@ namespace AgroBase.Forms.Operacoes
LogsPzem = JsonConvert.DeserializeObject<PzmSensoriamentoLogModel[]>(DeserializarDadosOperacao(ofd.FileName, data.pzm ?? Enums.T_Code.Pzm.ToString())).ToList();
LogsWt901C = JsonConvert.DeserializeObject<WitSensoriamentoLogModel[]>(DeserializarDadosOperacao(ofd.FileName, data.wit ?? Enums.T_Code.Wit.ToString())).ToList();
LogsA05 = JsonConvert.DeserializeObject<UltrasonicA05Model[]>(DeserializarDadosOperacao(ofd.FileName, data.a05 ?? Enums.T_Code.A05.ToString())).ToList();
LogsKinect = JsonConvert.DeserializeObject<KinectModel[]>(DeserializarDadosOperacao(ofd.FileName, data.knt ?? Enums.T_Code.Knt.ToString())).ToList();
//LogsKinect = JsonConvert.DeserializeObject<KinectModel[]>(DeserializarDadosOperacao(ofd.FileName, data.knt ?? Enums.T_Code.Knt.ToString())).ToList();
LogsCam_Caminho = JsonConvert.DeserializeObject<CamCaminhoSensoriamentoLogModel[]>(DeserializarDadosOperacao(ofd.FileName, data.cam_caminho)).ToList();
pathImagensSonarRGB = ofd.FileName.Replace("data.lgop", data.knt + "/rgb_");
pathImagensMapaCalor = ofd.FileName.Replace("data.lgop", data.knt + "/heat_");
@ -939,7 +939,7 @@ namespace AgroBase.Forms.Operacoes
AtualizarInformacoesSensoriamento();
AtualizarInformacoesKinect();
//AtualizarInformacoesKinect();
AtualizarInformacoesCPU();
}
@ -1006,7 +1006,7 @@ namespace AgroBase.Forms.Operacoes
noModulos.Nodes.Add(AtualizarTextoStatusModulo(Enums.T_Code.Wit, LogsWt901C[idxMomentoAtual].Inicializado, LogsWt901C[idxMomentoAtual].UltimoComandoRespondido));
noModulos.Nodes.Add(AtualizarTextoStatusModulo(Enums.T_Code.A05, LogsA05[idxMomentoAtual].Conectado, LogsA05[idxMomentoAtual].UltimoComandoRespondido));
noModulos.Nodes.Add(AtualizarTextoStatusModulo(Enums.T_Code.Gps, LogsGPS[idxMomentoAtual].Inicializado, LogsGPS[idxMomentoAtual].UltimoComandoRespondido));
noModulos.Nodes.Add(AtualizarTextoStatusModulo(Enums.T_Code.Knt, LogsKinect[idxMomentoAtual].Iniciado, LogsKinect[idxMomentoAtual].UltimoComandoRespondido));
//noModulos.Nodes.Add(AtualizarTextoStatusModulo(Enums.T_Code.Knt, LogsCam_Caminho[idxMomentoAtual].Inicializado, LogsCam_Caminho[idxMomentoAtual].UltimaMensagemRecebida));
tvwFalhas.ExpandAll();
}
@ -1068,13 +1068,13 @@ namespace AgroBase.Forms.Operacoes
pnlOrientacaoGPS.Invalidate();
var LogKnt = LogsKinect.FirstOrDefault(x => x.Momento == MomentoAtual);
//var LogKnt = LogsKinect.FirstOrDefault(x => x.Momento == MomentoAtual);
var LogOpe = LogsOperacao.FirstOrDefault(x => x.Momento == MomentoAtual);
double AnguloCarro = (LogsWt901C.FirstOrDefault(x => x.Momento == MomentoAtual)?.Inicializado ?? false ? LogOpe.WitMotion.AnguloCorrigido : LogOpe.Gps.OrientacaoReal);
MapaDinamico.AtualizarDados(
LogKnt.Iniciado && LogKnt.StatusDetecao ? LogKnt.ObstaculoCritico : null,
null, //LogKnt.Iniciado && LogKnt.StatusDetecao ? LogKnt.ObstaculoCritico : null,
(float)LogOpe.Trajetoria.AnguloCaminho,
(float)AnguloCarro,
new GPSModel() { Latitude = Log.Latitude, Longitude = Log.Longitude },
@ -1296,7 +1296,7 @@ namespace AgroBase.Forms.Operacoes
}
private void AtualizarInformacoesKinect()
/*private void AtualizarInformacoesKinect()
{
var Log = LogsKinect.FirstOrDefault(x => x.Momento == MomentoAtual);
txtEspacoLivreEsquerda.Text = Log.EspacoLivreEsquerda.ToString("0.00");
@ -1343,7 +1343,7 @@ namespace AgroBase.Forms.Operacoes
bitmap = (Bitmap)KinectService.ExibirObstaculos(bitmap, Log.Subdivisoes, Log.Obstaculos).Clone();
picSonarRGB.Image = (Bitmap)bitmap.Clone();
}
}
}*/
private void AtualizarInformacoesCameraCaminho()
{

View File

@ -110,7 +110,7 @@ namespace AgroBase.Forms.Sensoriamento
{
if (chromiumWebBrowser == null)
{
chromiumWebBrowser = new ChromiumWebBrowser(cameraService.URLcamera);
chromiumWebBrowser = new ChromiumWebBrowser(cameraService.URLcamera[0]);
this.pnlCamera.Controls.Add(chromiumWebBrowser);
chromiumWebBrowser.Dock = DockStyle.Fill;
}

View File

@ -4,8 +4,6 @@ using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
@ -14,7 +12,6 @@ namespace AgroBase.Models
{
public class CameraCaminhoModel
{
public AsyncTaskTimerModel tmrLeitura;
public Bitmap bitmapSegmentado;
public string ID { get; set; }
@ -22,14 +19,18 @@ namespace AgroBase.Models
public int Posicao { get; set; }
public bool Iniciada { get; set; } = false;
public CameraService camera { get; set; }
public ChromiumWebBrowser browser { get; set; }
public Panel panel { get; set; }
public ChromiumWebBrowser browserRgb { get; set; }
public Panel panelRgb { get; set; }
public ChromiumWebBrowser browserHeatmap { get; set; }
public Panel panelHeatmap { get; set; }
public ComboBox combo { get; set; }
public string ArquivoLeitura { get; set; }
public int MaxLeituras { get; set; }
public string VideoPorta { get; set; }
public string VideoUrl { get; set; }
public string VideoRgbUrl { get; set; }
public string VideoHeatmapUrl { get; set; }
public string DeviceID { get; set; }
public string MqttTopico { get; set; } = "street_detector";
public Dictionary<string, Color> ClassMap = new Dictionary<string, Color>()
{
@ -45,37 +46,21 @@ namespace AgroBase.Models
public double AnguloEsquerdo { get; set; }
public double AnguloDireito { get; set; }
public DateTime UltimoEnvio { get; set; } = DateTime.MinValue;
private int DelayEnvio { get; set; } = 500;
public DateTime UltimaMensagemRecebida { get; set; } = DateTime.MinValue;
public CameraCaminhoModel()
{
}
public void InicializarCamera()
{
List<AsyncTaskTimerModel> timers = new List<AsyncTaskTimerModel>();
tmrLeitura?.Dispose();
tmrLeitura = new AsyncTaskTimerModel("tmrLeitura_" + ID, tmrLeitura_Tick, 100);
tmrLeitura.Start();
}
public void DesligarCamera()
{
try
{
tmrLeitura?.Dispose();
Iniciada = false;
Variaveis.MqttService.Topicos.Remove(camera._mqttTopico);
if (panel != null)
if (panelRgb != null)
{
panel.Controls.Remove(browser);
panelRgb.Controls.Remove(browserRgb);
}
browser = null;
browserRgb = null;
Task.Run(async () => await Variaveis.MqttService.UnsubscribeAsync(camera._mqttTopico));
if (camera.pythonProcess != null)
{
@ -88,97 +73,6 @@ namespace AgroBase.Models
}
}
private async Task tmrLeitura_Tick()
{
if (Iniciada)
{
DateTime Agora = DateTime.Now;
// Se ja passou 5 segundos sem ter enviado uma mensagem
if (UltimoEnvio.AddMilliseconds(5000) < Agora)
{
//await Variaveis.MqttService.AdicionarNovoTopico(CamerasVaraiveisModel.TopicoRecebimentoBitmap, true);
await EnviarBitmapParaProcessamento();
}
}
}
public async Task EnviarBitmapParaProcessamento()
{
Bitmap bitmap = null;
lock (KinectService._lockBitmapRGB)
{
bitmap = (Bitmap)KinectService.bitmapRGB.Clone();
}
//Console.WriteLine("Enviando bitmap para segmentacao...");
// Converta o Bitmap para um array de bytes no formato JPEG
byte[] bitmapBytes;
using (var ms = new MemoryStream())
{
bitmap.Save(ms, ImageFormat.Jpeg);
bitmapBytes = ms.ToArray();
}
var topico = Variaveis.MqttService.Topicos.FirstOrDefault(x => x.Topico == CamerasVaraiveisModel.TopicoEnvioBitmap);
if (topico != null)
{
// Publique o array de bytes via MQTT
await Variaveis.MqttService.PublishAsync(
topico,
bitmapBytes
);
UltimoEnvio = DateTime.Now;
}
}
public Bitmap ExtraiImagemDaMensagem(string message)
{
// Decodificar o payload JSON
dynamic jsonData = JsonConvert.DeserializeObject(message);
// Extrair a imagem base64 do JSON
string imageBase64 = jsonData.segmented_image;
// Converter a string base64 de volta para um array de bytes
byte[] imageBytes = Convert.FromBase64String(imageBase64);
// Converter o array de bytes em um Bitmap
using (var ms = new MemoryStream(imageBytes))
{
Bitmap bitmap = new Bitmap(ms);
return bitmap;
}
}
public async Task AtualizarBitmapTela(Bitmap bitmap)
{
//Console.WriteLine("Recebido bitmap segmentado");
await FuncoesGlobais.ExecutarMetodoComVerificacaoCrossThreadAsync(panel, async () =>
{
bitmapSegmentado = bitmap;
if (panel != null)
{
panel.BackgroundImage = bitmapSegmentado;
panel.Refresh();
}
});
await Task.Delay(DelayEnvio);
await EnviarBitmapParaProcessamento();
}
public static List<Point> DefinirPontosExtremos(List<Point> PontosCamera)
{
if (!PontosCamera.Any())
@ -249,8 +143,139 @@ namespace AgroBase.Models
return Pontos;
}
// 🔹 Gera um mapa de calor corretamente escalado
public Bitmap GenerateHeatmapBitmap()
{
double MaxDepth = 10000;
var depth_data = Leitura.depth_data;
if (depth_data == null || depth_data.Count == 0)
throw new InvalidOperationException("Dados de profundidade inválidos.");
int height = depth_data.Count;
int width = depth_data[0].Count;
Bitmap heatmap = new Bitmap(width, height);
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
int depth = depth_data[y][x];
// 🔹 Normaliza a profundidade entre 0 (vermelho, perto) e 10.000 mm (azul, longe)
int normalized = (int)FuncoesMatematicas.Clamp(255 - ((depth * 255) / MaxDepth), 0, 255);
Color color = ApplyJetColormap(normalized);
heatmap.SetPixel(x, y, color);
}
}
return heatmap;
}
// 🔹 Função para converter um valor normalizado (0-255) para um colormap "Jet" correto
private Color ApplyJetColormap(int value)
{
double normalized = value / 255.0;
int r = (int)(FuncoesMatematicas.Clamp(255 * Math.Max(0, Math.Min(1, 1.5 - Math.Abs(normalized * 2 - 1.5))), 0, 255));
int g = (int)(FuncoesMatematicas.Clamp(255 * Math.Max(0, Math.Min(1, 1.5 - Math.Abs(normalized * 2 - 1.0))), 0, 255));
int b = (int)(FuncoesMatematicas.Clamp(255 * Math.Max(0, Math.Min(1, 1.5 - Math.Abs(normalized * 2 - 0.5))), 0, 255));
return Color.FromArgb(r, g, b);
}
}
public class GridMap
{
public int x_max { get; set; }
public int y_max { get; set; }
public Subdivisoes subdivisoes_cima { get; set; }
public Subdivisoes subdivisoes_chao { get; set; }
}
public class Subdivisoes
{
public int num_linhas { get; set; }
public int num_colunas { get; set; }
public List<Celula> celulas { get; set; }
}
public class Celula
{
public int linha { get; set; }
public int coluna { get; set; }
public int x { get; set; }
public int y { get; set; }
public int largura { get; set; }
public int altura { get; set; }
public double profundidade_calibragem { get; set; }
public double profundidade_media { get; set; }
public double dif_profundidade
{
get
{
return profundidade_media - profundidade_calibragem;
}
}
public Color cor
{
get
{
double margem = 200;
if (dif_profundidade < margem)
{
return Color.Red;
}
else if (dif_profundidade > margem)
{
return Color.Gold;
}
return Color.Transparent;
}
}
}
public class GridDrawer
{
public static GridMap LoadJson(string json)
{
return JsonConvert.DeserializeObject<GridMap>(json);
}
public static Bitmap DrawGridOnBitmap(GridMap gridMap, Bitmap image)
{
Bitmap output = new Bitmap(image);
using (Graphics g = Graphics.FromImage(output))
{
foreach (var celula in gridMap.subdivisoes_chao.celulas)
{
using (Brush brush = new SolidBrush(Color.FromArgb(100, celula.cor))) // 100 = opacidade
{
g.FillRectangle(brush, celula.x, celula.y, celula.largura, celula.altura);
}
g.DrawRectangle(Pens.White, celula.x, celula.y, celula.largura, celula.altura);
}
foreach (var celula in gridMap.subdivisoes_cima.celulas)
{
using (Brush brush = new SolidBrush(Color.FromArgb(100, celula.cor)))
{
g.FillRectangle(brush, celula.x, celula.y, celula.largura, celula.altura);
}
g.DrawRectangle(Pens.White, celula.x, celula.y, celula.largura, celula.altura);
}
}
return output;
}
}
}

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
namespace AgroBase.Models
@ -15,10 +16,10 @@ namespace AgroBase.Models
public class CameraCoordenadasFrameModel
{
public double timestamp { get; set; }
public OAKCameraModel device_data { get; set; } = new OAKCameraModel();
public double x_max { get; set; }
public double y_max { get; set; }
public CameraCoordenadasObjetoModel[] objetos { get; set; } = new List<CameraCoordenadasObjetoModel>().ToArray();
public OAKCameraModel device_data { get; set; } = new OAKCameraModel();
public CameraCoordenadasFrameModel Clone()
{
@ -56,14 +57,20 @@ namespace AgroBase.Models
{
public double timestamp { get; set; }
public OAKCameraModel device_data { get; set; }
public double x_max { get; set; }
public double y_max { get; set; }
public List<List<int>> depth_data { get; set; } = new List<List<int>>();
public List<CameraDeepLabV3PlusClasseModel> Classes { get; set; } = new List<CameraDeepLabV3PlusClasseModel>();
public CameraDeepLabV3PlusModel Clone()
{
return new CameraDeepLabV3PlusModel()
{
timestamp = timestamp,
device_data = device_data?.Clone(),
x_max = x_max,
y_max = y_max,
depth_data = new List<List<int>>(depth_data),
Classes = new List<CameraDeepLabV3PlusClasseModel>(Classes)
};
}

View File

@ -734,7 +734,7 @@ namespace AgroBase.Models
Variaveis.OperacaoEmAndamento.OpMapaGPS.Sensoriamento.ReiniciarLeituras();
Variaveis.OperacaoEmAndamento.AtualizaListaCameras(null, new List<Panel>(), false);
Variaveis.OperacaoEmAndamento.AtualizaListaCameras(new List<Panel>(), new List<Panel>(), false);
if (Variaveis.OperacaoEmAndamento.DispSen != null)
{
@ -978,7 +978,7 @@ namespace AgroBase.Models
#region CAMERAS
public void AtualizaListaCameras(Panel pnlCameraCaminho, List<Panel> pnlCameraSolo, bool Iniciar)
public void AtualizaListaCameras(List<Panel> pnlCameraCaminho, List<Panel> pnlCameraSolo, bool Iniciar)
{
if (DispSen == null)
{
@ -1026,7 +1026,7 @@ namespace AgroBase.Models
}
string deviceIdCaminho =
pnlCameraCaminho != null && pnlCameraCaminho.Tag != null && !string.IsNullOrEmpty(pnlCameraCaminho.Tag.ToString()) ? pnlCameraCaminho.Tag.ToString() :
pnlCameraCaminho != null && pnlCameraCaminho.Any(x => x.Tag != null) && !string.IsNullOrEmpty(pnlCameraCaminho.FirstOrDefault().Tag.ToString()) ? pnlCameraCaminho.FirstOrDefault().Tag.ToString() :
DispSen.Dados.CameraID_Caminho;
var cameraCaminho = new CameraCaminhoModel()
@ -1035,21 +1035,32 @@ namespace AgroBase.Models
Nome = "Cam_Caminho",
DeviceID = deviceIdCaminho,
ArquivoLeitura = "leitura_ruas.json",
browser = null,
browserRgb = null,
camera = new CameraService(),
MaxLeituras = 1,
MaxLeituras = 10,
Angulo = 0,
AnguloDireito = 0,
AnguloEsquerdo = 0,
Leitura = new CameraDeepLabV3PlusModel(),
PontosExtremos = new List<System.Drawing.Point>()
PontosExtremos = new List<System.Drawing.Point>(),
VideoPorta = (VariaveisPortas.CameraCaminho).ToString(),
VideoRgbUrl = "oak_rgb",
VideoHeatmapUrl = "oak_heatmap",
MqttTopico = "street_detector",
};
CameraCaminho = cameraCaminho;
//CameraCaminhoKinect.camera.selectedCamera = KinectService.Iniciado ? 1 : 0;
CameraCaminho.camera.cameraIndex = listaCameras.FirstOrDefault(x => x.Id == CameraCaminho.DeviceID)?.Index ?? -1;
if (CameraCaminho.camera.cameraIndex > -1 && Iniciar && Variaveis.OperacaoEmAndamento.Controle.SegmentacaoSemantica)
if (CameraCaminho.camera.cameraIndex > -1 && Iniciar) // && Variaveis.OperacaoEmAndamento.Controle.SegmentacaoSemantica
{
CameraCaminho.panel = pnlCameraCaminho;
if (pnlCameraCaminho.Count() > 0)
{
CameraCaminho.panelRgb = pnlCameraCaminho[0];
}
if (pnlCameraCaminho.Count() > 1)
{
CameraCaminho.panelHeatmap = pnlCameraCaminho[1];
}
IniciarCameraCaminho(CameraCaminho);
}
}
@ -1077,7 +1088,7 @@ namespace AgroBase.Models
ArquivoVersao.Versao,
},
CameraSolo.VideoPorta,
CameraSolo.VideoUrl,
new List<string>() { CameraSolo.VideoUrl },
CameraSolo.MqttTopico,
async (Mensagem) =>
{
@ -1088,7 +1099,7 @@ namespace AgroBase.Models
if (CameraSolo.browser == null)
{
CameraSolo.Iniciada = true;
CameraSolo.browser = new ChromiumWebBrowser(CameraSolo.camera.URLcamera);
CameraSolo.browser = new ChromiumWebBrowser(CameraSolo.camera.URLcamera[0]);
CameraSolo.browser.Dock = DockStyle.Fill;
CameraSolo.panel.Invoke(new Action(async () =>
@ -1120,47 +1131,59 @@ namespace AgroBase.Models
if (cameraCaminho.camera.cameraIndex > -1)
{
cameraCaminho.InicializarCamera();
VersaoArquivoModel ArquivoVersao = VersionamentoService.ArquivoModeloStreetDetector;
cameraCaminho.camera.IniciarCamera(
PythonService.ScriptStreetDetector,
new string[] {
CamerasVaraiveisModel.TopicoRecebimentoBitmap,
CamerasVaraiveisModel.TopicoEnvioBitmap,
ArquivoVersao.Diretorio,
ArquivoVersao.Versao,
cameraCaminho.MqttTopico,
cameraCaminho.VideoPorta,
cameraCaminho.VideoRgbUrl,
cameraCaminho.VideoHeatmapUrl,
cameraCaminho.MaxLeituras.ToString(),
cameraCaminho.camera.cameraIndex.ToString(),
},
cameraCaminho.VideoPorta,
cameraCaminho.VideoUrl,
CamerasVaraiveisModel.TopicoRecebimentoBitmap,
new List<string>() { cameraCaminho.VideoRgbUrl, cameraCaminho.VideoHeatmapUrl },
cameraCaminho.MqttTopico,
async (Mensagem) =>
{
cameraCaminho.UltimaMensagemRecebida = DateTime.Now;
if (Mensagem.Momento > cameraCaminho.camera.IniciadoEm && Mensagem.Mensagem == "OK")
{
cameraCaminho.camera.ProntoEm = Mensagem.Momento;
if (cameraCaminho.browser == null)
if (cameraCaminho.browserRgb == null)
{
cameraCaminho.Iniciada = true;
if (cameraCaminho.panel != null)
if (cameraCaminho.camera.URLcamera.Count() > 0)
{
cameraCaminho.panel.GetType().GetMethod("SetStyle", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).Invoke(cameraCaminho.panel, new object[] { ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, true });
cameraCaminho.panel.BackgroundImageLayout = ImageLayout.Zoom;
cameraCaminho.browserRgb = new ChromiumWebBrowser(cameraCaminho.camera.URLcamera[0]);
cameraCaminho.browserRgb.Dock = DockStyle.Fill;
cameraCaminho.panelRgb.Invoke(new Action(async () =>
{
cameraCaminho.panelRgb.Controls.Add(cameraCaminho.browserRgb);
cameraCaminho.panelRgb.Refresh();
await Task.Delay(500);
cameraCaminho.panelRgb.Refresh();
}));
}
await Task.Delay(1000);
if (cameraCaminho.camera.URLcamera.Count() > 1)
{
cameraCaminho.browserHeatmap = new ChromiumWebBrowser(cameraCaminho.camera.URLcamera[1]);
cameraCaminho.browserHeatmap.Dock = DockStyle.Fill;
cameraCaminho.panelHeatmap.Invoke(new Action(async () =>
{
cameraCaminho.panelHeatmap.Controls.Add(cameraCaminho.browserHeatmap);
cameraCaminho.panelHeatmap.Refresh();
await Task.Delay(500);
cameraCaminho.panelHeatmap.Refresh();
}));
}
}
}
else if (Mensagem.Mensagem != "OK")
{
try
{
Bitmap bitmap = Variaveis.OperacaoEmAndamento.CameraCaminho.ExtraiImagemDaMensagem(Mensagem.Mensagem);
await Variaveis.OperacaoEmAndamento.CameraCaminho.AtualizarBitmapTela(bitmap);
}
catch (Exception ex)
{
Console.WriteLine($"Erro ao processar a mensagem MQTT: {ex.Message}");
}
}
}
@ -1443,8 +1466,6 @@ namespace AgroBase.Models
Console.WriteLine("Erro ao ler dados da câmera do solo (" + CameraSolo.Nome + "): " + ex.Message);
}
}
Variaveis.OperacaoEmAndamento.OpMapaGPS.Sensoriamento.ErvasNoRadar = Variaveis.OperacaoEmAndamento.CamerasSolo.Sum(x => x.ErvasNoRadar);
try
{
@ -1610,8 +1631,6 @@ namespace AgroBase.Models
cameraCaminho.AnguloEsquerdo = 0;
}
Variaveis.OperacaoEmAndamento.Sensoriamento.AnguloRuaCamera = cameraCaminho.Angulo;
// Atualizar angulo de inclinação calculado através do modelo de IA para reconhecimento do caminho
//pnlAnguloCamera.Invalidate();
@ -1975,7 +1994,7 @@ namespace AgroBase.Models
};
SalvarLog(T_Code.A05.ToString(), logA05, Fim);
SalvarLogKinect(Agora);
//SalvarLogKinect(Agora);
Task.Run(async () => await SalvarLogCameras(Agora));
@ -2052,7 +2071,7 @@ namespace AgroBase.Models
new {
tipo = typeof(CamCaminhoSensoriamentoLogModel),
nome = cameraCaminho.Nome,
browser = cameraCaminho.browser
browser = cameraCaminho.browserRgb
}
})
.ToList();
@ -2080,7 +2099,7 @@ namespace AgroBase.Models
SalvarLog(Camera.nome, logCam, false);
frame = await FuncoesGlobais.GetBrowserImage(Camera.browser);
FuncoesGlobais.SalvarImagemComprimida(frame, Caminho, 100L, 512, 512);
FuncoesGlobais.SalvarImagemComprimida(frame, Caminho, 100L, 716, 403);
}
else if (Camera.tipo == typeof(CamCaminhoSensoriamentoLogModel))
{
@ -2093,16 +2112,16 @@ namespace AgroBase.Models
Angulo = cameraCaminho.Angulo,
AnguloDireito = cameraCaminho.AnguloDireito,
AnguloEsquerdo = cameraCaminho.AnguloEsquerdo,
Leitura = leitura.Clone()
Leitura = leitura.Clone(),
UltimaMensagemRecebida = cameraCaminho.UltimaMensagemRecebida
};
SalvarLog(Camera.nome, logCam, false);
if (cameraCaminho.camera._mqttTopico != null && cameraCaminho.camera._mqttTopico.Mensagens.Any())
{
string message = cameraCaminho.camera._mqttTopico.Mensagens[cameraCaminho.camera._mqttTopico.Mensagens.Count() - 1].Mensagem;
frame = cameraCaminho.ExtraiImagemDaMensagem(message);
FuncoesGlobais.SalvarImagemComprimida(frame, Caminho, 30L);
}
frame = await FuncoesGlobais.GetBrowserImage(Camera.browser);
FuncoesGlobais.SalvarImagemComprimida(frame, Caminho, 100L, 716, 403);
Bitmap heatBitmap = cameraCaminho.GenerateHeatmapBitmap();
FuncoesGlobais.SalvarImagemComprimida(heatBitmap, Caminho.Replace(".jpg", "_heat.jpg"), 30L);
}
}
}
@ -2954,7 +2973,7 @@ namespace AgroBase.Models
HerbicidaConsumido = _DispAtu.Dados.VolumeVazaoML,
PercentualErvasTerreno = _DispAtu.Dados.PercentualErvasTerreno,
HerbicidaPorErva = HerbicidaPorErva,
ErvasNoRadar = _OpMapaGPS.Sensoriamento.ErvasNoRadar,
ErvasNoRadar = Variaveis.OperacaoEmAndamento.CamerasSolo.Sum(x => x.ErvasNoRadar),
ErvasIdentificadas = new List<Dictionary<string, int>>(_OpMapaGPS.Sensoriamento.AtuacoesPorBico),
PercentualReservatorio = FuncoesMatematicas.Clamp(_DispAtu.Dados.PercentualReservatorio, 0, 100),
VolumeReservatorio = _DispAtu.Dados.VolumeReservatorio,
@ -3024,7 +3043,7 @@ namespace AgroBase.Models
TempoDecorridoSeg = Variaveis.OperacaoEmAndamento.TempoDecorridoSegs,
AnguloDif = _OpMapaGPS.Sensoriamento.AnguloDif,
AnguloRuaCamera = _OpMapaGPS.Sensoriamento.AnguloRuaCamera,
AnguloRuaCamera = Variaveis.OperacaoEmAndamento.CameraCaminho?.Angulo ?? 0,
Atuador = dadosAtuador,
Movimentacao = dadosMovimentacao,
@ -3327,6 +3346,14 @@ namespace AgroBase.Models
public double AnguloEsquerdo { get; set; }
public double AnguloDireito { get; set; }
public CameraDeepLabV3PlusModel Leitura { get; set; }
public bool Inicializado
{
get
{
return Leitura?.device_data?.Is_Pipeline_Running ?? false;
}
}
public DateTime UltimaMensagemRecebida { get; set; }
}
}

View File

@ -24,10 +24,10 @@ namespace AgroBase.Services
public int cameraIndex = 0;
private double conf_threshold = 0.5;
private double nms_threshold = 0.4;
public string URLcamera = "";
public List<string> URLcamera = new List<string>();
public void IniciarCamera(string Script, string[] Argumentos, string FlaskPorta, string FlaskUrl, string MqttTopico, Func<MqttTopicosMensagensModel, Task> mqttCallback = null)
public void IniciarCamera(string Script, string[] Argumentos, string FlaskPorta, List<string> FlaskUrl, string MqttTopico, Func<MqttTopicosMensagensModel, Task> mqttCallback = null)
{
Task.Run(async () =>
{
@ -37,12 +37,16 @@ namespace AgroBase.Services
pythonProcess = PythonService.RunScript(Script, Argumentos);
IniciadoEm = DateTime.Now;
URLcamera = "http://" + Endereco + ":" + FlaskPorta + "/" + FlaskUrl +
"?conf_threshold=" + conf_threshold.ToString().Replace(",", ".") +
"&nms_threshold=" + nms_threshold.ToString().Replace(",", ".") +
"&camera_index=" + cameraIndex;
foreach (string url in FlaskUrl)
{
string _url = "http://" + Endereco + ":" + FlaskPorta + "/" + url +
"?conf_threshold=" + conf_threshold.ToString().Replace(",", ".") +
"&nms_threshold=" + nms_threshold.ToString().Replace(",", ".") +
"&camera_index=" + cameraIndex;
URLcamera.Add(_url);
Console.WriteLine(URLcamera);
Console.WriteLine(_url);
}
});
}
@ -103,10 +107,4 @@ namespace AgroBase.Services
}
public class CamerasVaraiveisModel
{
public static string TopicoEnvioBitmap { get; set; } = "street_detector_send";
public static string TopicoRecebimentoBitmap { get; set; } = "street_detector_receive";
}
}

View File

@ -83,7 +83,6 @@ public class MqttService
await AdicionarNovoTopico(MapasVariaveisModel.TopicoCoordenadasGPS);
await AdicionarNovoTopico(MapasVariaveisModel.TopicoSelecaoRuasMapa, true, 1, async (message) => { Variaveis.OperacaoEmAndamento.Mapa.AtualizarRuasSelecionadas(); });
await AdicionarNovoTopico(MapasVariaveisModel.TopicoTrajetoriaDinamica);
await AdicionarNovoTopico(CamerasVaraiveisModel.TopicoEnvioBitmap);
});
tmrCheck = new AsyncTaskTimerModel("tmrCheck", tmrCheck_Tick, 1000);

View File

@ -72,7 +72,7 @@ namespace AgroBase.Services
try
{
string ScriptPath = CaminhoGeral + CaminhoScripts + Script;
string Args = string.Join(" ", Argumentos.Select(arg => $"\"{arg.Replace("\\", "/")}\""));
string Args = string.Join(" ", Argumentos.Select(arg => $"\"{(arg ?? "").Replace("\\", "/")}\""));
Process pythonProcess = new Process
{

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -142,12 +142,12 @@
{
"id": 13,
"Diretorio": "Python\\Scripts\\",
"Arquivo": "street_detector",
"Arquivo": "street_detector_oak",
"Extensao": ".py",
"Versao": "1_0",
"ArquivoDownload": "street_detector-1_0.py",
"NomeArquivoLocal": "street_detector-1_0.py",
"CaminhoCompleto": "Python\\Scripts\\street_detector-1_0.py",
"ArquivoDownload": "street_detector_oak-1_0.py",
"NomeArquivoLocal": "street_detector_oak-1_0.py",
"CaminhoCompleto": "Python\\Scripts\\street_detector_oak-1_0.py",
"Atualizado": true,
"ArquivoAtualizado": true,
"TipoArquivo": 8

View File

@ -25,7 +25,7 @@
<meta name="viewport" content="width=device-width,
initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<style>
#map_7d810bce8c49728809e1dfd9f77d8cb5 {
#map_40b1108e333517ffcf33c7dc2f290963 {
position: relative;
width: 100.0%;
height: 100.0%;
@ -39,14 +39,14 @@
<body>
<div class="folium-map" id="map_7d810bce8c49728809e1dfd9f77d8cb5" ></div>
<div class="folium-map" id="map_40b1108e333517ffcf33c7dc2f290963" ></div>
</body>
<script>
var map_7d810bce8c49728809e1dfd9f77d8cb5 = L.map(
"map_7d810bce8c49728809e1dfd9f77d8cb5",
var map_40b1108e333517ffcf33c7dc2f290963 = L.map(
"map_40b1108e333517ffcf33c7dc2f290963",
{
center: [0.0, 0.0],
crs: L.CRS.EPSG3857,
@ -60,13 +60,13 @@
var tile_layer_216fc5661167ee0178b6878cb28fd987 = L.tileLayer(
var tile_layer_12f8b928aba0bec9587603488d6125aa = L.tileLayer(
"https://tile.openstreetmap.org/{z}/{x}/{y}.png",
{"attribution": "\u0026copy; \u003ca href=\"https://www.openstreetmap.org/copyright\"\u003eOpenStreetMap\u003c/a\u003e contributors", "detectRetina": false, "maxNativeZoom": 19, "maxZoom": 19, "minZoom": 0, "noWrap": false, "opacity": 1, "subdomains": "abc", "tms": false}
);
tile_layer_216fc5661167ee0178b6878cb28fd987.addTo(map_7d810bce8c49728809e1dfd9f77d8cb5);
tile_layer_12f8b928aba0bec9587603488d6125aa.addTo(map_40b1108e333517ffcf33c7dc2f290963);
</script>
@ -87,7 +87,7 @@
}
trajeto_json_add({"features": []});
trajeto_json.addTo(map_7d810bce8c49728809e1dfd9f77d8cb5);
trajeto_json.addTo(map_40b1108e333517ffcf33c7dc2f290963);
function adicionarGeometria(novaGeometria) {
trajeto_json.addData(novaGeometria);
@ -145,7 +145,7 @@
var marcadorDinamico = L.marker([0, 0], {
icon: customIcon
}).addTo(map_7d810bce8c49728809e1dfd9f77d8cb5);
}).addTo(map_40b1108e333517ffcf33c7dc2f290963);
// Conectar ao broker MQTT
const client = mqtt.connect('ws://localhost:9001'); // Use wss para conexão segura
@ -183,7 +183,7 @@
marcadorDinamico.setRotationAngle(angulo);
adicionarCoordenada("Tj", [novaLongitude, novaLatitude]);
map_7d810bce8c49728809e1dfd9f77d8cb5.setView(novaPosicao, map_7d810bce8c49728809e1dfd9f77d8cb5.getZoom());
map_40b1108e333517ffcf33c7dc2f290963.setView(novaPosicao, map_40b1108e333517ffcf33c7dc2f290963.getZoom());
});
function calcularOrientacao(P1latitude, P1longitude, P2latitude, P2longitude) {

View File

@ -0,0 +1,597 @@
import depthai as dai
import cv2
import numpy as np
import time
import json
import sys
import threading
import paho.mqtt.client as mqtt
import uuid
from flask import Flask, Response
# 🔹 Configurações MQTT
mqtt_client = mqtt.Client(f"client_oak_d_lite_{uuid.uuid4()}")
mqtt_client.connect("localhost", port=1883)
# 🔹 Parâmetros de entrada
output_folder = 'Python/Output/'
mqtt_topic = sys.argv[1] # Tópico MQTT para envio dos dados
porta = int(sys.argv[2]) # Porta do Flask
url_rgb = sys.argv[3] # URL do vídeo RGB
url_heatmap = sys.argv[4] # URL do vídeo do Heatmap
max_readings = int(sys.argv[5])
camera_index = int(sys.argv[6])
# 🔹 Flask App
app = Flask(__name__)
# Função para calcular a moda de um array unidimensional
def calc_mode(values):
# Arredonda os valores para eliminar pequenas variações
rounded = np.round(values, 0)
vals, counts = np.unique(rounded, return_counts=True)
return vals[np.argmax(counts)]
# Configuração de filtros
NUM_FRAMES_SMOOTH = 5 # Número de frames para suavização temporal
SMOOTH_KERNEL = (5, 5) # Tamanho do kernel para suavização espacial
OUTLIER_THRESHOLD = 50 # Limiar para remoção de outliers
depth_buffer = [] # Buffer para armazenar os últimos frames de profundidade
# Suavização Temporal
def smooth_depth(depth_frame):
global depth_buffer
if len(depth_buffer) >= NUM_FRAMES_SMOOTH:
depth_buffer.pop(0) # Remove o frame mais antigo
depth_buffer.append(depth_frame) # Adiciona o novo frame
return np.mean(depth_buffer, axis=0).astype(np.uint16) # Retorna a média
# Suavização Espacial (Filtro Gaussiano)
def gaussian_smooth(depth_frame):
return cv2.GaussianBlur(depth_frame, SMOOTH_KERNEL, 0)
# Filtro de Mediana para Remover Ruídos
def median_filter(depth_frame):
return cv2.medianBlur(depth_frame, 5)
# Remover Outliers (Saltos Extremos)
def remove_outliers(depth_frame):
depth_median = cv2.medianBlur(depth_frame, 5)
diff = np.abs(depth_frame - depth_median)
depth_frame[diff > OUTLIER_THRESHOLD] = depth_median[diff > OUTLIER_THRESHOLD]
return depth_frame
# Aplicar filtros no depthFrame
def apply_depth_filters(depth_frame):
if filtro1:
depth_frame = smooth_depth(depth_frame) # 1. Suavização Temporal
if filtro2:
depth_frame = gaussian_smooth(depth_frame) # 2. Suavização Espacial
if filtro3:
depth_frame = median_filter(depth_frame) # 3. Filtro de Mediana
if filtro4:
depth_frame = remove_outliers(depth_frame) # 4. Remoção de Outliers
return depth_frame
# Função para gerar o mapa de calor da profundidade
def generate_heatmap(depth_frame):
normalized_depth = cv2.normalize(depth_frame, None, 0, 255, cv2.NORM_MINMAX)
heatmap = cv2.applyColorMap(normalized_depth.astype(np.uint8), cv2.COLORMAP_JET)
return heatmap
# ───────────────────────────────────────────────
# Configurações da câmera e das matrizes
# ───────────────────────────────────────────────
RGB_WIDTH, RGB_HEIGHT = 640, 480
WIDTH, HEIGHT = 320, 240
# Região do solo (grid A - parte inferior): ocupa 50% da altura da imagem
GROUND_ROWS = 10 # Número de linhas da matriz do solo
GROUND_CELLS = 10 # Número de células por linha
GROUND_REGION_HEIGHT = int(HEIGHT * 0.5) # 50% da altura da imagem
GROUND_TOP_SCALE = 0.5 # A linha mais distante (superior da região) terá 35% da largura total
DEPTH_LIMIT = 50 # (Valor de referência para comparação, mas agora usamos calibração)
# Região aérea (parte superior): ocupa o restante da imagem
AIR_ROWS = 10 # Número de linhas na matriz aérea
AIR_COLS = 15 # Número de colunas na matriz aérea
AIR_REGION_HEIGHT = HEIGHT - GROUND_REGION_HEIGHT # Altura da região aérea
# ───────────────────────────────────────────────
# Parâmetros para calibração e detecção com moda
# ───────────────────────────────────────────────
NUM_CALIB_FRAMES = 100 # Número de frames para calibração
NUM_DETECT_FRAMES = 10 # Número de frames para acumulação antes de calcular a moda na detecção
# Variável global para armazenar o frame de profundidade atual (para uso no callback)
current_depth_frame = None
show_grid = False
filtro1 = True
filtro2 = True
filtro3 = True
filtro4 = True
# ───────────────────────────────────────────────
# Criação do pipeline DepthAI
# ───────────────────────────────────────────────
pipeline = dai.Pipeline()
# Nó da câmera RGB
cam_rgb = pipeline.create(dai.node.ColorCamera)
cam_rgb.setPreviewSize(RGB_WIDTH, RGB_HEIGHT)
cam_rgb.setBoardSocket(dai.CameraBoardSocket.RGB)
cam_rgb.setInterleaved(False)
xout_rgb = pipeline.create(dai.node.XLinkOut)
xout_rgb.setStreamName("rgb")
cam_rgb.preview.link(xout_rgb.input)
# Nó de profundidade
mono_left = pipeline.create(dai.node.MonoCamera)
mono_right = pipeline.create(dai.node.MonoCamera)
mono_left.setResolution(dai.MonoCameraProperties.SensorResolution.THE_480_P)
mono_right.setResolution(dai.MonoCameraProperties.SensorResolution.THE_480_P)
mono_left.setBoardSocket(dai.CameraBoardSocket.LEFT)
mono_right.setBoardSocket(dai.CameraBoardSocket.RIGHT)
stereo = pipeline.create(dai.node.StereoDepth)
stereo.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.ROBOTICS)
#stereo.initialConfig.setConfidenceThreshold(250) # Remove ruído, mantendo apenas pontos confiáveis
#stereo.initialConfig.setMedianFilter(dai.MedianFilter.KERNEL_7x7) # Usa filtro de mediana forte
#stereo.setLeftRightCheck(True) # Ativa verificação para evitar erros
#stereo.setExtendedDisparity(False) # Reduz ruído em distâncias curtas
#stereo.setSubpixel(True) # Aumenta a precisão da profundidade
mono_left.out.link(stereo.left)
mono_right.out.link(stereo.right)
xout_depth = pipeline.create(dai.node.XLinkOut)
xout_depth.setStreamName("depth")
stereo.depth.link(xout_depth.input)
device_global = None
selected_device_info = None
camera_iniciada = False
def initialize_device():
global device_global, selected_device_info, camera_iniciada
# Listar todas as câmeras conectadas
devices = dai.Device.getAllAvailableDevices()
if len(devices) == 0:
print("Nenhuma câmera OAK conectada.")
return
if camera_index >= len(devices):
print(f"Índice da câmera ({camera_index}) inválido. Apenas {len(devices)} câmeras disponíveis.")
return
selected_device_info = devices[camera_index] # Seleciona a câmera correta pelo índice
print(f"Usando câmera: {selected_device_info.name} (ID: {selected_device_info.mxid})")
device_global = dai.Device(pipeline, selected_device_info)
camera_iniciada = True
# 🔹 Função para enviar vídeo via Flask
def process_depth_data():
if camera_iniciada == False:
initialize_device()
global device_global, selected_device_info
depth_queue = device_global.getOutputQueue(name="depth", maxSize=1, blocking=False)
# Função para calibrar a região do solo utilizando a moda
def calibrate_ground():
print("Calibrando região do solo: coletando {} frames...".format(NUM_CALIB_FRAMES))
calib_data = np.zeros((GROUND_ROWS, GROUND_CELLS, NUM_CALIB_FRAMES))
for frame_idx in range(NUM_CALIB_FRAMES):
depth_frame = depth_queue.get().getFrame()
depth_frame = apply_depth_filters(depth_frame)
for i in range(GROUND_ROWS):
y_start = HEIGHT - GROUND_REGION_HEIGHT + i * row_height_ground
y_end = y_start + row_height_ground
scale = GROUND_TOP_SCALE + (i / (GROUND_ROWS - 1)) * (1 - GROUND_TOP_SCALE) if GROUND_ROWS > 1 else 1
effective_width = int(WIDTH * scale)
cell_width = effective_width / GROUND_CELLS
for j in range(GROUND_CELLS):
x_start = int(WIDTH / 2 - effective_width / 2 + j * cell_width)
x_end = int(x_start + cell_width)
calib_data[i, j, frame_idx] = np.mean(depth_frame[y_start:y_end, x_start:x_end])
ground_ref = np.zeros((GROUND_ROWS, GROUND_CELLS))
for i in range(GROUND_ROWS):
for j in range(GROUND_CELLS):
ground_ref[i, j] = calc_mode(calib_data[i, j, :])
print("Calibração da região do solo concluída!")
return ground_ref
# Função para calibrar a região aérea utilizando a moda
def calibrate_air():
print("Calibrando região aérea: coletando {} frames...".format(NUM_CALIB_FRAMES))
calib_data_air = np.zeros((AIR_ROWS, AIR_COLS, NUM_CALIB_FRAMES))
row_height_air = AIR_REGION_HEIGHT // AIR_ROWS
cell_width_air = WIDTH // AIR_COLS
for frame_idx in range(NUM_CALIB_FRAMES):
depth_frame = depth_queue.get().getFrame()
depth_frame = apply_depth_filters(depth_frame)
for i in range(AIR_ROWS):
y_start = i * row_height_air
y_end = y_start + row_height_air
for j in range(AIR_COLS):
x_start = j * cell_width_air
x_end = x_start + cell_width_air
calib_data_air[i, j, frame_idx] = np.mean(depth_frame[y_start:y_end, x_start:x_end])
air_ref = np.zeros((AIR_ROWS, AIR_COLS))
for i in range(AIR_ROWS):
for j in range(AIR_COLS):
air_ref[i, j] = calc_mode(calib_data_air[i, j, :])
print("Calibração da região aérea concluída!")
return air_ref
# Função para calibrar a distância utilizando a moda
def calibrate_distance(alvo=2500):
global GROUND_REGION_HEIGHT, AIR_REGION_HEIGHT # Garantir que estamos alterando as variáveis globais
NUM_LINHAS_ANALISE = 80 # Número de linhas horizontais para análise
ALTURA_LINHA = HEIGHT // NUM_LINHAS_ANALISE # Altura de cada linha
PROFUNDIDADE_ALVO = alvo # Profundidade alvo em cm para definir a região do solo dinamicamente
media_profundidade_acumulada = np.zeros((NUM_LINHAS_ANALISE, NUM_CALIB_FRAMES))
print("Calibrando profundidade média com {} frames...".format(NUM_CALIB_FRAMES))
for frame_idx in range(NUM_CALIB_FRAMES):
depth_frame = depth_queue.get().getFrame()
depth_frame = apply_depth_filters(depth_frame)
for i in range(NUM_LINHAS_ANALISE):
y_start = HEIGHT - (i + 1) * ALTURA_LINHA
y_end = y_start + ALTURA_LINHA
x_start_crop = int(WIDTH * 0.0)
x_end_crop = int(WIDTH * 1.0)
region_values = depth_frame[y_start:y_end, x_start_crop:x_end_crop].flatten()
valid_values = region_values[(region_values > 0) & (region_values < 10000)]
if valid_values.size > 0:
media_profundidade_acumulada[i, frame_idx] = np.mean(valid_values)
else:
media_profundidade_acumulada[i, frame_idx] = 9999
media_profundidade = np.array([calc_mode(media_profundidade_acumulada[i, :]) for i in range(NUM_LINHAS_ANALISE)])
#print("Médias de profundidade por linha (usando moda):", media_profundidade)
erro_minimo = float('inf')
linha_alvo = None
for i in range(NUM_LINHAS_ANALISE):
erro = abs(media_profundidade[i] - PROFUNDIDADE_ALVO)
if erro < erro_minimo:
erro_minimo = erro
linha_alvo = i
if linha_alvo is not None:
linha_alvo += 1
GROUND_REGION_HEIGHT = HEIGHT - ((NUM_LINHAS_ANALISE - linha_alvo) * ALTURA_LINHA)
else:
GROUND_REGION_HEIGHT = HEIGHT // 2
AIR_REGION_HEIGHT = HEIGHT - GROUND_REGION_HEIGHT
v = GROUND_REGION_HEIGHT // GROUND_ROWS
print("Novo GROUND_REGION_HEIGHT:", GROUND_REGION_HEIGHT)
print("Novo AIR_REGION_HEIGHT:", AIR_REGION_HEIGHT)
print("Novo tamanho das linhas do solo:", v)
return v
# Gerar json dos dados das subdivisoes
def gerar_json_subdivisoes():
data = {
"x_max": WIDTH,
"y_max": HEIGHT,
"subdivisoes_cima": {
"num_linhas": AIR_ROWS,
"num_colunas": AIR_COLS,
"celulas": []
},
"subdivisoes_chao": {
"num_linhas": GROUND_ROWS,
"num_colunas": GROUND_CELLS,
"celulas": []
}
}
# Subdivisões da região aérea
row_height_air = AIR_REGION_HEIGHT // AIR_ROWS
cell_width_air = WIDTH // AIR_COLS
for i in range(AIR_ROWS):
for j in range(AIR_COLS):
x_start = j * cell_width_air
y_start = i * row_height_air
data["subdivisoes_cima"]["celulas"].append({
"linha": i,
"coluna": j,
"x": x_start,
"y": y_start,
"largura": cell_width_air,
"altura": row_height_air,
"profundidade_media": smoothed_air[i, j],
"profundidade_calibragem": air_reference[i, j]
})
# Subdivisões da região do solo
row_height_ground = GROUND_REGION_HEIGHT // GROUND_ROWS
for i in range(GROUND_ROWS):
scale = (GROUND_TOP_SCALE + (i / (GROUND_ROWS - 1)) * (1 - GROUND_TOP_SCALE)) if GROUND_ROWS > 1 else 1
effective_width = int(WIDTH * scale)
cell_width = effective_width / GROUND_CELLS
y_start = HEIGHT - GROUND_REGION_HEIGHT + i * row_height_ground
start_x = int(WIDTH / 2 - effective_width / 2)
for j in range(GROUND_CELLS):
x_start = int(start_x + j * cell_width)
data["subdivisoes_chao"]["celulas"].append({
"linha": i,
"coluna": j,
"x": x_start,
"y": y_start,
"largura": int(cell_width),
"altura": row_height_ground,
"profundidade_media": smoothed_ground[i, j],
"profundidade_calibragem": ground_reference[i, j]
})
return json.dumps(data, indent=4)
# Calibração inicial
row_height_ground = calibrate_distance()
ground_reference = calibrate_ground()
air_reference = calibrate_air()
# Preparação para a detecção usando moda (acumula dados de alguns frames)
detect_counter = 0
detect_data_ground = np.zeros((GROUND_ROWS, GROUND_CELLS, NUM_DETECT_FRAMES))
detect_data_air = np.zeros((AIR_ROWS, AIR_COLS, NUM_DETECT_FRAMES))
last_detect_ground = np.zeros((GROUND_ROWS, GROUND_CELLS))
last_detect_air = np.zeros((AIR_ROWS, AIR_COLS))
# Parâmetro para o filtro de média móvel
alpha = 0.4 # ajuste entre 0 e 1 (valores menores = mais suave)
# Inicialize as grids filtradas com os valores de calibração (ou com zeros, se preferir)
smoothed_ground = ground_reference.copy()
smoothed_air = air_reference.copy()
last_time = time.time()
target_fps = 30 # Limita o FPS
readings = []
while True:
current_time = time.time()
if current_time - last_time < 1 / target_fps:
time.sleep(0.01)
continue
last_time = current_time
in_depth = depth_queue.tryGet()
if in_depth is None:
continue
depth_frame = in_depth.getFrame()
# Aplicação dos filtros em sequência
depth_frame = apply_depth_filters(depth_frame)
# Gera o mapa de calor
heatmap = generate_heatmap(depth_frame)
# Cria uma cópia do heatmap para desenhar o overlay
heatmap_overlay = heatmap.copy()
# ─────────────────────────────────────────────
# Atualiza a grid do SOLO (região inferior) com média móvel
# ─────────────────────────────────────────────
for i in range(GROUND_ROWS):
y_start = HEIGHT - GROUND_REGION_HEIGHT + i * row_height_ground
y_end = y_start + row_height_ground
scale = (GROUND_TOP_SCALE +
(i / (GROUND_ROWS - 1)) * (1 - GROUND_TOP_SCALE)) if GROUND_ROWS > 1 else 1
effective_width = int(WIDTH * scale)
cell_width = effective_width / GROUND_CELLS
for j in range(GROUND_CELLS):
x_start = int(WIDTH / 2 - effective_width / 2 + j * cell_width)
x_end = int(x_start + cell_width)
# Obtém os valores válidos da região
region_values = depth_frame[y_start:y_end, x_start:x_end].flatten()
valid_values = region_values[(region_values > 0) & (region_values < 10000)] # Remove valores inválidos
# Calcula a média da célula e atualiza as variáveis
if valid_values.size > 0:
measurement = np.mean(valid_values)
else:
measurement = 9999 # Define um valor alto se não houver dados válidos
# Atualiza a grid suavizada com a média móvel
smoothed_ground[i, j] = alpha * measurement + (1 - alpha) * smoothed_ground[i, j]
# Atualiza a matriz last_detect_ground com a média real da célula
last_detect_ground[i, j] = measurement
# ─────────────────────────────────────────────
# Atualiza a grid da REGIÃO AÉREA (parte superior) com média móvel
# ─────────────────────────────────────────────
row_height_air = AIR_REGION_HEIGHT // AIR_ROWS
cell_width_air = WIDTH // AIR_COLS
for i in range(AIR_ROWS):
y_start = i * row_height_air
y_end = y_start + row_height_air
for j in range(AIR_COLS):
x_start = j * cell_width_air
x_end = x_start + cell_width_air
# Obtém os valores válidos da região
region_values = depth_frame[y_start:y_end, x_start:x_end].flatten()
valid_values = region_values[(region_values > 0) & (region_values < 10000)] # Remove valores inválidos
# Calcula a média da célula e atualiza as variáveis
if valid_values.size > 0:
measurement = np.mean(valid_values)
else:
measurement = 9999 # Define um valor alto se não houver dados válidos
# Atualiza a grid suavizada com a média móvel
smoothed_air[i, j] = alpha * measurement + (1 - alpha) * smoothed_air[i, j]
# Atualiza a matriz last_detect_air com a média real da célula
last_detect_air[i, j] = measurement
# ─────────────────────────────────────────────
# Processamento da detecção: Região do SOLO
# ─────────────────────────────────────────────
for i in range(GROUND_ROWS):
y_start = HEIGHT - GROUND_REGION_HEIGHT + i * row_height_ground
y_end = y_start + row_height_ground
scale = (GROUND_TOP_SCALE + (i / (GROUND_ROWS - 1)) * (1 - GROUND_TOP_SCALE)) if GROUND_ROWS > 1 else 1
effective_width = int(WIDTH * scale)
cell_width = effective_width / GROUND_CELLS
for j in range(GROUND_CELLS):
x_start = int(WIDTH / 2 - effective_width / 2 + j * cell_width)
x_end = int(x_start + cell_width)
# Compara o valor suavizado com a referência calibrada
if smoothed_ground[i, j] < (ground_reference[i, j] - DEPTH_LIMIT):
# Obstáculo (valor menor: objeto mais próximo)
cv2.rectangle(heatmap_overlay, (x_start, y_start), (x_end, y_end), (0, 0, 255), -1)
elif smoothed_ground[i, j] > (ground_reference[i, j] + DEPTH_LIMIT):
# Erosão (valor maior: superfície rebaixada)
cv2.rectangle(heatmap_overlay, (x_start, y_start), (x_end, y_end), (0, 255, 255), -1)
if show_grid:
cv2.rectangle(heatmap, (x_start, y_start), (x_end, y_end), (255, 255, 255), 1)
# ─────────────────────────────────────────────
# Processamento da detecção: Região AÉREA
# ─────────────────────────────────────────────
for i in range(AIR_ROWS):
y_start = i * row_height_air
y_end = y_start + row_height_air
for j in range(AIR_COLS):
x_start = j * cell_width_air
x_end = x_start + cell_width_air
if smoothed_air[i, j] < (air_reference[i, j] - DEPTH_LIMIT):
cv2.rectangle(heatmap_overlay, (x_start, y_start), (x_end, y_end), (0, 255, 0), -1)
if show_grid:
cv2.rectangle(heatmap, (x_start, y_start), (x_end, y_end), (255, 255, 255), 1)
try:
memory_usage = device_global.getDdrMemoryUsage()
memory_info = {
"remaining": memory_usage.remaining,
"total": memory_usage.total,
"used": memory_usage.used
}
except:
memory_info = None # Se houver erro, define como None
try:
temp = device_global.getChipTemperature()
temp_info = {
"css": temp.css,
"mss": temp.mss,
"upa": temp.upa,
"dss": temp.dss
}
except:
temp_info = None # Se houver erro, define como None
device_data = {
"id": selected_device_info.getMxId(), # ID do dispositivo
"name": selected_device_info.name, # Nome do dispositivo
"state": selected_device_info.state.name, # Estado do dispositivo
"usb_speed": str(device_global.getUsbSpeed().name) if hasattr(device_global, 'getUsbSpeed') else None, # Velocidade USB
"available_camera_sensors": [sensor.name for sensor in device_global.getConnectedCameras()], # Sensores de câmera disponíveis
"version": str(device_global.getDeviceInfo().protocol) if hasattr(device_global, 'getDeviceInfo') else None, # Versão do protocolo
"memory_usage": memory_info, # Uso de memória DDR
"temperature": temp_info, # Temperatura do chip
"bootloader_version": str(device_global.getBootloaderVersion()) if hasattr(device_global, 'getBootloaderVersion') else None, # Bootloader
"is_pipeline_running": device_global.isPipelineRunning() if hasattr(device_global, 'isPipelineRunning') else None # Pipeline rodando?
}
json_data = {
'timestamp': current_time,
'device_data': device_data,
'x_max': WIDTH,
'y_max': HEIGHT,
'subdivisoes': gerar_json_subdivisoes(),
}
readings.append(json_data)
# Enviar apenas a cada max_readings capturas
if len(readings) >= max_readings:
mensagem_relevante = readings[-1]
mqtt_client.publish(mqtt_topic, json.dumps(mensagem_relevante).encode('utf-8'))
readings.clear()
video_frame = heatmap
ret, buffer = cv2.imencode('.jpg', video_frame, [cv2.IMWRITE_JPEG_QUALITY, 80]) # Reduz qualidade para 80%
frame = buffer.tobytes()
yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
# 🔹 Função para enviar vídeo via Flask
def view_rgb_video():
if camera_iniciada == False:
initialize_device()
global device_global, selected_device_info
rgb_queue = device_global.getOutputQueue(name="rgb", maxSize=1, blocking=False)
while True:
in_rgb = rgb_queue.tryGet()
if in_rgb is None:
continue
rgb_frame = in_rgb.getCvFrame()
video_frame = rgb_frame
ret, buffer = cv2.imencode('.jpg', video_frame, [cv2.IMWRITE_JPEG_QUALITY, 80]) # Reduz qualidade para 80%
frame = buffer.tobytes()
yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
def send_script_ready():
mqtt_client.publish(mqtt_topic, "OK")
def run_flask_server():
app.run(host='0.0.0.0', port=porta, threaded=True, debug=False)
# 🔹 Servidores Flask para RGB e Heatmap
@app.route('/' + url_rgb, methods=['GET'])
def video_feed_rgb():
return Response(view_rgb_video(), mimetype='multipart/x-mixed-replace; boundary=frame')
@app.route('/' + url_heatmap, methods=['GET'])
def video_feed_heatmap():
return Response(process_depth_data(), mimetype='multipart/x-mixed-replace; boundary=frame')
if __name__ == '__main__':
mqtt_thread = threading.Thread(target=send_script_ready)
mqtt_thread.start()
run_flask_server()

View File

@ -0,0 +1,182 @@
import depthai as dai
import cv2
import numpy as np
import time
import json
import sys
import threading
import paho.mqtt.client as mqtt
import uuid
from flask import Flask, Response, request
# 🔹 Configurações MQTT
mqtt_client = mqtt.Client(f"client_oak_d_lite_{uuid.uuid4()}")
mqtt_client.connect("localhost", port=1883)
# 🔹 Parâmetros de entrada
output_folder = 'Python/Output/'
mqtt_topic = sys.argv[1] # Tópico MQTT para envio dos dados de profundidade
porta = int(sys.argv[2]) # Porta do Flask
url = sys.argv[3] # URL do vídeo
max_readings = int(sys.argv[4])
# 🔹 Flask App
app = Flask(__name__)
# 🔹 Criando o pipeline
pipeline = dai.Pipeline()
width, height = 1280, 720
# 📷 Câmera RGB
cam_rgb = pipeline.create(dai.node.ColorCamera)
cam_rgb.setPreviewSize(width, height)
cam_rgb.setBoardSocket(dai.CameraBoardSocket.RGB)
cam_rgb.setInterleaved(False)
# Criar XLinkOut para saída RGB
xout_rgb = pipeline.create(dai.node.XLinkOut)
xout_rgb.setStreamName("rgb")
cam_rgb.preview.link(xout_rgb.input)
# 🔹 Câmera de Profundidade
mono_left = pipeline.create(dai.node.MonoCamera)
mono_right = pipeline.create(dai.node.MonoCamera)
stereo = pipeline.create(dai.node.StereoDepth)
mono_left.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P)
mono_right.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P)
mono_left.setBoardSocket(dai.CameraBoardSocket.LEFT)
mono_right.setBoardSocket(dai.CameraBoardSocket.RIGHT)
# Configuração do StereoDepth
stereo.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_DENSITY)
stereo.setLeftRightCheck(True)
stereo.setSubpixel(True)
mono_left.out.link(stereo.left)
mono_right.out.link(stereo.right)
# Criar XLinkOut para profundidade
xout_depth = pipeline.create(dai.node.XLinkOut)
xout_depth.setStreamName("depth")
stereo.depth.link(xout_depth.input)
# 🔹 Função para enviar vídeo via Flask
def generate_video(camera_index):
# Listar todas as câmeras conectadas
devices = dai.Device.getAllAvailableDevices()
if len(devices) == 0:
print("Nenhuma câmera OAK conectada.")
return
if camera_index >= len(devices):
print(f"Índice da câmera ({camera_index}) inválido. Apenas {len(devices)} câmeras disponíveis.")
return
selected_device_info = devices[camera_index] # Seleciona a câmera correta pelo índice
print(f"Usando câmera: {selected_device_info.name} (ID: {selected_device_info.mxid})")
with dai.Device(pipeline, selected_device_info) as device: # 🔹 Agora usa a câmera correta
rgb_queue = device.getOutputQueue(name="rgb", maxSize=1, blocking=False)
depth_queue = device.getOutputQueue(name="depth", maxSize=1, blocking=False)
readings = []
while True:
frame_data = rgb_queue.get()
if frame_data is None:
continue
timestamp = time.time()
# 📏 Captura Profundidade
depth_frame = depth_queue.get().getFrame()
# Normaliza a profundidade para visualização (mapa de calor)
depth_visual = cv2.normalize(depth_frame, None, 0, 255, cv2.NORM_MINMAX, dtype=cv2.CV_8U)
depth_visual = cv2.applyColorMap(depth_visual, cv2.COLORMAP_JET)
# 📡 Enviar dados via MQTT (Apenas a matriz de profundidade reduzida)
depth_data = depth_frame.tolist() # Converte a matriz para lista JSON
depth_small = cv2.resize(depth_frame, (width // 2, height // 2)) # Reduz para metade
depth_data = depth_small.tolist()
try:
memory_usage = device.getDdrMemoryUsage()
memory_info = {
"remaining": memory_usage.remaining,
"total": memory_usage.total,
"used": memory_usage.used
}
except:
memory_info = None # Se houver erro, define como None
try:
temp = device.getChipTemperature()
temp_info = {
"css": temp.css,
"mss": temp.mss,
"upa": temp.upa,
"dss": temp.dss
}
except:
temp_info = None # Se houver erro, define como None
device_data = {
"id": selected_device_info.getMxId(), # ID do dispositivo
"name": selected_device_info.name, # Nome do dispositivo
"state": selected_device_info.state.name, # Estado do dispositivo
"usb_speed": str(device.getUsbSpeed().name) if hasattr(device, 'getUsbSpeed') else None, # Velocidade USB
"available_camera_sensors": [sensor.name for sensor in device.getConnectedCameras()], # Sensores de câmera disponíveis
"version": str(device.getDeviceInfo().protocol) if hasattr(device, 'getDeviceInfo') else None, # Versão do protocolo
"memory_usage": memory_info, # Uso de memória DDR
"temperature": temp_info, # Temperatura do chip
"bootloader_version": str(device.getBootloaderVersion()) if hasattr(device, 'getBootloaderVersion') else None, # Bootloader
"is_pipeline_running": device.isPipelineRunning() if hasattr(device, 'isPipelineRunning') else None # Pipeline rodando?
}
json_data = {
'timestamp': timestamp,
'device_data': device_data,
'x_max': width,
'y_max': height,
'depth_data': depth_data,
}
readings.append(json_data)
# Enviar apenas a cada max_readings capturas
if len(readings) >= max_readings:
mensagem_relevante = readings[-1]
mqtt_client.publish(mqtt_topic, json.dumps(mensagem_relevante).encode('utf-8'))
readings.clear()
frame = frame_data.getCvFrame()
ret, buffer = cv2.imencode('.jpg', frame, [cv2.IMWRITE_JPEG_QUALITY, 80]) # Reduz qualidade para 80%
frame = buffer.tobytes()
yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
def send_script_ready():
mqtt_client.publish(mqtt_topic, "OK")
def run_flask_server():
app.run(host='0.0.0.0', port=porta, threaded=True, debug=False)
@app.route('/' + url, methods=['GET'])
def video_feed():
camera_index = int(request.args.get('camera_index'))
return Response(generate_video(camera_index), mimetype='multipart/x-mixed-replace; boundary=frame')
if __name__ == '__main__':
mqtt_thread = threading.Thread(target=send_script_ready)
mqtt_thread.start()
run_flask_server()

View File

@ -198,8 +198,14 @@ def detect_oak(camera_index):
"is_pipeline_running": device.isPipelineRunning() if hasattr(device, 'isPipelineRunning') else None # Pipeline rodando?
}
json_data = {'timestamp': timestamp, 'x_max': width, 'y_max': height, 'objetos': current_readings, 'device_data': device_data}
print(json_data)
json_data = {
'timestamp': timestamp,
'device_data': device_data,
'x_max': width,
'y_max': height,
'objetos': current_readings
}
readings.append(json_data)
if len(readings) >= max_readings:

View File

@ -7,7 +7,7 @@ import sys
import threading
import paho.mqtt.client as mqtt
import uuid
from flask import Flask, Response
from flask import Flask, Response, request
# 🔹 Configurações MQTT
mqtt_client = mqtt.Client(f"client_oak_d_lite_{uuid.uuid4()}")
@ -18,7 +18,7 @@ output_folder = 'Python/Output/'
mqtt_topic = sys.argv[1] # Tópico MQTT para envio dos dados de profundidade
porta = int(sys.argv[2]) # Porta do Flask
url = sys.argv[3] # URL do vídeo
mostrar_linhas = sys.argv[4] == "1" # Se deve exibir sobreposições na imagem
max_readings = int(sys.argv[4])
# 🔹 Flask App
app = Flask(__name__)
@ -26,9 +26,11 @@ app = Flask(__name__)
# 🔹 Criando o pipeline
pipeline = dai.Pipeline()
width, height = 1280, 720
# 📷 Câmera RGB
cam_rgb = pipeline.create(dai.node.ColorCamera)
cam_rgb.setPreviewSize(1280, 720)
cam_rgb.setPreviewSize(width, height)
cam_rgb.setBoardSocket(dai.CameraBoardSocket.RGB)
cam_rgb.setInterleaved(False)
@ -61,49 +63,46 @@ xout_depth = pipeline.create(dai.node.XLinkOut)
xout_depth.setStreamName("depth")
stereo.depth.link(xout_depth.input)
# 🔹 Função para capturar e processar os frames
def process_frames():
with dai.Device(pipeline) as device:
rgb_queue = device.getOutputQueue(name="rgb", maxSize=4, blocking=False)
depth_queue = device.getOutputQueue(name="depth", maxSize=4, blocking=False)
while True:
timestamp = time.time()
# 📷 Captura RGB
rgb_frame = rgb_queue.get().getCvFrame()
# 📏 Captura Profundidade
depth_frame = depth_queue.get().getFrame()
# Normaliza a profundidade para visualização (mapa de calor)
depth_visual = cv2.normalize(depth_frame, None, 0, 255, cv2.NORM_MINMAX, dtype=cv2.CV_8U)
depth_visual = cv2.applyColorMap(depth_visual, cv2.COLORMAP_JET)
# 📡 Enviar dados via MQTT (Apenas a matriz de profundidade reduzida)
depth_data = depth_frame.tolist() # Converte a matriz para lista JSON
json_data = {
'timestamp': timestamp,
'depth_data': depth_data
}
mqtt_client.publish(mqtt_topic, json.dumps(json_data).encode('utf-8'))
# 🔹 Função para enviar vídeo via Flask
def generate_video():
with dai.Device(pipeline) as device:
rgb_queue = device.getOutputQueue(name="rgb", maxSize=4, blocking=False)
depth_queue = device.getOutputQueue(name="depth", maxSize=4, blocking=False)
def generate_video(camera_index):
# Listar todas as câmeras conectadas
devices = dai.Device.getAllAvailableDevices()
if len(devices) == 0:
print("Nenhuma câmera OAK conectada.")
return
if camera_index >= len(devices):
print(f"Índice da câmera ({camera_index}) inválido. Apenas {len(devices)} câmeras disponíveis.")
return
selected_device_info = devices[camera_index] # Seleciona a câmera correta pelo índice
print(f"Usando câmera: {selected_device_info.name} (ID: {selected_device_info.mxid})")
with dai.Device(pipeline, selected_device_info) as device: # 🔹 Agora usa a câmera correta
rgb_queue = device.getOutputQueue(name="rgb", maxSize=1, blocking=True)
depth_queue = device.getOutputQueue(name="depth", maxSize=1, blocking=True)
readings = []
rgb_frame = None
depth_frame = None
while True:
frame_data = rgb_queue.get()
if frame_data is None:
continue
timestamp = time.time()
# Pega os dois frames ao mesmo tempo, para evitar desincronização
in_rgb = rgb_queue.tryGet()
in_depth = depth_queue.tryGet()
# 📏 Captura Profundidade
depth_frame = depth_queue.get().getFrame()
if in_rgb is not None and in_depth is not None:
rgb_frame = in_rgb.getCvFrame()
depth_frame = in_depth.getFrame()
# 🔹 Verifica se depth_frame está vazio antes de processar
if depth_frame is None or depth_frame.size == 0:
print("⚠️ Frame de profundidade inválido. Pulando esta iteração.")
continue
# Normaliza a profundidade para visualização (mapa de calor)
depth_visual = cv2.normalize(depth_frame, None, 0, 255, cv2.NORM_MINMAX, dtype=cv2.CV_8U)
@ -111,22 +110,81 @@ def generate_video():
# 📡 Enviar dados via MQTT (Apenas a matriz de profundidade reduzida)
depth_data = depth_frame.tolist() # Converte a matriz para lista JSON
depth_small = cv2.resize(depth_frame, (width // 2, height // 2)) # Reduz para metade
depth_data = depth_small.tolist()
try:
memory_usage = device.getDdrMemoryUsage()
memory_info = {
"remaining": memory_usage.remaining,
"total": memory_usage.total,
"used": memory_usage.used
}
except:
memory_info = None # Se houver erro, define como None
try:
temp = device.getChipTemperature()
temp_info = {
"css": temp.css,
"mss": temp.mss,
"upa": temp.upa,
"dss": temp.dss
}
except:
temp_info = None # Se houver erro, define como None
device_data = {
"id": selected_device_info.getMxId(), # ID do dispositivo
"name": selected_device_info.name, # Nome do dispositivo
"state": selected_device_info.state.name, # Estado do dispositivo
"usb_speed": str(device.getUsbSpeed().name) if hasattr(device, 'getUsbSpeed') else None, # Velocidade USB
"available_camera_sensors": [sensor.name for sensor in device.getConnectedCameras()], # Sensores de câmera disponíveis
"version": str(device.getDeviceInfo().protocol) if hasattr(device, 'getDeviceInfo') else None, # Versão do protocolo
"memory_usage": memory_info, # Uso de memória DDR
"temperature": temp_info, # Temperatura do chip
"bootloader_version": str(device.getBootloaderVersion()) if hasattr(device, 'getBootloaderVersion') else None, # Bootloader
"is_pipeline_running": device.isPipelineRunning() if hasattr(device, 'isPipelineRunning') else None # Pipeline rodando?
}
json_data = {
'timestamp': timestamp,
'depth_data': depth_data
'device_data': device_data,
'x_max': width,
'y_max': height,
'depth_data': depth_data,
}
mqtt_client.publish(mqtt_topic, json.dumps(json_data).encode('utf-8'))
frame = frame_data.getCvFrame()
ret, buffer = cv2.imencode('.jpg', frame)
readings.append(json_data)
# Enviar apenas a cada max_readings capturas
if len(readings) >= max_readings:
mensagem_relevante = readings[-1]
mqtt_client.publish(mqtt_topic, json.dumps(mensagem_relevante).encode('utf-8'))
readings.clear()
frame = rgb_frame.getCvFrame()
ret, buffer = cv2.imencode('.jpg', frame, [cv2.IMWRITE_JPEG_QUALITY, 80]) # Reduz qualidade para 80%
frame = buffer.tobytes()
yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
def send_script_ready():
mqtt_client.publish(mqtt_topic, "OK")
def run_flask_server():
app.run(host='0.0.0.0', port=porta, threaded=True, debug=False)
@app.route('/' + url, methods=['GET'])
def video_feed():
return Response(generate_video(), mimetype='multipart/x-mixed-replace; boundary=frame')
camera_index = int(request.args.get('camera_index'))
return Response(generate_video(camera_index), mimetype='multipart/x-mixed-replace; boundary=frame')
# 🔹 Rodar Flask e processamento em threads separadas
if __name__ == '__main__':
app.run(host='0.0.0.0', port=porta, threaded=True, debug=False)
mqtt_thread = threading.Thread(target=send_script_ready)
mqtt_thread.start()
run_flask_server()

View File

@ -0,0 +1,154 @@
import depthai as dai
import numpy as np
import cv2
import time
# 📏 Definições da resolução da câmera e das matrizes
WIDTH, HEIGHT = 640, 400
GROUND_ROWS = 10 # 🔹 Número de linhas da matriz do solo
GROUND_COLS = 10 # 🔹 Todas as linhas terão 10 colunas (células podem ter larguras diferentes)
GROUND_PERCENTAGE = 0.25 # 🔹 35% da tela representa 2m
GROUND_HEIGHT = int(HEIGHT * GROUND_PERCENTAGE) # 🔹 Altura da matriz do solo
AIR_ROWS = 3 # Linhas superiores para obstáculos elevados
AIR_COLS = 6 # Colunas para obstáculos suspensos
GROUND_DEPTH_LIMIT = 2000 # Distância limite do solo (2m)
selected_cell = None # Variável global para armazenar célula selecionada no clique
# Criando o pipeline
pipeline = dai.Pipeline()
# 📷 Criar nó da câmera RGB
cam_rgb = pipeline.create(dai.node.ColorCamera)
cam_rgb.setPreviewSize(WIDTH, HEIGHT)
cam_rgb.setBoardSocket(dai.CameraBoardSocket.RGB)
cam_rgb.setInterleaved(False)
xout_rgb = pipeline.create(dai.node.XLinkOut)
xout_rgb.setStreamName("rgb")
cam_rgb.preview.link(xout_rgb.input)
# 📏 Criar nó de profundidade
mono_left = pipeline.create(dai.node.MonoCamera)
mono_right = pipeline.create(dai.node.MonoCamera)
stereo = pipeline.create(dai.node.StereoDepth)
mono_left.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P)
mono_right.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P)
mono_left.setBoardSocket(dai.CameraBoardSocket.LEFT)
mono_right.setBoardSocket(dai.CameraBoardSocket.RIGHT)
stereo.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_DENSITY)
stereo.setLeftRightCheck(True)
stereo.setSubpixel(True)
mono_left.out.link(stereo.left)
mono_right.out.link(stereo.right)
xout_depth = pipeline.create(dai.node.XLinkOut)
xout_depth.setStreamName("depth")
stereo.depth.link(xout_depth.input)
# 🚀 Função para capturar clique do mouse
def mouse_callback(event, x, y, flags, param):
global selected_cell
if event == cv2.EVENT_LBUTTONDOWN:
selected_cell = (x, y)
# 🚀 Inicializar Dispositivo
with dai.Device(pipeline) as device:
rgb_queue = device.getOutputQueue(name="rgb", maxSize=1, blocking=False)
depth_queue = device.getOutputQueue(name="depth", maxSize=1, blocking=False)
print("📏 Calibrando profundidade média das subdivisões...")
# 🔹 Pegar um frame inicial para calibração
depth_frame = depth_queue.get().getFrame()
ground_reference = np.zeros((GROUND_ROWS, GROUND_COLS))
# 🚀 Gerar matriz afunilada corretamente (base larga, topo estreito)
for i in range(GROUND_ROWS):
# 🔹 A base (i = 0) começa larga e a parte superior (i = GROUND_ROWS-1) é mais estreita
y_start = HEIGHT - (GROUND_HEIGHT - (i * (GROUND_HEIGHT // GROUND_ROWS))) # 🔹 Começa na base e sobe
y_end = HEIGHT - (GROUND_HEIGHT - ((i + 1) * (GROUND_HEIGHT // GROUND_ROWS)))
# 🔹 A base começa larga e vai afunilando progressivamente para cima
row_width = WIDTH - (i * (WIDTH // (GROUND_ROWS // 2))) # 🔹 Agora o afunilamento está correto
cell_width = row_width // GROUND_COLS # 🔹 Mantém 10 células por linha
for j in range(GROUND_COLS):
x_start = (WIDTH // 2) - (row_width // 2) + (j * cell_width)
x_end = x_start + cell_width
ground_reference[i, j] = np.mean(depth_frame[y_start:y_end, x_start:x_end])
print("✅ Calibração concluída!")
last_time = time.time()
target_fps = 15 # 🔹 Limita o FPS para evitar travamentos
cv2.namedWindow("Detecção de Obstáculos - OAK-D Lite")
cv2.setMouseCallback("Detecção de Obstáculos - OAK-D Lite", mouse_callback)
while True:
# 🔹 Controle de FPS
current_time = time.time()
if current_time - last_time < 1 / target_fps:
time.sleep(0.01)
continue
last_time = current_time
in_rgb = rgb_queue.tryGet()
in_depth = depth_queue.tryGet()
if in_rgb is None or in_depth is None:
continue
rgb_frame = in_rgb.getCvFrame()
depth_frame = in_depth.getFrame()
# 🔹 Criar camada para sobrepor na imagem
overlay = rgb_frame.copy()
# 🚀 Analisar o solo (matriz "A" dentro dos 35%)
for i in range(GROUND_ROWS):
y_start = HEIGHT - GROUND_HEIGHT + (i * (GROUND_HEIGHT // GROUND_ROWS))
y_end = HEIGHT - GROUND_HEIGHT + ((i + 1) * (GROUND_HEIGHT // GROUND_ROWS))
row_width = WIDTH - (i * (WIDTH // GROUND_ROWS)) # 🔹 Afunilamento progressivo
cell_width = row_width // GROUND_COLS
for j in range(GROUND_COLS):
x_start = (WIDTH // 2) - (row_width // 2) + (j * cell_width)
x_end = x_start + cell_width
region_depth = np.mean(depth_frame[y_start:y_end, x_start:x_end])
# 🚨 Comparação com a calibração inicial
if region_depth < (ground_reference[i, j] - 200): # 🔹 Diferença de 20 cm
cv2.rectangle(overlay, (x_start, y_start), (x_end, y_end), (0, 0, 255), -1) # 🔴 Vermelho semi-opaco
cv2.rectangle(rgb_frame, (x_start, y_start), (x_end, y_end), (255, 255, 255), 1) # 🔹 Grade branca
# 🚀 Exibir profundidade ao clicar
if selected_cell and (x_start <= selected_cell[0] <= x_end) and (y_start <= selected_cell[1] <= y_end):
depth_at_pixel = depth_frame[selected_cell[1], selected_cell[0]]
cv2.putText(rgb_frame, f"Prof: {region_depth:.1f} mm", (x_start, y_start - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 255, 0), 1)
cv2.putText(rgb_frame, f"Esperado: {ground_reference[i, j]:.1f} mm", (x_start, y_start + 10), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 255, 0), 1)
cv2.putText(rgb_frame, f"Pixel: {depth_at_pixel:.1f} mm", (x_start, y_start + 25), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 0, 0), 1)
# 🔹 Aplicar transparência na sobreposição
alpha = 0.4
cv2.addWeighted(overlay, alpha, rgb_frame, 1 - alpha, 0, rgb_frame)
# 🚀 Exibir imagem processada
cv2.imshow("Detecção de Obstáculos - OAK-D Lite", rgb_frame)
if cv2.waitKey(1) == ord('q'):
break
cv2.destroyAllWindows()

View File

@ -0,0 +1,131 @@
import depthai as dai
import numpy as np
import cv2
import time
from scipy.ndimage import label
# 📏 Definições da resolução da câmera
WIDTH, HEIGHT = 640, 400
DEPTH_THRESHOLD = 300 # 🔹 Diferença mínima de profundidade para considerar obstáculo (mm)
CAMERA_HEIGHT_CM = 55 # 🔹 Altura da câmera em cm
# Criando o pipeline
pipeline = dai.Pipeline()
# 📷 Criar nó da câmera RGB
cam_rgb = pipeline.create(dai.node.ColorCamera)
cam_rgb.setPreviewSize(WIDTH, HEIGHT)
cam_rgb.setBoardSocket(dai.CameraBoardSocket.RGB)
cam_rgb.setInterleaved(False)
xout_rgb = pipeline.create(dai.node.XLinkOut)
xout_rgb.setStreamName("rgb")
cam_rgb.preview.link(xout_rgb.input)
# 📏 Criar nó de profundidade
mono_left = pipeline.create(dai.node.MonoCamera)
mono_right = pipeline.create(dai.node.MonoCamera)
stereo = pipeline.create(dai.node.StereoDepth)
mono_left.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P)
mono_right.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P)
mono_left.setBoardSocket(dai.CameraBoardSocket.LEFT)
mono_right.setBoardSocket(dai.CameraBoardSocket.RIGHT)
stereo.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_DENSITY)
stereo.setLeftRightCheck(True)
stereo.setSubpixel(True)
mono_left.out.link(stereo.left)
mono_right.out.link(stereo.right)
xout_depth = pipeline.create(dai.node.XLinkOut)
xout_depth.setStreamName("depth")
stereo.depth.link(xout_depth.input)
# 🚀 Inicializar Dispositivo
with dai.Device(pipeline) as device:
rgb_queue = device.getOutputQueue(name="rgb", maxSize=1, blocking=False)
depth_queue = device.getOutputQueue(name="depth", maxSize=1, blocking=False)
last_time = time.time()
target_fps = 15 # 🔹 Limita o FPS para evitar travamentos
while True:
# 🔹 Controle de FPS
current_time = time.time()
if current_time - last_time < 1 / target_fps:
time.sleep(0.01)
continue
last_time = current_time
in_rgb = rgb_queue.tryGet()
in_depth = depth_queue.tryGet()
if in_rgb is None or in_depth is None:
continue
rgb_frame = in_rgb.getCvFrame()
depth_frame = in_depth.getFrame()
# 🔹 Normaliza profundidade para análise
depth_visual = cv2.normalize(depth_frame, None, 0, 255, cv2.NORM_MINMAX, dtype=cv2.CV_8U)
depth_visual = cv2.applyColorMap(depth_visual, cv2.COLORMAP_JET)
# 🔹 Criar máscara para identificar variações significativas de profundidade
median_depth = np.median(depth_frame) # 🔹 Calcula a profundidade média do ambiente
depth_mask = np.abs(depth_frame - median_depth) > DEPTH_THRESHOLD # 🔹 Detecta regiões diferentes do fundo
# 🔹 Agrupar pixels próximos em objetos distintos
labeled_mask, num_features = label(depth_mask)
detected_objects = []
for obj_id in range(1, num_features + 1):
y, x = np.where(labeled_mask == obj_id)
if len(x) < 200: # 🔹 Ignorar detecções muito pequenas (ruído)
continue
x_min, x_max = x.min(), x.max()
y_min, y_max = y.min(), y.max()
width_px = x_max - x_min
height_px = y_max - y_min
# 🔹 Calcula distância média do objeto
object_depth_values = depth_frame[y, x]
object_depth_values = object_depth_values[object_depth_values > 0] # 🔹 Remove valores inválidos
avg_depth = np.mean(object_depth_values) if len(object_depth_values) > 0 else 0
# 🔹 Conversão aproximada de pixels para cm
distance_cm = avg_depth / 10 # 🔹 Supondo 1 mm por unidade
width_cm = (width_px / WIDTH) * (distance_cm / CAMERA_HEIGHT_CM) * 100
height_cm = (height_px / HEIGHT) * (distance_cm / CAMERA_HEIGHT_CM) * 100
# 🔹 Filtrar objetos irrelevantes
if distance_cm > 500 or height_cm < 10: # 🔹 Ignorar objetos muito longe ou pequenos
continue
# 🔹 Adicionar aos obstáculos detectados
detected_objects.append({
"x_min": x_min,
"y_min": y_min,
"width_px": width_px,
"height_px": height_px,
"width_cm": round(width_cm, 2),
"height_cm": round(height_cm, 2),
"distance_cm": round(distance_cm, 2),
})
# 🔹 Desenhar bounding box na imagem RGB
cv2.rectangle(rgb_frame, (x_min, y_min), (x_max, y_max), (0, 255, 0), 2)
cv2.putText(rgb_frame, f"{distance_cm:.1f} cm", (x_min, y_min - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
# 🚀 Exibir imagem processada
cv2.imshow("Detecção de Obstáculos - OAK-D Lite", rgb_frame)
if cv2.waitKey(1) == ord('q'):
break
cv2.destroyAllWindows()

BIN
Python/OAK/model/midas.blob Normal file

Binary file not shown.

534
Python/OAK/sonarIA.py Normal file
View File

@ -0,0 +1,534 @@
import depthai as dai
import numpy as np
import cv2
import time
import json
# Função para calcular a moda de um array unidimensional
def calc_mode(values):
# Arredonda os valores para eliminar pequenas variações
rounded = np.round(values, 0)
vals, counts = np.unique(rounded, return_counts=True)
return vals[np.argmax(counts)]
# Configuração de filtros
NUM_FRAMES_SMOOTH = 5 # Número de frames para suavização temporal
SMOOTH_KERNEL = (5, 5) # Tamanho do kernel para suavização espacial
OUTLIER_THRESHOLD = 50 # Limiar para remoção de outliers
depth_buffer = [] # Buffer para armazenar os últimos frames de profundidade
# Suavização Temporal
def smooth_depth(depth_frame):
global depth_buffer
if len(depth_buffer) >= NUM_FRAMES_SMOOTH:
depth_buffer.pop(0) # Remove o frame mais antigo
depth_buffer.append(depth_frame) # Adiciona o novo frame
return np.mean(depth_buffer, axis=0).astype(np.uint16) # Retorna a média
# Suavização Espacial (Filtro Gaussiano)
def gaussian_smooth(depth_frame):
return cv2.GaussianBlur(depth_frame, SMOOTH_KERNEL, 0)
# Filtro de Mediana para Remover Ruídos
def median_filter(depth_frame):
return cv2.medianBlur(depth_frame, 5)
# Remover Outliers (Saltos Extremos)
def remove_outliers(depth_frame):
depth_median = cv2.medianBlur(depth_frame, 5)
diff = np.abs(depth_frame - depth_median)
depth_frame[diff > OUTLIER_THRESHOLD] = depth_median[diff > OUTLIER_THRESHOLD]
return depth_frame
# Aplicar filtros no depthFrame
def apply_depth_filters(depth_frame):
if filtro1:
depth_frame = smooth_depth(depth_frame) # 1. Suavização Temporal
if filtro2:
depth_frame = gaussian_smooth(depth_frame) # 2. Suavização Espacial
if filtro3:
depth_frame = median_filter(depth_frame) # 3. Filtro de Mediana
if filtro4:
depth_frame = remove_outliers(depth_frame) # 4. Remoção de Outliers
return depth_frame
# Função para gerar o mapa de calor da profundidade
def generate_heatmap(depth_frame):
normalized_depth = cv2.normalize(depth_frame, None, 0, 255, cv2.NORM_MINMAX)
heatmap = cv2.applyColorMap(normalized_depth.astype(np.uint8), cv2.COLORMAP_JET)
return heatmap
def gerar_json_subdivisoes():
data = {
"x_max": WIDTH,
"y_max": HEIGHT,
"subdivisoes_cima": {
"num_linhas": AIR_ROWS,
"num_colunas": AIR_COLS,
"celulas": []
},
"subdivisoes_chao": {
"num_linhas": GROUND_ROWS,
"num_colunas": GROUND_CELLS,
"celulas": []
}
}
# Subdivisões da região aérea
row_height_air = AIR_REGION_HEIGHT // AIR_ROWS
cell_width_air = WIDTH // AIR_COLS
for i in range(AIR_ROWS):
for j in range(AIR_COLS):
x_start = j * cell_width_air
y_start = i * row_height_air
data["subdivisoes_cima"]["celulas"].append({
"linha": i,
"coluna": j,
"x": x_start,
"y": y_start,
"largura": cell_width_air,
"altura": row_height_air,
"profundidade_media": smoothed_air[i, j],
"profundidade_calibragem": air_reference[i, j]
})
# Subdivisões da região do solo
row_height_ground = GROUND_REGION_HEIGHT // GROUND_ROWS
for i in range(GROUND_ROWS):
scale = (GROUND_TOP_SCALE + (i / (GROUND_ROWS - 1)) * (1 - GROUND_TOP_SCALE)) if GROUND_ROWS > 1 else 1
effective_width = int(WIDTH * scale)
cell_width = effective_width / GROUND_CELLS
y_start = HEIGHT - GROUND_REGION_HEIGHT + i * row_height_ground
start_x = int(WIDTH / 2 - effective_width / 2)
for j in range(GROUND_CELLS):
x_start = int(start_x + j * cell_width)
data["subdivisoes_chao"]["celulas"].append({
"linha": i,
"coluna": j,
"x": x_start,
"y": y_start,
"largura": int(cell_width),
"altura": row_height_ground,
"profundidade_media": smoothed_ground[i, j],
"profundidade_calibragem": ground_reference[i, j]
})
return json.dumps(data, indent=4)
# ───────────────────────────────────────────────
# Configurações da câmera e das matrizes
# ───────────────────────────────────────────────
WIDTH, HEIGHT = 320, 240
# Região do solo (grid A - parte inferior): ocupa 50% da altura da imagem
GROUND_ROWS = 10 # Número de linhas da matriz do solo
GROUND_CELLS = 10 # Número de células por linha
GROUND_REGION_HEIGHT = int(HEIGHT * 0.5) # 50% da altura da imagem
GROUND_TOP_SCALE = 0.5 # A linha mais distante (superior da região) terá 35% da largura total
DEPTH_LIMIT = 50 # (Valor de referência para comparação, mas agora usamos calibração)
# Região aérea (parte superior): ocupa o restante da imagem
AIR_ROWS = 10 # Número de linhas na matriz aérea
AIR_COLS = 15 # Número de colunas na matriz aérea
AIR_REGION_HEIGHT = HEIGHT - GROUND_REGION_HEIGHT # Altura da região aérea
# ───────────────────────────────────────────────
# Parâmetros para calibração e detecção com moda
# ───────────────────────────────────────────────
NUM_CALIB_FRAMES = 100 # Número de frames para calibração
NUM_DETECT_FRAMES = 10 # Número de frames para acumulação antes de calcular a moda na detecção
# Variável global para armazenar o frame de profundidade atual (para uso no callback)
current_depth_frame = None
show_grid = False
filtro1 = True
filtro2 = True
filtro3 = True
filtro4 = True
# ───────────────────────────────────────────────
# Criação do pipeline DepthAI
# ───────────────────────────────────────────────
pipeline = dai.Pipeline()
# Nó da câmera RGB
cam_rgb = pipeline.create(dai.node.ColorCamera)
cam_rgb.setPreviewSize(640, 480)
cam_rgb.setBoardSocket(dai.CameraBoardSocket.RGB)
cam_rgb.setInterleaved(False)
xout_rgb = pipeline.create(dai.node.XLinkOut)
xout_rgb.setStreamName("rgb")
cam_rgb.preview.link(xout_rgb.input)
# Nó de profundidade
mono_left = pipeline.create(dai.node.MonoCamera)
mono_right = pipeline.create(dai.node.MonoCamera)
mono_left.setResolution(dai.MonoCameraProperties.SensorResolution.THE_480_P)
mono_right.setResolution(dai.MonoCameraProperties.SensorResolution.THE_480_P)
mono_left.setBoardSocket(dai.CameraBoardSocket.LEFT)
mono_right.setBoardSocket(dai.CameraBoardSocket.RIGHT)
stereo = pipeline.create(dai.node.StereoDepth)
stereo.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.ROBOTICS)
#stereo.initialConfig.setConfidenceThreshold(250) # Remove ruído, mantendo apenas pontos confiáveis
#stereo.initialConfig.setMedianFilter(dai.MedianFilter.KERNEL_7x7) # Usa filtro de mediana forte
#stereo.setLeftRightCheck(True) # Ativa verificação para evitar erros
#stereo.setExtendedDisparity(False) # Reduz ruído em distâncias curtas
#stereo.setSubpixel(True) # Aumenta a precisão da profundidade
mono_left.out.link(stereo.left)
mono_right.out.link(stereo.right)
xout_depth = pipeline.create(dai.node.XLinkOut)
xout_depth.setStreamName("depth")
stereo.depth.link(xout_depth.input)
# ───────────────────────────────────────────────
# Callback de Mouse para exibir informações da célula clicada
# ───────────────────────────────────────────────
def mouse_callback(event, x, y, flags, param):
global current_depth_frame, ground_reference, air_reference, last_detect_ground, last_detect_air
if event != cv2.EVENT_LBUTTONDOWN or current_depth_frame is None:
return
# Se o clique for na região do solo:
if y >= HEIGHT - GROUND_REGION_HEIGHT:
local_y = y - (HEIGHT - GROUND_REGION_HEIGHT)
row_height_ground = GROUND_REGION_HEIGHT // GROUND_ROWS
row = int(local_y // row_height_ground)
if row < 0 or row >= GROUND_ROWS:
print("Clique fora da região do solo.")
return
# Calcular a escala para essa linha
scale = GROUND_TOP_SCALE + (row / (GROUND_ROWS - 1)) * (1 - GROUND_TOP_SCALE) if GROUND_ROWS > 1 else 1
effective_width = int(WIDTH * scale)
start_x = int(WIDTH / 2 - effective_width / 2)
if x < start_x or x > start_x + effective_width:
print("Clique fora das células da região do solo.")
return
cell_width = effective_width / GROUND_CELLS
col = int((x - start_x) // cell_width)
ref_val = ground_reference[row, col]
detect_val = last_detect_ground[row, col]
pixel_val = current_depth_frame[y, x]
print(f"Ground cell [{row}, {col}]: Calibração: {ref_val:.2f}, Média detectada: {detect_val:.2f}, Pixel: {pixel_val:.2f}")
# Se o clique for na região aérea:
elif y < AIR_REGION_HEIGHT:
row_height_air = AIR_REGION_HEIGHT // AIR_ROWS
cell_width_air = WIDTH // AIR_COLS
row = int(y // row_height_air)
col = int(x // cell_width_air)
if row < 0 or row >= AIR_ROWS or col < 0 or col >= AIR_COLS:
print("Clique fora da região aérea.")
return
ref_val = air_reference[row, col]
detect_val = last_detect_air[row, col]
pixel_val = current_depth_frame[y, x]
print(f"Air cell [{row}, {col}]: Calibração: {ref_val:.2f}, Média detectada: {detect_val:.2f}, Pixel: {pixel_val:.2f}")
else:
print("Clique fora das regiões definidas.")
cv2.namedWindow("Deteccao de Obstaculos - OAK-D Lite")
cv2.setMouseCallback("Deteccao de Obstaculos - OAK-D Lite", mouse_callback)
cv2.namedWindow("Mapa de Calor da Profundidade")
cv2.setMouseCallback("Mapa de Calor da Profundidade", mouse_callback)
# ───────────────────────────────────────────────
# Execução do dispositivo e funções de calibração
# ───────────────────────────────────────────────
with dai.Device(pipeline) as device:
rgb_queue = device.getOutputQueue(name="rgb", maxSize=1, blocking=False)
depth_queue = device.getOutputQueue(name="depth", maxSize=1, blocking=False)
# Função para calibrar a região do solo utilizando a moda
def calibrate_ground():
print("Calibrando região do solo: coletando {} frames...".format(NUM_CALIB_FRAMES))
calib_data = np.zeros((GROUND_ROWS, GROUND_CELLS, NUM_CALIB_FRAMES))
for frame_idx in range(NUM_CALIB_FRAMES):
depth_frame = depth_queue.get().getFrame()
depth_frame = apply_depth_filters(depth_frame)
for i in range(GROUND_ROWS):
y_start = HEIGHT - GROUND_REGION_HEIGHT + i * row_height_ground
y_end = y_start + row_height_ground
scale = GROUND_TOP_SCALE + (i / (GROUND_ROWS - 1)) * (1 - GROUND_TOP_SCALE) if GROUND_ROWS > 1 else 1
effective_width = int(WIDTH * scale)
cell_width = effective_width / GROUND_CELLS
for j in range(GROUND_CELLS):
x_start = int(WIDTH / 2 - effective_width / 2 + j * cell_width)
x_end = int(x_start + cell_width)
calib_data[i, j, frame_idx] = np.mean(depth_frame[y_start:y_end, x_start:x_end])
ground_ref = np.zeros((GROUND_ROWS, GROUND_CELLS))
for i in range(GROUND_ROWS):
for j in range(GROUND_CELLS):
ground_ref[i, j] = calc_mode(calib_data[i, j, :])
print("Calibração da região do solo concluída!")
return ground_ref
# Função para calibrar a região aérea utilizando a moda
def calibrate_air():
print("Calibrando região aérea: coletando {} frames...".format(NUM_CALIB_FRAMES))
calib_data_air = np.zeros((AIR_ROWS, AIR_COLS, NUM_CALIB_FRAMES))
row_height_air = AIR_REGION_HEIGHT // AIR_ROWS
cell_width_air = WIDTH // AIR_COLS
for frame_idx in range(NUM_CALIB_FRAMES):
depth_frame = depth_queue.get().getFrame()
depth_frame = apply_depth_filters(depth_frame)
for i in range(AIR_ROWS):
y_start = i * row_height_air
y_end = y_start + row_height_air
for j in range(AIR_COLS):
x_start = j * cell_width_air
x_end = x_start + cell_width_air
calib_data_air[i, j, frame_idx] = np.mean(depth_frame[y_start:y_end, x_start:x_end])
air_ref = np.zeros((AIR_ROWS, AIR_COLS))
for i in range(AIR_ROWS):
for j in range(AIR_COLS):
air_ref[i, j] = calc_mode(calib_data_air[i, j, :])
print("Calibração da região aérea concluída!")
return air_ref
# Função para calibrar a distância utilizando a moda
def calibrate_distance(alvo=2500):
global GROUND_REGION_HEIGHT, AIR_REGION_HEIGHT # Garantir que estamos alterando as variáveis globais
NUM_LINHAS_ANALISE = 80 # Número de linhas horizontais para análise
ALTURA_LINHA = HEIGHT // NUM_LINHAS_ANALISE # Altura de cada linha
PROFUNDIDADE_ALVO = alvo # Profundidade alvo em cm para definir a região do solo dinamicamente
media_profundidade_acumulada = np.zeros((NUM_LINHAS_ANALISE, NUM_CALIB_FRAMES))
print("Calibrando profundidade média com {} frames...".format(NUM_CALIB_FRAMES))
for frame_idx in range(NUM_CALIB_FRAMES):
depth_frame = depth_queue.get().getFrame()
depth_frame = apply_depth_filters(depth_frame)
for i in range(NUM_LINHAS_ANALISE):
y_start = HEIGHT - (i + 1) * ALTURA_LINHA
y_end = y_start + ALTURA_LINHA
x_start_crop = int(WIDTH * 0.0)
x_end_crop = int(WIDTH * 1.0)
region_values = depth_frame[y_start:y_end, x_start_crop:x_end_crop].flatten()
valid_values = region_values[(region_values > 0) & (region_values < 10000)]
if valid_values.size > 0:
media_profundidade_acumulada[i, frame_idx] = np.mean(valid_values)
else:
media_profundidade_acumulada[i, frame_idx] = 9999
media_profundidade = np.array([calc_mode(media_profundidade_acumulada[i, :]) for i in range(NUM_LINHAS_ANALISE)])
#print("Médias de profundidade por linha (usando moda):", media_profundidade)
erro_minimo = float('inf')
linha_alvo = None
for i in range(NUM_LINHAS_ANALISE):
erro = abs(media_profundidade[i] - PROFUNDIDADE_ALVO)
if erro < erro_minimo:
erro_minimo = erro
linha_alvo = i
if linha_alvo is not None:
linha_alvo += 1
GROUND_REGION_HEIGHT = HEIGHT - ((NUM_LINHAS_ANALISE - linha_alvo) * ALTURA_LINHA)
else:
GROUND_REGION_HEIGHT = HEIGHT // 2
AIR_REGION_HEIGHT = HEIGHT - GROUND_REGION_HEIGHT
v = GROUND_REGION_HEIGHT // GROUND_ROWS
print("Novo GROUND_REGION_HEIGHT:", GROUND_REGION_HEIGHT)
print("Novo AIR_REGION_HEIGHT:", AIR_REGION_HEIGHT)
print("Novo tamanho das linhas do solo:", v)
return v
# Calibração inicial
row_height_ground = calibrate_distance()
ground_reference = calibrate_ground()
air_reference = calibrate_air()
# Preparação para a detecção usando moda (acumula dados de alguns frames)
detect_counter = 0
detect_data_ground = np.zeros((GROUND_ROWS, GROUND_CELLS, NUM_DETECT_FRAMES))
detect_data_air = np.zeros((AIR_ROWS, AIR_COLS, NUM_DETECT_FRAMES))
last_detect_ground = np.zeros((GROUND_ROWS, GROUND_CELLS))
last_detect_air = np.zeros((AIR_ROWS, AIR_COLS))
# Parâmetro para o filtro de média móvel
alpha = 0.4 # ajuste entre 0 e 1 (valores menores = mais suave)
# Inicialize as grids filtradas com os valores de calibração (ou com zeros, se preferir)
smoothed_ground = ground_reference.copy()
smoothed_air = air_reference.copy()
last_time = time.time()
target_fps = 30 # Limita o FPS
while True:
current_time = time.time()
if current_time - last_time < 1 / target_fps:
time.sleep(0.01)
continue
last_time = current_time
in_rgb = rgb_queue.tryGet()
in_depth = depth_queue.tryGet()
if in_rgb is None or in_depth is None:
continue
rgb_frame = in_rgb.getCvFrame()
depth_frame = in_depth.getFrame()
# Aplicação dos filtros em sequência
depth_frame = apply_depth_filters(depth_frame)
# Gera o mapa de calor
heatmap = generate_heatmap(depth_frame)
# Cria uma cópia do heatmap para desenhar o overlay
heatmap_overlay = heatmap.copy()
# Atualiza o frame global para o callback de mouse
current_depth_frame = depth_frame.copy()
overlay = rgb_frame.copy()
# ─────────────────────────────────────────────
# Atualiza a grid do SOLO (região inferior) com média móvel
# ─────────────────────────────────────────────
for i in range(GROUND_ROWS):
y_start = HEIGHT - GROUND_REGION_HEIGHT + i * row_height_ground
y_end = y_start + row_height_ground
scale = (GROUND_TOP_SCALE +
(i / (GROUND_ROWS - 1)) * (1 - GROUND_TOP_SCALE)) if GROUND_ROWS > 1 else 1
effective_width = int(WIDTH * scale)
cell_width = effective_width / GROUND_CELLS
for j in range(GROUND_CELLS):
x_start = int(WIDTH / 2 - effective_width / 2 + j * cell_width)
x_end = int(x_start + cell_width)
# Obtém os valores válidos da região
region_values = depth_frame[y_start:y_end, x_start:x_end].flatten()
valid_values = region_values[(region_values > 0) & (region_values < 10000)] # Remove valores inválidos
# Calcula a média da célula e atualiza as variáveis
if valid_values.size > 0:
measurement = np.mean(valid_values)
else:
measurement = 9999 # Define um valor alto se não houver dados válidos
# Atualiza a grid suavizada com a média móvel
smoothed_ground[i, j] = alpha * measurement + (1 - alpha) * smoothed_ground[i, j]
# Atualiza a matriz last_detect_ground com a média real da célula
last_detect_ground[i, j] = measurement
# ─────────────────────────────────────────────
# Atualiza a grid da REGIÃO AÉREA (parte superior) com média móvel
# ─────────────────────────────────────────────
row_height_air = AIR_REGION_HEIGHT // AIR_ROWS
cell_width_air = WIDTH // AIR_COLS
for i in range(AIR_ROWS):
y_start = i * row_height_air
y_end = y_start + row_height_air
for j in range(AIR_COLS):
x_start = j * cell_width_air
x_end = x_start + cell_width_air
# Obtém os valores válidos da região
region_values = depth_frame[y_start:y_end, x_start:x_end].flatten()
valid_values = region_values[(region_values > 0) & (region_values < 10000)] # Remove valores inválidos
# Calcula a média da célula e atualiza as variáveis
if valid_values.size > 0:
measurement = np.mean(valid_values)
else:
measurement = 9999 # Define um valor alto se não houver dados válidos
# Atualiza a grid suavizada com a média móvel
smoothed_air[i, j] = alpha * measurement + (1 - alpha) * smoothed_air[i, j]
# Atualiza a matriz last_detect_air com a média real da célula
last_detect_air[i, j] = measurement
# ─────────────────────────────────────────────
# Processamento da detecção: Região do SOLO
# ─────────────────────────────────────────────
for i in range(GROUND_ROWS):
y_start = HEIGHT - GROUND_REGION_HEIGHT + i * row_height_ground
y_end = y_start + row_height_ground
scale = (GROUND_TOP_SCALE + (i / (GROUND_ROWS - 1)) * (1 - GROUND_TOP_SCALE)) if GROUND_ROWS > 1 else 1
effective_width = int(WIDTH * scale)
cell_width = effective_width / GROUND_CELLS
for j in range(GROUND_CELLS):
x_start = int(WIDTH / 2 - effective_width / 2 + j * cell_width)
x_end = int(x_start + cell_width)
# Compara o valor suavizado com a referência calibrada
if smoothed_ground[i, j] < (ground_reference[i, j] - DEPTH_LIMIT):
# Obstáculo (valor menor: objeto mais próximo)
cv2.rectangle(overlay, (x_start, y_start), (x_end, y_end), (0, 0, 255), -1)
cv2.rectangle(heatmap_overlay, (x_start, y_start), (x_end, y_end), (0, 0, 255), -1)
elif smoothed_ground[i, j] > (ground_reference[i, j] + DEPTH_LIMIT):
# Erosão (valor maior: superfície rebaixada)
cv2.rectangle(overlay, (x_start, y_start), (x_end, y_end), (0, 255, 255), -1)
cv2.rectangle(heatmap_overlay, (x_start, y_start), (x_end, y_end), (0, 255, 255), -1)
cv2.rectangle(rgb_frame, (x_start, y_start), (x_end, y_end), (255, 255, 255), 1)
if show_grid:
cv2.rectangle(heatmap, (x_start, y_start), (x_end, y_end), (255, 255, 255), 1)
# ─────────────────────────────────────────────
# Processamento da detecção: Região AÉREA
# ─────────────────────────────────────────────
for i in range(AIR_ROWS):
y_start = i * row_height_air
y_end = y_start + row_height_air
for j in range(AIR_COLS):
x_start = j * cell_width_air
x_end = x_start + cell_width_air
if smoothed_air[i, j] < (air_reference[i, j] - DEPTH_LIMIT):
cv2.rectangle(overlay, (x_start, y_start), (x_end, y_end), (0, 255, 0), -1)
cv2.rectangle(heatmap_overlay, (x_start, y_start), (x_end, y_end), (0, 255, 0), -1)
cv2.rectangle(rgb_frame, (x_start, y_start), (x_end, y_end), (255, 255, 255), 1)
if show_grid:
cv2.rectangle(heatmap, (x_start, y_start), (x_end, y_end), (255, 255, 255), 1)
cv2.addWeighted(overlay, 0.4, rgb_frame, 0.6, 0, rgb_frame)
cv2.imshow("Deteccao de Obstaculos - OAK-D Lite", rgb_frame)
if show_grid:
cv2.addWeighted(heatmap_overlay, 0.4, heatmap, 0.6, 0, heatmap)
cv2.imshow("Mapa de Calor da Profundidade", heatmap)
key = cv2.waitKey(1)
if key == ord('q'):
break
elif key == ord('c'):
print("Recalibrando regiões...")
row_height_ground = calibrate_distance()
ground_reference = calibrate_ground()
air_reference = calibrate_air()
elif key == ord('g'):
show_grid = True if show_grid == False else False
print(f"Grade {'Ligada' if show_grid else 'Desligada'}")
elif key == ord('1'):
filtro1 = True if filtro1 == False else False
print(f"Filtro suavizacao temporal {'Ligado' if filtro1 else 'Desligado'}")
elif key == ord('2'):
filtro2 = True if filtro2 == False else False
print(f"Filtro suavizacao espacial {'Ligado' if filtro2 else 'Desligado'}")
elif key == ord('3'):
filtro3 = True if filtro3 == False else False
print(f"Filtro mediana {'Ligado' if filtro3 else 'Desligado'}")
elif key == ord('4'):
filtro4 = True if filtro4 == False else False
print(f"Filtro remocao outliers {'Ligado' if filtro4 else 'Desligado'}")
cv2.destroyAllWindows()

View File

@ -0,0 +1,597 @@
import depthai as dai
import cv2
import numpy as np
import time
import json
import sys
import threading
import paho.mqtt.client as mqtt
import uuid
from flask import Flask, Response
# 🔹 Configurações MQTT
mqtt_client = mqtt.Client(f"client_oak_d_lite_{uuid.uuid4()}")
mqtt_client.connect("localhost", port=1883)
# 🔹 Parâmetros de entrada
output_folder = 'Python/Output/'
mqtt_topic = sys.argv[1] # Tópico MQTT para envio dos dados
porta = int(sys.argv[2]) # Porta do Flask
url_rgb = sys.argv[3] # URL do vídeo RGB
url_heatmap = sys.argv[4] # URL do vídeo do Heatmap
max_readings = int(sys.argv[5])
camera_index = int(sys.argv[6])
# 🔹 Flask App
app = Flask(__name__)
# Função para calcular a moda de um array unidimensional
def calc_mode(values):
# Arredonda os valores para eliminar pequenas variações
rounded = np.round(values, 0)
vals, counts = np.unique(rounded, return_counts=True)
return vals[np.argmax(counts)]
# Configuração de filtros
NUM_FRAMES_SMOOTH = 5 # Número de frames para suavização temporal
SMOOTH_KERNEL = (5, 5) # Tamanho do kernel para suavização espacial
OUTLIER_THRESHOLD = 50 # Limiar para remoção de outliers
depth_buffer = [] # Buffer para armazenar os últimos frames de profundidade
# Suavização Temporal
def smooth_depth(depth_frame):
global depth_buffer
if len(depth_buffer) >= NUM_FRAMES_SMOOTH:
depth_buffer.pop(0) # Remove o frame mais antigo
depth_buffer.append(depth_frame) # Adiciona o novo frame
return np.mean(depth_buffer, axis=0).astype(np.uint16) # Retorna a média
# Suavização Espacial (Filtro Gaussiano)
def gaussian_smooth(depth_frame):
return cv2.GaussianBlur(depth_frame, SMOOTH_KERNEL, 0)
# Filtro de Mediana para Remover Ruídos
def median_filter(depth_frame):
return cv2.medianBlur(depth_frame, 5)
# Remover Outliers (Saltos Extremos)
def remove_outliers(depth_frame):
depth_median = cv2.medianBlur(depth_frame, 5)
diff = np.abs(depth_frame - depth_median)
depth_frame[diff > OUTLIER_THRESHOLD] = depth_median[diff > OUTLIER_THRESHOLD]
return depth_frame
# Aplicar filtros no depthFrame
def apply_depth_filters(depth_frame):
if filtro1:
depth_frame = smooth_depth(depth_frame) # 1. Suavização Temporal
if filtro2:
depth_frame = gaussian_smooth(depth_frame) # 2. Suavização Espacial
if filtro3:
depth_frame = median_filter(depth_frame) # 3. Filtro de Mediana
if filtro4:
depth_frame = remove_outliers(depth_frame) # 4. Remoção de Outliers
return depth_frame
# Função para gerar o mapa de calor da profundidade
def generate_heatmap(depth_frame):
normalized_depth = cv2.normalize(depth_frame, None, 0, 255, cv2.NORM_MINMAX)
heatmap = cv2.applyColorMap(normalized_depth.astype(np.uint8), cv2.COLORMAP_JET)
return heatmap
# ───────────────────────────────────────────────
# Configurações da câmera e das matrizes
# ───────────────────────────────────────────────
RGB_WIDTH, RGB_HEIGHT = 640, 480
WIDTH, HEIGHT = 320, 240
# Região do solo (grid A - parte inferior): ocupa 50% da altura da imagem
GROUND_ROWS = 10 # Número de linhas da matriz do solo
GROUND_CELLS = 10 # Número de células por linha
GROUND_REGION_HEIGHT = int(HEIGHT * 0.5) # 50% da altura da imagem
GROUND_TOP_SCALE = 0.5 # A linha mais distante (superior da região) terá 35% da largura total
DEPTH_LIMIT = 50 # (Valor de referência para comparação, mas agora usamos calibração)
# Região aérea (parte superior): ocupa o restante da imagem
AIR_ROWS = 10 # Número de linhas na matriz aérea
AIR_COLS = 15 # Número de colunas na matriz aérea
AIR_REGION_HEIGHT = HEIGHT - GROUND_REGION_HEIGHT # Altura da região aérea
# ───────────────────────────────────────────────
# Parâmetros para calibração e detecção com moda
# ───────────────────────────────────────────────
NUM_CALIB_FRAMES = 100 # Número de frames para calibração
NUM_DETECT_FRAMES = 10 # Número de frames para acumulação antes de calcular a moda na detecção
# Variável global para armazenar o frame de profundidade atual (para uso no callback)
current_depth_frame = None
show_grid = False
filtro1 = True
filtro2 = True
filtro3 = True
filtro4 = True
# ───────────────────────────────────────────────
# Criação do pipeline DepthAI
# ───────────────────────────────────────────────
pipeline = dai.Pipeline()
# Nó da câmera RGB
cam_rgb = pipeline.create(dai.node.ColorCamera)
cam_rgb.setPreviewSize(RGB_WIDTH, RGB_HEIGHT)
cam_rgb.setBoardSocket(dai.CameraBoardSocket.RGB)
cam_rgb.setInterleaved(False)
xout_rgb = pipeline.create(dai.node.XLinkOut)
xout_rgb.setStreamName("rgb")
cam_rgb.preview.link(xout_rgb.input)
# Nó de profundidade
mono_left = pipeline.create(dai.node.MonoCamera)
mono_right = pipeline.create(dai.node.MonoCamera)
mono_left.setResolution(dai.MonoCameraProperties.SensorResolution.THE_480_P)
mono_right.setResolution(dai.MonoCameraProperties.SensorResolution.THE_480_P)
mono_left.setBoardSocket(dai.CameraBoardSocket.LEFT)
mono_right.setBoardSocket(dai.CameraBoardSocket.RIGHT)
stereo = pipeline.create(dai.node.StereoDepth)
stereo.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.ROBOTICS)
#stereo.initialConfig.setConfidenceThreshold(250) # Remove ruído, mantendo apenas pontos confiáveis
#stereo.initialConfig.setMedianFilter(dai.MedianFilter.KERNEL_7x7) # Usa filtro de mediana forte
#stereo.setLeftRightCheck(True) # Ativa verificação para evitar erros
#stereo.setExtendedDisparity(False) # Reduz ruído em distâncias curtas
#stereo.setSubpixel(True) # Aumenta a precisão da profundidade
mono_left.out.link(stereo.left)
mono_right.out.link(stereo.right)
xout_depth = pipeline.create(dai.node.XLinkOut)
xout_depth.setStreamName("depth")
stereo.depth.link(xout_depth.input)
device_global = None
selected_device_info = None
camera_iniciada = False
def initialize_device():
global device_global, selected_device_info, camera_iniciada
# Listar todas as câmeras conectadas
devices = dai.Device.getAllAvailableDevices()
if len(devices) == 0:
print("Nenhuma câmera OAK conectada.")
return
if camera_index >= len(devices):
print(f"Índice da câmera ({camera_index}) inválido. Apenas {len(devices)} câmeras disponíveis.")
return
selected_device_info = devices[camera_index] # Seleciona a câmera correta pelo índice
print(f"Usando câmera: {selected_device_info.name} (ID: {selected_device_info.mxid})")
device_global = dai.Device(pipeline, selected_device_info)
camera_iniciada = True
# 🔹 Função para enviar vídeo via Flask
def process_depth_data():
if camera_iniciada == False:
initialize_device()
global device_global, selected_device_info
depth_queue = device_global.getOutputQueue(name="depth", maxSize=1, blocking=False)
# Função para calibrar a região do solo utilizando a moda
def calibrate_ground():
print("Calibrando região do solo: coletando {} frames...".format(NUM_CALIB_FRAMES))
calib_data = np.zeros((GROUND_ROWS, GROUND_CELLS, NUM_CALIB_FRAMES))
for frame_idx in range(NUM_CALIB_FRAMES):
depth_frame = depth_queue.get().getFrame()
depth_frame = apply_depth_filters(depth_frame)
for i in range(GROUND_ROWS):
y_start = HEIGHT - GROUND_REGION_HEIGHT + i * row_height_ground
y_end = y_start + row_height_ground
scale = GROUND_TOP_SCALE + (i / (GROUND_ROWS - 1)) * (1 - GROUND_TOP_SCALE) if GROUND_ROWS > 1 else 1
effective_width = int(WIDTH * scale)
cell_width = effective_width / GROUND_CELLS
for j in range(GROUND_CELLS):
x_start = int(WIDTH / 2 - effective_width / 2 + j * cell_width)
x_end = int(x_start + cell_width)
calib_data[i, j, frame_idx] = np.mean(depth_frame[y_start:y_end, x_start:x_end])
ground_ref = np.zeros((GROUND_ROWS, GROUND_CELLS))
for i in range(GROUND_ROWS):
for j in range(GROUND_CELLS):
ground_ref[i, j] = calc_mode(calib_data[i, j, :])
print("Calibração da região do solo concluída!")
return ground_ref
# Função para calibrar a região aérea utilizando a moda
def calibrate_air():
print("Calibrando região aérea: coletando {} frames...".format(NUM_CALIB_FRAMES))
calib_data_air = np.zeros((AIR_ROWS, AIR_COLS, NUM_CALIB_FRAMES))
row_height_air = AIR_REGION_HEIGHT // AIR_ROWS
cell_width_air = WIDTH // AIR_COLS
for frame_idx in range(NUM_CALIB_FRAMES):
depth_frame = depth_queue.get().getFrame()
depth_frame = apply_depth_filters(depth_frame)
for i in range(AIR_ROWS):
y_start = i * row_height_air
y_end = y_start + row_height_air
for j in range(AIR_COLS):
x_start = j * cell_width_air
x_end = x_start + cell_width_air
calib_data_air[i, j, frame_idx] = np.mean(depth_frame[y_start:y_end, x_start:x_end])
air_ref = np.zeros((AIR_ROWS, AIR_COLS))
for i in range(AIR_ROWS):
for j in range(AIR_COLS):
air_ref[i, j] = calc_mode(calib_data_air[i, j, :])
print("Calibração da região aérea concluída!")
return air_ref
# Função para calibrar a distância utilizando a moda
def calibrate_distance(alvo=2500):
global GROUND_REGION_HEIGHT, AIR_REGION_HEIGHT # Garantir que estamos alterando as variáveis globais
NUM_LINHAS_ANALISE = 80 # Número de linhas horizontais para análise
ALTURA_LINHA = HEIGHT // NUM_LINHAS_ANALISE # Altura de cada linha
PROFUNDIDADE_ALVO = alvo # Profundidade alvo em cm para definir a região do solo dinamicamente
media_profundidade_acumulada = np.zeros((NUM_LINHAS_ANALISE, NUM_CALIB_FRAMES))
print("Calibrando profundidade média com {} frames...".format(NUM_CALIB_FRAMES))
for frame_idx in range(NUM_CALIB_FRAMES):
depth_frame = depth_queue.get().getFrame()
depth_frame = apply_depth_filters(depth_frame)
for i in range(NUM_LINHAS_ANALISE):
y_start = HEIGHT - (i + 1) * ALTURA_LINHA
y_end = y_start + ALTURA_LINHA
x_start_crop = int(WIDTH * 0.0)
x_end_crop = int(WIDTH * 1.0)
region_values = depth_frame[y_start:y_end, x_start_crop:x_end_crop].flatten()
valid_values = region_values[(region_values > 0) & (region_values < 10000)]
if valid_values.size > 0:
media_profundidade_acumulada[i, frame_idx] = np.mean(valid_values)
else:
media_profundidade_acumulada[i, frame_idx] = 9999
media_profundidade = np.array([calc_mode(media_profundidade_acumulada[i, :]) for i in range(NUM_LINHAS_ANALISE)])
#print("Médias de profundidade por linha (usando moda):", media_profundidade)
erro_minimo = float('inf')
linha_alvo = None
for i in range(NUM_LINHAS_ANALISE):
erro = abs(media_profundidade[i] - PROFUNDIDADE_ALVO)
if erro < erro_minimo:
erro_minimo = erro
linha_alvo = i
if linha_alvo is not None:
linha_alvo += 1
GROUND_REGION_HEIGHT = HEIGHT - ((NUM_LINHAS_ANALISE - linha_alvo) * ALTURA_LINHA)
else:
GROUND_REGION_HEIGHT = HEIGHT // 2
AIR_REGION_HEIGHT = HEIGHT - GROUND_REGION_HEIGHT
v = GROUND_REGION_HEIGHT // GROUND_ROWS
print("Novo GROUND_REGION_HEIGHT:", GROUND_REGION_HEIGHT)
print("Novo AIR_REGION_HEIGHT:", AIR_REGION_HEIGHT)
print("Novo tamanho das linhas do solo:", v)
return v
# Gerar json dos dados das subdivisoes
def gerar_json_subdivisoes():
data = {
"x_max": WIDTH,
"y_max": HEIGHT,
"subdivisoes_cima": {
"num_linhas": AIR_ROWS,
"num_colunas": AIR_COLS,
"celulas": []
},
"subdivisoes_chao": {
"num_linhas": GROUND_ROWS,
"num_colunas": GROUND_CELLS,
"celulas": []
}
}
# Subdivisões da região aérea
row_height_air = AIR_REGION_HEIGHT // AIR_ROWS
cell_width_air = WIDTH // AIR_COLS
for i in range(AIR_ROWS):
for j in range(AIR_COLS):
x_start = j * cell_width_air
y_start = i * row_height_air
data["subdivisoes_cima"]["celulas"].append({
"linha": i,
"coluna": j,
"x": x_start,
"y": y_start,
"largura": cell_width_air,
"altura": row_height_air,
"profundidade_media": smoothed_air[i, j],
"profundidade_calibragem": air_reference[i, j]
})
# Subdivisões da região do solo
row_height_ground = GROUND_REGION_HEIGHT // GROUND_ROWS
for i in range(GROUND_ROWS):
scale = (GROUND_TOP_SCALE + (i / (GROUND_ROWS - 1)) * (1 - GROUND_TOP_SCALE)) if GROUND_ROWS > 1 else 1
effective_width = int(WIDTH * scale)
cell_width = effective_width / GROUND_CELLS
y_start = HEIGHT - GROUND_REGION_HEIGHT + i * row_height_ground
start_x = int(WIDTH / 2 - effective_width / 2)
for j in range(GROUND_CELLS):
x_start = int(start_x + j * cell_width)
data["subdivisoes_chao"]["celulas"].append({
"linha": i,
"coluna": j,
"x": x_start,
"y": y_start,
"largura": int(cell_width),
"altura": row_height_ground,
"profundidade_media": smoothed_ground[i, j],
"profundidade_calibragem": ground_reference[i, j]
})
return json.dumps(data, indent=4)
# Calibração inicial
row_height_ground = calibrate_distance()
ground_reference = calibrate_ground()
air_reference = calibrate_air()
# Preparação para a detecção usando moda (acumula dados de alguns frames)
detect_counter = 0
detect_data_ground = np.zeros((GROUND_ROWS, GROUND_CELLS, NUM_DETECT_FRAMES))
detect_data_air = np.zeros((AIR_ROWS, AIR_COLS, NUM_DETECT_FRAMES))
last_detect_ground = np.zeros((GROUND_ROWS, GROUND_CELLS))
last_detect_air = np.zeros((AIR_ROWS, AIR_COLS))
# Parâmetro para o filtro de média móvel
alpha = 0.4 # ajuste entre 0 e 1 (valores menores = mais suave)
# Inicialize as grids filtradas com os valores de calibração (ou com zeros, se preferir)
smoothed_ground = ground_reference.copy()
smoothed_air = air_reference.copy()
last_time = time.time()
target_fps = 30 # Limita o FPS
readings = []
while True:
current_time = time.time()
if current_time - last_time < 1 / target_fps:
time.sleep(0.01)
continue
last_time = current_time
in_depth = depth_queue.tryGet()
if in_depth is None:
continue
depth_frame = in_depth.getFrame()
# Aplicação dos filtros em sequência
depth_frame = apply_depth_filters(depth_frame)
# Gera o mapa de calor
heatmap = generate_heatmap(depth_frame)
# Cria uma cópia do heatmap para desenhar o overlay
heatmap_overlay = heatmap.copy()
# ─────────────────────────────────────────────
# Atualiza a grid do SOLO (região inferior) com média móvel
# ─────────────────────────────────────────────
for i in range(GROUND_ROWS):
y_start = HEIGHT - GROUND_REGION_HEIGHT + i * row_height_ground
y_end = y_start + row_height_ground
scale = (GROUND_TOP_SCALE +
(i / (GROUND_ROWS - 1)) * (1 - GROUND_TOP_SCALE)) if GROUND_ROWS > 1 else 1
effective_width = int(WIDTH * scale)
cell_width = effective_width / GROUND_CELLS
for j in range(GROUND_CELLS):
x_start = int(WIDTH / 2 - effective_width / 2 + j * cell_width)
x_end = int(x_start + cell_width)
# Obtém os valores válidos da região
region_values = depth_frame[y_start:y_end, x_start:x_end].flatten()
valid_values = region_values[(region_values > 0) & (region_values < 10000)] # Remove valores inválidos
# Calcula a média da célula e atualiza as variáveis
if valid_values.size > 0:
measurement = np.mean(valid_values)
else:
measurement = 9999 # Define um valor alto se não houver dados válidos
# Atualiza a grid suavizada com a média móvel
smoothed_ground[i, j] = alpha * measurement + (1 - alpha) * smoothed_ground[i, j]
# Atualiza a matriz last_detect_ground com a média real da célula
last_detect_ground[i, j] = measurement
# ─────────────────────────────────────────────
# Atualiza a grid da REGIÃO AÉREA (parte superior) com média móvel
# ─────────────────────────────────────────────
row_height_air = AIR_REGION_HEIGHT // AIR_ROWS
cell_width_air = WIDTH // AIR_COLS
for i in range(AIR_ROWS):
y_start = i * row_height_air
y_end = y_start + row_height_air
for j in range(AIR_COLS):
x_start = j * cell_width_air
x_end = x_start + cell_width_air
# Obtém os valores válidos da região
region_values = depth_frame[y_start:y_end, x_start:x_end].flatten()
valid_values = region_values[(region_values > 0) & (region_values < 10000)] # Remove valores inválidos
# Calcula a média da célula e atualiza as variáveis
if valid_values.size > 0:
measurement = np.mean(valid_values)
else:
measurement = 9999 # Define um valor alto se não houver dados válidos
# Atualiza a grid suavizada com a média móvel
smoothed_air[i, j] = alpha * measurement + (1 - alpha) * smoothed_air[i, j]
# Atualiza a matriz last_detect_air com a média real da célula
last_detect_air[i, j] = measurement
# ─────────────────────────────────────────────
# Processamento da detecção: Região do SOLO
# ─────────────────────────────────────────────
for i in range(GROUND_ROWS):
y_start = HEIGHT - GROUND_REGION_HEIGHT + i * row_height_ground
y_end = y_start + row_height_ground
scale = (GROUND_TOP_SCALE + (i / (GROUND_ROWS - 1)) * (1 - GROUND_TOP_SCALE)) if GROUND_ROWS > 1 else 1
effective_width = int(WIDTH * scale)
cell_width = effective_width / GROUND_CELLS
for j in range(GROUND_CELLS):
x_start = int(WIDTH / 2 - effective_width / 2 + j * cell_width)
x_end = int(x_start + cell_width)
# Compara o valor suavizado com a referência calibrada
if smoothed_ground[i, j] < (ground_reference[i, j] - DEPTH_LIMIT):
# Obstáculo (valor menor: objeto mais próximo)
cv2.rectangle(heatmap_overlay, (x_start, y_start), (x_end, y_end), (0, 0, 255), -1)
elif smoothed_ground[i, j] > (ground_reference[i, j] + DEPTH_LIMIT):
# Erosão (valor maior: superfície rebaixada)
cv2.rectangle(heatmap_overlay, (x_start, y_start), (x_end, y_end), (0, 255, 255), -1)
if show_grid:
cv2.rectangle(heatmap, (x_start, y_start), (x_end, y_end), (255, 255, 255), 1)
# ─────────────────────────────────────────────
# Processamento da detecção: Região AÉREA
# ─────────────────────────────────────────────
for i in range(AIR_ROWS):
y_start = i * row_height_air
y_end = y_start + row_height_air
for j in range(AIR_COLS):
x_start = j * cell_width_air
x_end = x_start + cell_width_air
if smoothed_air[i, j] < (air_reference[i, j] - DEPTH_LIMIT):
cv2.rectangle(heatmap_overlay, (x_start, y_start), (x_end, y_end), (0, 255, 0), -1)
if show_grid:
cv2.rectangle(heatmap, (x_start, y_start), (x_end, y_end), (255, 255, 255), 1)
try:
memory_usage = device_global.getDdrMemoryUsage()
memory_info = {
"remaining": memory_usage.remaining,
"total": memory_usage.total,
"used": memory_usage.used
}
except:
memory_info = None # Se houver erro, define como None
try:
temp = device_global.getChipTemperature()
temp_info = {
"css": temp.css,
"mss": temp.mss,
"upa": temp.upa,
"dss": temp.dss
}
except:
temp_info = None # Se houver erro, define como None
device_data = {
"id": selected_device_info.getMxId(), # ID do dispositivo
"name": selected_device_info.name, # Nome do dispositivo
"state": selected_device_info.state.name, # Estado do dispositivo
"usb_speed": str(device_global.getUsbSpeed().name) if hasattr(device_global, 'getUsbSpeed') else None, # Velocidade USB
"available_camera_sensors": [sensor.name for sensor in device_global.getConnectedCameras()], # Sensores de câmera disponíveis
"version": str(device_global.getDeviceInfo().protocol) if hasattr(device_global, 'getDeviceInfo') else None, # Versão do protocolo
"memory_usage": memory_info, # Uso de memória DDR
"temperature": temp_info, # Temperatura do chip
"bootloader_version": str(device_global.getBootloaderVersion()) if hasattr(device_global, 'getBootloaderVersion') else None, # Bootloader
"is_pipeline_running": device_global.isPipelineRunning() if hasattr(device_global, 'isPipelineRunning') else None # Pipeline rodando?
}
json_data = {
'timestamp': current_time,
'device_data': device_data,
'x_max': WIDTH,
'y_max': HEIGHT,
'subdivisoes': gerar_json_subdivisoes(),
}
readings.append(json_data)
# Enviar apenas a cada max_readings capturas
if len(readings) >= max_readings:
mensagem_relevante = readings[-1]
mqtt_client.publish(mqtt_topic, json.dumps(mensagem_relevante).encode('utf-8'))
readings.clear()
video_frame = heatmap
ret, buffer = cv2.imencode('.jpg', video_frame, [cv2.IMWRITE_JPEG_QUALITY, 80]) # Reduz qualidade para 80%
frame = buffer.tobytes()
yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
# 🔹 Função para enviar vídeo via Flask
def view_rgb_video():
if camera_iniciada == False:
initialize_device()
global device_global, selected_device_info
rgb_queue = device_global.getOutputQueue(name="rgb", maxSize=1, blocking=False)
while True:
in_rgb = rgb_queue.tryGet()
if in_rgb is None:
continue
rgb_frame = in_rgb.getCvFrame()
video_frame = rgb_frame
ret, buffer = cv2.imencode('.jpg', video_frame, [cv2.IMWRITE_JPEG_QUALITY, 80]) # Reduz qualidade para 80%
frame = buffer.tobytes()
yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
def send_script_ready():
mqtt_client.publish(mqtt_topic, "OK")
def run_flask_server():
app.run(host='0.0.0.0', port=porta, threaded=True, debug=False)
# 🔹 Servidores Flask para RGB e Heatmap
@app.route('/' + url_rgb, methods=['GET'])
def video_feed_rgb():
return Response(view_rgb_video(), mimetype='multipart/x-mixed-replace; boundary=frame')
@app.route('/' + url_heatmap, methods=['GET'])
def video_feed_heatmap():
return Response(process_depth_data(), mimetype='multipart/x-mixed-replace; boundary=frame')
if __name__ == '__main__':
mqtt_thread = threading.Thread(target=send_script_ready)
mqtt_thread.start()
run_flask_server()