avancado com o operador visual

This commit is contained in:
Diego Freitas 2025-05-21 17:07:43 -03:00
parent cee4a90f76
commit a92d95d4c0
45 changed files with 3484 additions and 7 deletions

Binary file not shown.

View File

@ -341,6 +341,12 @@
<Compile Include="Forms\frmOID.Designer.cs">
<DependentUpon>frmOID.cs</DependentUpon>
</Compile>
<Compile Include="Forms\frmOperarioSonar.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Forms\frmOperarioSonar.Designer.cs">
<DependentUpon>frmOperarioSonar.cs</DependentUpon>
</Compile>
<Compile Include="Forms\frmPinout.cs">
<SubType>Form</SubType>
</Compile>
@ -511,6 +517,7 @@
<Compile Include="Models\Operacoes\OperacaoMapaGPSModel.cs" />
<Compile Include="Models\Operacoes\OperacaoModel.cs" />
<Compile Include="Models\Operacoes\OperacaoParametrosModel.cs" />
<Compile Include="Models\Operadores\VisualWorkerModel.cs" />
<Compile Include="Models\PerformanceModel.cs" />
<Compile Include="Models\PIDModel.cs" />
<Compile Include="Models\PZEMModel.cs" />
@ -557,6 +564,7 @@
<Compile Include="Services\OAKCameraService.cs" />
<Compile Include="Services\OIDCanService.cs" />
<Compile Include="Services\OIDService.cs" />
<Compile Include="Services\Operadores\VisualWorkerService.cs" />
<Compile Include="Services\PythonService.cs" />
<Compile Include="Services\PZEMService.cs" />
<Compile Include="Services\SerialService.cs" />
@ -781,6 +789,9 @@
<EmbeddedResource Include="Forms\frmOID.resx">
<DependentUpon>frmOID.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Forms\frmOperarioSonar.resx">
<DependentUpon>frmOperarioSonar.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Forms\frmPinout.resx">
<DependentUpon>frmPinout.cs</DependentUpon>
</EmbeddedResource>

View File

@ -0,0 +1,511 @@
namespace AgroBase.Forms
{
partial class frmOperarioSonar
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.Windows.Forms.DataVisualization.Charting.ChartArea chartArea2 = new System.Windows.Forms.DataVisualization.Charting.ChartArea();
System.Windows.Forms.DataVisualization.Charting.Legend legend2 = new System.Windows.Forms.DataVisualization.Charting.Legend();
System.Windows.Forms.DataVisualization.Charting.Series series2 = new System.Windows.Forms.DataVisualization.Charting.Series();
this.picRGB = new System.Windows.Forms.PictureBox();
this.picHeatmap = new System.Windows.Forms.PictureBox();
this.lblRGB = new System.Windows.Forms.Label();
this.lblHeatmap = new System.Windows.Forms.Label();
this.btnLeituraRGB = new System.Windows.Forms.Button();
this.lblLeituraRGB = new System.Windows.Forms.Label();
this.lblLeituraHeatmap = new System.Windows.Forms.Label();
this.btnLeituraHeatmap = new System.Windows.Forms.Button();
this.btnIniciarProcessamento = new System.Windows.Forms.Button();
this.lblCameras = new System.Windows.Forms.Label();
this.cmbCameras = new System.Windows.Forms.ComboBox();
this.btnAtualizarCameras = new System.Windows.Forms.Button();
this.btnPing = new System.Windows.Forms.Button();
this.btnCalibrarGrades = new System.Windows.Forms.Button();
this.lblUltimaLeitura = new System.Windows.Forms.Label();
this.btnReqStatusDispositivo = new System.Windows.Forms.Button();
this.btnReqAnomalias = new System.Windows.Forms.Button();
this.btnReqSolo = new System.Windows.Forms.Button();
this.chbIniciarScript = new System.Windows.Forms.CheckBox();
this.lblDebugFrame = new System.Windows.Forms.Label();
this.picDebug = new System.Windows.Forms.PictureBox();
this.lblLeituraDebug = new System.Windows.Forms.Label();
this.dgvAnomalias = new System.Windows.Forms.DataGridView();
this.dgvSolo = new System.Windows.Forms.DataGridView();
this.chartPerfilSolo = new System.Windows.Forms.DataVisualization.Charting.Chart();
this.lblAnomalias = new System.Windows.Forms.Label();
this.lblSolo = new System.Windows.Forms.Label();
this.lblPerfilSolo = new System.Windows.Forms.Label();
this.txtVelocidade = new System.Windows.Forms.TextBox();
this.txtDistanciaMax = new System.Windows.Forms.TextBox();
this.lblVelocidade = new System.Windows.Forms.Label();
this.lblDistanciaMax = new System.Windows.Forms.Label();
this.lblTamanhoMin = new System.Windows.Forms.Label();
this.txtTamanhoMin = new System.Windows.Forms.TextBox();
this.chbReqAutomatico = new System.Windows.Forms.CheckBox();
((System.ComponentModel.ISupportInitialize)(this.picRGB)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.picHeatmap)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.picDebug)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.dgvAnomalias)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.dgvSolo)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.chartPerfilSolo)).BeginInit();
this.SuspendLayout();
//
// picRGB
//
this.picRGB.Location = new System.Drawing.Point(15, 25);
this.picRGB.Name = "picRGB";
this.picRGB.Size = new System.Drawing.Size(288, 267);
this.picRGB.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom;
this.picRGB.TabIndex = 0;
this.picRGB.TabStop = false;
//
// picHeatmap
//
this.picHeatmap.Location = new System.Drawing.Point(309, 25);
this.picHeatmap.Name = "picHeatmap";
this.picHeatmap.Size = new System.Drawing.Size(288, 267);
this.picHeatmap.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom;
this.picHeatmap.TabIndex = 1;
this.picHeatmap.TabStop = false;
//
// lblRGB
//
this.lblRGB.AutoSize = true;
this.lblRGB.Location = new System.Drawing.Point(12, 9);
this.lblRGB.Name = "lblRGB";
this.lblRGB.Size = new System.Drawing.Size(62, 13);
this.lblRGB.TabIndex = 2;
this.lblRGB.Text = "RGB Frame";
//
// lblHeatmap
//
this.lblHeatmap.AutoSize = true;
this.lblHeatmap.Location = new System.Drawing.Point(309, 9);
this.lblHeatmap.Name = "lblHeatmap";
this.lblHeatmap.Size = new System.Drawing.Size(82, 13);
this.lblHeatmap.TabIndex = 3;
this.lblHeatmap.Text = "Heatmap Frame";
//
// btnLeituraRGB
//
this.btnLeituraRGB.Location = new System.Drawing.Point(15, 298);
this.btnLeituraRGB.Name = "btnLeituraRGB";
this.btnLeituraRGB.Size = new System.Drawing.Size(75, 23);
this.btnLeituraRGB.TabIndex = 4;
this.btnLeituraRGB.Text = "Requisitar";
this.btnLeituraRGB.UseVisualStyleBackColor = true;
this.btnLeituraRGB.Click += new System.EventHandler(this.btnLeituraRGB_Click);
//
// lblLeituraRGB
//
this.lblLeituraRGB.AutoSize = true;
this.lblLeituraRGB.Location = new System.Drawing.Point(96, 303);
this.lblLeituraRGB.Name = "lblLeituraRGB";
this.lblLeituraRGB.Size = new System.Drawing.Size(62, 13);
this.lblLeituraRGB.TabIndex = 5;
this.lblLeituraRGB.Text = "Leitura em: ";
//
// lblLeituraHeatmap
//
this.lblLeituraHeatmap.AutoSize = true;
this.lblLeituraHeatmap.Location = new System.Drawing.Point(390, 303);
this.lblLeituraHeatmap.Name = "lblLeituraHeatmap";
this.lblLeituraHeatmap.Size = new System.Drawing.Size(62, 13);
this.lblLeituraHeatmap.TabIndex = 6;
this.lblLeituraHeatmap.Text = "Leitura em: ";
//
// btnLeituraHeatmap
//
this.btnLeituraHeatmap.Location = new System.Drawing.Point(309, 298);
this.btnLeituraHeatmap.Name = "btnLeituraHeatmap";
this.btnLeituraHeatmap.Size = new System.Drawing.Size(75, 23);
this.btnLeituraHeatmap.TabIndex = 7;
this.btnLeituraHeatmap.Text = "Requisitar";
this.btnLeituraHeatmap.UseVisualStyleBackColor = true;
this.btnLeituraHeatmap.Click += new System.EventHandler(this.btnLeituraHeatmap_Click);
//
// btnIniciarProcessamento
//
this.btnIniciarProcessamento.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.btnIniciarProcessamento.Location = new System.Drawing.Point(923, 81);
this.btnIniciarProcessamento.Name = "btnIniciarProcessamento";
this.btnIniciarProcessamento.Size = new System.Drawing.Size(179, 23);
this.btnIniciarProcessamento.TabIndex = 8;
this.btnIniciarProcessamento.Text = "Iniciar Processamento";
this.btnIniciarProcessamento.UseVisualStyleBackColor = true;
this.btnIniciarProcessamento.Click += new System.EventHandler(this.btnIniciarProcessamento_Click);
//
// lblCameras
//
this.lblCameras.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.lblCameras.AutoSize = true;
this.lblCameras.Location = new System.Drawing.Point(920, 9);
this.lblCameras.Name = "lblCameras";
this.lblCameras.Size = new System.Drawing.Size(108, 13);
this.lblCameras.TabIndex = 9;
this.lblCameras.Text = "Cameras Conectadas";
//
// cmbCameras
//
this.cmbCameras.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.cmbCameras.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cmbCameras.FormattingEnabled = true;
this.cmbCameras.Location = new System.Drawing.Point(923, 25);
this.cmbCameras.Name = "cmbCameras";
this.cmbCameras.Size = new System.Drawing.Size(179, 21);
this.cmbCameras.TabIndex = 10;
//
// btnAtualizarCameras
//
this.btnAtualizarCameras.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.btnAtualizarCameras.Location = new System.Drawing.Point(923, 52);
this.btnAtualizarCameras.Name = "btnAtualizarCameras";
this.btnAtualizarCameras.Size = new System.Drawing.Size(179, 23);
this.btnAtualizarCameras.TabIndex = 11;
this.btnAtualizarCameras.Text = "Atualizar Cameras";
this.btnAtualizarCameras.UseVisualStyleBackColor = true;
this.btnAtualizarCameras.Click += new System.EventHandler(this.btnAtualizarCameras_Click);
//
// btnPing
//
this.btnPing.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.btnPing.Location = new System.Drawing.Point(923, 133);
this.btnPing.Name = "btnPing";
this.btnPing.Size = new System.Drawing.Size(179, 23);
this.btnPing.TabIndex = 12;
this.btnPing.Text = "Requisitar Ping";
this.btnPing.UseVisualStyleBackColor = true;
this.btnPing.Click += new System.EventHandler(this.btnPing_Click);
//
// btnCalibrarGrades
//
this.btnCalibrarGrades.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.btnCalibrarGrades.Location = new System.Drawing.Point(923, 162);
this.btnCalibrarGrades.Name = "btnCalibrarGrades";
this.btnCalibrarGrades.Size = new System.Drawing.Size(179, 23);
this.btnCalibrarGrades.TabIndex = 13;
this.btnCalibrarGrades.Text = "Calibrar Grades";
this.btnCalibrarGrades.UseVisualStyleBackColor = true;
this.btnCalibrarGrades.Click += new System.EventHandler(this.btnCalibrarGrades_Click);
//
// lblUltimaLeitura
//
this.lblUltimaLeitura.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.lblUltimaLeitura.AutoSize = true;
this.lblUltimaLeitura.Location = new System.Drawing.Point(12, 737);
this.lblUltimaLeitura.Name = "lblUltimaLeitura";
this.lblUltimaLeitura.Size = new System.Drawing.Size(94, 13);
this.lblUltimaLeitura.TabIndex = 14;
this.lblUltimaLeitura.Text = "Ultima Leitura em: ";
//
// btnReqStatusDispositivo
//
this.btnReqStatusDispositivo.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.btnReqStatusDispositivo.Location = new System.Drawing.Point(923, 191);
this.btnReqStatusDispositivo.Name = "btnReqStatusDispositivo";
this.btnReqStatusDispositivo.Size = new System.Drawing.Size(179, 23);
this.btnReqStatusDispositivo.TabIndex = 15;
this.btnReqStatusDispositivo.Text = "Requisitar Status do Dispositivo";
this.btnReqStatusDispositivo.UseVisualStyleBackColor = true;
this.btnReqStatusDispositivo.Click += new System.EventHandler(this.btnReqStatusDispositivo_Click);
//
// btnReqAnomalias
//
this.btnReqAnomalias.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.btnReqAnomalias.Location = new System.Drawing.Point(923, 220);
this.btnReqAnomalias.Name = "btnReqAnomalias";
this.btnReqAnomalias.Size = new System.Drawing.Size(179, 23);
this.btnReqAnomalias.TabIndex = 16;
this.btnReqAnomalias.Text = "Requisitar Análise de Anomalias";
this.btnReqAnomalias.UseVisualStyleBackColor = true;
this.btnReqAnomalias.Click += new System.EventHandler(this.btnReqAnomalias_Click);
//
// btnReqSolo
//
this.btnReqSolo.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.btnReqSolo.Location = new System.Drawing.Point(923, 249);
this.btnReqSolo.Name = "btnReqSolo";
this.btnReqSolo.Size = new System.Drawing.Size(179, 23);
this.btnReqSolo.TabIndex = 17;
this.btnReqSolo.Text = "Requisitar Análise do Solo";
this.btnReqSolo.UseVisualStyleBackColor = true;
this.btnReqSolo.Click += new System.EventHandler(this.btnReqSolo_Click);
//
// chbIniciarScript
//
this.chbIniciarScript.AutoSize = true;
this.chbIniciarScript.Location = new System.Drawing.Point(923, 110);
this.chbIniciarScript.Name = "chbIniciarScript";
this.chbIniciarScript.Size = new System.Drawing.Size(84, 17);
this.chbIniciarScript.TabIndex = 24;
this.chbIniciarScript.Text = "Iniciar Script";
this.chbIniciarScript.UseVisualStyleBackColor = true;
//
// lblDebugFrame
//
this.lblDebugFrame.AutoSize = true;
this.lblDebugFrame.Location = new System.Drawing.Point(603, 9);
this.lblDebugFrame.Name = "lblDebugFrame";
this.lblDebugFrame.Size = new System.Drawing.Size(71, 13);
this.lblDebugFrame.TabIndex = 26;
this.lblDebugFrame.Text = "Debug Frame";
//
// picDebug
//
this.picDebug.Location = new System.Drawing.Point(603, 25);
this.picDebug.Name = "picDebug";
this.picDebug.Size = new System.Drawing.Size(288, 267);
this.picDebug.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom;
this.picDebug.TabIndex = 25;
this.picDebug.TabStop = false;
//
// lblLeituraDebug
//
this.lblLeituraDebug.AutoSize = true;
this.lblLeituraDebug.Location = new System.Drawing.Point(603, 303);
this.lblLeituraDebug.Name = "lblLeituraDebug";
this.lblLeituraDebug.Size = new System.Drawing.Size(62, 13);
this.lblLeituraDebug.TabIndex = 27;
this.lblLeituraDebug.Text = "Leitura em: ";
//
// dgvAnomalias
//
this.dgvAnomalias.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dgvAnomalias.Location = new System.Drawing.Point(15, 347);
this.dgvAnomalias.Name = "dgvAnomalias";
this.dgvAnomalias.Size = new System.Drawing.Size(582, 175);
this.dgvAnomalias.TabIndex = 28;
//
// dgvSolo
//
this.dgvSolo.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dgvSolo.Location = new System.Drawing.Point(15, 542);
this.dgvSolo.Name = "dgvSolo";
this.dgvSolo.Size = new System.Drawing.Size(582, 175);
this.dgvSolo.TabIndex = 29;
//
// chartPerfilSolo
//
chartArea2.Name = "ChartArea1";
this.chartPerfilSolo.ChartAreas.Add(chartArea2);
legend2.Name = "Legend1";
this.chartPerfilSolo.Legends.Add(legend2);
this.chartPerfilSolo.Location = new System.Drawing.Point(606, 412);
this.chartPerfilSolo.Name = "chartPerfilSolo";
series2.ChartArea = "ChartArea1";
series2.Legend = "Legend1";
series2.Name = "Series1";
this.chartPerfilSolo.Series.Add(series2);
this.chartPerfilSolo.Size = new System.Drawing.Size(496, 305);
this.chartPerfilSolo.TabIndex = 30;
this.chartPerfilSolo.Text = "chart1";
//
// lblAnomalias
//
this.lblAnomalias.AutoSize = true;
this.lblAnomalias.Location = new System.Drawing.Point(12, 331);
this.lblAnomalias.Name = "lblAnomalias";
this.lblAnomalias.Size = new System.Drawing.Size(113, 13);
this.lblAnomalias.TabIndex = 31;
this.lblAnomalias.Text = "Anomalias Detectadas";
//
// lblSolo
//
this.lblSolo.AutoSize = true;
this.lblSolo.Location = new System.Drawing.Point(12, 526);
this.lblSolo.Name = "lblSolo";
this.lblSolo.Size = new System.Drawing.Size(154, 13);
this.lblSolo.TabIndex = 32;
this.lblSolo.Text = "Anomalias Detectadas No Solo";
//
// lblPerfilSolo
//
this.lblPerfilSolo.AutoSize = true;
this.lblPerfilSolo.Location = new System.Drawing.Point(603, 396);
this.lblPerfilSolo.Name = "lblPerfilSolo";
this.lblPerfilSolo.Size = new System.Drawing.Size(69, 13);
this.lblPerfilSolo.TabIndex = 33;
this.lblPerfilSolo.Text = "Perfil do Solo";
//
// txtVelocidade
//
this.txtVelocidade.Location = new System.Drawing.Point(606, 347);
this.txtVelocidade.Name = "txtVelocidade";
this.txtVelocidade.Size = new System.Drawing.Size(88, 20);
this.txtVelocidade.TabIndex = 34;
this.txtVelocidade.Text = "0,00";
this.txtVelocidade.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
//
// txtDistanciaMax
//
this.txtDistanciaMax.Location = new System.Drawing.Point(794, 347);
this.txtDistanciaMax.Name = "txtDistanciaMax";
this.txtDistanciaMax.Size = new System.Drawing.Size(88, 20);
this.txtDistanciaMax.TabIndex = 35;
this.txtDistanciaMax.Text = "2,00";
this.txtDistanciaMax.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
//
// lblVelocidade
//
this.lblVelocidade.AutoSize = true;
this.lblVelocidade.Location = new System.Drawing.Point(603, 331);
this.lblVelocidade.Name = "lblVelocidade";
this.lblVelocidade.Size = new System.Drawing.Size(87, 13);
this.lblVelocidade.TabIndex = 36;
this.lblVelocidade.Text = "Velocidade (m/s)";
//
// lblDistanciaMax
//
this.lblDistanciaMax.AutoSize = true;
this.lblDistanciaMax.Location = new System.Drawing.Point(791, 331);
this.lblDistanciaMax.Name = "lblDistanciaMax";
this.lblDistanciaMax.Size = new System.Drawing.Size(91, 13);
this.lblDistanciaMax.TabIndex = 37;
this.lblDistanciaMax.Text = "Distancia Max (m)";
//
// lblTamanhoMin
//
this.lblTamanhoMin.AutoSize = true;
this.lblTamanhoMin.Location = new System.Drawing.Point(697, 331);
this.lblTamanhoMin.Name = "lblTamanhoMin";
this.lblTamanhoMin.Size = new System.Drawing.Size(89, 13);
this.lblTamanhoMin.TabIndex = 39;
this.lblTamanhoMin.Text = "Tamanho Min (m)";
//
// txtTamanhoMin
//
this.txtTamanhoMin.Location = new System.Drawing.Point(700, 347);
this.txtTamanhoMin.Name = "txtTamanhoMin";
this.txtTamanhoMin.Size = new System.Drawing.Size(88, 20);
this.txtTamanhoMin.TabIndex = 38;
this.txtTamanhoMin.Text = "0,30";
this.txtTamanhoMin.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
//
// chbReqAutomatico
//
this.chbReqAutomatico.AutoSize = true;
this.chbReqAutomatico.Location = new System.Drawing.Point(1013, 110);
this.chbReqAutomatico.Name = "chbReqAutomatico";
this.chbReqAutomatico.Size = new System.Drawing.Size(102, 17);
this.chbReqAutomatico.TabIndex = 40;
this.chbReqAutomatico.Text = "Req Automatico";
this.chbReqAutomatico.UseVisualStyleBackColor = true;
//
// frmOperarioSonar
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(1114, 759);
this.Controls.Add(this.chbReqAutomatico);
this.Controls.Add(this.lblTamanhoMin);
this.Controls.Add(this.txtTamanhoMin);
this.Controls.Add(this.lblDistanciaMax);
this.Controls.Add(this.lblVelocidade);
this.Controls.Add(this.txtDistanciaMax);
this.Controls.Add(this.txtVelocidade);
this.Controls.Add(this.lblPerfilSolo);
this.Controls.Add(this.lblSolo);
this.Controls.Add(this.lblAnomalias);
this.Controls.Add(this.chartPerfilSolo);
this.Controls.Add(this.dgvSolo);
this.Controls.Add(this.dgvAnomalias);
this.Controls.Add(this.lblLeituraDebug);
this.Controls.Add(this.lblDebugFrame);
this.Controls.Add(this.picDebug);
this.Controls.Add(this.chbIniciarScript);
this.Controls.Add(this.btnReqSolo);
this.Controls.Add(this.btnReqAnomalias);
this.Controls.Add(this.btnReqStatusDispositivo);
this.Controls.Add(this.lblUltimaLeitura);
this.Controls.Add(this.btnCalibrarGrades);
this.Controls.Add(this.btnPing);
this.Controls.Add(this.btnAtualizarCameras);
this.Controls.Add(this.cmbCameras);
this.Controls.Add(this.lblCameras);
this.Controls.Add(this.btnIniciarProcessamento);
this.Controls.Add(this.btnLeituraHeatmap);
this.Controls.Add(this.lblLeituraHeatmap);
this.Controls.Add(this.lblLeituraRGB);
this.Controls.Add(this.btnLeituraRGB);
this.Controls.Add(this.lblHeatmap);
this.Controls.Add(this.lblRGB);
this.Controls.Add(this.picHeatmap);
this.Controls.Add(this.picRGB);
this.Name = "frmOperarioSonar";
this.Text = "frmOperarioSonar";
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.frmOperarioSonar_FormClosing);
this.Load += new System.EventHandler(this.frmOperarioSonar_Load);
((System.ComponentModel.ISupportInitialize)(this.picRGB)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.picHeatmap)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.picDebug)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.dgvAnomalias)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.dgvSolo)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.chartPerfilSolo)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.PictureBox picRGB;
private System.Windows.Forms.PictureBox picHeatmap;
private System.Windows.Forms.Label lblRGB;
private System.Windows.Forms.Label lblHeatmap;
private System.Windows.Forms.Button btnLeituraRGB;
private System.Windows.Forms.Label lblLeituraRGB;
private System.Windows.Forms.Label lblLeituraHeatmap;
private System.Windows.Forms.Button btnLeituraHeatmap;
private System.Windows.Forms.Button btnIniciarProcessamento;
private System.Windows.Forms.Label lblCameras;
private System.Windows.Forms.ComboBox cmbCameras;
private System.Windows.Forms.Button btnAtualizarCameras;
private System.Windows.Forms.Button btnPing;
private System.Windows.Forms.Button btnCalibrarGrades;
private System.Windows.Forms.Label lblUltimaLeitura;
private System.Windows.Forms.Button btnReqStatusDispositivo;
private System.Windows.Forms.Button btnReqAnomalias;
private System.Windows.Forms.Button btnReqSolo;
private System.Windows.Forms.CheckBox chbIniciarScript;
private System.Windows.Forms.Label lblDebugFrame;
private System.Windows.Forms.PictureBox picDebug;
private System.Windows.Forms.Label lblLeituraDebug;
private System.Windows.Forms.DataGridView dgvAnomalias;
private System.Windows.Forms.DataGridView dgvSolo;
private System.Windows.Forms.DataVisualization.Charting.Chart chartPerfilSolo;
private System.Windows.Forms.Label lblAnomalias;
private System.Windows.Forms.Label lblSolo;
private System.Windows.Forms.Label lblPerfilSolo;
private System.Windows.Forms.TextBox txtVelocidade;
private System.Windows.Forms.TextBox txtDistanciaMax;
private System.Windows.Forms.Label lblVelocidade;
private System.Windows.Forms.Label lblDistanciaMax;
private System.Windows.Forms.Label lblTamanhoMin;
private System.Windows.Forms.TextBox txtTamanhoMin;
private System.Windows.Forms.CheckBox chbReqAutomatico;
}
}

View File

@ -0,0 +1,221 @@
using AgroBase.Models;
using AgroBase.Models.Operadores;
using AgroBase.Services;
using AgroBase.Services.Operadores;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;
namespace AgroBase.Forms
{
public partial class frmOperarioSonar : Form
{
VisualWorkerService visualWorker = new VisualWorkerService();
AsyncTaskTimerModel tmrLeitura;
public frmOperarioSonar()
{
InitializeComponent();
}
private void frmOperarioSonar_Load(object sender, EventArgs e)
{
AtualizarListaCameras();
ConfigurarGridDeteccoes(dgvAnomalias);
ConfigurarGridDeteccoes(dgvSolo);
tmrLeitura = new AsyncTaskTimerModel("tmrLeitura_VisalWorker", tmrLeitura_Tick, 500, this);
tmrLeitura.Start();
}
private async void frmOperarioSonar_FormClosing(object sender, FormClosingEventArgs e)
{
tmrLeitura?.Stop();
await visualWorker.Encerrar();
}
private void AtualizarListaCameras()
{
cmbCameras.Items.Clear();
cmbCameras.Items.AddRange(OAKCameraService.GetListaCameras().Skip(1).Select(x => x.Id).ToArray());
cmbCameras.SelectedIndex = -1;
}
private async Task tmrLeitura_Tick()
{
if (!chbReqAutomatico.Checked) return;
btnLeituraRGB_Click(new object(), new EventArgs());
btnLeituraHeatmap_Click(new object(), new EventArgs());
btnReqAnomalias_Click(new object(), new EventArgs());
btnReqSolo_Click(new object(), new EventArgs());
}
private void btnAtualizarCameras_Click(object sender, EventArgs e)
{
OAKCameraService.AtualizarListaCameras(true);
AtualizarListaCameras();
}
private async void btnIniciarProcessamento_Click(object sender, EventArgs e)
{
bool sucesso = await visualWorker.IniciarProcessamento(cmbCameras.SelectedIndex, chbIniciarScript.Checked);
AtualizarLabelLeitura($", Comando: Iniciar, Sucesso: {sucesso}");
if (!sucesso)
{
MessageBox.Show("Selecione uma camera para iniciar o processamento!");
}
else
{
MessageBox.Show("Processamento iniciado com sucesso!");
}
}
private async Task EnviarComando(VisualWorkerCommandType cmd, int timeout = 1000, dynamic payload = null)
{
bool sucesso = await visualWorker.EnviarComando(cmd, true, timeout, payload);
AtualizarLabelLeitura($", Comando: {cmd.ToString()}, Sucesso: {sucesso}");
}
private void AtualizarLabelLeitura(string texto)
{
lblUltimaLeitura.Text = $"Ultima Leitura em: {visualWorker.DadosLeitura.Leitura._Momento.ToString("dd/MM/yyyy HH:mm:ss.fff")} {texto}";
}
private async void btnPing_Click(object sender, EventArgs e)
{
await EnviarComando(VisualWorkerCommandType.Ping);
}
private async void btnCalibrarGrades_Click(object sender, EventArgs e)
{
await EnviarComando(VisualWorkerCommandType.Calibrar_Grades, 10000);
}
private async void btnLeituraRGB_Click(object sender, EventArgs e)
{
await EnviarComando(VisualWorkerCommandType.Get_RGB_Frame);
VisualWorkerMessageFrameModel frame = visualWorker.DadosLeitura.Leitura.obj.rgb_frame;
picRGB.Image?.Dispose();
picRGB.Image = frame?.image;
lblLeituraRGB.Text = $"Leitura em: {FuncoesGlobais.TimestampToDate(frame?.timestamp ?? 0)}";
}
private async void btnLeituraHeatmap_Click(object sender, EventArgs e)
{
await EnviarComando(VisualWorkerCommandType.Get_Heatmap_Frame);
VisualWorkerMessageFrameModel frame = visualWorker.DadosLeitura.Leitura.obj.heatmap_frame;
picHeatmap.Image?.Dispose();
picHeatmap.Image = frame?.image;
lblLeituraHeatmap.Text = $"Leitura em: {FuncoesGlobais.TimestampToDate(frame?.timestamp ?? 0)}";
}
private async void btnReqStatusDispositivo_Click(object sender, EventArgs e)
{
await EnviarComando(VisualWorkerCommandType.Get_Status_Dispositivo);
}
private async void btnReqAnomalias_Click(object sender, EventArgs e)
{
await EnviarComando(VisualWorkerCommandType.Get_Analise_Anomalia, 1000, new { velocidade_ms = txtVelocidade.Text.Replace(",", "."), distancia_max = txtDistanciaMax.Text.Replace(",", "."), tamanho_min = txtTamanhoMin.Text.Replace(",", ".") });
AtualizarDadosAnalise();
}
private async void btnReqSolo_Click(object sender, EventArgs e)
{
await EnviarComando(VisualWorkerCommandType.Get_Analise_Solo, 1000, new { velocidade_ms = txtVelocidade.Text.Replace(",", "."), distancia_max = txtDistanciaMax.Text.Replace(",", "."), tamanho_min = txtTamanhoMin.Text.Replace(",", ".") });
AtualizarDadosAnalise();
}
private void AtualizarDadosAnalise()
{
lblLeituraDebug.Text = $"Leitura em: {FuncoesGlobais.TimestampToDate(visualWorker.DadosLeitura.Leitura.obj.analise?.timestamp ?? 0)}";
VisualWorkerMessageAnaliseModel analise = visualWorker.DadosLeitura.Leitura.obj.analise;
VisualWorkerMessageFrameModel frame = visualWorker.DadosLeitura.Leitura.obj.heatmap_frame;
picDebug.Image?.Dispose();
picDebug.Image = analise.PlotarAnalise(frame);
PreencherGridAnomalias(analise.anomalias ?? new List<VisualWorkerMessageAnaliseBBoxModel>());
PreencherGridSolo(analise.solo?.bbox_list ?? new List<VisualWorkerMessageAnaliseBBoxModel>());
PlotarGraficoPerfilSolo(analise.solo?.perfil ?? new List<double>());
}
void ConfigurarGridDeteccoes(DataGridView dgv)
{
dgv.Columns.Clear();
dgv.Columns.Add("Tipo", "Tipo");
dgv.Columns.Add("X", "X");
dgv.Columns.Add("Y", "Y");
dgv.Columns.Add("W", "Largura (px)");
dgv.Columns.Add("H", "Altura (px)");
dgv.Columns.Add("Centro", "Centro (%)");
dgv.Columns.Add("Distancia", "Distância (m)");
dgv.Columns.Add("LarguraM", "Largura (m)");
dgv.Columns.Add("AlturaM", "Altura (m)");
dgv.Columns.Add("Variacao", "Variação (mm)");
dgv.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
dgv.ReadOnly = true;
dgv.AllowUserToAddRows = false;
dgv.AllowUserToDeleteRows = false;
}
void PreencherGridAnomalias(List<VisualWorkerMessageAnaliseBBoxModel> bboxes)
{
dgvAnomalias.Rows.Clear();
foreach (var bbox in bboxes)
{
int rowIndex = dgvAnomalias.Rows.Add(
bbox.tipo,
bbox.bbox[0], bbox.bbox[1], bbox.bbox[2], bbox.bbox[3],
$"{bbox.centro_percentual[0]:0.0}%, {bbox.centro_percentual[1]:0.0}%",
bbox.distancia_m?.ToString("0.00") ?? "-",
bbox.largura_m?.ToString("0.00") ?? "-",
bbox.altura_m?.ToString("0.00") ?? "-",
bbox.variacao?.ToString("0.0") ?? "-"
);
if (bbox.distancia_m < 1.0)
{
dgvAnomalias.Rows[rowIndex].DefaultCellStyle.BackColor = Color.LightCoral;
}
}
}
void PreencherGridSolo(List<VisualWorkerMessageAnaliseBBoxModel> bboxes)
{
dgvSolo.Rows.Clear();
foreach (var bbox in bboxes)
{
dgvSolo.Rows.Add(
bbox.tipo,
bbox.bbox[0], bbox.bbox[1], bbox.bbox[2], bbox.bbox[3],
$"{bbox.centro_percentual[0]:0.0}%, {bbox.centro_percentual[1]:0.0}%",
bbox.distancia_m?.ToString("0.00") ?? "-",
bbox.largura_m?.ToString("0.00") ?? "-",
bbox.altura_m?.ToString("0.00") ?? "-",
bbox.variacao?.ToString("0.0") ?? "-"
);
}
}
void PlotarGraficoPerfilSolo(List<double> perfil)
{
chartPerfilSolo.Series.Clear();
var serie = chartPerfilSolo.Series.Add("Perfil");
serie.ChartType = SeriesChartType.Line;
for (int i = 0; i < perfil.Count; i++)
{
serie.Points.AddXY(i + 1, perfil[i]);
}
}
}
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -105,6 +105,7 @@ namespace AgroBase
this.btnForcarAtualizacao = new System.Windows.Forms.Button();
this.clbRotinas = new System.Windows.Forms.CheckedListBox();
this.btnAtualizarRotinas = new System.Windows.Forms.Button();
this.btnVisualWorker = new System.Windows.Forms.Button();
this.gpbAcoes.SuspendLayout();
this.erroStrip.SuspendLayout();
this.pnlLateral.SuspendLayout();
@ -646,6 +647,7 @@ namespace AgroBase
// pnlAtalhos
//
this.pnlAtalhos.AutoScroll = true;
this.pnlAtalhos.Controls.Add(this.btnVisualWorker);
this.pnlAtalhos.Controls.Add(this.btnTreinamentoIA);
this.pnlAtalhos.Controls.Add(this.btnOID);
this.pnlAtalhos.Controls.Add(this.btnEthernet);
@ -961,6 +963,17 @@ namespace AgroBase
this.btnAtualizarRotinas.UseVisualStyleBackColor = true;
this.btnAtualizarRotinas.Click += new System.EventHandler(this.btnAtualizarRotinas_Click);
//
// btnVisualWorker
//
this.btnVisualWorker.Location = new System.Drawing.Point(5, 463);
this.btnVisualWorker.Margin = new System.Windows.Forms.Padding(2);
this.btnVisualWorker.Name = "btnVisualWorker";
this.btnVisualWorker.Size = new System.Drawing.Size(84, 20);
this.btnVisualWorker.TabIndex = 33;
this.btnVisualWorker.Text = "Visual Worker";
this.btnVisualWorker.UseVisualStyleBackColor = true;
this.btnVisualWorker.Click += new System.EventHandler(this.btnVisualWorker_Click);
//
// frmPrincipal
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
@ -1091,6 +1104,7 @@ namespace AgroBase
private System.Windows.Forms.TextBox txtCodErro;
private System.Windows.Forms.Button btnOID;
private System.Windows.Forms.Button btnTreinamentoIA;
private System.Windows.Forms.Button btnVisualWorker;
}
}

View File

@ -499,7 +499,11 @@ namespace AgroBase
frm.Show();
}
private void btnVisualWorker_Click(object sender, EventArgs e)
{
frmOperarioSonar frm = new frmOperarioSonar();
frm.Show();
}
#endregion
@ -513,5 +517,7 @@ namespace AgroBase
MessageBox.Show(desc);
}
}
}
}

View File

@ -15,6 +15,8 @@ namespace AgroBase.Models
public OAKCameraTemperaturaModel Temperature { get; set; }
public string Bootloader_Version { get; set; }
public bool Is_Pipeline_Running { get; set; }
public double fx { get; set; }
public double baseline { get; set; }
public OAKCameraModel Clone()
{
@ -30,7 +32,9 @@ namespace AgroBase.Models
Is_Pipeline_Running = Is_Pipeline_Running,
Memory_Usage = Memory_Usage?.Clone(),
Temperature = Temperature?.Clone(),
Version = Version
Version = Version,
fx = fx,
baseline = baseline,
};
}
}

View File

@ -0,0 +1,342 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using static AgroBase.Models.Enums;
namespace AgroBase.Models.Operadores
{
public class VisualWorkerModel
{
public bool Iniciado { get; set; }
public DateTime IniciadoEm { get; set; }
public bool Pronto { get; set; }
public DateTime ProntoEm { get; set; }
public VisualWorkerMessageModel UltimaMensagem { get; set; }
public VisualWorkerMessageModel Leitura { get; set; } = new VisualWorkerMessageModel();
public void ReiniciarEstado()
{
Iniciado = false;
IniciadoEm = DateTime.MinValue;
Pronto = false;
ProntoEm = DateTime.MinValue;
UltimaMensagem = new VisualWorkerMessageModel();
Leitura = new VisualWorkerMessageModel();
}
}
public class VisualWorkerMessageModel
{
public DateTime _Momento { get; set; } = DateTime.MinValue;
public long momento { get; set; }
public VisualWorkerMessageType tipo_cmd { get; set; }
public VisualWorkerCommandType cmd { get; set; }
public VisualWorkerMessageResponseModel obj { get; set; } = new VisualWorkerMessageResponseModel();
public dynamic payload { get; set; }
}
public class VisualWorkerMessageResponseModel
{
public OAKCameraModel device_status { get; set; }
public VisualWorkerMessageFrameModel rgb_frame { get; set; }
public VisualWorkerMessageFrameModel heatmap_frame { get; set; }
public VisualWorkerMessageFrameModel debug_frame { get; set; }
public VisualWorkerMessageSetupModel ping { get; set; }
public VisualWorkerMessageSetupModel calibragem { get; set; }
public VisualWorkerMessageObstaculosModel obstaculos { get; set; }
public VisualWorkerMessageCorredorModel corredor { get; set; }
public VisualWorkerMessageMapaProfundidadeModel mapa_profundidade { get; set; }
public VisualWorkerMessageObstaculos3DModel obstaculos_3d { get; set; }
public VisualWorkerMessageAnaliseModel analise { get; set; } = new VisualWorkerMessageAnaliseModel();
}
public class VisualWorkerMessageFrameModel
{
public long timestamp { get; set; }
public string frame { get; set; }
public Bitmap image { get; set; }
public VisualWorkerMessageFrameModel Clone()
{
return new VisualWorkerMessageFrameModel()
{
timestamp = timestamp,
frame = frame,
image = FuncoesGlobais.Base64ToBitmap(frame),
};
}
}
public class VisualWorkerMessageSetupModel
{
public long timestamp { get; set; }
public bool status { get; set; }
public VisualWorkerMessageSetupModel Clone()
{
return new VisualWorkerMessageSetupModel()
{
timestamp = timestamp,
status = status,
};
}
}
public class VisualWorkerMessageObstaculosModel
{
public long timestamp { get; set; }
public bool precisa_micro { get; set; }
public bool alerta { get; set; }
public int quantidade { get; set; }
public bool frontal_bloqueado { get; set; }
public Dictionary<Direcao, bool> laterais { get; set; }
public Direcao zona_livre { get; set; }
public double distancia_minima { get; set; }
public List<VisualWorkerMessageGridModel> detalhes { get; set; }
public VisualWorkerMessageObstaculosModel Clone()
{
return new VisualWorkerMessageObstaculosModel()
{
timestamp = timestamp,
precisa_micro = precisa_micro,
alerta = alerta,
quantidade = quantidade,
frontal_bloqueado = frontal_bloqueado,
laterais = new Dictionary<Direcao, bool>(laterais),
zona_livre = zona_livre,
distancia_minima = distancia_minima,
detalhes = new List<VisualWorkerMessageGridModel>(detalhes)
};
}
}
public class VisualWorkerMessageGridModel
{
public int linha { get; set; }
public int coluna { get; set; }
public double distancia { get; set; }
public VisualWorkerDetectionType tipo { get; set; }
public VisualWorkerRegionType regiao { get; set; }
}
public class VisualWorkerMessageCorredorModel
{
public long timestamp { get; set; }
public double largura_corredor_mm { get; set; }
public double posicao_central_fracao { get; set; }
public double[][] pontos_debug { get; set; }
public VisualWorkerMessageCorredorModel Clone()
{
return new VisualWorkerMessageCorredorModel()
{
timestamp = timestamp,
largura_corredor_mm = largura_corredor_mm,
posicao_central_fracao = posicao_central_fracao,
pontos_debug = pontos_debug
};
}
}
public class VisualWorkerMessageMapaProfundidadeModel
{
public long timestamp { get; set; }
public string resolucao { get; set; }
public string unidade { get; set; }
public double?[][] grid { get; set; }
public VisualWorkerMessageMapaProfundidadeModel Clone()
{
return new VisualWorkerMessageMapaProfundidadeModel()
{
timestamp = timestamp,
resolucao = resolucao,
unidade = unidade,
grid = grid
};
}
}
public class VisualWorkerMessageObstaculos3DModel
{
public long timestamp { get; set; }
public List<VisualWorkerMessageObstaculos3DDetectadosModel> obstaculos_detectados { get; set; }
public VisualWorkerMessageObstaculos3DModel Clone()
{
return new VisualWorkerMessageObstaculos3DModel()
{
timestamp = timestamp,
obstaculos_detectados = new List<VisualWorkerMessageObstaculos3DDetectadosModel>(obstaculos_detectados)
};
}
}
public class VisualWorkerMessageObstaculos3DDetectadosModel
{
public double posicao { get; set; }
public double distancia { get; set; }
public int largura_px { get; set; }
}
public class VisualWorkerMessageAnaliseModel
{
public long timestamp { get; set; }
public List<VisualWorkerMessageAnaliseBBoxModel> anomalias { get; set; }
public VisualWorkerMessageAnaliseSoloModel solo { get; set; }
public VisualWorkerMessageAnaliseModel Clone()
{
return new VisualWorkerMessageAnaliseModel()
{
timestamp = timestamp,
anomalias = new List<VisualWorkerMessageAnaliseBBoxModel>(anomalias),
solo = solo.Clone(),
};
}
public Bitmap PlotarAnalise(VisualWorkerMessageFrameModel frame)
{
if (frame?.image == null) return null;
Bitmap img = new Bitmap(frame.image);
using (Graphics g = Graphics.FromImage(img))
{
Pen penObstaculo = new Pen(Color.Red, 2);
Pen penErosao = new Pen(Color.Blue, 2);
Pen penLombada = new Pen(Color.Orange, 2);
Pen penFaixaSolo = new Pen(Color.Black, 2);
Pen penGrafico = new Pen(Color.DarkGreen, 2);
Font font = new Font("Arial", 8);
Brush brush = Brushes.Black;
int imgWidth = img.Width;
int imgHeight = img.Height;
// 🔸 Desenhar Bounding Boxes (Anomalias + Solo)
List<VisualWorkerMessageAnaliseBBoxModel> bboxes = new List<VisualWorkerMessageAnaliseBBoxModel>();
if (anomalias != null) bboxes.AddRange(anomalias);
if (solo?.bbox_list != null) bboxes.AddRange(solo.bbox_list);
foreach (var bbox in bboxes)
{
var pen = penObstaculo;
if (bbox.tipo == "erosao") pen = penErosao;
if (bbox.tipo == "lombada") pen = penLombada;
var rect = new Rectangle(bbox.bbox[0], bbox.bbox[1], bbox.bbox[2], bbox.bbox[3]);
g.DrawRectangle(pen, rect);
string label = $"{bbox.tipo} {bbox.distancia_m}m Δ{bbox.variacao}mm";
g.DrawString(label, font, brush, rect.X, rect.Y - 12);
}
// 🔸 Desenhar linha da faixa do solo
if (solo != null)
{
int yInicio = solo.y_inicio;
g.DrawLine(penFaixaSolo, 0, yInicio, imgWidth, yInicio);
}
// 🔸 Desenhar gráfico do perfil de solo
if (solo?.perfil != null && solo.perfil.Count > 1)
{
var perfil = solo.perfil;
int setores = solo.n_setores;
int yInicio = solo.y_inicio;
int alturaRegiao = solo.altura_regiao;
double maxZ = perfil.Max();
double minZ = perfil.Min();
if (maxZ == minZ) maxZ += 1; // evita divisão por zero
for (int i = 0; i < perfil.Count - 1; i++)
{
float x1 = (float)(i * imgWidth / setores + imgWidth / (2.0 * setores));
float x2 = (float)((i + 1) * imgWidth / setores + imgWidth / (2.0 * setores));
float y1 = (float)(yInicio + alturaRegiao - ((perfil[i] - minZ) / (maxZ - minZ)) * alturaRegiao);
float y2 = (float)(yInicio + alturaRegiao - ((perfil[i + 1] - minZ) / (maxZ - minZ)) * alturaRegiao);
g.DrawLine(penGrafico, x1, y1, x2, y2);
g.FillEllipse(Brushes.DarkGreen, x1 - 3, y1 - 3, 6, 6);
}
// Desenha o último ponto
float xLast = (float)((perfil.Count - 1) * imgWidth / setores + imgWidth / (2.0 * setores));
float yLast = (float)(yInicio + alturaRegiao - ((perfil.Last() - minZ) / (maxZ - minZ)) * alturaRegiao);
g.FillEllipse(Brushes.DarkGreen, xLast - 3, yLast - 3, 6, 6);
}
}
return img;
}
}
public class VisualWorkerMessageAnaliseSoloModel
{
public List<double> perfil { get; set; }
public List<VisualWorkerMessageAnaliseBBoxModel> bbox_list { get; set; }
public int y_inicio { get; set; }
public int altura_regiao { get; set; }
public int n_setores { get; set; }
public VisualWorkerMessageAnaliseSoloModel Clone()
{
return new VisualWorkerMessageAnaliseSoloModel()
{
perfil = new List<double>(perfil),
bbox_list = new List<VisualWorkerMessageAnaliseBBoxModel>(bbox_list),
y_inicio = y_inicio,
altura_regiao = altura_regiao,
n_setores = n_setores
};
}
}
public class VisualWorkerMessageAnaliseBBoxModel
{
public string tipo { get; set; }
public List<int> bbox { get; set; } // [x, y, w, h]
public List<double> centro_percentual { get; set; } // [x%, y%]
public double? distancia_m { get; set; }
public double? largura_m { get; set; }
public double? altura_m { get; set; }
public List<double> profundidades { get; set; } // [z1, z2] ou null
public double? variacao { get; set; } // delta Z ou null
}
public enum VisualWorkerMessageType
{
TX = 1,
RX = 2
}
public enum VisualWorkerCommandType
{
Ping = 1,
Get_Status_Dispositivo = 2,
Get_RGB_Frame = 3,
Get_Heatmap_Frame = 4,
Calibrar_Grades = 5,
Get_Analise_Anomalia = 6,
Get_Analise_Solo = 7,
}
public enum VisualWorkerDetectionType
{
Seguro = 1,
Obstaculo = 2,
Depressao = 3,
}
public enum VisualWorkerRegionType
{
Solo = 1,
Aereo = 2,
}
public enum VisualWorkerAnaliseType
{
Anomalia = 1,
Solo = 2,
}
}

View File

@ -1137,6 +1137,18 @@ namespace AgroBase.Models
return "0x" + _bt.ToString("X").PadLeft(2, '0');
}
public static Bitmap Base64ToBitmap(string base64String)
{
if (string.IsNullOrEmpty(base64String))
return null;
byte[] imageBytes = Convert.FromBase64String(base64String);
using (var ms = new MemoryStream(imageBytes))
{
return new Bitmap(ms);
}
}
}
public static class FuncoesMatematicas

View File

@ -1,8 +1,5 @@
using AgroBase.Forms;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace AgroBase

View File

@ -0,0 +1,170 @@
using AgroBase.Models;
using AgroBase.Models.Operadores;
using Newtonsoft.Json;
using System;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using static MqttService;
namespace AgroBase.Services.Operadores
{
public class VisualWorkerService
{
private string mqtt_topico = "agrobot/operador/sonar";
private MqttTopicosModel topico;
private Process pythonProcess;
private int cameraIndex = -1;
public VisualWorkerModel DadosLeitura = new VisualWorkerModel();
private bool DebugMode = true;
private void MostrarLog(string message, int limite = 500)
{
if (DebugMode)
{
Console.WriteLine($"[VisualWorker] {message.Substring(0, Math.Min(limite, message.Length))}{(message.Length > limite ? "..." : "")}");
}
}
public async Task<bool> IniciarProcessamento(int _cameraIndex, bool iniciarScript = true)
{
if (_cameraIndex == -1)
{
MostrarLog("Camera nao selecionada");
DadosLeitura.ReiniciarEstado();
return false;
}
cameraIndex = _cameraIndex;
topico = await Variaveis.MqttService.AdicionarNovoTopico(mqtt_topico, true, 2, MqttCallback);
if (iniciarScript)
{
pythonProcess = PythonService.RunScript(Path.Combine("visual_worker", "main.py"), new string[] { mqtt_topico, cameraIndex.ToString() });
}
DadosLeitura.IniciadoEm = DateTime.Now;
DadosLeitura.Iniciado = true;
bool pronto() => DadosLeitura.Pronto;
await FuncoesGlobais.AguardarCondicaoAsync(pronto);
if (pronto())
{
MostrarLog("Processamento iniciado com sucesso");
return true;
}
MostrarLog("Timeout ao iniciar script");
DadosLeitura.ReiniciarEstado();
return false;
}
public async Task<bool> Encerrar()
{
pythonProcess?.Kill();
pythonProcess = null;
await Variaveis.MqttService.UnsubscribeAsync(topico);
topico = null;
return true;
}
public async Task<bool> EnviarComando(VisualWorkerCommandType cmd, bool waitCallback = false, int timeout = 1000, dynamic payload = null)
{
VisualWorkerMessageModel msg = new VisualWorkerMessageModel()
{
tipo_cmd = VisualWorkerMessageType.TX,
cmd = cmd,
payload = payload
};
DateTime EnviadoEm = DateTime.Now;
await Variaveis.MqttService.PublishAsync(
topico,
JsonConvert.SerializeObject(msg, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }),
true
);
if (waitCallback)
{
bool respondido() => DadosLeitura.Leitura._Momento > EnviadoEm;
await FuncoesGlobais.AguardarCondicaoAsync(respondido, timeout);
return respondido();
}
return true;
}
private async Task<bool> MqttCallback(MqttTopicosMensagensModel Mensagem)
{
if (!DadosLeitura.Pronto && Mensagem.Momento > DadosLeitura.IniciadoEm && Mensagem.Mensagem == "OK")
{
DadosLeitura.ProntoEm = DateTime.Now;
DadosLeitura.Pronto = true;
}
else
{
var msg = JsonConvert.DeserializeObject<VisualWorkerMessageModel>(Mensagem.Mensagem);
if (msg == null || msg.tipo_cmd == VisualWorkerMessageType.TX)
{
return false;
}
DadosLeitura.UltimaMensagem = msg;
AtualizarDadosLeitura(msg);
}
MostrarLog(Mensagem.Mensagem);
return true;
}
private void AtualizarDadosLeitura(VisualWorkerMessageModel msg)
{
switch (msg.cmd)
{
case VisualWorkerCommandType.Ping:
{
DadosLeitura.Leitura.obj.ping = msg.obj.ping.Clone();
break;
}
case VisualWorkerCommandType.Get_Status_Dispositivo:
{
DadosLeitura.Leitura.obj.device_status = msg.obj.device_status?.Clone();
break;
}
case VisualWorkerCommandType.Get_RGB_Frame:
{
DadosLeitura.Leitura.obj.rgb_frame = msg.obj.rgb_frame.Clone();
break;
}
case VisualWorkerCommandType.Get_Heatmap_Frame:
{
DadosLeitura.Leitura.obj.heatmap_frame = msg.obj.heatmap_frame.Clone();
break;
}
case VisualWorkerCommandType.Calibrar_Grades:
{
DadosLeitura.Leitura.obj.calibragem = msg.obj.calibragem.Clone();
break;
}
case VisualWorkerCommandType.Get_Analise_Anomalia:
{
DadosLeitura.Leitura.obj.analise.timestamp = msg.obj.analise.timestamp;
DadosLeitura.Leitura.obj.analise.anomalias = new System.Collections.Generic.List<VisualWorkerMessageAnaliseBBoxModel>(msg.obj.analise.anomalias);
break;
}
case VisualWorkerCommandType.Get_Analise_Solo:
{
DadosLeitura.Leitura.obj.analise.timestamp = msg.obj.analise.timestamp;
DadosLeitura.Leitura.obj.analise.solo = msg.obj.analise.solo.Clone();
break;
}
}
DadosLeitura.Leitura._Momento = DateTime.Now;
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,248 @@
# camera_manager.py
import depthai as dai
import cv2
import numpy as np
import time
# Parâmetros da câmera
RGB_WIDTH, RGB_HEIGHT = 640, 480
#DEPTH_WIDTH, DEPTH_HEIGHT = 320, 240
DEPTH_WIDTH, DEPTH_HEIGHT = 640, 400
FX = 440.0 # distância focal em pixels (aproximado)
FY = 440.0 # distância focal em pixels (aproximado)
BASELINE = 0.075 # distância entre câmeras estéreo (em metros)
FOV_H = 1
FOV_V = 1
DISTANCIA_MAXIMA = 3000.0
# Variáveis globais
device = None
device_info = None
camera_pronta = False
rgb_queue = None
depth_queue = None
ultimo_frame_depth = None
timestamp_ultimo_depth_frame = None
ultimo_frame_rgb = None
timestamp_ultimo_rgb_frame = None
def iniciar_camera(index=0, distancia_max=3000.0):
global device, device_info, rgb_queue, depth_queue, camera_pronta
camera_pronta = False
print("Iniciando câmera OAK-D Lite...")
pipeline = dai.Pipeline()
# RGB
cam_rgb = pipeline.create(dai.node.ColorCamera)
cam_rgb.setPreviewSize(RGB_WIDTH, RGB_HEIGHT)
cam_rgb.setInterleaved(False)
cam_rgb.setBoardSocket(dai.CameraBoardSocket.RGB)
xout_rgb = pipeline.create(dai.node.XLinkOut)
xout_rgb.setStreamName("rgb")
cam_rgb.preview.link(xout_rgb.input)
# 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_480_P)
mono_right.setResolution(dai.MonoCameraProperties.SensorResolution.THE_480_P)
mono_left.setBoardSocket(dai.CameraBoardSocket.LEFT)
mono_right.setBoardSocket(dai.CameraBoardSocket.RIGHT)
stereo.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.ROBOTICS)
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)
# Dispositivo
available_devices = dai.Device.getAllAvailableDevices()
if index >= len(available_devices):
raise ValueError(f"Câmera de índice {index} não encontrada.")
device_info = available_devices[index]
device = dai.Device(pipeline, device_info)
rgb_queue = device.getOutputQueue(name="rgb", maxSize=1, blocking=False)
depth_queue = device.getOutputQueue(name="depth", maxSize=1, blocking=False)
camera_pronta = True
print(f"Câmera iniciada: {device_info.name} (ID: {device_info.getMxId()})")
set_camera_calib(distancia_max)
def set_camera_calib(distancia_max):
if not verificar_camera_ativa():
return
global FX, FY, BASELINE, FOV_H, FOV_V, DISTANCIA_MAXIMA
calib = device.readCalibration()
DISTANCIA_MAXIMA = distancia_max
# Obtem matriz intrínseca da câmera LEFT (com resolução padrão 640x400)
intrinsics = calib.getCameraIntrinsics(dai.CameraBoardSocket.LEFT, DEPTH_WIDTH, DEPTH_HEIGHT)
FX = intrinsics[0][0] # fx
FY = intrinsics[1][1] # fy
BASELINE = calib.getBaselineDistance() / 100.0 # de cm → m
FOV_H = 2 * np.arctan(DEPTH_WIDTH / (2 * FX))
FOV_V = 2 * np.arctan(DEPTH_HEIGHT / (2 * FY))
print(f"[CALIB] FX: {FX:.2f} px, FY: {FY:.2f} px, FOV_H: {FOV_H:.2f}, FOV_V: {FOV_V:.2f}, BASELINE: {BASELINE:.4f} m")
def get_camera_calib():
return {
"rgb_width": RGB_WIDTH,
"rgb_height": RGB_HEIGHT,
"depth_width": DEPTH_WIDTH,
"depth_height": DEPTH_HEIGHT,
"fx": FX,
"fy": FY,
"baseline": BASELINE,
"fov_h": FOV_H,
"fov_v": FOV_V,
"distancia_maxima": DISTANCIA_MAXIMA
}
def verificar_camera_ativa():
global device, depth_queue, rgb_queue, camera_pronta
# Se a câmera ainda não foi inicializada
if device is None or not camera_pronta:
print("[INFO] Câmera não ativa. Tentando reiniciar...")
try:
iniciar_camera() # essa função já configura tudo
return True
except Exception as e:
print(f"[ERRO] Falha ao reiniciar a câmera: {e}")
return False
return camera_pronta
# Se o device existe mas o pipeline caiu (fila morta)
try:
test = depth_queue.tryGet()
except Exception as e:
print(f"[ERRO] Falha ao acessar depth_queue: {e}. Tentando reiniciar câmera...")
try:
device.close()
except:
pass
device = None
camera_pronta = False
return verificar_camera_ativa() # tenta de novo
return True
def get_rgb_frame():
if not verificar_camera_ativa():
return None, None
global ultimo_frame_rgb, timestamp_ultimo_rgb_frame
if rgb_queue is None:
return None, None
frame = rgb_queue.tryGet()
if frame is not None:
ultimo_frame_rgb = frame.getCvFrame()
timestamp_ultimo_rgb_frame = time.time()
return ultimo_frame_rgb, timestamp_ultimo_rgb_frame
def get_depth_frame():
if not verificar_camera_ativa():
return None, None
global ultimo_frame_depth, timestamp_ultimo_depth_frame
if depth_queue is None:
return None, None
frame = depth_queue.tryGet()
if frame is not None:
ultimo_frame_depth = frame.getFrame()
timestamp_ultimo_depth_frame = time.time()
return ultimo_frame_depth, timestamp_ultimo_depth_frame
def get_heatmap_frame():
frame, timestamp = get_depth_frame()
if frame is not None:
from visual_worker.utils import gerar_heatmap
return gerar_heatmap(frame, DISTANCIA_MAXIMA), timestamp
return None, None
def get_status_dispositivo():
if not verificar_camera_ativa():
return None
from depthai import UsbSpeed
try:
memory = device.getDdrMemoryUsage()
memory_info = {
"used": memory.used,
"remaining": memory.remaining,
"total": memory.total
}
except:
memory_info = None
try:
temp = device.getChipTemperature()
temp_info = {
"css": temp.css,
"mss": temp.mss,
"upa": temp.upa,
"dss": temp.dss
}
except:
temp_info = None
try:
info = device.getDeviceInfo()
protocol = str(info.protocol)
except:
protocol = None
try:
bootloader = str(device.getBootloaderVersion())
except:
bootloader = None
try:
usb_speed = str(device.getUsbSpeed().name)
except:
usb_speed = None
try:
pipeline_running = device.isPipelineRunning()
except:
pipeline_running = None
try:
cameras = [sensor.name for sensor in device.getConnectedCameras()]
except:
cameras = None
return {
"id": device_info.getMxId(),
"name": device_info.name,
"state": device_info.state.name,
"usb_speed": usb_speed,
"available_camera_sensors": cameras,
"version": protocol,
"bootloader_version": bootloader,
"is_pipeline_running": pipeline_running,
"memory_usage": memory_info,
"temperature": temp_info,
"fx": FX,
"baseline": BASELINE
}

View File

@ -0,0 +1,40 @@
from enum import IntEnum
class Comando(IntEnum):
PING = 1
GET_STATUS_DISPOSITIVO = 2
GET_RGB_FRAME = 3
GET_HEATMAP_FRAME = 4
CALIBRAR_GRADES = 5
GET_ANALISE_ANOMALIA = 6
GET_ANALISE_SOLO = 7
class TipoComando(IntEnum):
TX = 1
RX = 2
class TipoDeteccao(IntEnum):
SEGURO = 1
OBSTACULO = 2
DEPRESSAO = 3
class TipoRegiaoRadar(IntEnum):
SOLO = 1
AEREO = 2
class Direcao(IntEnum):
PARADO = 0
FRENTE = 1
TRAS = 2
ESQUERDA = 3
DIREITA = 4
CIMA = 5
BAIXO = 6
ESQUERDABAIXO = 7
ESQUERDACIMA = 8
DIREITABAIXO = 9
DIREITACIMA = 10
class TipoAnalise(IntEnum):
ANOMALIA = 1
SOLO = 2

View File

@ -0,0 +1,144 @@
import sys
import os
import time
import json
from enums import Comando, TipoAnalise, TipoComando
from mqtt_handler import enviar_mensagem_mqtt
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
# ─────────────────────────────
# 🔹 Parâmetros de Execução
# ─────────────────────────────
mqtt_topic = sys.argv[1] if len(sys.argv) > 1 else "agrobot/operador/sonar"
camera_index = int(sys.argv[2]) if len(sys.argv) > 2 else 0
# ─────────────────────────────
# 🔹 Callback de Comando MQTT
# ─────────────────────────────
def ao_receber_comando(topico, payload):
try:
comando = json.loads(payload.decode())
tipo_cmd = int(comando.get("tipo_cmd"))
codigo = int(comando.get("cmd"))
payload = comando.get("payload", {})
# Ignora mensagens enviadas por si proprio
if (tipo_cmd == TipoComando.RX):
return
resposta = {}
if codigo == Comando.PING:
resposta = {
"ping": {
"timestamp": time.time(),
"status": True
}
}
elif codigo == Comando.GET_STATUS_DISPOSITIVO:
from camera_manager import get_status_dispositivo
resposta = {
"device_status": get_status_dispositivo()
}
elif codigo == Comando.GET_RGB_FRAME:
from camera_manager import get_rgb_frame
frame, timestamp = get_rgb_frame()
base64_img = None
if frame is not None:
from utils import encode_image_base64
base64_img = encode_image_base64(frame)
resposta = {
"rgb_frame": {
"timestamp": timestamp,
"frame": base64_img
}
}
elif codigo == Comando.GET_HEATMAP_FRAME:
from camera_manager import get_heatmap_frame
frame, timestamp = get_heatmap_frame()
base64_img = None
if frame is not None:
from utils import encode_image_base64
base64_img = encode_image_base64(frame)
resposta = {
"heatmap_frame": {
"timestamp": timestamp,
"frame": base64_img
}
}
elif codigo == Comando.CALIBRAR_GRADES:
from camera_manager import get_depth_frame
from processamento.percepcao3d import calibrar
sucesso = calibrar(get_depth_frame, n_frames=50)
resposta = {
"calibragem": {
"timestamp": time.time(),
"status": sucesso
}
}
elif codigo == Comando.GET_ANALISE_ANOMALIA:
from camera_manager import get_depth_frame, get_camera_calib
from processamento.percepcao3d import analisar
calib = get_camera_calib()
velocidade_ms = float(payload.get("velocidade_ms", 0.0))
distancia_max = float(payload.get("distancia_max", 2.0))
tamanho_min = float(payload.get("tamanho_min", 0.25))
resultado = analisar(TipoAnalise.ANOMALIA, get_depth_frame, calib, velocidade_ms, distancia_max, tamanho_min)
resposta = {
"timestamp": time.time(),
"analise": resultado
}
elif codigo == Comando.GET_ANALISE_SOLO:
from camera_manager import get_depth_frame, get_camera_calib
from processamento.percepcao3d import analisar
calib = get_camera_calib()
velocidade_ms = float(payload.get("velocidade_ms", 0.0))
distancia_max = float(payload.get("distancia_max", 2.0))
tamanho_min = float(payload.get("tamanho_min", 0.25))
resultado = analisar(TipoAnalise.SOLO, get_depth_frame, calib, velocidade_ms, distancia_max, tamanho_min)
resposta = {
"timestamp": time.time(),
"analise": resultado
}
enviar_mensagem_mqtt(codigo, resposta)
except Exception as e:
print("❌ Erro ao processar comando MQTT:", e)
# ─────────────────────────────
# 🔹 Execução Principal
# ─────────────────────────────
if __name__ == "__main__":
print("🚀 Iniciando Operário Visual com OAK-D Lite...")
# 1. Inicia a câmera
from camera_manager import iniciar_camera
iniciar_camera(camera_index)
from mqtt_handler import iniciar_mqtt, enviar_mensagem_script_carregado
# 2. Inicia MQTT e registra callback
iniciar_mqtt(mqtt_topic, ao_receber_comando)
print(f"✅ Escutando comandos no tópico: {mqtt_topic}")
enviar_mensagem_script_carregado()
# 3. Loop principal
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
print("Encerrando...")

View File

@ -0,0 +1,51 @@
import paho.mqtt.client as mqtt
import uuid
import json
import time
from enums import TipoComando
mqtt_client = None
mqtt_topic = None
def iniciar_mqtt(topico, funcao_callback, broker="localhost", porta=1883):
"""
Inicia conexão MQTT e escuta o tópico.
:param tópico: string com o nome do tópico
:param funcao_callback: função que será chamada ao receber mensagem
"""
global mqtt_client, mqtt_topic
mqtt_client = mqtt.Client(f"visual_worker_{uuid.uuid4()}")
mqtt_topic = topico
def on_connect(client, userdata, flags, rc):
if rc == 0:
print(f"[MQTT] Conectado ao broker em {broker}:{porta}")
client.subscribe(mqtt_topic)
else:
print("[MQTT] Falha ao conectar, código de retorno:", rc)
def on_message(client, userdata, msg):
try:
funcao_callback(msg.topic, msg.payload)
except Exception as e:
print("[MQTT] Erro no callback:", e)
mqtt_client.on_connect = on_connect
mqtt_client.on_message = on_message
mqtt_client.connect(broker, porta)
mqtt_client.loop_start()
def enviar_mensagem_script_carregado():
mqtt_client.publish(mqtt_topic, "OK")
def enviar_mensagem_mqtt(comando, objeto):
mensagem = {
"momento": time.time(),
"tipo_cmd": TipoComando.RX,
"cmd": comando,
"obj": objeto
}
mqtt_client.publish(mqtt_topic, json.dumps(mensagem))

View File

@ -0,0 +1,157 @@
import numpy as np
import cv2
def detectar_anomalias(depth_frame, depth_referencia, limiar, distancia_max, tamanho_min, calib):
# Gera mapa de anomalias
mapa = gerar_mapa_anomalias(depth_frame, depth_referencia, limiar)
mapa_anomalia = aplicar_filtros_mapa_anomalias(mapa)
# Extrai bounding boxes
lista_bboxes = extrair_bounding_boxes(mapa_anomalia, depth_frame, depth_referencia, calib)
# Filtra as detecções relevantes
from visual_worker.utils import filtrar_deteccoes
lista_bboxes_filtrada = filtrar_deteccoes(lista_bboxes, distancia_max, tamanho_min)
return lista_bboxes_filtrada
# Geração do Mapa de Anomalias
def gerar_mapa_anomalias(frame, referencia, threshold=200):
diferenca = frame - referencia
mask_proximo = diferenca < -threshold
mask_distante = diferenca > threshold
mapa = np.ones(frame.shape + (3,), dtype=np.uint8) * 255
mapa[mask_proximo] = (0, 0, 255) # Obstáculo (vermelho)
mapa[mask_distante] = (255, 0, 0) # Erosão (azul)
return mapa
# Aplicação dos filtros (morfologia + área)
def aplicar_filtros_mapa_anomalias(mapa):
kernel = np.ones((5, 5), np.uint8)
# Separa máscaras binárias
mask_proximo = (mapa == [0, 0, 255]).all(axis=2).astype(np.uint8) # Vermelho
mask_distante = (mapa == [255, 0, 0]).all(axis=2).astype(np.uint8) # Azul
# Morfologia (remove ruído e preenche buracos)
mask_proximo = cv2.morphologyEx(mask_proximo, cv2.MORPH_OPEN, kernel)
mask_proximo = cv2.morphologyEx(mask_proximo, cv2.MORPH_CLOSE, kernel)
mask_distante = cv2.morphologyEx(mask_distante, cv2.MORPH_OPEN, kernel)
mask_distante = cv2.morphologyEx(mask_distante, cv2.MORPH_CLOSE, kernel)
# Filtro por área mínima
mask_proximo = filtrar_por_area(mask_proximo, area_minima=100)
mask_distante = filtrar_por_area(mask_distante, area_minima=100)
# Reconstrói o mapa colorido
mapa_filtrado = np.ones(mapa.shape, dtype=np.uint8) * 255 # Fundo branco
mapa_filtrado[mask_proximo == 1] = (0, 0, 255) # Vermelho
mapa_filtrado[mask_distante == 1] = (255, 0, 0) # Azul
return mapa_filtrado
# Função para filtrar blobs menores que área mínima
def filtrar_por_area(mask, area_minima=100):
contornos, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
mask_filtrada = np.zeros_like(mask)
for cnt in contornos:
area = cv2.contourArea(cnt)
if area >= area_minima:
cv2.drawContours(mask_filtrada, [cnt], -1, 1, -1)
return mask_filtrada
def extrair_bounding_boxes(mapa, depth_frame, depth_referencia, calib):
altura, largura = mapa.shape[:2]
resultados = []
# Processa obstáculos (vermelho)
resultados += processar_cor(mapa, depth_frame, depth_referencia, (0, 0, 255), "obstaculo", largura, altura, calib)
# Processa erosões (azul)
resultados += processar_cor(mapa, depth_frame, depth_referencia, (255, 0, 0), "erosao", largura, altura, calib)
return resultados
def processar_cor(mapa, depth_frame, depth_referencia, cor_bgr, tipo, largura, altura, calib):
mask = (mapa == cor_bgr).all(axis=2).astype(np.uint8)
contornos, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
lista = []
for cnt in contornos:
area = cv2.contourArea(cnt)
if area < 100:
continue
x, y, w, h = cv2.boundingRect(cnt)
depth_crop = depth_frame[y:y+h, x:x+w]
from visual_worker.utils import filtrar_depth_valid
depth_crop_valid = filtrar_depth_valid(depth_crop, calib["distancia_maxima"])
if len(depth_crop_valid) == 0:
continue
z_atual = np.median(depth_crop_valid) / 1000.0 # 🔥 Em metros (mantém)
depth_ref_crop = depth_referencia[y:y+h, x:x+w]
depth_ref_valid = filtrar_depth_valid(depth_ref_crop, calib["distancia_maxima"])
z_ref = (np.median(depth_ref_valid) / 1000.0) if len(depth_ref_valid) > 0 else z_atual
# 🔥 Variacao em mm
variacao_mm = abs(z_atual - z_ref) * 1000.0 # Convertendo metros → mm
largura_real = z_atual * 2 * np.tan(calib["fov_h"] / 2) * (w / largura)
altura_real = z_atual * 2 * np.tan(calib["fov_v"] / 2) * (h / altura)
centro_x = (x + w / 2) / largura * 100
centro_y = (y + h / 2) / altura * 100
lista.append({
"tipo": tipo,
"bbox": (int(x), int(y), int(w), int(h)),
"centro_percentual": (float(centro_x), float(centro_y)),
"distancia_m": round(z_atual, 3),
"largura_m": round(largura_real, 3),
"altura_m": round(altura_real, 3),
"profundidades": (round(z_ref, 3), round(z_atual, 3)),
"variacao": round(variacao_mm, 1)
})
return lista
def desenhar_bounding_boxes(mapa, lista):
mapa_bboxes = mapa.copy()
for obj in lista:
x, y, w, h = obj["bbox"]
if obj["tipo"] == "obstaculo":
cor = (0, 0, 255)
else:
cor = (255, 0, 0)
cv2.rectangle(mapa_bboxes, (x, y), (x + w, y + h), cor, 2)
texto = f'{obj["tipo"]} {obj["distancia_m"]}m'
cv2.putText(mapa_bboxes, texto, (x, y - 5),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, cor, 2)
return mapa_bboxes

View File

@ -0,0 +1,93 @@
import numpy as np
def analisar_solo(depth_frame, percentual_altura, n_setores, limiar_variacao_mm, distancia_max, tamanho_min, calib):
"""
Analisa o perfil do solo na parte inferior da imagem.
Detecta variações como lombadas ou buracos.
"""
altura, largura = depth_frame.shape
# Define a região do solo (parte inferior)
linhas_solo = int((percentual_altura / 100) * altura)
y_inicio = altura - linhas_solo
depth_solo = depth_frame[y_inicio:altura, :]
# Divide em setores laterais
largura_setor = largura // n_setores
profundidades = []
for i in range(n_setores):
x_inicio = i * largura_setor
x_fim = (i + 1) * largura_setor if (i < n_setores - 1) else largura
setor = depth_solo[:, x_inicio:x_fim]
from visual_worker.utils import filtrar_depth_valid
setor_valido = filtrar_depth_valid(setor, calib["distancia_maxima"])
if len(setor_valido) == 0:
z = 0
else:
z = np.median(setor_valido) # Usa mediana para robustez
profundidades.append(z)
profundidades = np.array(profundidades)
# Detecta variações abruptas
variacoes = np.abs(np.diff(profundidades))
indices_var = np.where(variacoes > limiar_variacao_mm)[0]
# Gera bounding boxes das variações
lista_bboxes = []
for idx in indices_var:
x = idx * largura_setor
y = y_inicio
w = largura_setor * 2
h = linhas_solo
tipo = "lombada" if profundidades[idx] > profundidades[idx + 1] else "erosao"
# Centro percentual na imagem
centro_x = (x + w / 2) / largura * 100
centro_y = (y + h / 2) / altura * 100
z1 = profundidades[idx]
z2 = profundidades[idx + 1]
variacao = abs(z1 - z2)
# Como no solo não usamos distância real na frente (Z em metros), podemos:
# → Calcular uma média se quiser, ou deixar como None/0
z_media = (z1 + z2) / 2000.0 # Converte de mm pra metros (aproximação)
largura_real = z_media * 2 * np.tan(calib["fov_h"] / 2) * (w / largura)
altura_real = z_media * 2 * np.tan(calib["fov_v"] / 2) * (h / altura)
# Pode calcular largura e altura em metros (opcional) — aqui deixei None, mas pode fazer igual fizemos na anomalia
lista_bboxes.append({
"tipo": tipo,
"bbox": (int(x), int(y), int(w), int(h)),
"centro_percentual": (float(centro_x), float(centro_y)),
"distancia_m": round(z_media, 3),
"largura_m": round(largura_real, 3),
"altura_m": round(altura_real, 3),
"profundidades": (round(z1, 1), round(z2, 1)),
"variacao": round(variacao, 1)
})
# Filtra as detecções relevantes
from visual_worker.utils import filtrar_deteccoes
lista_bboxes_filtrada = filtrar_deteccoes(lista_bboxes, distancia_max, tamanho_min)
# 🔥 Retorno estruturado
resultado = {
"perfil": [float(v) for v in profundidades.tolist()],
"bbox_list": lista_bboxes_filtrada,
"y_inicio": int(y_inicio),
"altura_regiao": int(linhas_solo),
"n_setores": int(n_setores)
}
return resultado

View File

@ -0,0 +1,54 @@
import numpy as np
import cv2
def estimar_largura_corredor(depth_frame, fx, baseline, largura_mm_max=2000):
"""
Estima a largura do corredor com base no mapa de profundidade.
Parâmetros:
- depth_frame: imagem de profundidade (em disparidades)
- fx: distância focal da câmera (pixels)
- baseline: distância entre as câmeras estéreo (metros)
- largura_mm_max: largura máxima esperada do corredor (em mm)
Retorna:
- largura_estimada (em mm)
- posicao_central (0 a 1 fração da imagem)
- lista de pontos com (x, distancia_mm)
"""
altura, largura = depth_frame.shape
faixa_inicio = int(altura * 0.45)
faixa_fim = int(altura * 0.55)
# Recorte central horizontal
faixa = depth_frame[faixa_inicio:faixa_fim, :]
# Média por coluna
profundidade_media = np.median(faixa, axis=0)
# Converte de disparidade para distância (mm)
with np.errstate(divide='ignore'): # evita divisão por zero
distancias_mm = (fx * baseline * 1000) / profundidade_media
distancias_mm = np.clip(distancias_mm, 0, largura_mm_max)
# Aplica filtro para remover ruído (média móvel simples)
distancias_mm_suave = cv2.blur(distancias_mm.reshape(1, -1).astype(np.float32), (15, 1)).flatten()
# Detecta bordas esquerda e direita: picos de distância próxima
limite = 2500 # Limite máximo para considerar obstáculo lateral (em mm)
idx_validos = np.where((distancias_mm_suave > 100) & (distancias_mm_suave < limite))[0]
if len(idx_validos) < 2:
return None, None, []
esquerda = idx_validos[0]
direita = idx_validos[-1]
largura_estimada = abs(distancias_mm_suave[direita] - distancias_mm_suave[esquerda])
posicao_central = (esquerda + direita) / 2 / largura
# Para visualização/debug
pontos_debug = [(int(x), float(distancias_mm_suave[x])) for x in range(0, largura, 10)]
return largura_estimada, posicao_central, pontos_debug

View File

@ -0,0 +1,236 @@
# obstaculos.py
import numpy as np
import time
from scipy import stats
from visual_worker.enums import Direcao, TipoDeteccao, TipoRegiaoRadar
LIMIAR_OBSTACULO = 1000 # mm
LIMIAR_DEPRESSAO = 2500 # mm
buffer_macro = None
buffer_micro = None
ref_macro = None
ref_micro = None
MACRO_ROWS = 3
MACRO_COLS = 4
MICRO_ROWS = 10
MICRO_COLS = 10
def analisar_macro_grid(depth_frame, velocidade):
global ref_macro
if ref_macro is None:
print("[ERRO] ref_macro não foi calibrado. Execute CALIBRAR_GRADES antes.")
return {
"erro": "Calibração ausente",
"precisa_micro": False
}
altura, largura = depth_frame.shape
row_h = altura // MACRO_ROWS
col_w = largura // MACRO_COLS
LIMIAR_OBSTACULO = calcular_limiar_dinamico(velocidade)
LIMIAR_DEPRESSAO = 2500 # Pode virar dinâmico depois
zonas_ocupadas = []
dist_min = 9999
frontal_bloqueado = False
laterais = { Direcao.ESQUERDA: False, Direcao.DIREITA: False }
zona_livre = Direcao.FRENTE
for i in range(MACRO_ROWS):
for j in range(MACRO_COLS):
y1, y2 = i * row_h, (i + 1) * row_h
x1, x2 = j * col_w, (j + 1) * col_w
area = depth_frame[y1:y2, x1:x2]
validos = area[(area > 0) & (area < 10000)]
if validos.size == 0:
continue
media = np.mean(validos)
erro = media - ref_macro[i][j]
dist_min = min(dist_min, media)
tipo = TipoDeteccao.SEGURO
if erro < LIMIAR_OBSTACULO:
tipo = TipoDeteccao.OBSTACULO
elif erro > LIMIAR_DEPRESSAO:
tipo = TipoDeteccao.DEPRESSAO
if tipo != TipoDeteccao.SEGURO:
regiao = TipoRegiaoRadar.SOLO if i == MACRO_ROWS - 1 else TipoRegiaoRadar.AEREO
zonas_ocupadas.append({
"linha": i,
"coluna": j,
"distancia": round(float(media) / 1000, 2),
"tipo": tipo,
"regiao": regiao
})
if i == MACRO_ROWS - 1 and j in [1, 2]:
frontal_bloqueado = True
if j == 0:
laterais[Direcao.ESQUERDA] = True
if j == MACRO_COLS - 1:
laterais[Direcao.DIREITA] = True
if not zonas_ocupadas:
zona_livre = Direcao.FRENTE
elif all(z["coluna"] < MACRO_COLS // 2 for z in zonas_ocupadas):
zona_livre = Direcao.DIREITA
elif all(z["coluna"] >= MACRO_COLS // 2 for z in zonas_ocupadas):
zona_livre = Direcao.ESQUERDA
else:
zona_livre = Direcao.PARADO
return {
"alerta": len(zonas_ocupadas) > 0,
"frontal_bloqueado": frontal_bloqueado,
"laterais": laterais,
"zona_livre": zona_livre,
"distancia_minima": round(float(dist_min) / 1000, 2),
"detalhes": zonas_ocupadas,
"precisa_micro": frontal_bloqueado
}
def analisar_micro_grid(depth_frame):
altura, largura = depth_frame.shape
row_h = altura // MICRO_ROWS
col_w = largura // MICRO_COLS
detalhes = []
dist_min = 9999
for i in range(MICRO_ROWS):
for j in range(MICRO_COLS):
y1, y2 = i * row_h, (i + 1) * row_h
x1, x2 = j * col_w, (j + 1) * col_w
area = depth_frame[y1:y2, x1:x2]
validos = area[(area > 0) & (area < 10000)]
if validos.size == 0:
continue
media = np.mean(validos)
erro = media - ref_macro[i][j]
dist_min = min(dist_min, media)
tipo = TipoDeteccao.SEGURO
if erro < LIMIAR_OBSTACULO:
tipo = TipoDeteccao.OBSTACULO
elif erro > LIMIAR_DEPRESSAO:
tipo = TipoDeteccao.DEPRESSAO
if tipo != TipoDeteccao.SEGURO:
regiao = TipoRegiaoRadar.SOLO if i >= MICRO_ROWS * 0.6 else TipoRegiaoRadar.AEREO
detalhes.append({
"linha": i,
"coluna": j,
"distancia": round(float(media) / 1000, 2),
"tipo": tipo,
"regiao": regiao
})
return {
"alerta": len(detalhes) > 0,
"quantidade": len(detalhes),
"distancia_minima": round(float(dist_min) / 1000, 2),
"detalhes": detalhes
}
def calcular_limiar_dinamico(velocidade):
# Exemplo: de 0.6m a 1.5m dependendo da velocidade
return int(min(max(600 + velocidade * 800, 600), 1500))
def gerar_ref(buffer):
rows = len(buffer)
cols = len(buffer[0])
ref_out = np.zeros((rows, cols), dtype=np.float32)
for i in range(rows):
for j in range(cols):
valores = buffer[i][j]
if len(valores) == 0:
ref_out[i][j] = 0 # ou -1 para representar célula vazia
continue
# Tenta usar moda
moda = stats.mode(valores, keepdims=True).mode
if moda.size > 0:
ref_out[i][j] = moda[0]
else:
ref_out[i][j] = np.median(valores)
return ref_out
def calibrar_macro(frame):
global buffer_macro
altura, largura = frame.shape
row_h = altura // MACRO_ROWS
col_w = largura // MACRO_COLS
for i in range(MACRO_ROWS):
for j in range(MACRO_COLS):
y1, y2 = i * row_h, (i + 1) * row_h
x1, x2 = j * col_w, (j + 1) * col_w
area = frame[y1:y2, x1:x2]
validos = area[(area > 0) & (area < 10000)]
if validos.size > 0:
buffer_macro[i][j].extend(validos.tolist())
def calibrar_micro(frame):
global buffer_micro
altura, largura = frame.shape
row_h = altura // MICRO_ROWS
col_w = largura // MICRO_COLS
for i in range(MICRO_ROWS):
for j in range(MICRO_COLS):
y1, y2 = i * row_h, (i + 1) * row_h
x1, x2 = j * col_w, (j + 1) * col_w
area = frame[y1:y2, x1:x2]
validos = area[(area > 0) & (area < 10000)]
if validos.size > 0:
buffer_micro[i][j].extend(validos.tolist())
def calibrar_grades(capturar_depth_frame, n_frames=50):
global buffer_macro, buffer_micro, ref_macro, ref_micro
buffer_macro = [[[] for _ in range(MACRO_COLS)] for _ in range(MACRO_ROWS)]
buffer_micro = [[[] for _ in range(MICRO_COLS)] for _ in range(MICRO_ROWS)]
print(f"📡 Iniciando calibração de {n_frames} frames...")
total_validos = 0
for i in range(n_frames):
frame, timestamp = capturar_depth_frame()
if frame is not None:
calibrar_macro(frame)
calibrar_micro(frame)
total_validos += 1
print(f"✔️ Frame {i+1}/{n_frames} calibrado.")
else:
print(f"⚠️ Frame {i+1}/{n_frames} inválido, ignorado.")
time.sleep(0.03) # pausa leve pra estabilizar captura
if total_validos == 0:
print("❌ Nenhum frame válido recebido. Calibração cancelada.")
return False
ref_macro = gerar_ref(buffer_macro)
ref_micro = gerar_ref(buffer_micro)
print("✅ Calibração concluída com sucesso!")
return True

View File

@ -0,0 +1,68 @@
import numpy as np
import time
from visual_worker.enums import TipoAnalise
from visual_worker.utils import aplicar_mascara_margens, calcular_threshold_anomalias
# Variáveis globais
depth_referencia = None
calibrado = False
# Calibração
def calibrar(obter_depth_frame, n_frames=50):
global depth_referencia, calibrado
calibrado = False
print(f"📡 Iniciando calibração de {n_frames} frames...")
lista_frames = []
total_validos = 0
for i in range(n_frames):
frame, timestamp = obter_depth_frame()
if frame is not None:
lista_frames.append(frame)
print(f"✔️ Frame de timestamp {timestamp} {i+1}/{n_frames} calibrado.")
total_validos += 1
else:
print(f"⚠️ Frame de timestamp {timestamp} {i+1}/{n_frames} inválido, ignorado.")
time.sleep(0.05)
if total_validos == 0:
print("❌ Nenhum frame válido recebido. Calibração cancelada.")
return False
depth_referencia = np.median(np.stack(lista_frames), axis=0)
calibrado = True
print("✅ Calibração concluída com sucesso!")
return True
def analisar(analise, obter_depth_frame, camera_calib, velocidade_ms, distancia_max, tamanho_min):
global depth_referencia, calibrado
resultado = {
"timestamp": None,
"anomalias": None,
"solo": None
}
if not calibrado:
print("[ERRO] Calibração não realizada!")
return resultado
frame, timestamp = obter_depth_frame()
resultado["timestamp"] = timestamp
depth_frame = aplicar_mascara_margens(frame)
limiar = calcular_threshold_anomalias(velocidade_ms)
if analise == TipoAnalise.ANOMALIA:
from visual_worker.processamento.analise_anomalias import detectar_anomalias
resultado["anomalias"] = detectar_anomalias(frame, depth_referencia, limiar, distancia_max, tamanho_min, camera_calib)
elif analise == TipoAnalise.SOLO:
from visual_worker.processamento.analise_solo import analisar_solo
resultado["solo"] = analisar_solo(frame, 20, 50, limiar, distancia_max, tamanho_min, camera_calib)
return resultado

View File

@ -0,0 +1,48 @@
import numpy as np
def detectar_obstaculos_em_frente(depth_frame, fx, baseline, faixa_altura=(0.45, 0.55), max_distancia_mm=2500):
"""
Detecta obstáculos com base no perfil de profundidade frontal.
Retorna uma lista de obstáculos com:
- posição_percentual (0 à 100)
- distancia (em metros)
- largura_aproximada (em px)
"""
altura, largura = depth_frame.shape
y1 = int(altura * faixa_altura[0])
y2 = int(altura * faixa_altura[1])
faixa = depth_frame[y1:y2, :]
profundidade_media = np.median(faixa, axis=0)
with np.errstate(divide='ignore'):
distancias_mm = (fx * baseline * 1000) / profundidade_media
distancias_mm = np.clip(distancias_mm, 0, max_distancia_mm)
# Simplifica usando limiar: onde há objetos "próximos"
mascara = (distancias_mm > 100) & (distancias_mm < max_distancia_mm)
obstaculos = []
inicio = None
for x in range(largura):
if mascara[x]:
if inicio is None:
inicio = x
elif inicio is not None:
fim = x
centro = (inicio + fim) // 2
largura_px = fim - inicio
distancia = np.min(distancias_mm[inicio:fim])
posicao_pct = 100 * centro / largura
obstaculos.append({
"posicao": round(float(posicao_pct), 1),
"distancia": round(float(distancia) / 1000, 2),
"largura_px": largura_px
})
inicio = None
return obstaculos

View File

@ -0,0 +1,73 @@
# utils.py
import cv2
import base64
import numpy as np
def encode_image_base64(frame):
"""
Codifica uma imagem (np.ndarray) como string base64 JPEG.
"""
ret, buffer = cv2.imencode('.jpg', frame, [cv2.IMWRITE_JPEG_QUALITY, 80])
if not ret:
return None
base64_str = base64.b64encode(buffer).decode('utf-8')
return base64_str
def gerar_heatmap(depth_frame, dist_max_mm=3000.0):
mask_valid = depth_frame <= dist_max_mm
depth_normalized = np.zeros(depth_frame.shape, dtype=np.uint8)
depth_normalized[mask_valid] = (255 - ((depth_frame[mask_valid] / dist_max_mm) * 255)).astype(np.uint8)
heatmap = cv2.applyColorMap(depth_normalized, cv2.COLORMAP_JET)
heatmap[~mask_valid] = [0, 0, 0]
return heatmap
def filtrar_deteccoes(lista, distancia_maxima=2.0, tamanho_minimo=0.25):
"""
Filtra a lista de detecções baseado em:
- Distância máxima (em metros).
- Largura OU altura mínima (em metros).
"""
lista_filtrada = []
for obj in lista:
if obj["distancia_m"] > distancia_maxima:
continue
if obj["largura_m"] < tamanho_minimo and obj["altura_m"] < tamanho_minimo:
continue
lista_filtrada.append(obj)
return lista_filtrada
def aplicar_mascara_margens(depth_frame, margem_percentual=3, valor_mascara=0):
"""
Aplica uma máscara nas bordas do frame, preenchendo com valor alto para ignorar nas análises.
"""
frame_mascara = depth_frame.copy()
altura, largura = frame_mascara.shape
margem_x = int((margem_percentual / 100) * largura)
margem_y = int((margem_percentual / 100) * altura)
# Margens verticais (esquerda e direita)
frame_mascara[:, :margem_x] = valor_mascara
frame_mascara[:, largura - margem_x:] = valor_mascara
# Margens horizontais (superior e inferior)
frame_mascara[:margem_y, :] = valor_mascara
frame_mascara[altura - margem_y:, :] = valor_mascara
return frame_mascara
def calcular_threshold_anomalias(velocidade_ms, threshold_base=200, fator_sensibilidade=100):
"""
Calcula o threshold dinamicamente baseado na velocidade do robô.
- velocidade_ms: Velocidade em metros por segundo (m/s).
"""
threshold = threshold_base + (velocidade_ms * fator_sensibilidade)
return round(threshold, 1)
def filtrar_depth_valid(depth_array, max_value=3000.0):
return depth_array[(depth_array > 0) & (depth_array < max_value)]

View File

@ -0,0 +1,241 @@
# camera_manager.py
import depthai as dai
import cv2
import numpy as np
import time
# Parâmetros da câmera
RGB_WIDTH, RGB_HEIGHT = 640, 480
DEPTH_WIDTH, DEPTH_HEIGHT = 320, 240
FX = 440.0 # distância focal em pixels (aproximado)
BASELINE = 0.075 # distância entre câmeras estéreo (em metros)
# Variáveis globais
device = None
device_info = None
rgb_queue = None
depth_queue = None
ultimo_frame_depth = None
timestamp_ultimo_depth_frame = None
ultimo_frame_rgb = None
timestamp_ultimo_rgb_frame = None
def iniciar_camera(index=0):
global device, device_info, rgb_queue, depth_queue
print("Iniciando câmera OAK-D Lite...")
pipeline = dai.Pipeline()
# RGB
cam_rgb = pipeline.create(dai.node.ColorCamera)
cam_rgb.setPreviewSize(RGB_WIDTH, RGB_HEIGHT)
cam_rgb.setInterleaved(False)
cam_rgb.setBoardSocket(dai.CameraBoardSocket.RGB)
xout_rgb = pipeline.create(dai.node.XLinkOut)
xout_rgb.setStreamName("rgb")
cam_rgb.preview.link(xout_rgb.input)
# 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_480_P)
mono_right.setResolution(dai.MonoCameraProperties.SensorResolution.THE_480_P)
mono_left.setBoardSocket(dai.CameraBoardSocket.LEFT)
mono_right.setBoardSocket(dai.CameraBoardSocket.RIGHT)
stereo.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_DENSITY)
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)
# Dispositivo
available_devices = dai.Device.getAllAvailableDevices()
if index >= len(available_devices):
raise ValueError(f"Câmera de índice {index} não encontrada.")
device_info = available_devices[index]
device = dai.Device(pipeline, device_info)
rgb_queue = device.getOutputQueue(name="rgb", maxSize=1, blocking=False)
depth_queue = device.getOutputQueue(name="depth", maxSize=1, blocking=False)
print(f"Câmera iniciada: {device_info.name} (ID: {device_info.getMxId()})")
get_camera_calib()
def get_camera_calib():
global FX, BASELINE
calib = device.readCalibration()
# Obtem matriz intrínseca da câmera LEFT (com resolução padrão 640x400)
intrinsics = calib.getCameraIntrinsics(dai.CameraBoardSocket.LEFT, 640, 400)
FX = intrinsics[0][0] # fx
BASELINE = calib.getBaselineDistance() / 1000.0 # de mm → m
print(f"[CALIB] FX: {FX:.2f} px, BASELINE: {BASELINE:.4f} m")
def get_rgb_frame():
global ultimo_frame_rgb, timestamp_ultimo_rgb_frame
if rgb_queue is None:
return None, None
frame = rgb_queue.tryGet()
if frame is not None:
ultimo_frame_rgb = frame.getCvFrame()
timestamp_ultimo_rgb_frame = time.time()
return ultimo_frame_rgb, timestamp_ultimo_rgb_frame
def get_heatmap_frame():
frame = get_depth_frame()
if frame is not None:
return gerar_heatmap(frame), timestamp_ultimo_depth_frame
return None, None
def gerar_heatmap(depth_frame):
# Normaliza e aplica colormap
normalized = cv2.normalize(depth_frame, None, 0, 255, cv2.NORM_MINMAX)
heatmap = cv2.applyColorMap(normalized.astype(np.uint8), cv2.COLORMAP_JET)
return heatmap
def get_depth_frame():
global ultimo_frame_depth, timestamp_ultimo_depth_frame
if depth_queue is None:
return None, None
frame = depth_queue.tryGet()
if frame is not None:
ultimo_frame_depth = frame.getFrame()
timestamp_ultimo_depth_frame = time.time()
return ultimo_frame_depth, timestamp_ultimo_depth_frame
def get_status_dispositivo():
from depthai import UsbSpeed
try:
memory = device.getDdrMemoryUsage()
memory_info = {
"used": memory.used,
"remaining": memory.remaining,
"total": memory.total
}
except:
memory_info = None
try:
temp = device.getChipTemperature()
temp_info = {
"css": temp.css,
"mss": temp.mss,
"upa": temp.upa,
"dss": temp.dss
}
except:
temp_info = None
try:
info = device.getDeviceInfo()
protocol = str(info.protocol)
except:
protocol = None
try:
bootloader = str(device.getBootloaderVersion())
except:
bootloader = None
try:
usb_speed = str(device.getUsbSpeed().name)
except:
usb_speed = None
try:
pipeline_running = device.isPipelineRunning()
except:
pipeline_running = None
try:
cameras = [sensor.name for sensor in device.getConnectedCameras()]
except:
cameras = None
return {
"id": device_info.getMxId(),
"name": device_info.name,
"state": device_info.state.name,
"usb_speed": usb_speed,
"available_camera_sensors": cameras,
"version": protocol,
"bootloader_version": bootloader,
"is_pipeline_running": pipeline_running,
"memory_usage": memory_info,
"temperature": temp_info
}
def analisar_obstaculos(velocidade=0.0, refinar=False):
from processamento.obstaculos import analisar_macro_grid, analisar_micro_grid
global timestamp_ultimo_depth_frame
frame = get_depth_frame()
if frame is None:
if ultimo_frame_depth is None:
return { "erro": "Sem frame disponível", "timestamp": None }
frame = ultimo_frame_depth
macro = analisar_macro_grid(frame, velocidade)
if macro["precisa_micro"] and refinar:
micro = analisar_micro_grid(frame)
micro["timestamp"] = timestamp_ultimo_depth_frame
return micro
macro["timestamp"] = timestamp_ultimo_depth_frame
return macro
def analisar_corredor():
from processamento.corredor import estimar_largura_corredor
global timestamp_ultimo_depth_frame # precisa garantir que fx e baseline existam
frame = get_depth_frame()
if frame is None:
if ultimo_frame_depth is None:
return { "erro": "Sem frame disponível", "timestamp": None }
frame = ultimo_frame_depth
largura_mm, pos_central, pontos_debug = estimar_largura_corredor(frame, FX, BASELINE)
return {
"largura_corredor_mm": float(largura_mm) if largura_mm is not None else None,
"posicao_central_fracao": float(pos_central) if pos_central is not None else None,
"timestamp": timestamp_ultimo_depth_frame,
"pontos_debug": [ [int(x), float(y)] for x, y in pontos_debug ]
}
def analisar_obstaculos_3d():
from processamento.visao3d import detectar_obstaculos_em_frente
global timestamp_ultimo_depth_frame
frame = get_depth_frame()
if frame is None:
if ultimo_frame_depth is None:
return { "erro": "Sem frame disponível", "timestamp": None }
frame = ultimo_frame_depth
lista = detectar_obstaculos_em_frente(frame, FX, BASELINE)
return {
"obstaculos_detectados": lista,
"timestamp": timestamp_ultimo_depth_frame
}

View File

@ -0,0 +1,38 @@
from enum import IntEnum
class Comando(IntEnum):
PING = 1
GET_RGB_FRAME = 2
GET_HEATMAP_FRAME = 3
GET_OBSTACULOS = 4
GET_STATUS_DISPOSITIVO = 5
CALIBRAR_GRADES = 6
GET_MAPA_PROFUNDIDADE = 7
GET_LARGURA_CORREDOR = 8
GET_OBSTACULOS_3D = 9
class TipoComando(IntEnum):
TX = 1
RX = 2
class TipoDeteccao(IntEnum):
SEGURO = 1
OBSTACULO = 2
DEPRESSAO = 3
class TipoRegiaoRadar(IntEnum):
SOLO = 1
AEREO = 2
class Direcao(IntEnum):
PARADO = 0
FRENTE = 1
TRAS = 2
ESQUERDA = 3
DIREITA = 4
CIMA = 5
BAIXO = 6
ESQUERDABAIXO = 7
ESQUERDACIMA = 8
DIREITABAIXO = 9
DIREITACIMA = 10

View File

View File

@ -0,0 +1,151 @@
import sys
import os
import time
import json
from enums import Comando, TipoComando
from mqtt_handler import enviar_mensagem_mqtt
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
# ─────────────────────────────
# 🔹 Parâmetros de Execução
# ─────────────────────────────
mqtt_topic = sys.argv[1] if len(sys.argv) > 1 else "agrobot/operador/sonar"
camera_index = int(sys.argv[2]) if len(sys.argv) > 2 else 0
# ─────────────────────────────
# 🔹 Callback de Comando MQTT
# ─────────────────────────────
def ao_receber_comando(topico, payload):
try:
comando = json.loads(payload.decode())
tipo_cmd = int(comando.get("tipo_cmd"))
codigo = int(comando.get("cmd"))
# Ignora mensagens enviadas por si proprio
if (tipo_cmd == TipoComando.RX):
return
resposta = {}
if codigo == Comando.PING:
resposta = {
"ping": {
"timestamp": time.time(),
"status": True
}
}
elif codigo == Comando.GET_STATUS_DISPOSITIVO:
from camera_manager import get_status_dispositivo
resposta = {
"device_status": get_status_dispositivo()
}
elif codigo == Comando.GET_RGB_FRAME:
from camera_manager import get_rgb_frame
frame, timestamp = get_rgb_frame()
base64_img = None
if frame is not None:
from utils import encode_image_base64
base64_img = encode_image_base64(frame)
resposta = {
"timestamp": timestamp,
"frame": base64_img
}
elif codigo == Comando.GET_HEATMAP_FRAME:
from camera_manager import get_heatmap_frame
frame, timestamp = get_heatmap_frame()
base64_img = None
if frame is not None:
from utils import encode_image_base64
base64_img = encode_image_base64(frame)
resposta = {
"timestamp": timestamp,
"frame": base64_img
}
elif codigo == Comando.CALIBRAR_GRADES:
from camera_manager import get_depth_frame
from processamento.obstaculos import calibrar_grades
sucesso = calibrar_grades(get_depth_frame, n_frames=50)
resposta = {
"calibragem": {
"timestamp": time.time(),
"status": sucesso
}
}
elif codigo == Comando.GET_OBSTACULOS:
from camera_manager import analisar_obstaculos
velocidade = float(comando.get("velocidade", 0.0)) # padrão = 0.0 m/s, parado
precisao_micro = float(comando.get("precisao_micro", False))
resposta = {
"obstaculos": analisar_obstaculos(velocidade=velocidade, refinar=precisao_micro)
}
elif codigo == Comando.GET_LARGURA_CORREDOR:
from camera_manager import analisar_corredor
resposta = {
"corredor": analisar_corredor()
}
elif codigo == Comando.GET_MAPA_PROFUNDIDADE:
from camera_manager import get_depth_frame
from utils import gerar_mapa_profundidade
depth, timestamp = get_depth_frame()
if depth is not None:
resolucao = str(comando.get("resolucao", "10x10"))
try:
linhas, colunas = map(int, resolucao.lower().split("x"))
except:
linhas, colunas = 10, 10
grid = gerar_mapa_profundidade(depth, linhas, colunas)
grid["timestamp"] = timestamp
resposta = {
"mapa_profundidade": grid
}
elif codigo == Comando.GET_OBSTACULOS_3D:
from camera_manager import analisar_obstaculos_3d
resposta = {
"obstaculos_3d": analisar_obstaculos_3d()
}
enviar_mensagem_mqtt(codigo, resposta)
except Exception as e:
print("❌ Erro ao processar comando MQTT:", e)
# ─────────────────────────────
# 🔹 Execução Principal
# ─────────────────────────────
if __name__ == "__main__":
print("🚀 Iniciando Operário Visual com OAK-D Lite...")
# 1. Inicia a câmera
from camera_manager import iniciar_camera
iniciar_camera(camera_index)
from mqtt_handler import iniciar_mqtt, enviar_mensagem_script_carregado
# 2. Inicia MQTT e registra callback
iniciar_mqtt(mqtt_topic, ao_receber_comando)
print(f"✅ Escutando comandos no tópico: {mqtt_topic}")
enviar_mensagem_script_carregado()
# 3. Loop principal
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
print("Encerrando...")

View File

@ -0,0 +1,51 @@
import paho.mqtt.client as mqtt
import uuid
import json
import time
from enums import TipoComando
mqtt_client = None
mqtt_topic = None
def iniciar_mqtt(topico, funcao_callback, broker="localhost", porta=1883):
"""
Inicia conexão MQTT e escuta o tópico.
:param tópico: string com o nome do tópico
:param funcao_callback: função que será chamada ao receber mensagem
"""
global mqtt_client, mqtt_topic
mqtt_client = mqtt.Client(f"visual_worker_{uuid.uuid4()}")
mqtt_topic = topico
def on_connect(client, userdata, flags, rc):
if rc == 0:
print(f"[MQTT] Conectado ao broker em {broker}:{porta}")
client.subscribe(mqtt_topic)
else:
print("[MQTT] Falha ao conectar, código de retorno:", rc)
def on_message(client, userdata, msg):
try:
funcao_callback(msg.topic, msg.payload)
except Exception as e:
print("[MQTT] Erro no callback:", e)
mqtt_client.on_connect = on_connect
mqtt_client.on_message = on_message
mqtt_client.connect(broker, porta)
mqtt_client.loop_start()
def enviar_mensagem_script_carregado():
mqtt_client.publish(mqtt_topic, "OK")
def enviar_mensagem_mqtt(comando, objeto):
mensagem = {
"momento": time.time(),
"tipo_cmd": TipoComando.RX,
"cmd": comando,
"obj": objeto
}
mqtt_client.publish(mqtt_topic, json.dumps(mensagem))

View File

@ -0,0 +1,54 @@
import numpy as np
import cv2
def estimar_largura_corredor(depth_frame, fx, baseline, largura_mm_max=2000):
"""
Estima a largura do corredor com base no mapa de profundidade.
Parâmetros:
- depth_frame: imagem de profundidade (em disparidades)
- fx: distância focal da câmera (pixels)
- baseline: distância entre as câmeras estéreo (metros)
- largura_mm_max: largura máxima esperada do corredor (em mm)
Retorna:
- largura_estimada (em mm)
- posicao_central (0 a 1 fração da imagem)
- lista de pontos com (x, distancia_mm)
"""
altura, largura = depth_frame.shape
faixa_inicio = int(altura * 0.45)
faixa_fim = int(altura * 0.55)
# Recorte central horizontal
faixa = depth_frame[faixa_inicio:faixa_fim, :]
# Média por coluna
profundidade_media = np.median(faixa, axis=0)
# Converte de disparidade para distância (mm)
with np.errstate(divide='ignore'): # evita divisão por zero
distancias_mm = (fx * baseline * 1000) / profundidade_media
distancias_mm = np.clip(distancias_mm, 0, largura_mm_max)
# Aplica filtro para remover ruído (média móvel simples)
distancias_mm_suave = cv2.blur(distancias_mm.reshape(1, -1).astype(np.float32), (15, 1)).flatten()
# Detecta bordas esquerda e direita: picos de distância próxima
limite = 2500 # Limite máximo para considerar obstáculo lateral (em mm)
idx_validos = np.where((distancias_mm_suave > 100) & (distancias_mm_suave < limite))[0]
if len(idx_validos) < 2:
return None, None, []
esquerda = idx_validos[0]
direita = idx_validos[-1]
largura_estimada = abs(distancias_mm_suave[direita] - distancias_mm_suave[esquerda])
posicao_central = (esquerda + direita) / 2 / largura
# Para visualização/debug
pontos_debug = [(int(x), float(distancias_mm_suave[x])) for x in range(0, largura, 10)]
return largura_estimada, posicao_central, pontos_debug

View File

@ -0,0 +1,227 @@
# obstaculos.py
import numpy as np
import time
from scipy import stats
from visual_worker.enums import Direcao, TipoDeteccao, TipoRegiaoRadar
LIMIAR_OBSTACULO = 1000 # mm
LIMIAR_DEPRESSAO = 2500 # mm
buffer_macro = None
buffer_micro = None
ref_macro = None
ref_micro = None
MACRO_ROWS = 3
MACRO_COLS = 4
MICRO_ROWS = 10
MICRO_COLS = 10
def analisar_macro_grid(depth_frame, velocidade):
altura, largura = depth_frame.shape
row_h = altura // MACRO_ROWS
col_w = largura // MACRO_COLS
LIMIAR_OBSTACULO = calcular_limiar_dinamico(velocidade)
LIMIAR_DEPRESSAO = 2500 # Pode virar dinâmico depois
zonas_ocupadas = []
dist_min = 9999
frontal_bloqueado = False
laterais = { Direcao.ESQUERDA: False, Direcao.DIREITA: False }
zona_livre = Direcao.FRENTE
for i in range(MACRO_ROWS):
for j in range(MACRO_COLS):
y1, y2 = i * row_h, (i + 1) * row_h
x1, x2 = j * col_w, (j + 1) * col_w
area = depth_frame[y1:y2, x1:x2]
validos = area[(area > 0) & (area < 10000)]
if validos.size == 0:
continue
media = np.mean(validos)
erro = media - ref_macro[i][j]
dist_min = min(dist_min, media)
tipo = TipoDeteccao.SEGURO
if erro < LIMIAR_OBSTACULO:
tipo = TipoDeteccao.OBSTACULO
elif erro > LIMIAR_DEPRESSAO:
tipo = TipoDeteccao.DEPRESSAO
if tipo != TipoDeteccao.SEGURO:
regiao = TipoRegiaoRadar.SOLO if i == MACRO_ROWS - 1 else TipoRegiaoRadar.AEREO
zonas_ocupadas.append({
"linha": i,
"coluna": j,
"distancia": round(float(media) / 1000, 2),
"tipo": tipo,
"regiao": regiao
})
if i == MACRO_ROWS - 1 and j in [1, 2]:
frontal_bloqueado = True
if j == 0:
laterais[Direcao.ESQUERDA] = True
if j == MACRO_COLS - 1:
laterais[Direcao.DIREITA] = True
if not zonas_ocupadas:
zona_livre = Direcao.FRENTE
elif all(z["coluna"] < MACRO_COLS // 2 for z in zonas_ocupadas):
zona_livre = Direcao.DIREITA
elif all(z["coluna"] >= MACRO_COLS // 2 for z in zonas_ocupadas):
zona_livre = Direcao.ESQUERDA
else:
zona_livre = Direcao.PARADO
return {
"alerta": len(zonas_ocupadas) > 0,
"frontal_bloqueado": frontal_bloqueado,
"laterais": laterais,
"zona_livre": zona_livre,
"distancia_minima": round(float(dist_min) / 1000, 2),
"detalhes": zonas_ocupadas,
"precisa_micro": frontal_bloqueado
}
def analisar_micro_grid(depth_frame):
altura, largura = depth_frame.shape
row_h = altura // MICRO_ROWS
col_w = largura // MICRO_COLS
detalhes = []
dist_min = 9999
for i in range(MICRO_ROWS):
for j in range(MICRO_COLS):
y1, y2 = i * row_h, (i + 1) * row_h
x1, x2 = j * col_w, (j + 1) * col_w
area = depth_frame[y1:y2, x1:x2]
validos = area[(area > 0) & (area < 10000)]
if validos.size == 0:
continue
media = np.mean(validos)
erro = media - ref_macro[i][j]
dist_min = min(dist_min, media)
tipo = TipoDeteccao.SEGURO
if erro < LIMIAR_OBSTACULO:
tipo = TipoDeteccao.OBSTACULO
elif erro > LIMIAR_DEPRESSAO:
tipo = TipoDeteccao.DEPRESSAO
if tipo != TipoDeteccao.SEGURO:
regiao = TipoRegiaoRadar.SOLO if i >= MICRO_ROWS * 0.6 else TipoRegiaoRadar.AEREO
detalhes.append({
"linha": i,
"coluna": j,
"distancia": round(float(media) / 1000, 2),
"tipo": tipo,
"regiao": regiao
})
return {
"alerta": len(detalhes) > 0,
"quantidade": len(detalhes),
"distancia_minima": round(float(dist_min) / 1000, 2),
"detalhes": detalhes
}
def calcular_limiar_dinamico(velocidade):
# Exemplo: de 0.6m a 1.5m dependendo da velocidade
return int(min(max(600 + velocidade * 800, 600), 1500))
def gerar_ref(buffer):
rows = len(buffer)
cols = len(buffer[0])
ref_out = np.zeros((rows, cols), dtype=np.float32)
for i in range(rows):
for j in range(cols):
valores = buffer[i][j]
if len(valores) == 0:
ref_out[i][j] = 0 # ou -1 para representar célula vazia
continue
# Tenta usar moda
moda = stats.mode(valores, keepdims=True).mode
if moda.size > 0:
ref_out[i][j] = moda[0]
else:
ref_out[i][j] = np.median(valores)
return ref_out
def calibrar_macro(frame):
global buffer_macro
altura, largura = frame.shape
row_h = altura // MACRO_ROWS
col_w = largura // MACRO_COLS
for i in range(MACRO_ROWS):
for j in range(MACRO_COLS):
y1, y2 = i * row_h, (i + 1) * row_h
x1, x2 = j * col_w, (j + 1) * col_w
area = frame[y1:y2, x1:x2]
validos = area[(area > 0) & (area < 10000)]
if validos.size > 0:
buffer_macro[i][j].extend(validos.tolist())
def calibrar_micro(frame):
global buffer_micro
altura, largura = frame.shape
row_h = altura // MICRO_ROWS
col_w = largura // MICRO_COLS
for i in range(MICRO_ROWS):
for j in range(MICRO_COLS):
y1, y2 = i * row_h, (i + 1) * row_h
x1, x2 = j * col_w, (j + 1) * col_w
area = frame[y1:y2, x1:x2]
validos = area[(area > 0) & (area < 10000)]
if validos.size > 0:
buffer_micro[i][j].extend(validos.tolist())
def calibrar_grades(capturar_depth_frame, n_frames=50):
global buffer_macro, buffer_micro, ref_macro, ref_micro
buffer_macro = [[[] for _ in range(MACRO_COLS)] for _ in range(MACRO_ROWS)]
buffer_micro = [[[] for _ in range(MICRO_COLS)] for _ in range(MICRO_ROWS)]
print(f"📡 Iniciando calibração de {n_frames} frames...")
total_validos = 0
for i in range(n_frames):
frame = capturar_depth_frame()
if frame is not None:
calibrar_macro(frame)
calibrar_micro(frame)
total_validos += 1
print(f"✔️ Frame {i+1}/{n_frames} calibrado.")
else:
print(f"⚠️ Frame {i+1}/{n_frames} inválido, ignorado.")
time.sleep(0.03) # pausa leve pra estabilizar captura
if total_validos == 0:
print("❌ Nenhum frame válido recebido. Calibração cancelada.")
return False
ref_macro = gerar_ref(buffer_macro)
ref_micro = gerar_ref(buffer_micro)
print("✅ Calibração concluída com sucesso!")
return True

View File

@ -0,0 +1,48 @@
import numpy as np
def detectar_obstaculos_em_frente(depth_frame, fx, baseline, faixa_altura=(0.45, 0.55), max_distancia_mm=2500):
"""
Detecta obstáculos com base no perfil de profundidade frontal.
Retorna uma lista de obstáculos com:
- posição_percentual (0 à 100)
- distancia (em metros)
- largura_aproximada (em px)
"""
altura, largura = depth_frame.shape
y1 = int(altura * faixa_altura[0])
y2 = int(altura * faixa_altura[1])
faixa = depth_frame[y1:y2, :]
profundidade_media = np.median(faixa, axis=0)
with np.errstate(divide='ignore'):
distancias_mm = (fx * baseline * 1000) / profundidade_media
distancias_mm = np.clip(distancias_mm, 0, max_distancia_mm)
# Simplifica usando limiar: onde há objetos "próximos"
mascara = (distancias_mm > 100) & (distancias_mm < max_distancia_mm)
obstaculos = []
inicio = None
for x in range(largura):
if mascara[x]:
if inicio is None:
inicio = x
elif inicio is not None:
fim = x
centro = (inicio + fim) // 2
largura_px = fim - inicio
distancia = np.min(distancias_mm[inicio:fim])
posicao_pct = 100 * centro / largura
obstaculos.append({
"posicao": round(float(posicao_pct), 1),
"distancia": round(float(distancia) / 1000, 2),
"largura_px": largura_px
})
inicio = None
return obstaculos

View File

@ -0,0 +1,47 @@
# utils.py
import cv2
import base64
import numpy as np
def encode_image_base64(frame):
"""
Codifica uma imagem (np.ndarray) como string base64 JPEG.
"""
ret, buffer = cv2.imencode('.jpg', frame, [cv2.IMWRITE_JPEG_QUALITY, 80])
if not ret:
return None
base64_str = base64.b64encode(buffer).decode('utf-8')
return base64_str
def gerar_mapa_profundidade(depth_frame, linhas=10, colunas=10):
altura, largura = depth_frame.shape
h = altura // linhas
w = largura // colunas
grid = []
resposta = {
"resolucao": f"{linhas}x{colunas}",
"unidade": "cm",
"grid": grid
}
if depth_frame is None:
return resposta
for i in range(linhas):
linha = []
for j in range(colunas):
y1, y2 = i * h, (i + 1) * h
x1, x2 = j * w, (j + 1) * w
celula = depth_frame[y1:y2, x1:x2]
validos = celula[(celula > 0) & (celula < 10000)]
if validos.size == 0:
linha.append(None)
else:
media_cm = np.mean(validos) / 100.0
linha.append(round(float(media_cm), 2))
grid.append(linha)
resposta["grid"] = grid
return resposta