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