avancado com o operador visual
This commit is contained in:
parent
cee4a90f76
commit
a92d95d4c0
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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...")
|
||||
|
|
@ -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))
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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)]
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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...")
|
||||
|
|
@ -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))
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue