ajustes no visual_worker

This commit is contained in:
Diego Freitas 2025-05-22 10:48:28 -03:00
parent a92d95d4c0
commit 0a62a1d21a
21 changed files with 555 additions and 126 deletions

Binary file not shown.

View File

@ -28,9 +28,9 @@
/// </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();
System.Windows.Forms.DataVisualization.Charting.ChartArea chartArea1 = new System.Windows.Forms.DataVisualization.Charting.ChartArea();
System.Windows.Forms.DataVisualization.Charting.Legend legend1 = new System.Windows.Forms.DataVisualization.Charting.Legend();
System.Windows.Forms.DataVisualization.Charting.Series series1 = 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();
@ -63,9 +63,11 @@
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.lblLarguraMin = new System.Windows.Forms.Label();
this.txtLarguraMin = new System.Windows.Forms.TextBox();
this.chbReqAutomatico = new System.Windows.Forms.CheckBox();
this.lblAlturaMin = new System.Windows.Forms.Label();
this.txtAlturaMin = new System.Windows.Forms.TextBox();
((System.ComponentModel.ISupportInitialize)(this.picRGB)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.picHeatmap)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.picDebug)).BeginInit();
@ -310,16 +312,16 @@
//
// chartPerfilSolo
//
chartArea2.Name = "ChartArea1";
this.chartPerfilSolo.ChartAreas.Add(chartArea2);
legend2.Name = "Legend1";
this.chartPerfilSolo.Legends.Add(legend2);
chartArea1.Name = "ChartArea1";
this.chartPerfilSolo.ChartAreas.Add(chartArea1);
legend1.Name = "Legend1";
this.chartPerfilSolo.Legends.Add(legend1);
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);
series1.ChartArea = "ChartArea1";
series1.Legend = "Legend1";
series1.Name = "Series1";
this.chartPerfilSolo.Series.Add(series1);
this.chartPerfilSolo.Size = new System.Drawing.Size(496, 305);
this.chartPerfilSolo.TabIndex = 30;
this.chartPerfilSolo.Text = "chart1";
@ -362,7 +364,7 @@
//
// txtDistanciaMax
//
this.txtDistanciaMax.Location = new System.Drawing.Point(794, 347);
this.txtDistanciaMax.Location = new System.Drawing.Point(888, 347);
this.txtDistanciaMax.Name = "txtDistanciaMax";
this.txtDistanciaMax.Size = new System.Drawing.Size(88, 20);
this.txtDistanciaMax.TabIndex = 35;
@ -381,29 +383,29 @@
// lblDistanciaMax
//
this.lblDistanciaMax.AutoSize = true;
this.lblDistanciaMax.Location = new System.Drawing.Point(791, 331);
this.lblDistanciaMax.Location = new System.Drawing.Point(885, 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
// lblLarguraMin
//
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)";
this.lblLarguraMin.AutoSize = true;
this.lblLarguraMin.Location = new System.Drawing.Point(697, 331);
this.lblLarguraMin.Name = "lblLarguraMin";
this.lblLarguraMin.Size = new System.Drawing.Size(80, 13);
this.lblLarguraMin.TabIndex = 39;
this.lblLarguraMin.Text = "Largura Min (m)";
//
// txtTamanhoMin
// txtLarguraMin
//
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;
this.txtLarguraMin.Location = new System.Drawing.Point(700, 347);
this.txtLarguraMin.Name = "txtLarguraMin";
this.txtLarguraMin.Size = new System.Drawing.Size(88, 20);
this.txtLarguraMin.TabIndex = 38;
this.txtLarguraMin.Text = "0,30";
this.txtLarguraMin.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
//
// chbReqAutomatico
//
@ -415,14 +417,34 @@
this.chbReqAutomatico.Text = "Req Automatico";
this.chbReqAutomatico.UseVisualStyleBackColor = true;
//
// lblAlturaMin
//
this.lblAlturaMin.AutoSize = true;
this.lblAlturaMin.Location = new System.Drawing.Point(791, 331);
this.lblAlturaMin.Name = "lblAlturaMin";
this.lblAlturaMin.Size = new System.Drawing.Size(71, 13);
this.lblAlturaMin.TabIndex = 42;
this.lblAlturaMin.Text = "Altura Min (m)";
//
// txtAlturaMin
//
this.txtAlturaMin.Location = new System.Drawing.Point(794, 347);
this.txtAlturaMin.Name = "txtAlturaMin";
this.txtAlturaMin.Size = new System.Drawing.Size(88, 20);
this.txtAlturaMin.TabIndex = 41;
this.txtAlturaMin.Text = "0,30";
this.txtAlturaMin.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
//
// 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.lblAlturaMin);
this.Controls.Add(this.txtAlturaMin);
this.Controls.Add(this.chbReqAutomatico);
this.Controls.Add(this.lblTamanhoMin);
this.Controls.Add(this.txtTamanhoMin);
this.Controls.Add(this.lblLarguraMin);
this.Controls.Add(this.txtLarguraMin);
this.Controls.Add(this.lblDistanciaMax);
this.Controls.Add(this.lblVelocidade);
this.Controls.Add(this.txtDistanciaMax);
@ -504,8 +526,10 @@
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.Label lblLarguraMin;
private System.Windows.Forms.TextBox txtLarguraMin;
private System.Windows.Forms.CheckBox chbReqAutomatico;
private System.Windows.Forms.Label lblAlturaMin;
private System.Windows.Forms.TextBox txtAlturaMin;
}
}

View File

@ -27,7 +27,7 @@ namespace AgroBase.Forms
AtualizarListaCameras();
ConfigurarGridDeteccoes(dgvAnomalias);
ConfigurarGridDeteccoes(dgvSolo);
tmrLeitura = new AsyncTaskTimerModel("tmrLeitura_VisalWorker", tmrLeitura_Tick, 500, this);
tmrLeitura = new AsyncTaskTimerModel("tmrLeitura_VisalWorker", tmrLeitura_Tick, 10, this);
tmrLeitura.Start();
}
@ -48,10 +48,10 @@ namespace AgroBase.Forms
{
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());
await EnviarComandoReqFrame(VisualWorkerCommandType.Get_RGB_Frame);
await EnviarComandoReqFrame(VisualWorkerCommandType.Get_Heatmap_Frame);
await EnviarComandoAnalise(VisualWorkerCommandType.Get_Analise_Anomalia);
await EnviarComandoAnalise(VisualWorkerCommandType.Get_Analise_Solo);
}
private void btnAtualizarCameras_Click(object sender, EventArgs e)
@ -97,20 +97,23 @@ namespace AgroBase.Forms
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)}";
await EnviarComandoReqFrame(VisualWorkerCommandType.Get_RGB_Frame);
}
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)}";
await EnviarComandoReqFrame(VisualWorkerCommandType.Get_Heatmap_Frame);
}
private async Task EnviarComandoReqFrame(VisualWorkerCommandType comando)
{
await EnviarComando(comando);
VisualWorkerMessageFrameModel frame = comando == VisualWorkerCommandType.Get_RGB_Frame ? visualWorker.DadosLeitura.Leitura.obj.rgb_frame : visualWorker.DadosLeitura.Leitura.obj.heatmap_frame;
PictureBox pic = comando == VisualWorkerCommandType.Get_RGB_Frame ? picRGB : picHeatmap;
Label lbl = comando == VisualWorkerCommandType.Get_RGB_Frame ? lblLeituraRGB : lblLeituraHeatmap;
pic.Image?.Dispose();
try { pic.Image = frame?.image; } catch { }
lbl.Text = $"Leitura em: {FuncoesGlobais.TimestampToDate(frame?.timestamp ?? 0)}";
}
private async void btnReqStatusDispositivo_Click(object sender, EventArgs e)
@ -120,13 +123,17 @@ namespace AgroBase.Forms
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();
await EnviarComandoAnalise(VisualWorkerCommandType.Get_Analise_Anomalia);
}
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(",", ".") });
await EnviarComandoAnalise(VisualWorkerCommandType.Get_Analise_Solo);
}
private async Task EnviarComandoAnalise(VisualWorkerCommandType comando)
{
await EnviarComando(comando, 1000, new { velocidade_ms = txtVelocidade.Text.Replace(",", "."), distancia_max = txtDistanciaMax.Text.Replace(",", "."), largura_min = txtLarguraMin.Text.Replace(",", "."), altura_min = txtAlturaMin.Text.Replace(",", ".") });
AtualizarDadosAnalise();
}

View File

@ -56,7 +56,7 @@ namespace AgroBase.Models.Operadores
{
public long timestamp { get; set; }
public string frame { get; set; }
public Bitmap image { get; set; }
public Bitmap image { get; set; } = null;
public VisualWorkerMessageFrameModel Clone()
{
return new VisualWorkerMessageFrameModel()
@ -195,6 +195,8 @@ namespace AgroBase.Models.Operadores
{
if (frame?.image == null) return null;
try { frame.image.Clone(); } catch { return null; }
Bitmap img = new Bitmap(frame.image);
using (Graphics g = Graphics.FromImage(img))
{
@ -207,6 +209,11 @@ namespace AgroBase.Models.Operadores
Font font = new Font("Arial", 8);
Brush brush = Brushes.Black;
// 🔸 Definir brushes semi-transparentes para preenchimento
Brush brushObstaculo = new SolidBrush(Color.FromArgb(60, Color.Red));
Brush brushErosao = new SolidBrush(Color.FromArgb(60, Color.Blue));
Brush brushLombada = new SolidBrush(Color.FromArgb(60, Color.Orange));
int imgWidth = img.Width;
int imgHeight = img.Height;
@ -217,25 +224,41 @@ namespace AgroBase.Models.Operadores
foreach (var bbox in bboxes)
{
var pen = penObstaculo;
if (bbox.tipo == "erosao") pen = penErosao;
if (bbox.tipo == "lombada") pen = penLombada;
Pen pen = penObstaculo;
Brush fill = brushObstaculo;
if (bbox.tipo == "erosao")
{
pen = penErosao;
fill = brushErosao;
}
if (bbox.tipo == "lombada")
{
pen = penLombada;
fill = brushLombada;
}
var rect = new Rectangle(bbox.bbox[0], bbox.bbox[1], bbox.bbox[2], bbox.bbox[3]);
// 🔸 Preencher com opacidade
g.FillRectangle(fill, rect);
// 🔸 Contorno
g.DrawRectangle(pen, rect);
// 🔸 Label
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
// 🔸 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
// 🔸 Gráfico do perfil de solo
if (solo?.perfil != null && solo.perfil.Count > 1)
{
var perfil = solo.perfil;

View File

@ -157,7 +157,7 @@ namespace AgroBase.Services.Operadores
case VisualWorkerCommandType.Get_Analise_Solo:
{
DadosLeitura.Leitura.obj.analise.timestamp = msg.obj.analise.timestamp;
DadosLeitura.Leitura.obj.analise.solo = msg.obj.analise.solo.Clone();
DadosLeitura.Leitura.obj.analise.solo = msg.obj.analise.solo?.Clone();
break;
}
}

View File

@ -1,10 +1,11 @@
# camera_manager.py
import depthai as dai
import cv2
import numpy as np
import time
from visual_worker.filtros import filtrar_depth_morfologia
# Parâmetros da câmera
RGB_WIDTH, RGB_HEIGHT = 640, 480
#DEPTH_WIDTH, DEPTH_HEIGHT = 320, 240
@ -15,6 +16,7 @@ BASELINE = 0.075 # distância entre câmeras estéreo (em metros)
FOV_H = 1
FOV_V = 1
DISTANCIA_MAXIMA = 3000.0
QTD_SETORES_SOLO = 25
# Variáveis globais
device = None
@ -27,7 +29,9 @@ timestamp_ultimo_depth_frame = None
ultimo_frame_rgb = None
timestamp_ultimo_rgb_frame = None
def iniciar_camera(index=0, distancia_max=3000.0):
def iniciar_camera(index=0, distancia_max=3000.0, qtd_setores = 25):
global device, device_info, rgb_queue, depth_queue, camera_pronta
camera_pronta = False
@ -80,16 +84,17 @@ def iniciar_camera(index=0, distancia_max=3000.0):
print(f"Câmera iniciada: {device_info.name} (ID: {device_info.getMxId()})")
set_camera_calib(distancia_max)
set_camera_calib(distancia_max, qtd_setores)
def set_camera_calib(distancia_max):
def set_camera_calib(distancia_max, qtd_setores):
if not verificar_camera_ativa():
return
global FX, FY, BASELINE, FOV_H, FOV_V, DISTANCIA_MAXIMA
global FX, FY, BASELINE, FOV_H, FOV_V, DISTANCIA_MAXIMA, QTD_SETORES_SOLO
calib = device.readCalibration()
DISTANCIA_MAXIMA = distancia_max
QTD_SETORES_SOLO = qtd_setores
# Obtem matriz intrínseca da câmera LEFT (com resolução padrão 640x400)
intrinsics = calib.getCameraIntrinsics(dai.CameraBoardSocket.LEFT, DEPTH_WIDTH, DEPTH_HEIGHT)
@ -113,7 +118,8 @@ def get_camera_calib():
"baseline": BASELINE,
"fov_h": FOV_H,
"fov_v": FOV_V,
"distancia_maxima": DISTANCIA_MAXIMA
"distancia_maxima": DISTANCIA_MAXIMA,
"qtd_setores_solo": QTD_SETORES_SOLO
}
def verificar_camera_ativa():
@ -159,7 +165,7 @@ def get_rgb_frame():
timestamp_ultimo_rgb_frame = time.time()
return ultimo_frame_rgb, timestamp_ultimo_rgb_frame
def get_depth_frame():
def get_depth_frame_atual():
if not verificar_camera_ativa():
return None, None
global ultimo_frame_depth, timestamp_ultimo_depth_frame
@ -170,8 +176,13 @@ def get_depth_frame():
if frame is not None:
ultimo_frame_depth = frame.getFrame()
timestamp_ultimo_depth_frame = time.time()
#return filtrar_depth_morfologia(ultimo_frame_depth), timestamp_ultimo_depth_frame
return ultimo_frame_depth, timestamp_ultimo_depth_frame
def get_depth_frame():
#return buffer_depth.gerar_depth_filtrado(metodo="mediana"), timestamp_ultimo_depth_frame
return get_depth_frame_atual()
def get_heatmap_frame():
frame, timestamp = get_depth_frame()
if frame is not None:
@ -246,3 +257,5 @@ def get_status_dispositivo():
"fx": FX,
"baseline": BASELINE
}

View File

@ -0,0 +1,43 @@
import cv2
import numpy as np
def filtrar_depth_morfologia(depth_frame, distancia_maxima=3000.0, kernel_size=5):
"""
Aplica filtros morfológicos no depth para eliminar buracos e ruídos pequenos.
"""
# Cria máscara dos pixels inválidos (0 ou muito longe)
mask_invalid = (depth_frame == 0) | (depth_frame > distancia_maxima)
mask_invalid = mask_invalid.astype(np.uint8) * 255
# Kernel para morfologia
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (kernel_size, kernel_size))
# 🔧 Fechamento → Tapa buracos
mask_close = cv2.morphologyEx(mask_invalid, cv2.MORPH_CLOSE, kernel)
# 🔧 Abertura → Remove pontos isolados (opcional, pode comentar se não quiser)
mask_clean = cv2.morphologyEx(mask_close, cv2.MORPH_OPEN, kernel)
# → Encontrar onde deve corrigir
mask_corrigir = (mask_clean == 255)
depth_corrigido = depth_frame.copy()
# 🔥 Para cada pixel a corrigir → pega mediana dos vizinhos válidos
coords = np.argwhere(mask_corrigir)
for y, x in coords:
y1 = max(y - 2, 0)
y2 = min(y + 3, depth_frame.shape[0])
x1 = max(x - 2, 0)
x2 = min(x + 3, depth_frame.shape[1])
vizinhos = depth_frame[y1:y2, x1:x2]
vizinhos_validos = vizinhos[(vizinhos > 0) & (vizinhos < distancia_maxima)]
if len(vizinhos_validos) > 0:
depth_corrigido[y, x] = np.median(vizinhos_validos)
else:
depth_corrigido[y, x] = 0 # Continua inválido se não há vizinhos bons
return depth_corrigido

View File

@ -15,6 +15,8 @@ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
# ─────────────────────────────
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
distancia_max = int(sys.argv[3]) if len(sys.argv) > 3 else 3000
qtd_setores = int(sys.argv[4]) if len(sys.argv) > 4 else 25
# ─────────────────────────────
@ -76,9 +78,10 @@ def ao_receber_comando(topico, payload):
}
elif codigo == Comando.CALIBRAR_GRADES:
from camera_manager import get_depth_frame
from camera_manager import get_depth_frame, get_camera_calib
from processamento.percepcao3d import calibrar
sucesso = calibrar(get_depth_frame, n_frames=50)
calib = get_camera_calib()
sucesso = calibrar(get_depth_frame, calib, n_frames=50)
resposta = {
"calibragem": {
"timestamp": time.time(),
@ -92,8 +95,9 @@ def ao_receber_comando(topico, payload):
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)
largura_min = float(payload.get("largura_min", 0.25))
altura_min = float(payload.get("altura_min", 0.25))
resultado = analisar(TipoAnalise.ANOMALIA, get_depth_frame, calib, velocidade_ms, distancia_max, largura_min, altura_min)
resposta = {
"timestamp": time.time(),
"analise": resultado
@ -105,8 +109,9 @@ def ao_receber_comando(topico, payload):
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)
largura_min = float(payload.get("largura_min", 0.25))
altura_min = float(payload.get("altura_min", 0.25))
resultado = analisar(TipoAnalise.SOLO, get_depth_frame, calib, velocidade_ms, distancia_max, largura_min, altura_min)
resposta = {
"timestamp": time.time(),
"analise": resultado
@ -125,7 +130,7 @@ if __name__ == "__main__":
# 1. Inicia a câmera
from camera_manager import iniciar_camera
iniciar_camera(camera_index)
iniciar_camera(camera_index, distancia_max, qtd_setores)
from mqtt_handler import iniciar_mqtt, enviar_mensagem_script_carregado

View File

@ -1,8 +1,12 @@
import numpy as np
import cv2
from visual_worker.processamento.gestor_deteccoes import GestorDeteccoes
def detectar_anomalias(depth_frame, depth_referencia, limiar, distancia_max, tamanho_min, calib):
# 🔥 Instanciar fora do loop principal (no seu Visual Worker ou processamento):
gestor_anomalias = GestorDeteccoes(frames_minimos=3)
def detectar_anomalias(depth_frame, depth_referencia, limiar, distancia_max, largura_min, altura_min, calib):
# Gera mapa de anomalias
mapa = gerar_mapa_anomalias(depth_frame, depth_referencia, limiar)
mapa_anomalia = aplicar_filtros_mapa_anomalias(mapa)
@ -11,9 +15,12 @@ def detectar_anomalias(depth_frame, depth_referencia, limiar, distancia_max, tam
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)
lista_bboxes_filtrada = filtrar_deteccoes(lista_bboxes, distancia_max, largura_min, altura_min)
return lista_bboxes_filtrada
# 🔥 Aplica o filtro de persistência
lista_filtrada_temporal = gestor_anomalias.atualizar(lista_bboxes_filtrada)
return lista_filtrada_temporal
# Geração do Mapa de Anomalias

View File

@ -1,21 +1,17 @@
import numpy as np
from visual_worker.utils import filtrar_depth_valid, filtrar_deteccoes
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.
"""
def obter_regiao_solo(depth_frame, percentual_altura):
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, :]
return depth_solo, y_inicio, linhas_solo, altura, largura
# Divide em setores laterais
def calcular_profundidades(depth_solo, n_setores, largura, distancia_max):
largura_setor = largura // n_setores
profundidades = []
for i in range(n_setores):
@ -23,26 +19,55 @@ def analisar_solo(depth_frame, percentual_altura, n_setores, limiar_variacao_mm,
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
setor_valido = filtrar_depth_valid(setor, distancia_max)
z = np.median(setor_valido) if len(setor_valido) > 0 else 0
print(f"Setor {i}: profundidade = {z}, Total pixels = {setor.size}, Válidos após filtro = {setor_valido.size}")
profundidades.append(z)
profundidades = np.array(profundidades)
return np.array(profundidades), largura_setor
# Detecta variações abruptas
def calcular_referencia(profundidades):
desvio = np.std(profundidades[profundidades > 0])
if desvio > 150:
referencia = np.mean(profundidades[profundidades > 0])
metodo = "média"
else:
referencia = np.median(profundidades[profundidades > 0])
metodo = "mediana"
print(f"Referência do solo: {referencia} usando {metodo}")
return referencia
def gerar_bbox(x, y, w, h, z_media, calib, largura, altura, tipo, dados_variacao):
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)
centro_x = (x + w / 2) / largura * 100
centro_y = (y + h / 2) / altura * 100
print(f"BBOX → X={x}, W={w}, Distancia={z_media}, Largura_real={largura_real}, Altura_real={altura_real}")
return {
"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),
**dados_variacao
}
def detectar_variacao_lateral(profundidades, largura_setor, y_inicio, linhas_solo, calib, largura, altura, limiar):
lista = []
variacoes = np.abs(np.diff(profundidades))
indices_var = np.where(variacoes > limiar_variacao_mm)[0]
indices = np.where(variacoes > limiar)[0]
# Gera bounding boxes das variações
lista_bboxes = []
for idx in indices_var:
for idx in indices:
x = idx * largura_setor
y = y_inicio
w = largura_setor * 2
@ -50,44 +75,240 @@ def analisar_solo(depth_frame, percentual_altura, n_setores, limiar_variacao_mm,
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)
z_media = (z1 + z2) / 2000.0 # mm → m
# 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)
bbox = gerar_bbox(
x, y, w, h, z_media, calib, largura, altura, tipo,
{"profundidades": (round(z1, 1), round(z2, 1)), "variacao": round(variacao, 1)}
)
lista.append(bbox)
return lista
def detectar_desvios(profundidades, referencia, largura_setor, y_inicio, linhas_solo, calib, largura, altura, limiar):
lista = []
for i, z in enumerate(profundidades):
if z == 0:
continue
desvio = abs(z - referencia)
print(f"Setor {i}: Z = {z}, Ref = {referencia}, Desvio = {desvio}")
if desvio > limiar:
print(f"--> DESVIO DETECTADO no setor {i}, Desvio = {desvio}")
x = i * largura_setor
y = y_inicio
w = largura_setor
h = linhas_solo
tipo = "lombada" if z < referencia else "erosao"
z_media = z / 1000.0 # mm → m
bbox = gerar_bbox(
x, y, w, h, z_media, calib, largura, altura, tipo,
{"profundidades": (round(referencia, 1), round(z, 1)), "variacao": round(desvio, 1)}
)
lista.append(bbox)
return lista
def detectar_agrupado(profundidades, referencia, largura_setor, y_inicio, linhas_solo, calib, largura, altura, limiar):
"""
Detecta anomalias no padrão do perfil do solo.
Agrupa setores que estão fora do padrão (acima ou abaixo da linha) em bounding boxes únicas.
"""
estados = [] # 'normal', 'lombada', 'erosao'
for z in profundidades:
if z == 0:
estados.append('normal')
continue
desvio = z - referencia
if abs(desvio) > limiar:
if desvio < 0:
estados.append('lombada')
else:
estados.append('erosao')
else:
estados.append('normal')
lista_bboxes = []
# Agrupar por estado (lombada ou erosao)
i = 0
while i < len(estados):
estado_atual = estados[i]
if estado_atual == 'normal':
i += 1
continue
inicio = i
while i < len(estados) and estados[i] == estado_atual:
i += 1
fim = i - 1
# Calcular bbox do grupo
x = inicio * largura_setor
w = (fim - inicio + 1) * largura_setor
y = y_inicio
h = linhas_solo
z_media = np.mean([profundidades[j] for j in range(inicio, fim + 1) if profundidades[j] > 0]) / 1000.0
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,
centro_x = (x + w / 2) / largura * 100
centro_y = (y + h / 2) / altura * 100
bbox = {
"tipo": estado_atual,
"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)
})
"profundidades": (round(referencia, 1), round(z_media * 1000, 1)),
"variacao": round(abs(z_media * 1000 - referencia), 1)
}
# Filtra as detecções relevantes
from visual_worker.utils import filtrar_deteccoes
lista_bboxes_filtrada = filtrar_deteccoes(lista_bboxes, distancia_max, tamanho_min)
lista_bboxes.append(bbox)
return lista_bboxes
def detectar_anomalias_com_calibragem(profundidades, grade_referencia, largura_setor, y_inicio, linhas_solo, calib, largura, altura, limiar):
"""
Detecta anomalias comparando diretamente com a grade de referência da calibragem.
utiliza setores válidos (profundidade e referência diferentes de zero).
"""
estados = []
setores_validos = []
# Definir estados para cada setor
for i, (z, ref) in enumerate(zip(profundidades, grade_referencia)):
if z == 0 or ref == 0:
estados.append('normal')
continue
desvio = z - ref
if abs(desvio) > limiar:
estado = 'lombada' if desvio < 0 else 'erosao'
estados.append(estado)
setores_validos.append(i) # 👉 Só adiciona setores válidos e fora do padrão
else:
estados.append('normal')
lista_bboxes = []
if not setores_validos:
return lista_bboxes # 👉 Nenhuma anomalia detectada
# Agrupar setores consecutivos que estão no mesmo estado
limiar_continuidade = 200 # mm
i = 0
while i < len(setores_validos):
idx_inicio = setores_validos[i]
estado_atual = estados[idx_inicio]
profundidade_inicial = profundidades[idx_inicio]
j = i
while j + 1 < len(setores_validos):
idx_atual = setores_validos[j]
idx_proximo = setores_validos[j + 1]
profundidade_atual = profundidades[idx_atual]
profundidade_proximo = profundidades[idx_proximo]
mesma_anomalia = estados[idx_proximo] == estado_atual
continuidade_ok = abs(profundidade_proximo - profundidade_atual) <= limiar_continuidade
if idx_proximo == idx_atual + 1 and mesma_anomalia and continuidade_ok:
j += 1
else:
break
idx_fim = setores_validos[j]
# Calcular bbox do grupo
x = idx_inicio * largura_setor
w = (idx_fim - idx_inicio + 1) * largura_setor
y = y_inicio
h = linhas_solo
# Calcular profundidade média do grupo
valores_validos = [profundidades[k] for k in range(idx_inicio, idx_fim + 1) if profundidades[k] > 0]
if len(valores_validos) == 0:
i = j + 1
continue # Ignora se não houver dados válidos
z_media = np.mean(valores_validos) / 1000.0 # mm → metros
# Calcular tamanho real
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)
# Calcular centro percentual
centro_x = (x + w / 2) / largura * 100
centro_y = (y + h / 2) / altura * 100
# Montar bbox
bbox = {
"tipo": estado_atual,
"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),
"referencia_setor": np.mean([grade_referencia[k] for k in range(idx_inicio, idx_fim + 1) if grade_referencia[k] > 0]),
"profundidade_setor": round(z_media * 1000, 1),
"variacao": round(abs(z_media * 1000 - np.mean([grade_referencia[k] for k in range(idx_inicio, idx_fim + 1) if grade_referencia[k] > 0])), 1)
}
lista_bboxes.append(bbox)
i = j + 1 # Avança para o próximo grupo
return lista_bboxes
def analisar_solo(depth_frame, setores_referencia, percentual_altura, limiar_variacao_mm, distancia_max, largura_min, altura_min, calib):
depth_solo, y_inicio, linhas_solo, altura, largura = obter_regiao_solo(depth_frame, percentual_altura)
profundidades, largura_setor = calcular_profundidades(depth_solo, calib["qtd_setores_solo"], largura, distancia_max * 1000.0)
bboxes_var = detectar_variacao_lateral(
profundidades, largura_setor, y_inicio, linhas_solo,
calib, largura, altura, limiar_variacao_mm
)
bboxes_desvio = detectar_anomalias_com_calibragem(
profundidades, setores_referencia, largura_setor, y_inicio, linhas_solo,
calib, largura, altura, limiar_variacao_mm
)
lista_bboxes = bboxes_var + bboxes_desvio
print(f"Total de bboxes antes do filtro: {len(lista_bboxes)}")
lista_bboxes_filtrada = filtrar_deteccoes(lista_bboxes, distancia_max, largura_min, altura_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)
"n_setores": int(calib["qtd_setores_solo"])
}
return resultado

View File

@ -0,0 +1,50 @@
class GestorDeteccoes:
def __init__(self, frames_minimos=3, tolerancia_px=5, tolerancia_m=0.3):
self.frames_minimos = frames_minimos
self.tolerancia_px = tolerancia_px
self.tolerancia_m = tolerancia_m
self.deteccoes_ativas = {}
def _gerar_id(self, obj):
centro = obj["centro_percentual"]
distancia = obj["distancia_m"]
tipo = obj["tipo"]
chave = (
round(centro[0] / self.tolerancia_px),
round(distancia / self.tolerancia_m),
tipo
)
return chave
def atualizar(self, lista_bboxes):
novas_deteccoes = set()
for obj in lista_bboxes:
chave = self._gerar_id(obj)
novas_deteccoes.add(chave)
if chave in self.deteccoes_ativas:
if self.deteccoes_ativas[chave]["contador"] <= self.frames_minimos:
self.deteccoes_ativas[chave]["contador"] += 1
self.deteccoes_ativas[chave]["dados"] = obj
else:
self.deteccoes_ativas[chave] = {
"contador": 1,
"dados": obj
}
# Zera ou remove quem não apareceu nesse frame
chaves_atuais = set(self.deteccoes_ativas.keys())
for chave in chaves_atuais - novas_deteccoes:
self.deteccoes_ativas[chave]["contador"] -= 1
if self.deteccoes_ativas[chave]["contador"] <= 0:
del self.deteccoes_ativas[chave]
# Retorna só as que passaram do filtro de persistência
return [
dado["dados"]
for dado in self.deteccoes_ativas.values()
if dado["contador"] >= self.frames_minimos
]

View File

@ -2,15 +2,17 @@ import numpy as np
import time
from visual_worker.enums import TipoAnalise
from visual_worker.filtros import filtrar_depth_morfologia
from visual_worker.utils import aplicar_mascara_margens, calcular_threshold_anomalias
# Variáveis globais
depth_referencia = None
setores_referencia = None
calibrado = False
# Calibração
def calibrar(obter_depth_frame, n_frames=50):
global depth_referencia, calibrado
def calibrar(obter_depth_frame, camera_calib, n_frames=50):
global depth_referencia, setores_referencia, calibrado
calibrado = False
@ -33,13 +35,40 @@ def calibrar(obter_depth_frame, n_frames=50):
return False
depth_referencia = np.median(np.stack(lista_frames), axis=0)
salvar_grade_referencia(depth_referencia, camera_calib)
calibrado = True
print("✅ Calibração concluída com sucesso!")
print(f"✅ Calibração concluída com sucesso! Definido {len(setores_referencia)} setores no solo")
return True
def salvar_grade_referencia(depth_frame, calib):
global setores_referencia
def analisar(analise, obter_depth_frame, camera_calib, velocidade_ms, distancia_max, tamanho_min):
global depth_referencia, calibrado
n_setores = calib["qtd_setores_solo"]
distancia_max = calib["distancia_maxima"]
# Divide em setores igual fazemos na análise
altura, largura = depth_frame.shape
largura_setor = largura // n_setores
setores_referencia = []
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_frame[:, x_inicio:x_fim]
from visual_worker.utils import filtrar_depth_valid
setor_valido = filtrar_depth_valid(setor, distancia_max)
if len(setor_valido) > 0:
z = np.median(setor_valido) # ou média se preferir
else:
z = 0
setores_referencia.append(z)
return setores_referencia
def analisar(analise, obter_depth_frame, camera_calib, velocidade_ms, distancia_max, largura_min, altura_min):
global depth_referencia, setores_referencia, calibrado
resultado = {
"timestamp": None,
@ -54,15 +83,16 @@ def analisar(analise, obter_depth_frame, camera_calib, velocidade_ms, distancia_
frame, timestamp = obter_depth_frame()
resultado["timestamp"] = timestamp
depth_frame = aplicar_mascara_margens(frame)
#depth_frame = aplicar_mascara_margens(frame)
frame = filtrar_depth_morfologia(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)
resultado["anomalias"] = detectar_anomalias(frame, depth_referencia, limiar, distancia_max, largura_min, altura_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)
resultado["solo"] = analisar_solo(frame, setores_referencia, 20, limiar, distancia_max, largura_min, 0, camera_calib)
return resultado

View File

@ -22,7 +22,7 @@ def gerar_heatmap(depth_frame, dist_max_mm=3000.0):
heatmap[~mask_valid] = [0, 0, 0]
return heatmap
def filtrar_deteccoes(lista, distancia_maxima=2.0, tamanho_minimo=0.25):
def filtrar_deteccoes(lista, distancia_maxima=2.0, largura_minima=0.0, altura_minima=0.0):
"""
Filtra a lista de detecções baseado em:
- Distância máxima (em metros).
@ -34,7 +34,13 @@ def filtrar_deteccoes(lista, distancia_maxima=2.0, tamanho_minimo=0.25):
if obj["distancia_m"] > distancia_maxima:
continue
if obj["largura_m"] < tamanho_minimo and obj["altura_m"] < tamanho_minimo:
if largura_minima > 0 and altura_minima == 0 and obj["largura_m"] < largura_minima:
continue
if largura_minima == 0 and altura_minima > 0 and obj["altura_m"] < altura_minima:
continue
if obj["largura_m"] < largura_minima and obj["altura_m"] < altura_minima:
continue
lista_filtrada.append(obj)