346 lines
14 KiB
C#
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;
|
|
}
|
|
}
|
|
} |