TesteCaminhao/TesteImagemCaminhao/VehicleDetectionHelper.cs
2025-03-17 11:13:59 -03:00

346 lines
14 KiB
C#

using OpenCvSharp;
using OpenCvSharp.Dnn;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace CircleDetectionApi.Helpers
{
public class VehicleDetectionHelper
{
private readonly Net _net;
private readonly string[] _objectClasses;
private readonly string[] _vehicleClasses = new[] { "car", "motorbike", "bus", "truck", "bicycle" };
public VehicleDetectionHelper(string modelPath, string configPath, string classesPath)
{
// Carregar o modelo YOLO para detecção de objetos
_net = CvDnn.ReadNetFromDarknet(configPath, modelPath);
// Usar CPU ou GPU dependendo da disponibilidade
_net.SetPreferableBackend(Backend.DEFAULT);
_net.SetPreferableTarget(Target.CPU);
// Carregar as classes do COCO dataset
_objectClasses = File.ReadAllLines(classesPath);
}
public (bool isVehicle, string vehicleClass, Rect vehicleBox) DetectVehicle(Mat image)
{
// Obter as dimensões da imagem
int height = image.Rows;
int width = image.Cols;
// Criar um blob a partir da imagem para alimentar o modelo
using var blob = CvDnn.BlobFromImage(
image,
1 / 255.0,
new Size(416, 416),
new Scalar(0, 0, 0),
swapRB: true,
crop: false);
// Definir a entrada para a rede
_net.SetInput(blob);
// Obter os nomes das camadas de saída
var outNames = _net.GetUnconnectedOutLayersNames();
// Executar a inferência
var outs = new List<Mat>();
_net.Forward(outs, outNames);
// Listas para armazenar os resultados da detecção
var boxes = new List<Rect>();
var confidences = new List<float>();
var classIds = new List<int>();
// Para cada camada de saída
foreach (var mat in outs)
{
// Para cada detecção
for (int i = 0; i < mat.Rows; i++)
{
// Extrair as pontuações de confiança para cada classe
var scores = Mat.FromPixelData(1, _objectClasses.Length, MatType.CV_32F,
mat.Ptr(i, 5));
// Obter o índice da classe com maior pontuação
Point classIdPoint = new Point();
double confidence = 0;
Cv2.MinMaxLoc(scores, out _, out confidence, out _, out classIdPoint);
// Usar um limiar de confiança mais baixo para veículos
if (confidence > 0.3 && _vehicleClasses.Contains(_objectClasses[classIdPoint.X]))
{
// Calcular as coordenadas da caixa delimitadora
int centerX = (int)(mat.At<float>(i, 0) * width);
int centerY = (int)(mat.At<float>(i, 1) * height);
int boxWidth = (int)(mat.At<float>(i, 2) * width);
int boxHeight = (int)(mat.At<float>(i, 3) * height);
int left = centerX - boxWidth / 2;
int top = centerY - boxHeight / 2;
// Armazenar os resultados
boxes.Add(new Rect(left, top, boxWidth, boxHeight));
confidences.Add((float)confidence);
classIds.Add(classIdPoint.X);
}
}
}
// Se nenhum veículo foi detectado pelo YOLO, tentar métodos alternativos
if (boxes.Count == 0)
{
// Método alternativo: se a imagem tiver uma proporção típica de veículo e ocupar
// a maior parte do quadro, consideramos como um possível veículo
double aspectRatio = (double)width / height;
// Veículos geralmente têm proporção entre ~0.5 (caminhão) e ~2.5 (carro lateral)
if (aspectRatio >= 0.5 && aspectRatio <= 3.0)
{
// Criar uma caixa com margens de 10% em cada lado
int marginX = (int)(width * 0.1);
int marginY = (int)(height * 0.1);
// Se a proporção for mais próxima de caminhões (~1.5-3.0 horizontalmente)
string vehicleGuess = (aspectRatio >= 1.5 && aspectRatio <= 3.0) ? "truck" : "car";
return (true, vehicleGuess, new Rect(
marginX,
marginY,
width - 2 * marginX,
height - 2 * marginY
));
}
return (false, "Não detectado", new Rect());
}
// Aplicar supressão não-máxima para eliminar detecções redundantes
int[] indices;
CvDnn.NMSBoxes(boxes, confidences, 0.3f, 0.4f, out indices);
// Se nenhuma caixa sobreviver após NMS, usar o método alternativo
if (indices.Length == 0)
{
// Mesmo método alternativo descrito acima
double aspectRatio = (double)width / height;
if (aspectRatio >= 0.5 && aspectRatio <= 3.0)
{
int marginX = (int)(width * 0.1);
int marginY = (int)(height * 0.1);
string vehicleGuess = (aspectRatio >= 1.5 && aspectRatio <= 3.0) ? "truck" : "car";
return (true, vehicleGuess, new Rect(
marginX,
marginY,
width - 2 * marginX,
height - 2 * marginY
));
}
return (false, "Não detectado", new Rect());
}
// Obter a caixa com maior confiança
int maxConfidenceIndex = 0;
float maxConfidence = 0;
for (int i = 0; i < indices.Length; i++)
{
int idx = indices[i];
if (confidences[idx] > maxConfidence)
{
maxConfidence = confidences[idx];
maxConfidenceIndex = idx;
}
}
int bestClassId = classIds[indices[maxConfidenceIndex]];
string vehicleClass = _objectClasses[bestClassId];
// Obter a caixa delimitadora do veículo com maior confiança
var vehicleBox = boxes[indices[maxConfidenceIndex]];
// Ajustar a caixa para garantir que esteja dentro dos limites da imagem
vehicleBox.X = Math.Max(0, vehicleBox.X);
vehicleBox.Y = Math.Max(0, vehicleBox.Y);
vehicleBox.Width = Math.Min(width - vehicleBox.X, vehicleBox.Width);
vehicleBox.Height = Math.Min(height - vehicleBox.Y, vehicleBox.Height);
return (true, vehicleClass, vehicleBox);
}
// Método para desenhar a caixa delimitadora e informações do veículo na imagem
public void DrawVehicleBox(Mat image, Rect box, string vehicleClass)
{
if (box.Width <= 0 || box.Height <= 0)
return;
// Desenhar a caixa delimitadora
Cv2.Rectangle(image, box, new Scalar(0, 255, 0), 2);
// Adicionar o texto da classe
var textPoint = new Point(box.X, box.Y - 10);
Cv2.PutText(image, vehicleClass, textPoint, HersheyFonts.HersheySimplex, 0.7, new Scalar(0, 255, 0), 2);
}
// Método para detectar eixos em um veículo (não apenas rodas individuais)
public static List<(Point Center, int Radius)> DetectWheelsAndAxles(Mat image)
{
// Converter para escala de cinza
using var grayMat = new Mat();
Cv2.CvtColor(image, grayMat, ColorConversionCodes.BGR2GRAY);
// Aplicar blur para reduzir ruído
using var blurredMat = new Mat();
Cv2.MedianBlur(grayMat, blurredMat, 5);
// Aplicar equalização de histograma para melhorar o contraste
using var equalizedMat = new Mat();
Cv2.EqualizeHist(blurredMat, equalizedMat);
// Lista para armazenar resultados de múltiplos métodos
var allCircles = new List<(Point Center, int Radius)>();
// Método 1: HoughCircles com parâmetros otimizados para rodas de veículos
CircleSegment[] circles = Cv2.HoughCircles(
equalizedMat,
HoughModes.Gradient,
1,
100, // Distância mínima entre centros (aumentada para evitar detecções próximas)
param1: 150, // Limite superior para detector de bordas Canny (reduzido)
param2: 30, // Limite para detecção de centros (reduzido para detectar mais círculos)
minRadius: 20, // Raio mínimo aumentado para focar em rodas maiores
maxRadius: 150 // Raio máximo aumentado para rodas de caminhão
);
if (circles != null && circles.Length > 0)
{
foreach (var circle in circles)
{
allCircles.Add((new Point((int)circle.Center.X, (int)circle.Center.Y), (int)circle.Radius));
}
}
// Método 2: Detecção baseada em bordas e contornos
using var edges = new Mat();
Cv2.Canny(equalizedMat, edges, 50, 150);
// Dilatação para conectar bordas próximas
using var dilated = new Mat();
var kernel = Cv2.GetStructuringElement(MorphShapes.Ellipse, new Size(5, 5));
Cv2.Dilate(edges, dilated, kernel);
// Encontrar contornos
Point[][] contours;
HierarchyIndex[] hierarchy;
Cv2.FindContours(dilated, out contours, out hierarchy, RetrievalModes.External, ContourApproximationModes.ApproxSimple);
// Analisar contornos para encontrar possíveis rodas
foreach (var contour in contours)
{
// Calcular área e perímetro
double area = Cv2.ContourArea(contour);
double perimeter = Cv2.ArcLength(contour, true);
// Ignorar contornos muito pequenos
if (area < 500) continue;
// Calcular circularidade: 4π*área/perimetro²
// Para um círculo perfeito, este valor é 1.0
double circularity = (4 * Math.PI * area) / (perimeter * perimeter);
// Se for razoavelmente circular
if (circularity > 0.5)
{
// Encontrar a bounding box
var boundingRect = Cv2.BoundingRect(contour);
// Calcular o centro e o raio aproximado
var center = new Point(
boundingRect.X + boundingRect.Width / 2,
boundingRect.Y + boundingRect.Height / 2
);
int radius = Math.Max(boundingRect.Width, boundingRect.Height) / 2;
// Verificar se já existe um círculo próximo (evitar duplicatas)
bool isDuplicate = false;
foreach (var existingCircle in allCircles)
{
// Calcular distância entre centros
double distance = Math.Sqrt(
Math.Pow(existingCircle.Center.X - center.X, 2) +
Math.Pow(existingCircle.Center.Y - center.Y, 2)
);
// Se a distância for menor que o raio, considerar como duplicata
if (distance < radius)
{
isDuplicate = true;
break;
}
}
// Se não for duplicata, adicionar à lista
if (!isDuplicate)
{
allCircles.Add((center, radius));
}
}
}
// Método especial para detecção de eixos em caminhões:
// Procurar rodas alinhadas horizontalmente com alturas similares
var possibleAxles = new List<(Point Center, int Radius)>();
// Se temos mais de uma roda, analisar possíveis eixos
if (allCircles.Count > 1)
{
// Agrupar rodas por altura similar (tolerância de 10% da altura da imagem)
var heightGroups = allCircles
.GroupBy(c => c.Center.Y / (image.Height * 0.1))
.Where(g => g.Count() >= 2)
.ToList();
foreach (var group in heightGroups)
{
// Ordenar por posição X
var orderedCircles = group.OrderBy(c => c.Center.X).ToList();
// Considerar pares de rodas como possíveis eixos
for (int i = 0; i < orderedCircles.Count - 1; i++)
{
var left = orderedCircles[i];
var right = orderedCircles[i + 1];
// Calcular distância entre as rodas
double distance = right.Center.X - left.Center.X;
// Se a distância for razoável para um eixo (não muito pequena nem muito grande)
if (distance > 50 && distance < image.Width * 0.8)
{
// Adicionar as duas rodas como um eixo
possibleAxles.Add(left);
possibleAxles.Add(right);
}
}
}
}
// Se encontramos possíveis eixos, usar esses resultados
if (possibleAxles.Count > 0)
{
return possibleAxles;
}
return allCircles;
}
}
}