feat: versáo inicial
This commit is contained in:
commit
bfafb85d1c
398
.gitignore
vendored
Normal file
398
.gitignore
vendored
Normal 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
|
||||
21
MLTrainingVeiculos/MLTrainingVeiculos.csproj
Normal file
21
MLTrainingVeiculos/MLTrainingVeiculos.csproj
Normal 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>
|
||||
316
MLTrainingVeiculos/Program.cs
Normal file
316
MLTrainingVeiculos/Program.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
31
TesteCaminhao.sln
Normal file
31
TesteCaminhao.sln
Normal 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
44
TesteCaminhao/Program.cs
Normal 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);
|
||||
}
|
||||
41
TesteCaminhao/Properties/launchSettings.json
Normal file
41
TesteCaminhao/Properties/launchSettings.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
TesteCaminhao/TesteCaminhao.csproj
Normal file
14
TesteCaminhao/TesteCaminhao.csproj
Normal 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>
|
||||
6
TesteCaminhao/TesteCaminhao.http
Normal file
6
TesteCaminhao/TesteCaminhao.http
Normal file
@ -0,0 +1,6 @@
|
||||
@TesteCaminhao_HostAddress = http://localhost:5115
|
||||
|
||||
GET {{TesteCaminhao_HostAddress}}/weatherforecast/
|
||||
Accept: application/json
|
||||
|
||||
###
|
||||
8
TesteCaminhao/appsettings.Development.json
Normal file
8
TesteCaminhao/appsettings.Development.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
9
TesteCaminhao/appsettings.json
Normal file
9
TesteCaminhao/appsettings.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
389
TesteImagemCaminhao/Controllers/Circle1DetectionController.cs
Normal file
389
TesteImagemCaminhao/Controllers/Circle1DetectionController.cs
Normal 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";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
406
TesteImagemCaminhao/Controllers/CircleDetectionController.cs
Normal file
406
TesteImagemCaminhao/Controllers/CircleDetectionController.cs
Normal 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";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
233
TesteImagemCaminhao/Controllers/VehicleDetectionController .cs
Normal file
233
TesteImagemCaminhao/Controllers/VehicleDetectionController .cs
Normal 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; }
|
||||
}
|
||||
|
||||
}
|
||||
7
TesteImagemCaminhao/IWheelsDetectionService.cs
Normal file
7
TesteImagemCaminhao/IWheelsDetectionService.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace TesteImagemCaminhao
|
||||
{
|
||||
public interface IWheelsDetectionService
|
||||
{
|
||||
Task<int> DetectWheelsCount(string imagePath);
|
||||
}
|
||||
}
|
||||
80
TesteImagemCaminhao/Models/coco.names
Normal file
80
TesteImagemCaminhao/Models/coco.names
Normal 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
|
||||
1158
TesteImagemCaminhao/Models/yolov4.cfg
Normal file
1158
TesteImagemCaminhao/Models/yolov4.cfg
Normal file
File diff suppressed because it is too large
Load Diff
BIN
TesteImagemCaminhao/Models/yolov4.weights
Normal file
BIN
TesteImagemCaminhao/Models/yolov4.weights
Normal file
Binary file not shown.
65
TesteImagemCaminhao/Program.cs
Normal file
65
TesteImagemCaminhao/Program.cs
Normal 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();
|
||||
41
TesteImagemCaminhao/Properties/launchSettings.json
Normal file
41
TesteImagemCaminhao/Properties/launchSettings.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
45
TesteImagemCaminhao/TesteImagemCaminhao.csproj
Normal file
45
TesteImagemCaminhao/TesteImagemCaminhao.csproj
Normal 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>
|
||||
6
TesteImagemCaminhao/TesteImagemCaminhao.http
Normal file
6
TesteImagemCaminhao/TesteImagemCaminhao.http
Normal file
@ -0,0 +1,6 @@
|
||||
@TesteImagemCaminhao_HostAddress = http://localhost:5145
|
||||
|
||||
GET {{TesteImagemCaminhao_HostAddress}}/weatherforecast/
|
||||
Accept: application/json
|
||||
|
||||
###
|
||||
346
TesteImagemCaminhao/VehicleDetectionHelper.cs
Normal file
346
TesteImagemCaminhao/VehicleDetectionHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
398
TesteImagemCaminhao/WheelDetector.cs
Normal file
398
TesteImagemCaminhao/WheelDetector.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
217
TesteImagemCaminhao/WheelsDetectionService.cs
Normal file
217
TesteImagemCaminhao/WheelsDetectionService.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
8
TesteImagemCaminhao/appsettings.Development.json
Normal file
8
TesteImagemCaminhao/appsettings.Development.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
9
TesteImagemCaminhao/appsettings.json
Normal file
9
TesteImagemCaminhao/appsettings.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
BIN
TesteImagemCaminhao/neMmodel.zip
Normal file
BIN
TesteImagemCaminhao/neMmodel.zip
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user