feat: versáo inicial

This commit is contained in:
Ricardo Carneiro 2025-03-17 11:13:59 -03:00
commit bfafb85d1c
28 changed files with 4296 additions and 0 deletions

398
.gitignore vendored Normal file
View File

@ -0,0 +1,398 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
*.sln.iml

View File

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.3" />
<PackageReference Include="Microsoft.Extensions.ML" Version="3.0.1" />
<PackageReference Include="Microsoft.ML" Version="3.0.1" />
<PackageReference Include="Microsoft.ML.ImageAnalytics" Version="3.0.1" />
<PackageReference Include="Microsoft.ML.TensorFlow" Version="3.0.1" />
<PackageReference Include="Microsoft.ML.Vision" Version="3.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="SciSharp.TensorFlow.Redist" Version="2.3.1" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,316 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.ML;
using Microsoft.ML.Data;
using Microsoft.ML.Vision;
namespace VehicleModelTraining
{
class Program
{
static void Main(string[] args)
{
// Caminho para os diretórios de treinamento (organizados por categoria)
string datasetFolder = "C:\\Users\\USER\\Pictures\\Veiculos";
string workspaceFolder = "C:\\Users\\USER\\Pictures\\WorkSpace";
string outputModelPath = "C:\\Users\\USER\\Pictures\\neMmodel.zip";
// Inicializar MLContext
MLContext mlContext = new MLContext(seed: 1);
var preprocessingPipeline =
mlContext
.Transforms
.LoadRawImageBytes(
outputColumnName: "ImageBytes",
imageFolder: datasetFolder,
inputColumnName: "ImagePath")
.Append(
mlContext
.Transforms
.Conversion
.MapValueToKey(
inputColumnName: "Label",
outputColumnName: "LabelAsKey"
)
);
// Carregar dados - usamos um método mais direto aqui
var images = LoadImagesFromDirectory(datasetFolder);
Console.WriteLine($"Número total de imagens carregadas: {images.Count}");
// Verificar se há imagens
if (images.Count == 0)
{
Console.WriteLine("Nenhuma imagem foi carregada. Verifique o caminho e as extensões suportadas.");
return;
}
// Criar o DataFrame
IDataView imageData = mlContext.Data.LoadFromEnumerable(images);
IDataView shuffledImageDataView = mlContext
.Data
.ShuffleRows(imageData, 0);
Console.WriteLine("Pre processing images....");
var timestamp = DateTime.Now;
// Pre Process images and split into train/test/validation
IDataView preProcessedImageDataView = preprocessingPipeline
.Fit(shuffledImageDataView)
.Transform(shuffledImageDataView);
Console.WriteLine($"Image preprocessing done in {(DateTime.Now - timestamp).TotalSeconds} seconds");
Console.WriteLine();
var firstSplit = mlContext
.Data
.TrainTestSplit(data: preProcessedImageDataView,
testFraction: 0.3,
seed: 0);
var trainSet = firstSplit.TrainSet;
var secondSplit = mlContext
.Data
.TrainTestSplit(data: firstSplit.TestSet,
testFraction: 0.5, seed: 0);
var validationSet = secondSplit.TrainSet;
var testSet = secondSplit.TestSet;
var classifierOptions = new ImageClassificationTrainer.Options()
{
FeatureColumnName = "ImageBytes",
LabelColumnName = "LabelAsKey",
Arch = ImageClassificationTrainer.Architecture.InceptionV3,
//Arch = ImageClassificationTrainer.Architecture.MobilenetV2,
//Arch = ImageClassificationTrainer.Architecture.ResnetV250,
TestOnTrainSet = false,
ValidationSet = validationSet,
ReuseTrainSetBottleneckCachedValues = true,
ReuseValidationSetBottleneckCachedValues = true,
WorkspacePath = workspaceFolder,
MetricsCallback = Console.WriteLine
};
var trainingPipeline = mlContext
.MulticlassClassification
.Trainers
.ImageClassification(classifierOptions)
.Append(mlContext
.Transforms
.Conversion
.MapKeyToValue("PredictedLabel"));
Console.WriteLine("Training model....");
timestamp = DateTime.Now;
var trainedModel = trainingPipeline.Fit(trainSet);
Console.WriteLine($"Model training done in {(DateTime.Now - timestamp).TotalSeconds} seconds");
Console.WriteLine();
Console.WriteLine("Calculating metrics...");
IDataView evaluationData = trainedModel.Transform(testSet);
var metrics = mlContext
.MulticlassClassification
.Evaluate(evaluationData, "LabelAsKey");
Console.WriteLine($"LogLoss: {metrics.LogLoss}");
Console.WriteLine($"LogLossReduction: {metrics.LogLossReduction}");
Console.WriteLine($"MicroAccuracy: {metrics.MicroAccuracy}");
Console.WriteLine($"MacroAccuracy: {metrics.MacroAccuracy}");
Console.WriteLine();
Console.WriteLine($"{metrics.ConfusionMatrix.GetFormattedConfusionTable()}");
Console.WriteLine();
Console.WriteLine("Saving model");
Directory.CreateDirectory("Model");
mlContext.Model.Save(trainedModel, preProcessedImageDataView.Schema, outputModelPath);
//Console.WriteLine();
//// Exibir informações das categorias
//var categoryCount = images.GroupBy(x => x.Label).Select(g => new { Category = g.Key, Count = g.Count() }).ToList();
//foreach (var category in categoryCount)
//{
// Console.WriteLine($"Categoria: {category.Category}, Contagem: {category.Count}");
//}
//// Dividir em dados de treinamento e teste
//var dataSplit = mlContext.Data.TrainTestSplit(imageData, testFraction: 0.2);
//var trainSet = dataSplit.TrainSet;
//var testSet = dataSplit.TestSet;
//// IMPORTANTE: Para o ImageClassificationTrainer, vamos usar a abordagem recomendada
//var classifierOptions = new ImageClassificationTrainer.Options()
//{
// FeatureColumnName = "Image", // Alterado para 'Image' em vez de 'Input'
// LabelColumnName = "LabelKey",
// ValidationSet = testSet,
// Arch = ImageClassificationTrainer.Architecture.ResnetV2101,
// Epoch = 10,
// BatchSize = 5,
// LearningRate = 0.01f,
// MetricsCallback = (metrics) => Console.WriteLine(metrics),
// WorkspacePath = Path.Combine(Environment.CurrentDirectory, "workspace"),
// //TrainDatasetPath = Path.Combine(Environment.CurrentDirectory, "trainFolder"),
// //TestDatasetPath = Path.Combine(Environment.CurrentDirectory, "testFolder"),
// //OutputTensorName = "softmax2_pre_activation", // Nome da camada de saída do Resnet
// ReuseValidationSetBottleneckCachedValues = true,
// ReuseTrainSetBottleneckCachedValues = true
//};
//try
//{
// // Criar diretórios necessários
// EnsureDirectoryExists(classifierOptions.WorkspacePath);
// //EnsureDirectoryExists(classifierOptions.TrainDatasetPath);
// //EnsureDirectoryExists(classifierOptions.TestDatasetPath);
// // Construir pipeline com passos específicos para o ImageClassificationTrainer
// var pipeline = mlContext.Transforms.LoadImages(
// outputColumnName: "Image",
// imageFolder: "",
// inputColumnName: nameof(ImageData.ImagePath))
// .Append(mlContext.Transforms.Conversion.MapValueToKey(
// outputColumnName: "LabelKey",
// inputColumnName: nameof(ImageData.Label)))
// .Append(mlContext.MulticlassClassification.Trainers.ImageClassification(classifierOptions))
// .Append(mlContext.Transforms.Conversion.MapKeyToValue(
// outputColumnName: "PredictedLabel",
// inputColumnName: "PredictedLabel"));
// Console.WriteLine("\nIniciando o treinamento do modelo...");
// // Treinar o modelo
// ITransformer trainedModel = pipeline.Fit(trainSet);
// Console.WriteLine("Treinamento concluído com sucesso!");
// // Salvar o modelo
// mlContext.Model.Save(trainedModel, trainSet.Schema, outputModelPath);
// Console.WriteLine($"Modelo salvo em: {outputModelPath}");
// // Avaliar o modelo
// EvaluateModel(mlContext, testSet, trainedModel);
// // Opcional: Remover diretórios temporários após o uso
// //CleanupDirectory(classifierOptions.WorkspacePath);
// //CleanupDirectory(classifierOptions.TrainDatasetPath);
// //CleanupDirectory(classifierOptions.TestDatasetPath);
//}
//catch (Exception ex)
//{
// Console.WriteLine($"Erro durante o treinamento: {ex.Message}");
// Console.WriteLine($"Detalhes: {ex.StackTrace}");
// if (ex.InnerException != null)
// {
// Console.WriteLine($"Inner Exception: {ex.InnerException.Message}");
// Console.WriteLine($"Inner Stack Trace: {ex.InnerException.StackTrace}");
// }
//}
}
private static void EnsureDirectoryExists(string path)
{
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
Console.WriteLine($"Diretório criado: {path}");
}
}
private static void CleanupDirectory(string path)
{
if (Directory.Exists(path))
{
try
{
Directory.Delete(path, recursive: true);
Console.WriteLine($"Diretório removido: {path}");
}
catch (Exception ex)
{
Console.WriteLine($"Erro ao remover diretório {path}: {ex.Message}");
}
}
}
// Avaliar o desempenho do modelo nos dados de teste
private static void EvaluateModel(MLContext mlContext, IDataView testData, ITransformer trainedModel)
{
IDataView predictions = trainedModel.Transform(testData);
var metrics = mlContext.MulticlassClassification.Evaluate(predictions, labelColumnName: "LabelKey", predictedLabelColumnName: "PredictedLabel");
Console.WriteLine($"Acurácia micro: {metrics.MicroAccuracy:0.###}");
Console.WriteLine($"Acurácia macro: {metrics.MacroAccuracy:0.###}");
Console.WriteLine($"Pontuação de perda de log: {metrics.LogLoss:#.###}");
// Exibir matriz de confusão
Console.WriteLine("Matriz de confusão:");
Console.WriteLine(metrics.ConfusionMatrix.GetFormattedConfusionTable());
}
// Carregar imagens do diretório
private static List<ImageData> LoadImagesFromDirectory(string folder)
{
List<ImageData> images = new List<ImageData>();
try
{
var files = Directory.GetFiles(folder, "*", searchOption: SearchOption.AllDirectories);
var imageFiles = files.Where(file =>
Path.GetExtension(file).ToLower() == ".jpg" ||
Path.GetExtension(file).ToLower() == ".jpeg" ||
Path.GetExtension(file).ToLower() == ".png" ||
Path.GetExtension(file).ToLower() == ".webp").ToList();
Console.WriteLine($"Encontrados {imageFiles.Count} arquivos de imagem em {folder}");
foreach (var file in imageFiles)
{
// Verificar se o arquivo existe
if (!File.Exists(file))
{
Console.WriteLine($"Arquivo não encontrado: {file}");
continue;
}
// Determinar a categoria com base no nome da pasta pai
var label = Directory.GetParent(file).Name;
Console.WriteLine($"Processando imagem: {file}, Label: {label}");
images.Add(new ImageData()
{
ImagePath = file,
Label = label
});
}
}
catch (Exception ex)
{
Console.WriteLine($"Erro ao carregar imagens: {ex.Message}");
}
return images;
}
// Classe para representar dados de imagem
public class ImageData
{
public string ImagePath { get; set; }
public string Label { get; set; }
public byte[] ImageBytes { get; set; }
}
}
}

0
README.md Normal file
View File

31
TesteCaminhao.sln Normal file
View File

@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.10.35122.118
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TesteImagemCaminhao", "TesteImagemCaminhao\TesteImagemCaminhao.csproj", "{14E2FF5A-D3BC-4E6D-A421-2DAC489A2363}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MLTrainingVeiculos", "MLTrainingVeiculos\MLTrainingVeiculos.csproj", "{43A36E3B-3D8A-4F31-A959-2310B510957E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{14E2FF5A-D3BC-4E6D-A421-2DAC489A2363}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{14E2FF5A-D3BC-4E6D-A421-2DAC489A2363}.Debug|Any CPU.Build.0 = Debug|Any CPU
{14E2FF5A-D3BC-4E6D-A421-2DAC489A2363}.Release|Any CPU.ActiveCfg = Release|Any CPU
{14E2FF5A-D3BC-4E6D-A421-2DAC489A2363}.Release|Any CPU.Build.0 = Release|Any CPU
{43A36E3B-3D8A-4F31-A959-2310B510957E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{43A36E3B-3D8A-4F31-A959-2310B510957E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{43A36E3B-3D8A-4F31-A959-2310B510957E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{43A36E3B-3D8A-4F31-A959-2310B510957E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {62092363-AC4B-4A2E-B79F-E10DBC35E242}
EndGlobalSection
EndGlobal

44
TesteCaminhao/Program.cs Normal file
View File

@ -0,0 +1,44 @@
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
app.MapGet("/weatherforecast", () =>
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
})
.WithName("GetWeatherForecast")
.WithOpenApi();
app.Run();
internal record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}

View File

@ -0,0 +1,41 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:58312",
"sslPort": 0
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5115",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7210;http://localhost:5115",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.7" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,6 @@
@TesteCaminhao_HostAddress = http://localhost:5115
GET {{TesteCaminhao_HostAddress}}/weatherforecast/
Accept: application/json
###

View File

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@ -0,0 +1,389 @@
using Microsoft.AspNetCore.Mvc;
using OpenCvSharp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using CircleDetectionApi.Helpers;
namespace CircleDetectionApi.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class Circle1DetectionController : ControllerBase
{
private readonly IWebHostEnvironment _environment;
private readonly VehicleDetectionHelper _vehicleDetector;
public Circle1DetectionController(IWebHostEnvironment environment)
{
_environment = environment;
// Caminho para os arquivos do modelo YOLO
var modelPath = Path.Combine(environment.ContentRootPath, "Models", "yolov4.weights");
var configPath = Path.Combine(environment.ContentRootPath, "Models", "yolov4.cfg");
var classesPath = Path.Combine(environment.ContentRootPath, "Models", "coco.names");
// Inicializar o detector de veículos
_vehicleDetector = new VehicleDetectionHelper(modelPath, configPath, classesPath);
}
[HttpPost]
public async Task<IActionResult> DetectCircles(IFormFile image, bool detectVehicle)
{
// Verificar se a imagem foi enviada
if (image == null || image.Length == 0)
{
return BadRequest(new { error = "Nenhuma imagem foi enviada" });
}
// Verificar o tipo do arquivo
var allowedExtensions = new[] { ".jpg", ".jpeg", ".png", ".bmp", ".tiff" };
var extension = Path.GetExtension(image.FileName).ToLowerInvariant();
if (!allowedExtensions.Contains(extension))
{
return BadRequest(new { error = "Tipo de arquivo não suportado. Por favor, envie uma imagem JPG, PNG, BMP ou TIFF." });
}
try
{
// Ler a imagem em um MemoryStream
using var memoryStream = new MemoryStream();
await image.CopyToAsync(memoryStream);
memoryStream.Position = 0;
// Carregar a imagem usando OpenCvSharp
using var mat = Mat.FromImageData(memoryStream.ToArray(), ImreadModes.Color);
// Variáveis para armazenar informações sobre o veículo
bool isVehicle = false;
string vehicleClass = "Não detectado";
Rect vehicleBox = new Rect();
// Detectar veículo (se solicitado)
if (detectVehicle)
{
(isVehicle, vehicleClass, vehicleBox) = _vehicleDetector.DetectVehicle(mat);
}
// Converter para escala de cinza
using var grayMat = new Mat();
Cv2.CvtColor(mat, grayMat, ColorConversionCodes.BGR2GRAY);
// Aplicar blur para reduzir ruído
using var blurredMat = new Mat();
Cv2.MedianBlur(grayMat, blurredMat, 5);
// Usar o algoritmo de Hough Circle para detectar círculos
CircleSegment[] circles = Cv2.HoughCircles(
blurredMat,
HoughModes.Gradient,
1, // Razão entre resolução da imagem e acumulador
240, // Distância mínima entre centros de círculos detectados
param1: 250, // Limite superior para detector de bordas Canny
param2: 50, // Limite para detecção de centros
minRadius: 5, // Raio mínimo do círculo
maxRadius: 200 // Raio máximo do círculo
);
// Também tentar o método de detecção avançado
var wheelDetections = VehicleDetectionHelper.DetectWheelsAndAxles(mat);
// Criar lista para armazenar os círculos detectados
var detectedCircles = new List<object>();
// Criar uma cópia colorida da imagem para desenhar os círculos
using var colorMat = mat.Clone(); // Simplesmente clonar a imagem original
// Variável para armazenar contagem de rodas
int wheelCount = 0;
int estimatedAxles = 0; // Adicionar a variável estimatedAxles
if (circles != null && circles.Length > 0)
{
foreach (var circle in circles)
{
// Adicionar à lista de resultados
detectedCircles.Add(new
{
center_x = (int)circle.Center.X,
center_y = (int)circle.Center.Y,
radius = (int)circle.Radius
});
// Desenhar o círculo (como no código Python)
Cv2.Circle(colorMat,
(int)circle.Center.X,
(int)circle.Center.Y,
(int)circle.Radius,
new Scalar(0, 255, 0),
2);
// Desenhar o centro do círculo (como no código Python)
Cv2.Circle(colorMat,
(int)circle.Center.X,
(int)circle.Center.Y,
2,
new Scalar(0, 0, 255),
1);
}
// Atualizar contagem de rodas
wheelCount = detectedCircles.Count;
// Não precisamos mais deste bloco, pois já temos a classe do veículo
}
// Se o parâmetro saveImage for true, salve a imagem marcada e retorne o URL
bool saveImage = HttpContext.Request.Query.ContainsKey("saveImage") &&
bool.TryParse(HttpContext.Request.Query["saveImage"], out var saveValue) &&
saveValue;
string imageUrl = null;
if (saveImage)
{
// Criar diretório para imagens se não existir
var imagesDir = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "images");
Directory.CreateDirectory(imagesDir);
// Salvar a imagem com os círculos marcados
var filename = $"circles_{DateTime.Now:yyyyMMddHHmmss}.jpg";
var filePath = Path.Combine(imagesDir, filename);
Cv2.ImWrite(filePath, colorMat);
// Gerar URL para a imagem
imageUrl = $"{Request.Scheme}://{Request.Host}/images/{filename}";
}
// Desenhar a caixa delimitadora do veículo, se detectado
if (detectVehicle && isVehicle)
{
_vehicleDetector.DrawVehicleBox(colorMat, vehicleBox, vehicleClass);
}
// Determinar o tipo de veículo baseado na classe detectada e no número de rodas
string vehicleType = GetVehicleTypeFromClassAndWheels(vehicleClass, wheelCount);
// Se não detectou nenhuma roda mas identificou um caminhão, estimar um número padrão (4 ou 6)
if (wheelCount == 0 && vehicleClass.ToLower() == "truck")
{
wheelCount = 4; // Valor padrão conservador
}
// Retornar o resultado como JSON
return Ok(new
{
count = detectedCircles.Count,
estimatedWheels = wheelCount,
estimatedAxles = estimatedAxles,
circles = detectedCircles,
imageUrl = imageUrl,
vehicleInfo = detectVehicle ? new
{
isVehicle = isVehicle,
detectedClass = vehicleClass,
suggestedType = vehicleType,
wheelCount = wheelCount
} : null
});
}
catch (Exception ex)
{
return StatusCode(500, new { error = $"Erro ao processar a imagem: {ex.Message}" });
}
}
[HttpGet("file")]
public IActionResult DetectCirclesFromFile([FromQuery] string filename)
{
if (string.IsNullOrEmpty(filename))
{
return BadRequest(new { error = "Nome do arquivo não especificado" });
}
// Obter o caminho do arquivo
var imagesPath = Path.Combine(_environment.ContentRootPath, "Images");
var filePath = Path.Combine(imagesPath, filename);
// Verificar se o arquivo existe
if (!System.IO.File.Exists(filePath))
{
return NotFound(new { error = $"Arquivo {filename} não encontrado" });
}
try
{
// Carregar a imagem usando OpenCvSharp
using var mat = Cv2.ImRead(filePath, ImreadModes.Color);
// Verificar se a detecção de veículos está habilitada
bool detectVehicle = HttpContext.Request.Query.ContainsKey("detectVehicle") &&
bool.TryParse(HttpContext.Request.Query["detectVehicle"], out var detectValue) &&
detectValue;
// Variáveis para armazenar informações sobre o veículo
bool isVehicle = false;
string vehicleClass = "Não detectado";
Rect vehicleBox = new Rect();
// Detectar veículo (se solicitado)
if (detectVehicle)
{
(isVehicle, vehicleClass, vehicleBox) = _vehicleDetector.DetectVehicle(mat);
}
// Converter para escala de cinza
using var grayMat = new Mat();
Cv2.CvtColor(mat, grayMat, ColorConversionCodes.BGR2GRAY);
// Aplicar blur para reduzir ruído (medianBlur como no código original)
using var blurredMat = new Mat();
Cv2.MedianBlur(grayMat, blurredMat, 5);
// Usar o algoritmo de Hough Circle para detectar círculos
CircleSegment[] circles = Cv2.HoughCircles(
blurredMat,
HoughModes.Gradient,
1, // Razão entre resolução da imagem e acumulador
240, // Distância mínima entre centros
param1: 250, // Limite superior para detector de bordas Canny
param2: 50, // Limite para detecção de centros
minRadius: 5, // Raio mínimo do círculo
maxRadius: 200 // Raio máximo do círculo
);
// Preparar o resultado
var detectedCircles = new List<object>();
// Criar uma cópia colorida da imagem para desenhar os círculos
using var colorMat = new Mat();
Cv2.CvtColor(blurredMat, colorMat, ColorConversionCodes.GRAY2BGR);
// Variável para armazenar contagem de rodas
int wheelCount = 0;
if (circles != null && circles.Length > 0)
{
foreach (var circle in circles)
{
// Adicionar à lista de resultados
detectedCircles.Add(new
{
center_x = (int)circle.Center.X,
center_y = (int)circle.Center.Y,
radius = (int)circle.Radius
});
// Desenhar o círculo
Cv2.Circle(colorMat,
(int)circle.Center.X,
(int)circle.Center.Y,
(int)circle.Radius,
new Scalar(0, 255, 0),
2);
// Desenhar o centro do círculo
Cv2.Circle(colorMat,
(int)circle.Center.X,
(int)circle.Center.Y,
2,
new Scalar(0, 0, 255),
1);
}
// Atualizar contagem de rodas
wheelCount = detectedCircles.Count;
// Não precisamos mais deste bloco, pois já temos a classe do veículo
}
// Se o parâmetro saveImage for true, salve a imagem marcada e retorne o URL
bool saveImage = HttpContext.Request.Query.ContainsKey("saveImage") &&
bool.TryParse(HttpContext.Request.Query["saveImage"], out var saveValue) &&
saveValue;
string imageUrl = null;
if (saveImage)
{
// Criar diretório para imagens se não existir
var imagesDir = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "images");
Directory.CreateDirectory(imagesDir);
// Salvar a imagem com os círculos marcados
var outputFilename = $"circles_{Path.GetFileNameWithoutExtension(filename)}_{DateTime.Now:yyyyMMddHHmmss}.jpg";
var outputPath = Path.Combine(imagesDir, outputFilename);
Cv2.ImWrite(outputPath, colorMat);
// Gerar URL para a imagem
imageUrl = $"{Request.Scheme}://{Request.Host}/images/{outputFilename}";
}
// Desenhar a caixa delimitadora do veículo, se detectado
if (detectVehicle && isVehicle)
{
_vehicleDetector.DrawVehicleBox(colorMat, vehicleBox, vehicleClass);
}
// Determinar o tipo de veículo baseado na classe detectada e no número de rodas
string vehicleType = GetVehicleTypeFromClassAndWheels(vehicleClass, wheelCount);
// Retornar o resultado como JSON
return Ok(new
{
count = detectedCircles.Count,
circles = detectedCircles,
imageUrl = imageUrl,
vehicleInfo = detectVehicle ? new
{
isVehicle = isVehicle,
detectedClass = vehicleClass,
suggestedType = vehicleType,
wheelCount = wheelCount
} : null
});
}
catch (Exception ex)
{
return StatusCode(500, new { error = $"Erro ao processar a imagem: {ex.Message}" });
}
}
// Método para determinar o tipo de veículo com base na classe detectada e no número de rodas
private string GetVehicleTypeFromClassAndWheels(string vehicleClass, int wheelCount)
{
if (string.IsNullOrEmpty(vehicleClass) || vehicleClass == "Não detectado")
return "Veículo não identificado";
// Combinamos a classe detectada com o número de rodas para uma classificação mais precisa
switch (vehicleClass.ToLower())
{
case "car":
if (wheelCount <= 0) return "Carro (rodas não detectadas)";
if (wheelCount <= 4) return "Carro compacto/Sedan";
return "SUV/Crossover";
case "truck":
if (wheelCount <= 0) return "Caminhão (rodas não detectadas)";
if (wheelCount <= 4) return "Pickup";
if (wheelCount <= 6) return "Caminhão pequeno";
if (wheelCount <= 10) return "Caminhão médio";
return "Caminhão grande/Carreta";
case "bus":
return "Ônibus";
case "bicycle":
return "Bicicleta";
case "motorbike":
if (wheelCount == 3) return "Triciclo/Sidecar";
return "Motocicleta";
default:
if (wheelCount <= 0) return $"{vehicleClass} (rodas não detectadas)";
return $"{vehicleClass} com {wheelCount} rodas";
}
}
}
}

View File

@ -0,0 +1,406 @@
using OpenCvSharp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using CircleDetectionApi.Helpers;
using Microsoft.AspNetCore.Mvc;
namespace CircleDetectionApi.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class CircleDetectionController : ControllerBase
{
private readonly IWebHostEnvironment _environment;
private readonly VehicleDetectionHelper _vehicleDetector;
public CircleDetectionController(IWebHostEnvironment environment)
{
_environment = environment;
// Caminho para os arquivos do modelo YOLO
var modelPath = Path.Combine(environment.ContentRootPath, "Models", "yolov4.weights");
var configPath = Path.Combine(environment.ContentRootPath, "Models", "yolov4.cfg");
var classesPath = Path.Combine(environment.ContentRootPath, "Models", "coco.names");
// Inicializar o detector de veículos
_vehicleDetector = new VehicleDetectionHelper(modelPath, configPath, classesPath);
}
[HttpPost]
public async Task<IActionResult> DetectCircles(IFormFile image, bool detectVehicle)
{
// Verificar se a imagem foi enviada
if (image == null || image.Length == 0)
{
return BadRequest(new { error = "Nenhuma imagem foi enviada" });
}
// Verificar o tipo do arquivo
var allowedExtensions = new[] { ".jpg", ".jpeg", ".png", ".bmp", ".tiff" };
var extension = Path.GetExtension(image.FileName).ToLowerInvariant();
if (!allowedExtensions.Contains(extension))
{
return BadRequest(new { error = "Tipo de arquivo não suportado. Por favor, envie uma imagem JPG, PNG, BMP ou TIFF." });
}
try
{
// Ler a imagem em um MemoryStream
using var memoryStream = new MemoryStream();
await image.CopyToAsync(memoryStream);
memoryStream.Position = 0;
// Carregar a imagem usando OpenCvSharp
using var mat = Mat.FromImageData(memoryStream.ToArray(), ImreadModes.Color);
// Verificar se a detecção de veículos está habilitada
//bool detectVehicle = HttpContext.Request.Query.ContainsKey("detectVehicle") &&
// bool.TryParse(HttpContext.Request.Query["detectVehicle"], out var detectValue) &&
// detectValue;
// Variáveis para armazenar informações sobre o veículo
bool isVehicle = false;
string vehicleClass = "Não detectado";
Rect vehicleBox = new Rect();
// Detectar veículo (se solicitado)
if (detectVehicle)
{
(isVehicle, vehicleClass, vehicleBox) = _vehicleDetector.DetectVehicle(mat);
}
// Converter para escala de cinza
using var grayMat = new Mat();
Cv2.CvtColor(mat, grayMat, ColorConversionCodes.BGR2GRAY);
// Aplicar blur para reduzir ruído
using var blurredMat = new Mat();
Cv2.MedianBlur(grayMat, blurredMat, 5);
// Usar o algoritmo de Hough Circle para detectar círculos
CircleSegment[] circles = Cv2.HoughCircles(
blurredMat,
HoughModes.Gradient,
1, // Razão entre resolução da imagem e acumulador
240, // Distância mínima entre centros de círculos detectados
param1: 250, // Limite superior para detector de bordas Canny
param2: 50, // Limite para detecção de centros
minRadius: 5, // Raio mínimo do círculo
maxRadius: 200 // Raio máximo do círculo
);
// Criar lista para armazenar os círculos detectados
var detectedCircles = new List<object>();
// Criar uma cópia colorida da imagem para desenhar os círculos
using var colorMat = mat.Clone(); // Simplesmente clonar a imagem original
// Variável para armazenar contagem de rodas
int wheelCount = 0;
int estimatedAxles = 0; // Adicionar a variável estimatedAxles
if (circles != null && circles.Length > 0)
{
foreach (var circle in circles)
{
// Adicionar à lista de resultados
detectedCircles.Add(new
{
center_x = (int)circle.Center.X,
center_y = (int)circle.Center.Y,
radius = (int)circle.Radius
});
// Desenhar o círculo (como no código Python)
Cv2.Circle(colorMat,
(int)circle.Center.X,
(int)circle.Center.Y,
(int)circle.Radius,
new Scalar(0, 255, 0),
2);
// Desenhar o centro do círculo (como no código Python)
Cv2.Circle(colorMat,
(int)circle.Center.X,
(int)circle.Center.Y,
2,
new Scalar(0, 0, 255),
1);
}
// Atualizar contagem de rodas
wheelCount = detectedCircles.Count;
// Não precisamos mais deste bloco, pois já temos a classe do veículo
}
// Se o parâmetro saveImage for true, salve a imagem marcada e retorne o URL
bool saveImage = HttpContext.Request.Query.ContainsKey("saveImage") &&
bool.TryParse(HttpContext.Request.Query["saveImage"], out var saveValue) &&
saveValue;
string imageUrl = null;
if (saveImage)
{
// Criar diretório para imagens se não existir
var imagesDir = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "images");
Directory.CreateDirectory(imagesDir);
// Salvar a imagem com os círculos marcados
var filename = $"circles_{DateTime.Now:yyyyMMddHHmmss}.jpg";
var filePath = Path.Combine(imagesDir, filename);
Cv2.ImWrite(filePath, colorMat);
// Gerar URL para a imagem
imageUrl = $"{Request.Scheme}://{Request.Host}/images/{filename}";
}
// Desenhar a caixa delimitadora do veículo, se detectado
if (detectVehicle && isVehicle)
{
_vehicleDetector.DrawVehicleBox(colorMat, vehicleBox, vehicleClass);
}
// Determinar o tipo de veículo baseado na classe detectada e no número de rodas
string vehicleType = GetVehicleTypeFromClassAndWheels(vehicleClass, wheelCount);
// Se não detectou nenhuma roda mas identificou um caminhão, estimar um número padrão
if (wheelCount == 0 && vehicleClass.ToLower() == "truck")
{
wheelCount = 4; // Valor padrão conservador para caminhões
estimatedAxles = 2;
}
// Retornar o resultado como JSON com informações detalhadas
return Ok(new
{
count = wheelCount,
estimatedWheels = wheelCount,
estimatedAxles = estimatedAxles,
circles = detectedCircles,
imageUrl = imageUrl,
vehicleInfo = detectVehicle ? new
{
isVehicle = isVehicle,
detectedClass = vehicleClass,
suggestedType = vehicleType,
wheelCount = wheelCount,
axleCount = estimatedAxles
} : null
});
}
catch (Exception ex)
{
return StatusCode(500, new { error = $"Erro ao processar a imagem: {ex.Message}" });
}
}
[HttpGet("file")]
public IActionResult DetectCirclesFromFile([FromQuery] string filename)
{
if (string.IsNullOrEmpty(filename))
{
return BadRequest(new { error = "Nome do arquivo não especificado" });
}
// Obter o caminho do arquivo
var imagesPath = Path.Combine(_environment.ContentRootPath, "Images");
var filePath = Path.Combine(imagesPath, filename);
// Verificar se o arquivo existe
if (!System.IO.File.Exists(filePath))
{
return NotFound(new { error = $"Arquivo {filename} não encontrado" });
}
try
{
// Carregar a imagem usando OpenCvSharp
using var mat = Cv2.ImRead(filePath, ImreadModes.Color);
// Verificar se a detecção de veículos está habilitada
bool detectVehicle = HttpContext.Request.Query.ContainsKey("detectVehicle") &&
bool.TryParse(HttpContext.Request.Query["detectVehicle"], out var detectValue) &&
detectValue;
// Variáveis para armazenar informações sobre o veículo
bool isVehicle = false;
string vehicleClass = "Não detectado";
Rect vehicleBox = new Rect();
// Detectar veículo (se solicitado)
if (detectVehicle)
{
(isVehicle, vehicleClass, vehicleBox) = _vehicleDetector.DetectVehicle(mat);
}
// Converter para escala de cinza
using var grayMat = new Mat();
Cv2.CvtColor(mat, grayMat, ColorConversionCodes.BGR2GRAY);
// Aplicar blur para reduzir ruído (medianBlur como no código original)
using var blurredMat = new Mat();
Cv2.MedianBlur(grayMat, blurredMat, 5);
// Usar o algoritmo de Hough Circle para detectar círculos
CircleSegment[] circles = Cv2.HoughCircles(
blurredMat,
HoughModes.Gradient,
1, // Razão entre resolução da imagem e acumulador
240, // Distância mínima entre centros
param1: 250, // Limite superior para detector de bordas Canny
param2: 50, // Limite para detecção de centros
minRadius: 5, // Raio mínimo do círculo
maxRadius: 200 // Raio máximo do círculo
);
// Preparar o resultado
var detectedCircles = new List<object>();
// Criar uma cópia colorida da imagem para desenhar os círculos
using var colorMat = new Mat();
Cv2.CvtColor(blurredMat, colorMat, ColorConversionCodes.GRAY2BGR);
// Variável para armazenar contagem de rodas
int wheelCount = 0;
// Processar os círculos detectados
if (circles != null && circles.Length > 0)
{
foreach (var circle in circles)
{
// Adicionar à lista de resultados
detectedCircles.Add(new
{
center_x = (int)circle.Center.X,
center_y = (int)circle.Center.Y,
radius = (int)circle.Radius
});
// Desenhar o círculo
Cv2.Circle(colorMat,
(int)circle.Center.X,
(int)circle.Center.Y,
(int)circle.Radius,
new Scalar(0, 255, 0),
2);
// Desenhar o centro do círculo
Cv2.Circle(colorMat,
(int)circle.Center.X,
(int)circle.Center.Y,
2,
new Scalar(0, 0, 255),
1);
}
}
// Atualizar contagem de rodas
wheelCount = detectedCircles.Count;
// Se o número de rodas detectadas for ímpar, provavelmente estamos perdendo uma roda
// em um eixo, então arredondar para o próximo número par para veículos com mais de 2 rodas
if (wheelCount > 2 && wheelCount % 2 == 1)
{
wheelCount++;
}
// Tentar estimar o número de eixos (geralmente 2 rodas = 1 eixo)
var estimatedAxles = (int)Math.Ceiling(wheelCount / 2.0);
// Se o parâmetro saveImage for true, salve a imagem marcada e retorne o URL
bool saveImage = HttpContext.Request.Query.ContainsKey("saveImage") &&
bool.TryParse(HttpContext.Request.Query["saveImage"], out var saveValue) &&
saveValue;
string imageUrl = null;
if (saveImage)
{
// Criar diretório para imagens se não existir
var imagesDir = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "images");
Directory.CreateDirectory(imagesDir);
// Salvar a imagem com os círculos marcados
var outputFilename = $"circles_{Path.GetFileNameWithoutExtension(filename)}_{DateTime.Now:yyyyMMddHHmmss}.jpg";
var outputPath = Path.Combine(imagesDir, outputFilename);
Cv2.ImWrite(outputPath, colorMat);
// Gerar URL para a imagem
imageUrl = $"{Request.Scheme}://{Request.Host}/images/{outputFilename}";
}
// Desenhar a caixa delimitadora do veículo, se detectado
if (detectVehicle && isVehicle)
{
_vehicleDetector.DrawVehicleBox(colorMat, vehicleBox, vehicleClass);
}
// Determinar o tipo de veículo baseado na classe detectada e no número de rodas
string vehicleType = GetVehicleTypeFromClassAndWheels(vehicleClass, wheelCount);
// Retornar o resultado como JSON
return Ok(new
{
count = detectedCircles.Count,
circles = detectedCircles,
imageUrl = imageUrl,
vehicleInfo = detectVehicle ? new
{
isVehicle = isVehicle,
detectedClass = vehicleClass,
suggestedType = vehicleType,
wheelCount = wheelCount
} : null
});
}
catch (Exception ex)
{
return StatusCode(500, new { error = $"Erro ao processar a imagem: {ex.Message}" });
}
}
// Método para determinar o tipo de veículo com base na classe detectada e no número de rodas
private string GetVehicleTypeFromClassAndWheels(string vehicleClass, int wheelCount)
{
if (string.IsNullOrEmpty(vehicleClass) || vehicleClass == "Não detectado")
return "Veículo não identificado";
// Combinamos a classe detectada com o número de rodas para uma classificação mais precisa
switch (vehicleClass.ToLower())
{
case "car":
if (wheelCount <= 0) return "Carro (rodas não detectadas)";
if (wheelCount <= 4) return "Carro compacto/Sedan";
return "SUV/Crossover";
case "truck":
if (wheelCount <= 0) return "Caminhão (rodas não detectadas)";
if (wheelCount <= 4) return "Pickup";
if (wheelCount <= 6) return "Caminhão pequeno";
if (wheelCount <= 10) return "Caminhão médio";
return "Caminhão grande/Carreta";
case "bus":
return "Ônibus";
case "bicycle":
return "Bicicleta";
case "motorbike":
if (wheelCount == 3) return "Triciclo/Sidecar";
return "Motocicleta";
default:
// Se detectamos pelo menos 4 rodas mas a classe não é clara, provavelmente é um caminhão
if (wheelCount >= 4)
return "Provável caminhão com " + wheelCount + " rodas";
if (wheelCount <= 0) return $"{vehicleClass} (rodas não detectadas)";
return $"{vehicleClass} com {wheelCount} rodas";
}
}
}
}

View File

@ -0,0 +1,233 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.ML;
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using System.Drawing;
using Microsoft.ML;
using Microsoft.ML.Vision;
using static System.Net.Mime.MediaTypeNames;
using Microsoft.ML.Data;
using TesteImagemCaminhao;
using Microsoft.ML.Transforms.Image;
using static System.Runtime.InteropServices.JavaScript.JSType;
namespace VehicleDetectionAPI
{
[ApiController]
[Route("api/[controller]")]
public class VehicleDetectionController : ControllerBase
{
private readonly PredictionEngine<ModelInput, ModelOutput> _predictionEngine;
private readonly IWheelsDetectionService _wheelsDetectionService;
public VehicleDetectionController(
PredictionEnginePool<ModelInput, ModelOutput> predictionEnginePool,
IWheelsDetectionService wheelsDetectionService)
{
_predictionEngine = predictionEnginePool.GetPredictionEngine("VehicleTypeModel");
_wheelsDetectionService = wheelsDetectionService;
}
[HttpPost]
[Route("detect")]
public async Task<IActionResult> DetectVehicle(IFormFile imageFile)
{
if (imageFile == null || imageFile.Length == 0)
return BadRequest("Nenhuma imagem foi enviada.");
try
{
// Salvar a imagem temporariamente
var imagePath = Path.GetTempFileName();
using (var stream = new FileStream(imagePath, FileMode.Create))
{
await imageFile.CopyToAsync(stream);
}
// Detectar o tipo de veículo
var vehicleType = DetectVehicleType(imagePath);
var wheelsCount = 0;
switch (vehicleType.ToLower())
{
case "motos":
wheelsCount = 2;
break;
case "carros":
wheelsCount = 4;
break;
case "caminhao":
wheelsCount = 6;
break;
default:
wheelsCount = -1;
break;
}
// Excluir o arquivo temporário
System.IO.File.Delete(imagePath);
return Ok(new
{
VehicleType = vehicleType,
WheelsCount = wheelsCount
});
}
catch (Exception ex)
{
return StatusCode(500, $"Erro ao processar a imagem: {ex.Message}");
}
}
private string DetectVehicleType(string imagePath)
{
// Criar um MLContext
var mlContext = new MLContext();
// Carregar o modelo (você precisará ajustar o caminho)
var modelpath = AppContext.BaseDirectory.ToString();
var modelfile = Path.Combine(modelpath, "neMmodel.zip");
var model = modelfile;
//var model = mlContext.Model.Load("C:\\Users\\USER\\Pictures\\neMmodel.zip", out var _);
// Preparar a entrada de dados
var imageData = System.IO.File.ReadAllBytes(imagePath);
var preProcessingPipeline = mlContext
.Transforms
.LoadRawImageBytes(
outputColumnName: "ImageBytes",
imageFolder: Path.GetDirectoryName(imagePath),
inputColumnName: "ImagePath");
var imagePathAsArray = new[]
{
new
{
ImagePath = imagePath,
Label = string.Empty
}
};
//Load the image into a data view and process it into bytes
var imagePathDataView = mlContext
.Data
.LoadFromEnumerable(imagePathAsArray);
var imageBytesDataView = preProcessingPipeline
.Fit(imagePathDataView)
.Transform(imagePathDataView);
// Create a model input to use in the prediction engine
var modelInput = mlContext
.Data
.CreateEnumerable<ModelInput>(
imageBytesDataView,
true)
.First();
// Fazer a previsão
var prediction = _predictionEngine.Predict(modelInput);
// Retornar o tipo de veículo com maior confiança
return prediction.PredictedLabel;
}
private string DetectVehicleType2(string imagePath)
{
// Criar um MLContext
var mlContext = new MLContext();
// Carregar o modelo (você precisará ajustar o caminho)
var model = mlContext.Model.Load("C:\\Users\\USER\\Pictures\\neMmodel.zip", out var _);
// Preparar a entrada de dados
var imageData = System.IO.File.ReadAllBytes(imagePath);
// Criar um IDataView a partir da entrada
var dataView = mlContext.Data.LoadFromEnumerable(new List<ModelInput>
{
new ModelInput
{
ImagePath = imagePath//,
//input = MLImage.CreateFromFile(imagePath)
}
});
// Realizar a predição
var predictions = model.Transform(dataView);
// Converter para enumerável para acessar os resultados
var results = mlContext.Data.CreateEnumerable<ModelOutput>(predictions, reuseRowObject: false).ToList();
if (results.Count > 0)
{
// Obter a predição com maior score
var prediction = results[0];
// Exibir os scores para debug
Console.WriteLine($"Scores: {string.Join(", ", prediction.Score)}");
return prediction.PredictedLabel;
}
return "Desconhecido";
}
private string DetectVehicleType1(string imagePath)
{
// Carregar imagem para ML.NET
var image = MLImage.CreateFromFile(imagePath);
// Preparar os dados de entrada
ModelInput input = new ModelInput
{
ImageBytes = new byte[image.Width * image.Height]
};
// Fazer a previsão
var prediction = _predictionEngine.Predict(input);
// Retornar o tipo de veículo com maior confiança
return prediction.PredictedLabel;
}
private int EstimateAxlesFromWheels(string vehicleType, int wheelsCount)
{
// Lógica para estimar a quantidade de eixos com base no tipo de veículo e número de rodas
switch (vehicleType.ToLower())
{
case "motos":
return wheelsCount / 2;
case "carros":
return wheelsCount / 4 > 0 ? wheelsCount / 4 : 1;
case "caminhao":
// Caminhões normalmente têm mais de um eixo
return Math.Max(2, wheelsCount / 4);
default:
return wheelsCount / 4;
}
}
}
// Classes para o modelo ML.NET
public class ModelInput
{
public byte[] ImageBytes { get; set; }
public string Label { get; set; }
public string ImagePath { get; set; }
}
public class ModelOutput
{
public string PredictedLabel { get; set; }
public float[] Score { get; set; }
}
}

View File

@ -0,0 +1,7 @@
namespace TesteImagemCaminhao
{
public interface IWheelsDetectionService
{
Task<int> DetectWheelsCount(string imagePath);
}
}

View File

@ -0,0 +1,80 @@
person
bicycle
car
motorbike
aeroplane
bus
train
truck
boat
traffic light
fire hydrant
stop sign
parking meter
bench
bird
cat
dog
horse
sheep
cow
elephant
bear
zebra
giraffe
backpack
umbrella
handbag
tie
suitcase
frisbee
skis
snowboard
sports ball
kite
baseball bat
baseball glove
skateboard
surfboard
tennis racket
bottle
wine glass
cup
fork
knife
spoon
bowl
banana
apple
sandwich
orange
broccoli
carrot
hot dog
pizza
donut
cake
chair
sofa
pottedplant
bed
diningtable
toilet
tvmonitor
laptop
mouse
remote
keyboard
cell phone
microwave
oven
toaster
sink
refrigerator
book
clock
vase
scissors
teddy bear
hair drier
toothbrush

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -0,0 +1,65 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.ML;
using Microsoft.OpenApi.Models;
using TesteImagemCaminhao;
using VehicleDetectionAPI;
var builder = WebApplication.CreateBuilder(args);
// Adicionar serviços ao container
builder.Services.AddControllers();
// Configurar Swagger/OpenAPI
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Circle Detection API", Version = "v1" });
});
// Configurar limite de tamanho de upload
builder.Services.Configure<IISServerOptions>(options =>
{
options.MaxRequestBodySize = 30 * 1024 * 1024; // 30MB
});
builder.Services.Configure<KestrelServerOptions>(options =>
{
options.Limits.MaxRequestBodySize = 30 * 1024 * 1024; // 30MB
});
// Garantir que as pastas necessárias existam
var modelsDir = Path.Combine(builder.Environment.ContentRootPath, "Models");
Directory.CreateDirectory(modelsDir);
var imagesDir = Path.Combine(builder.Environment.ContentRootPath, "Images");
Directory.CreateDirectory(imagesDir);
var wwwrootImagesDir = Path.Combine(builder.Environment.ContentRootPath, "wwwroot", "images");
Directory.CreateDirectory(wwwrootImagesDir);
// Registrar o serviço de detecção de rodas
builder.Services.AddSingleton<IWheelsDetectionService, WheelsDetectionService>();
// Configurar o ML.NET PredictionEnginePool
builder.Services.AddPredictionEnginePool<ModelInput, ModelOutput>()
.FromFile(modelName: "VehicleTypeModel", filePath: "C:\\Users\\USER\\Pictures\\model.zip", watchForChanges: true);
var app = builder.Build();
// Configurar o pipeline de requisições HTTP
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Circle Detection API v1"));
}
app.UseHttpsRedirection();
app.UseStaticFiles(); // Para servir arquivos estáticos da pasta wwwroot
app.UseRouting();
app.UseAuthorization();
app.MapControllers();
app.Run();

View File

@ -0,0 +1,41 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:14253",
"sslPort": 0
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5145",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7049;http://localhost:5145",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -0,0 +1,45 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Controllers\Circle1DetectionController.cs" />
<Compile Remove="Controllers\CircleDetectionController.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Data.Analysis" Version="0.22.2" />
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.3" />
<PackageReference Include="Microsoft.Extensions.ML" Version="3.0.1" />
<PackageReference Include="Microsoft.ML" Version="3.0.1" />
<PackageReference Include="Microsoft.ML.ImageAnalytics" Version="3.0.1" />
<PackageReference Include="Microsoft.ML.TensorFlow" Version="3.0.1" />
<PackageReference Include="Microsoft.ML.Vision" Version="3.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="OpenCvSharp4" Version="4.10.0.20241108" />
<PackageReference Include="OpenCvSharp4.Extensions" Version="4.10.0.20241108" />
<PackageReference Include="OpenCvSharp4.runtime.win" Version="4.10.0.20241108" />
<PackageReference Include="SciSharp.TensorFlow.Redist" Version="2.3.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup>
<ItemGroup>
<None Update="Models\coco.names">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Models\yolov4.cfg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Models\yolov4.weights">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="neMmodel.zip">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@ -0,0 +1,6 @@
@TesteImagemCaminhao_HostAddress = http://localhost:5145
GET {{TesteImagemCaminhao_HostAddress}}/weatherforecast/
Accept: application/json
###

View File

@ -0,0 +1,346 @@
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;
}
}
}

View File

@ -0,0 +1,398 @@
using OpenCvSharp;
using System;
using System.Collections.Generic;
using System.Linq;
namespace CircleDetectionApi.Helpers
{
public static class WheelDetector
{
/// <summary>
/// Detector especializado para rodas de veículos em imagens laterais
/// </summary>
public static List<(Point Center, int Radius)> DetectWheelsInVehicle(Mat image)
{
var results = new List<(Point Center, int Radius)>();
// 1. Pré-processamento: converter para escala de cinza
using var grayImage = new Mat();
Cv2.CvtColor(image, grayImage, ColorConversionCodes.BGR2GRAY);
// 2. Aplicar equalização de histograma para melhorar o contraste
using var equalizedImage = new Mat();
Cv2.EqualizeHist(grayImage, equalizedImage);
// 3. Suavizar a imagem com blur para reduzir ruído
using var blurredImage = new Mat();
Cv2.GaussianBlur(equalizedImage, blurredImage, new Size(5, 5), 0);
// 4. Extrair bordas com Canny
using var edges = new Mat();
Cv2.Canny(blurredImage, edges, 30, 150);
// 5. Aplicar operações morfológicas para conectar bordas e remover ruído
var structElement = Cv2.GetStructuringElement(MorphShapes.Ellipse, new Size(5, 5));
using var dilatedEdges = new Mat();
Cv2.Dilate(edges, dilatedEdges, structElement);
Cv2.Erode(dilatedEdges, dilatedEdges, structElement);
// 6. Encontrar contornos
Point[][] contours;
HierarchyIndex[] hierarchy;
Cv2.FindContours(dilatedEdges, out contours, out hierarchy, RetrievalModes.List, ContourApproximationModes.ApproxSimple);
// 7. Criar uma máscara para mostrar apenas a parte inferior da imagem (onde as rodas estão)
int lowerRegionHeight = image.Height / 3;
using var lowerRegionMask = new Mat(dilatedEdges.Size(), MatType.CV_8UC1, Scalar.Black);
Rect lowerRect = new Rect(0, image.Height - lowerRegionHeight, image.Width, lowerRegionHeight);
lowerRegionMask[lowerRect].SetTo(Scalar.White);
// 8. Encontrar círculos usando HoughCircles com múltiplos parâmetros
List<CircleSegment> allCircles = new List<CircleSegment>();
// Tentar com diferentes parâmetros para maior robustez
DetectCirclesWithParams(blurredImage, allCircles, 1, 200, 120, 25, 20, 150);
DetectCirclesWithParams(blurredImage, allCircles, 1, 150, 100, 20, 30, 200);
DetectCirclesWithParams(blurredImage, allCircles, 1, 100, 80, 15, 40, 250);
// 9. Filtrar círculos encontrados
var filteredCircles = FilterCircles(allCircles, lowerRect, image.Size());
// 10. Procurar contornos circulares considerando proporções e posição na imagem
var circularContours = FindCircularContours(contours, lowerRect);
// 11. Combinar resultados de diferentes métodos
results.AddRange(filteredCircles.Select(c => (new Point((int)c.Center.X, (int)c.Center.Y), (int)c.Radius)));
results.AddRange(circularContours);
// 12. Remover duplicatas (círculos muito próximos)
results = RemoveDuplicateCircles(results);
// 13. Analisar a distribuição horizontal e selecionar rodas mais prováveis
if (results.Count > 0)
{
results = SelectMostProbableWheels(results, image.Size());
}
return results;
}
// Detectar círculos com diferentes parâmetros
private static void DetectCirclesWithParams(
Mat image, List<CircleSegment> circles,
double dp, double minDist, double param1, double param2,
int minRadius, int maxRadius)
{
CircleSegment[] detectedCircles = Cv2.HoughCircles(
image,
HoughModes.Gradient,
dp, minDist,
param1: param1,
param2: param2,
minRadius: minRadius,
maxRadius: maxRadius
);
if (detectedCircles != null && detectedCircles.Length > 0)
{
circles.AddRange(detectedCircles);
}
}
// Filtrar círculos fora da região de interesse e aqueles que são improváveis serem rodas
private static List<CircleSegment> FilterCircles(List<CircleSegment> circles, Rect lowerRegion, Size imageSize)
{
var filtered = new List<CircleSegment>();
foreach (var circle in circles)
{
// Verificar se o círculo está próximo à parte inferior da imagem (onde as rodas estariam)
bool isInLowerRegion = circle.Center.Y >= lowerRegion.Y;
// Verificar se o raio é razoável (não muito pequeno nem muito grande)
bool isReasonableSize = circle.Radius >= 20 && circle.Radius <= Math.Min(imageSize.Width, imageSize.Height) / 5;
if (isInLowerRegion && isReasonableSize)
{
filtered.Add(circle);
}
}
return filtered;
}
// Encontrar contornos que parecem circulares e têm características de rodas
private static List<(Point Center, int Radius)> FindCircularContours(Point[][] contours, Rect lowerRegion)
{
var results = new List<(Point Center, int Radius)>();
foreach (var contour in contours)
{
// Ignorar contornos muito pequenos
double area = Cv2.ContourArea(contour);
if (area < 300) continue;
// Calcular perímetro e circularidade
double perimeter = Cv2.ArcLength(contour, true);
double circularity = (4 * Math.PI * area) / (perimeter * perimeter);
// Obter bounding rect e características
var boundingRect = Cv2.BoundingRect(contour);
// Verificar se está na parte inferior da imagem
bool isInLowerRegion = boundingRect.Y + boundingRect.Height / 2 >= lowerRegion.Y;
// Verificar se é relativamente circular (valor entre 0 e 1, onde 1 é um círculo perfeito)
bool isCircular = circularity > 0.6;
// Verificar se a proporção altura/largura está próxima de 1 (como um círculo)
double aspectRatio = (double)boundingRect.Width / boundingRect.Height;
bool hasGoodAspectRatio = aspectRatio >= 0.8 && aspectRatio <= 1.2;
if (isInLowerRegion && isCircular && hasGoodAspectRatio)
{
// Calcular centro e raio aproximado
Point center = new Point(
boundingRect.X + boundingRect.Width / 2,
boundingRect.Y + boundingRect.Height / 2
);
int radius = Math.Max(boundingRect.Width, boundingRect.Height) / 2;
results.Add((center, radius));
}
}
return results;
}
// Remover círculos duplicados (círculos muito próximos um do outro)
private static List<(Point Center, int Radius)> RemoveDuplicateCircles(List<(Point Center, int Radius)> circles)
{
var result = new List<(Point Center, int Radius)>();
// Ordenar círculos por tamanho (do maior para o menor)
var sortedCircles = circles.OrderByDescending(c => c.Radius).ToList();
foreach (var circle in sortedCircles)
{
bool isDuplicate = false;
foreach (var existingCircle in result)
{
double distance = Math.Sqrt(
Math.Pow(existingCircle.Center.X - circle.Center.X, 2) +
Math.Pow(existingCircle.Center.Y - circle.Center.Y, 2)
);
// Se a distância for menor que o raio do maior círculo, considerar como duplicata
if (distance < Math.Max(existingCircle.Radius, circle.Radius))
{
isDuplicate = true;
break;
}
}
if (!isDuplicate)
{
result.Add(circle);
}
}
return result;
}
// Selecionar as rodas mais prováveis com base na distribuição horizontal
private static List<(Point Center, int Radius)> SelectMostProbableWheels(List<(Point Center, int Radius)> circles, Size imageSize)
{
// Se temos menos de 3 círculos, retornar todos
if (circles.Count <= 3)
return circles;
// Ordenar por posição X (da esquerda para a direita)
var sortedCircles = circles.OrderBy(c => c.Center.X).ToList();
// Calcular distâncias entre centros consecutivos
var distances = new List<double>();
for (int i = 0; i < sortedCircles.Count - 1; i++)
{
double distance = sortedCircles[i + 1].Center.X - sortedCircles[i].Center.X;
distances.Add(distance);
}
// Ordenar as distâncias
var sortedDistances = distances.OrderByDescending(d => d).ToList();
// Se houver uma diferença significativa entre as distâncias, isso pode indicar
// eixos separados em um caminhão (como no caso do de 3 rodas visíveis)
bool hasLargeGap = false;
if (sortedDistances.Count >= 2)
{
double largestGap = sortedDistances[0];
double secondLargestGap = sortedDistances[1];
// Se a maior distância for significativamente maior que a segunda maior,
// considerar que há uma separação clara entre grupos de rodas
if (largestGap > secondLargestGap * 1.8)
{
hasLargeGap = true;
}
}
// Tentar identificar rodas baseado na análise espacial
if (hasLargeGap)
{
// Identificar o índice onde ocorre a maior distância
int gapIndex = 0;
double maxDistance = 0;
for (int i = 0; i < distances.Count; i++)
{
if (distances[i] > maxDistance)
{
maxDistance = distances[i];
gapIndex = i;
}
}
// Separar em grupos antes e depois da maior distância
var leftGroup = sortedCircles.Take(gapIndex + 1).ToList();
var rightGroup = sortedCircles.Skip(gapIndex + 1).ToList();
// Selecionar rodas representativas de cada grupo
var selectedCircles = new List<(Point Center, int Radius)>();
// Se houver apenas uma roda no grupo da esquerda, adicioná-la
if (leftGroup.Count == 1)
{
selectedCircles.Add(leftGroup[0]);
}
// Caso contrário, pegar as duas rodas mais extremas (se houver mais de uma)
else if (leftGroup.Count > 1)
{
selectedCircles.Add(leftGroup.First()); // Roda mais à esquerda
selectedCircles.Add(leftGroup.Last()); // Roda mais à direita
}
// O mesmo para o grupo da direita
if (rightGroup.Count == 1)
{
selectedCircles.Add(rightGroup[0]);
}
else if (rightGroup.Count > 1)
{
selectedCircles.Add(rightGroup.First());
selectedCircles.Add(rightGroup.Last());
}
return selectedCircles;
}
else
{
// Se não houver uma distância claramente maior, selecionar com base no tamanho
// e espaçamento, tipicamente para veículos menores com 2 rodas
// Para casos com muitos círculos detectados, filtramos para ficar com os maiores
// e mais bem espaçados
if (circles.Count > 4)
{
// Ordenar por tamanho e pegar os maiores
var largestCircles = circles.OrderByDescending(c => c.Radius)
.Take(4)
.OrderBy(c => c.Center.X)
.ToList();
// Se ainda temos mais de 2 círculos, tentar agrupar
if (largestCircles.Count > 2)
{
// Olhar para a distribuição espacial e pegar os mais separados
var selected = new List<(Point Center, int Radius)>();
selected.Add(largestCircles.First()); // O mais à esquerda
selected.Add(largestCircles.Last()); // O mais à direita
return selected;
}
return largestCircles;
}
return circles;
}
}
/// <summary>
/// Estima a quantidade de eixos baseado nas rodas detectadas
/// </summary>
public static int EstimateAxles(List<(Point Center, int Radius)> wheels, Size imageSize)
{
if (wheels.Count == 0)
return 0;
// Para um único eixo (normalmente 2 rodas lado a lado)
if (wheels.Count <= 2)
return 1;
// Para mais rodas, analisar a distribuição no eixo X
var sortedByX = wheels.OrderBy(w => w.Center.X).ToList();
// Calcular distâncias entre rodas consecutivas
var distances = new List<double>();
for (int i = 0; i < sortedByX.Count - 1; i++)
{
distances.Add(sortedByX[i + 1].Center.X - sortedByX[i].Center.X);
}
// Se temos apenas 3 rodas, temos que decidir se é um veículo com 2 eixos
// onde uma roda está oculta, ou um veículo com 3 eixos
if (wheels.Count == 3)
{
// Se as distâncias entre as rodas são similares, provavelmente são 3 eixos individuais
if (Math.Abs(distances[0] - distances[1]) < Math.Min(distances[0], distances[1]) * 0.3)
return 3;
// Se uma distância é significativamente menor, provavelmente são 2 eixos com uma roda oculta
return 2;
}
// Para 4 rodas, verificar se estão agrupadas (2 eixos) ou igualmente espaçadas (4 eixos)
if (wheels.Count == 4)
{
// Ordenar distâncias
distances.Sort();
// Se a menor distância é significativamente menor que as outras,
// provavelmente são 2 eixos com 2 rodas cada
if (distances[0] < distances[2] * 0.5)
return 2;
// Se as distâncias são similares, podem ser 3 ou 4 eixos
// Vamos considerar a proporção da imagem
double aspectRatio = (double)imageSize.Width / imageSize.Height;
// Veículos longos como caminhões provavelmente têm mais eixos
if (aspectRatio > 2.0)
return 4;
return 3;
}
// Para 5 ou mais rodas, tentamos estimar pelo espaçamento
// Ordenar distâncias e procurar gaps significativos
distances.Sort();
// Se há uma diferença significativa entre distâncias, isso pode indicar grupos de eixos
double maxDistance = distances.Last();
double averageDistance = distances.Average();
if (maxDistance > averageDistance * 2)
{
// Provavelmente temos agrupamentos de rodas
// Estimar como metade do número de rodas arredondado para cima
return (int)Math.Ceiling(wheels.Count / 2.0);
}
// Se as rodas estão uniformemente espaçadas, cada uma provavelmente representa um eixo
return wheels.Count;
}
}
}

View File

@ -0,0 +1,217 @@
using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
using System.Drawing.Imaging;
using Newtonsoft.Json;
using Microsoft.Extensions.Configuration;
using TesteImagemCaminhao;
namespace VehicleDetectionAPI
{
public class WheelsDetectionService : IWheelsDetectionService
{
private readonly HttpClient _httpClient;
private readonly string _apiUrl;
private readonly string _apiKey;
public WheelsDetectionService(IConfiguration configuration)
{
_httpClient = new HttpClient();
_apiUrl = configuration["ComputerVision:ApiUrl"];
_apiKey = configuration["ComputerVision:ApiKey"];
}
public async Task<int> DetectWheelsCount(string imagePath)
{
try
{
// Método 1: Usar detecção de objetos para identificar rodas diretamente
// (método mais preciso se tivermos um modelo treinado para isso)
int wheelsCount = await DetectWheelsUsingObjectDetection(imagePath);
// Se o método anterior não detectar rodas, usar método alternativo
if (wheelsCount == 0)
{
// Método 2: Estimar com base em detecção de contornos e recursos geométricos
wheelsCount = DetectWheelsUsingContours(imagePath);
}
return wheelsCount;
}
catch (Exception ex)
{
Console.WriteLine($"Erro na detecção de rodas: {ex.Message}");
return EstimateDefaultWheelsCount(imagePath); // Fallback para estimativa padrão
}
}
public async Task<int> DetectWheelsCountOld(string imagePath)
{
try
{
// Método 1: Usar detecção de objetos para identificar rodas diretamente
// (método mais preciso se tivermos um modelo treinado para isso)
int wheelsCount = await DetectWheelsUsingObjectDetection(imagePath);
// Se o método anterior não detectar rodas, usar método alternativo
if (wheelsCount == 0)
{
// Método 2: Estimar com base em detecção de contornos e recursos geométricos
wheelsCount = DetectWheelsUsingContours(imagePath);
}
return wheelsCount;
}
catch (Exception ex)
{
Console.WriteLine($"Erro na detecção de rodas: {ex.Message}");
return EstimateDefaultWheelsCount(imagePath); // Fallback para estimativa padrão
}
}
private async Task<int> DetectWheelsUsingObjectDetection(string imagePath)
{
// Esta função envia a imagem para um serviço de detecção de objetos
// como Azure Custom Vision, Yolo ou um modelo ML personalizado
// Exemplo de integração com o Azure Custom Vision:
try
{
using var fileStream = new FileStream(imagePath, FileMode.Open);
using var content = new StreamContent(fileStream);
content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
// Adicionar a chave de API ao cabeçalho
_httpClient.DefaultRequestHeaders.Add("Prediction-Key", _apiKey);
// Enviar a imagem para o serviço
var response = await _httpClient.PostAsync(_apiUrl, content);
response.EnsureSuccessStatusCode();
// Analisar a resposta
var result = await response.Content.ReadAsStringAsync();
var detectionResult = JsonConvert.DeserializeObject<DetectionResult>(result);
// Contar apenas objetos detectados como "roda" com confiança > 0.5
int wheelsCount = detectionResult.Predictions
.Count(p => p.TagName.ToLower() == "roda" && p.Probability > 0.5);
return wheelsCount;
}
catch
{
// Se falhar, retornar 0 para que o método alternativo seja usado
return 0;
}
}
private int DetectWheelsUsingContours(string imagePath)
{
// Implementação simplificada usando processamento de imagem básico
// Em um ambiente real, usaríamos uma biblioteca de visão computacional como OpenCV
// Esta é uma implementação simulada
// Em produção, usaríamos algoritmos para:
// 1. Converter para escala de cinza
// 2. Aplicar detecção de bordas (Canny)
// 3. Encontrar contornos circulares (HoughCircles)
// 4. Filtrar por tamanho e posição para identificar rodas
// Como exemplo, retornamos um valor baseado no tipo de veículo detectado
using (var bitmap = new Bitmap(imagePath))
{
// Análise básica da imagem para simular processamento
// Em um sistema real, este seria um algoritmo complexo
// Se a imagem for larga (proporção > 2:1), provavelmente é um caminhão
if (bitmap.Width > bitmap.Height * 2)
{
return 6; // Estimar rodas para um caminhão
}
// Se for mais alta que larga, pode ser uma moto
else if (bitmap.Height > bitmap.Width)
{
return 2; // Estimar rodas para uma moto
}
else
{
return 4; // Estimar rodas para um carro
}
}
}
private int EstimateDefaultWheelsCount(string imagePath)
{
// Análise simples da imagem para fazer uma estimativa padrão
using (var bitmap = new Bitmap(imagePath))
{
// Detectar cor predominante para estimar o tipo de veículo
Color predominantColor = GetPredominantColor(bitmap);
// Tamanho da imagem
var size = bitmap.Width * bitmap.Height;
// Estimativa baseada na cor e tamanho
if (size < 100000) // Imagem pequena
return 2; // Possivelmente uma moto
else if (size > 500000) // Imagem grande
return 6; // Possivelmente um caminhão
else
return 4; // Padrão para carros
}
}
private Color GetPredominantColor(Bitmap bitmap)
{
// Método simplificado para obter cor predominante
int r = 0, g = 0, b = 0;
int total = 0;
// Amostragem de pixels
for (int x = 0; x < bitmap.Width; x += 10)
{
for (int y = 0; y < bitmap.Height; y += 10)
{
var pixel = bitmap.GetPixel(x, y);
r += pixel.R;
g += pixel.G;
b += pixel.B;
total++;
}
}
// Calcular média
return Color.FromArgb(r / total, g / total, b / total);
}
}
// Classe para deserializar a resposta da API de detecção de objetos
public class DetectionResult
{
public string Id { get; set; }
public string Project { get; set; }
public string Iteration { get; set; }
public DateTime Created { get; set; }
public Prediction[] Predictions { get; set; }
}
public class Prediction
{
public float Probability { get; set; }
public string TagId { get; set; }
public string TagName { get; set; }
public BoundingBox BoundingBox { get; set; }
}
public class BoundingBox
{
public float Left { get; set; }
public float Top { get; set; }
public float Width { get; set; }
public float Height { get; set; }
}
}

View File

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

Binary file not shown.