adicionado cameras OAK
This commit is contained in:
parent
c1d9b2fb30
commit
d95257ff2a
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -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(() =>
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
Binary file not shown.
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
Loading…
Reference in New Issue