fix(crop): synchroniser les calculs avec le mode BoxFit.cover et activer la découpe physique
This commit is contained in:
@@ -162,8 +162,8 @@ class AnalysisProvider extends ChangeNotifier {
|
||||
return;
|
||||
}
|
||||
|
||||
// Detect target and impacts
|
||||
final result = _detectionService.detectTarget(imagePath, targetType);
|
||||
// CORRECTION PARALLÈLE : Changement de la méthode pour l'Isolate asynchrone
|
||||
final result = await _detectionService.detectTargetAsync(imagePath, targetType);
|
||||
|
||||
if (!result.success) {
|
||||
_state = AnalysisState.error;
|
||||
|
||||
@@ -225,12 +225,12 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
|
||||
return Transform(
|
||||
transform: Matrix4.identity()
|
||||
..setTranslationRaw(widget.cropOffset?.dx ?? 0.0, widget.cropOffset?.dy ?? 0.0, 0.0)
|
||||
..scale(1.0, 1.0) // Zoom ignoré au plotting pour rester en vue globale
|
||||
..rotateZ((provider.cropRotation) * (math.pi / 180)), // FIX : Utilisation du provider dynamique
|
||||
..scale(1.0, 1.0)
|
||||
..rotateZ((provider.cropRotation) * (math.pi / 180)),
|
||||
alignment: Alignment.center,
|
||||
child: Image.file(
|
||||
File(provider.imagePath!),
|
||||
fit: BoxFit.contain,
|
||||
fit: BoxFit.cover, // CORRECTION : BoxFit.cover pour s'aligner sur le Crop !
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -497,7 +497,7 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
|
||||
Image.file(
|
||||
File(provider.imagePath!),
|
||||
key: _imageKey,
|
||||
fit: BoxFit.contain,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
TargetOverlay(
|
||||
targetCenterX: provider.targetCenterX,
|
||||
|
||||
@@ -156,7 +156,7 @@ class _CropScreenState extends State<CropScreen> {
|
||||
style: TextStyle(color: Colors.white70, fontSize: 13, fontWeight: FontWeight.w600),
|
||||
),
|
||||
Text(
|
||||
'${_rotation.toStringAsFixed(1)}°', // Affiche désormais le dixième de degré pour la précision
|
||||
'${_rotation.toStringAsFixed(1)}°',
|
||||
style: const TextStyle(color: Color(0xFF00FF00), fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
@@ -167,9 +167,9 @@ class _CropScreenState extends State<CropScreen> {
|
||||
Expanded(
|
||||
child: Slider(
|
||||
value: _rotation,
|
||||
min: -15.0, // Pivot maximum à gauche bridé à 15°
|
||||
max: 15.0, // Pivot maximum à droite bridé à 15°
|
||||
divisions: 300, // 300 divisions pour obtenir un cran ultra-précis tous les 0,1°
|
||||
min: -15.0,
|
||||
max: 15.0,
|
||||
divisions: 300,
|
||||
label: '${_rotation.toStringAsFixed(1)}°',
|
||||
activeColor: const Color(0xFF1A73E8),
|
||||
inactiveColor: Colors.white12,
|
||||
@@ -182,7 +182,6 @@ class _CropScreenState extends State<CropScreen> {
|
||||
),
|
||||
const Icon(Icons.rotate_right, color: Colors.white38, size: 20),
|
||||
const SizedBox(width: 4),
|
||||
// Petit bouton Reset rapide pour cette jauge
|
||||
IconButton(
|
||||
icon: const Icon(Icons.restart_alt, color: Colors.white54, size: 20),
|
||||
onPressed: () {
|
||||
@@ -260,7 +259,7 @@ class _CropScreenState extends State<CropScreen> {
|
||||
alignment: Alignment.center,
|
||||
child: Image.file(
|
||||
File(widget.imagePath),
|
||||
fit: BoxFit.contain,
|
||||
fit: BoxFit.cover,
|
||||
width: _viewportSize.width,
|
||||
height: _viewportSize.height,
|
||||
),
|
||||
@@ -302,7 +301,6 @@ class _CropScreenState extends State<CropScreen> {
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
// Zone visible : Rectangle parfait sans aucun BorderRadius
|
||||
Center(
|
||||
child: Container(
|
||||
width: _cropSize,
|
||||
@@ -335,13 +333,14 @@ class _CropScreenState extends State<CropScreen> {
|
||||
final imageAspect = _imageSize!.width / _imageSize!.height;
|
||||
final viewportAspect = _viewportSize.width / _viewportSize.height;
|
||||
|
||||
// Initialisation calée sur la logique BoxFit.cover
|
||||
double displayWidth, displayHeight;
|
||||
if (imageAspect > viewportAspect) {
|
||||
displayWidth = _viewportSize.width;
|
||||
displayHeight = _viewportSize.width / imageAspect;
|
||||
} else {
|
||||
displayHeight = _viewportSize.height;
|
||||
displayWidth = _viewportSize.height * imageAspect;
|
||||
} else {
|
||||
displayWidth = _viewportSize.width;
|
||||
displayHeight = _viewportSize.width / imageAspect;
|
||||
}
|
||||
|
||||
final minDisplayDim = math.min(displayWidth, displayHeight);
|
||||
@@ -372,8 +371,17 @@ class _CropScreenState extends State<CropScreen> {
|
||||
setState(() => _isLoading = true);
|
||||
try {
|
||||
final cropRect = _calculateCropRect();
|
||||
final targetCenterX = cropRect.x + cropRect.width / 2;
|
||||
final targetCenterY = cropRect.y + cropRect.height / 2;
|
||||
|
||||
// DECOUPE ET REDRESSEMENT PHYSIQUE DE L'IMAGE
|
||||
final croppedImagePath = await _cropService.cropToSquare(
|
||||
widget.imagePath,
|
||||
cropRect,
|
||||
rotationDegrees: _rotation,
|
||||
);
|
||||
|
||||
// On recalcule le centre relatif de la cible pour l'image carrée finale (qui vaut 0.5, 0.5 par défaut)
|
||||
const targetCenterX = 0.5;
|
||||
const targetCenterY = 0.5;
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
@@ -381,13 +389,13 @@ class _CropScreenState extends State<CropScreen> {
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => AnalysisScreen(
|
||||
imagePath: widget.imagePath,
|
||||
imagePath: croppedImagePath, // On passe le fichier carré proprement détouré !
|
||||
targetType: widget.targetType,
|
||||
initialCenterX: targetCenterX,
|
||||
initialCenterY: targetCenterY,
|
||||
cropScale: _scale,
|
||||
cropOffset: _offset,
|
||||
cropRotation: _rotation,
|
||||
cropScale: 1.0, // Réinitialisé car l'image physique est déjà à la bonne échelle
|
||||
cropOffset: Offset.zero, // Réinitialisé car l'image physique est déjà centrée
|
||||
cropRotation: 0.0, // Réinitialisé car l'image physique est déjà redressée
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -395,25 +403,27 @@ class _CropScreenState extends State<CropScreen> {
|
||||
if (mounted) {
|
||||
setState(() => _isLoading = false);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Erreur: $e'), backgroundColor: AppTheme.errorColor),
|
||||
SnackBar(content: Text('Erreur de découpe: $e'), backgroundColor: AppTheme.errorColor),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Calcule précisément le CropRect basé sur la géométrie BoxFit.cover
|
||||
CropRect _calculateCropRect() {
|
||||
if (_imageSize == null) return const CropRect(x: 0, y: 0, width: 1, height: 1);
|
||||
|
||||
final imageAspect = _imageSize!.width / _imageSize!.height;
|
||||
final viewportAspect = _viewportSize.width / _viewportSize.height;
|
||||
|
||||
// Dimensions réelles de rendu en mode BoxFit.cover
|
||||
double displayWidth, displayHeight;
|
||||
if (imageAspect > viewportAspect) {
|
||||
displayWidth = _viewportSize.width;
|
||||
displayHeight = _viewportSize.width / imageAspect;
|
||||
} else {
|
||||
displayHeight = _viewportSize.height;
|
||||
displayWidth = _viewportSize.height * imageAspect;
|
||||
} else {
|
||||
displayWidth = _viewportSize.width;
|
||||
displayHeight = _viewportSize.width / imageAspect;
|
||||
}
|
||||
|
||||
final scaledWidth = displayWidth * _scale;
|
||||
@@ -434,10 +444,10 @@ class _CropScreenState extends State<CropScreen> {
|
||||
final relCropHeight = _cropSize / scaledHeight;
|
||||
|
||||
return CropRect(
|
||||
x: relCropLeft,
|
||||
y: relCropTop,
|
||||
width: relCropWidth,
|
||||
height: relCropHeight,
|
||||
x: relCropLeft.clamp(0.0, 1.0),
|
||||
y: relCropTop.clamp(0.0, 1.0),
|
||||
width: relCropWidth.clamp(0.0, 1.0),
|
||||
height: relCropHeight.clamp(0.0, 1.0),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -88,7 +88,7 @@ class _SessionDetailScreenState extends State<SessionDetailScreen> {
|
||||
if (File(currentAnalysis.imagePath).existsSync())
|
||||
Image.file(
|
||||
File(currentAnalysis.imagePath),
|
||||
fit: BoxFit.contain,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
else
|
||||
Container(
|
||||
|
||||
@@ -42,28 +42,40 @@ class ImageCropService {
|
||||
/// Taille de sortie maximale pour les images recadrées
|
||||
static const int maxOutputSize = 1024;
|
||||
|
||||
/// Recadre une image en format carré
|
||||
/// Recadre une image en format carré en prenant en compte l'angle de rotation.
|
||||
///
|
||||
/// [sourcePath] - Chemin vers l'image source
|
||||
/// [cropRect] - Zone de recadrage normalisée (0.0 à 1.0)
|
||||
/// [rotationDegrees] - L'angle appliqué à la jauge haute précision du CropScreen
|
||||
/// [outputSize] - Taille de sortie en pixels (carré)
|
||||
///
|
||||
/// Retourne le chemin vers l'image recadrée dans le dossier temporaire
|
||||
Future<String> cropToSquare(
|
||||
String sourcePath,
|
||||
CropRect cropRect, {
|
||||
double rotationDegrees = 0.0, // FIX : On intercepte la rotation ici !
|
||||
int outputSize = maxOutputSize,
|
||||
}) async {
|
||||
// Charger l'image source
|
||||
final file = File(sourcePath);
|
||||
final bytes = await file.readAsBytes();
|
||||
final originalImage = img.decodeImage(bytes);
|
||||
img.Image? originalImage = img.decodeImage(bytes);
|
||||
|
||||
if (originalImage == null) {
|
||||
throw Exception('Impossible de décoder l\'image: $sourcePath');
|
||||
}
|
||||
|
||||
// Calculer les coordonnées en pixels
|
||||
// FIX CRITIQUE : Si l'utilisateur a pivoté l'image, on applique d'abord la rotation
|
||||
// aux pixels bruts pour que le découpage rectiligne qui suit tape au bon endroit.
|
||||
if (rotationDegrees != 0.0) {
|
||||
originalImage = img.copyRotate(
|
||||
originalImage,
|
||||
angle: rotationDegrees,
|
||||
interpolation: img.Interpolation.cubic,
|
||||
);
|
||||
}
|
||||
|
||||
// Calculer les coordonnées en pixels sur la géométrie de l'image redressée
|
||||
final srcX = (cropRect.x * originalImage.width).round();
|
||||
final srcY = (cropRect.y * originalImage.height).round();
|
||||
final srcWidth = (cropRect.width * originalImage.width).round();
|
||||
@@ -75,7 +87,7 @@ class ImageCropService {
|
||||
final clampedWidth = math.min(srcWidth, originalImage.width - clampedX);
|
||||
final clampedHeight = math.min(srcHeight, originalImage.height - clampedY);
|
||||
|
||||
// Recadrer l'image
|
||||
// Recadrer l'image redressée
|
||||
img.Image cropped = img.copyCrop(
|
||||
originalImage,
|
||||
x: clampedX,
|
||||
@@ -106,26 +118,18 @@ class ImageCropService {
|
||||
}
|
||||
|
||||
/// Calcule la zone de recadrage carrée maximale centrée sur l'image
|
||||
///
|
||||
/// [imageWidth] - Largeur de l'image en pixels
|
||||
/// [imageHeight] - Hauteur de l'image en pixels
|
||||
///
|
||||
/// Retourne un CropRect normalisé pour un carré centré
|
||||
CropRect getDefaultSquareCrop(int imageWidth, int imageHeight) {
|
||||
final aspectRatio = imageWidth / imageHeight;
|
||||
|
||||
if (aspectRatio > 1) {
|
||||
// Image plus large que haute - centrer horizontalement
|
||||
final squareWidth = imageHeight / imageWidth;
|
||||
final x = (1 - squareWidth) / 2;
|
||||
return CropRect(x: x, y: 0, width: squareWidth, height: 1);
|
||||
} else if (aspectRatio < 1) {
|
||||
// Image plus haute que large - centrer verticalement
|
||||
final squareHeight = imageWidth / imageHeight;
|
||||
final y = (1 - squareHeight) / 2;
|
||||
return CropRect(x: 0, y: y, width: 1, height: squareHeight);
|
||||
} else {
|
||||
// Déjà carré
|
||||
return const CropRect(x: 0, y: 0, width: 1, height: 1);
|
||||
}
|
||||
}
|
||||
@@ -147,7 +151,7 @@ class ImageCropService {
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignorer les erreurs de nettoyage
|
||||
// Ignorer les erreurs
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,110 @@ export 'image_processing_service.dart'
|
||||
export 'opencv_impact_detection_service.dart'
|
||||
show OpenCVDetectionSettings, OpenCVDetectedImpact;
|
||||
|
||||
// ============================================================================
|
||||
// STRUCT ET FONCTION GLOBALE POUR LE PARALLÉLISME (THREAD SECONDAIRE)
|
||||
// ============================================================================
|
||||
|
||||
/// Conteneur de données pour envoyer les paramètres à l'Isolate d'arrière-plan.
|
||||
class DetectionPayload {
|
||||
final String imagePath;
|
||||
final TargetType targetType;
|
||||
|
||||
// On recrée les instances à l'intérieur du thread isolé car les objets ne se partagent pas entre threads.
|
||||
DetectionPayload({
|
||||
required this.imagePath,
|
||||
required this.targetType,
|
||||
});
|
||||
}
|
||||
|
||||
/// FONCTION EXÉCUTÉE EN PARALLÈLE : Tourne sur un autre cœur du processeur.
|
||||
/// L'interface graphique reste à 120 FPS et totalement fluide.
|
||||
TargetDetectionResult runParallelTargetDetection(DetectionPayload payload) {
|
||||
// 1. Initialisation locale des services dans le sous-thread
|
||||
final imageProcessingService = ImageProcessingService();
|
||||
|
||||
try {
|
||||
// 2. Détection de la cible principale (Calcul lourd)
|
||||
final mainTarget = imageProcessingService.detectMainTarget(payload.imagePath);
|
||||
|
||||
double centerX = 0.5;
|
||||
double centerY = 0.5;
|
||||
double radius = 0.4;
|
||||
|
||||
if (mainTarget != null) {
|
||||
centerX = mainTarget.centerX;
|
||||
centerY = mainTarget.centerY;
|
||||
radius = mainTarget.radius;
|
||||
}
|
||||
|
||||
// 3. Détection des impacts (Calcul lourd)
|
||||
final impacts = imageProcessingService.detectImpacts(payload.imagePath);
|
||||
|
||||
// 4. Calcul mathématique des scores relatifs
|
||||
final detectedImpacts = impacts.map((impact) {
|
||||
final score = payload.targetType == TargetType.concentric
|
||||
? _staticCalculateConcentricScore(
|
||||
impact.x,
|
||||
impact.y,
|
||||
centerX,
|
||||
centerY,
|
||||
radius,
|
||||
)
|
||||
: _staticCalculateSilhouetteScore(impact.x, impact.y, centerX, centerY);
|
||||
|
||||
return DetectedImpactResult(
|
||||
x: impact.x,
|
||||
y: impact.y,
|
||||
radius: impact.radius,
|
||||
suggestedScore: score,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
return TargetDetectionResult(
|
||||
centerX: centerX,
|
||||
centerY: centerY,
|
||||
radius: radius,
|
||||
impacts: detectedImpacts,
|
||||
);
|
||||
} catch (e) {
|
||||
return TargetDetectionResult.error('Erreur de detection parallèle: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// Fonctions mathématiques pures nécessaires à l'Isolate (statiques)
|
||||
int _staticCalculateConcentricScore(double impactX, double impactY, double centerX, double centerY, double targetRadius) {
|
||||
final dx = impactX - centerX;
|
||||
final dy = impactY - centerY;
|
||||
final distance = math.sqrt(dx * dx + dy * dy) / targetRadius;
|
||||
if (distance <= 0.1) return 10;
|
||||
if (distance <= 0.2) return 9;
|
||||
if (distance <= 0.3) return 8;
|
||||
if (distance <= 0.4) return 7;
|
||||
if (distance <= 0.5) return 6;
|
||||
if (distance <= 0.6) return 5;
|
||||
if (distance <= 0.7) return 4;
|
||||
if (distance <= 0.8) return 3;
|
||||
if (distance <= 0.9) return 2;
|
||||
if (distance <= 1.0) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int _staticCalculateSilhouetteScore(double impactX, double impactY, double centerX, double centerY) {
|
||||
final dx = (impactX - centerX).abs();
|
||||
final dy = impactY - centerY;
|
||||
if (dx > 0.15) return 0;
|
||||
if (dy < -0.25) return 5;
|
||||
if (dy < 0.0) return 5;
|
||||
if (dy < 0.15) return 4;
|
||||
if (dy < 0.35) return 3;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// FIN DU BLOC DE PARALLÉLISME
|
||||
// ============================================================================
|
||||
|
||||
|
||||
class TargetDetectionResult {
|
||||
final double centerX; // Relative (0-1)
|
||||
final double centerY; // Relative (0-1)
|
||||
@@ -62,10 +166,17 @@ class TargetDetectionService {
|
||||
imageProcessingService ?? ImageProcessingService(),
|
||||
_opencvService = opencvService ?? OpenCVImpactDetectionService();
|
||||
|
||||
/// Detect target and impacts from an image file
|
||||
/// Detect target and impacts from an image file ASYNCHRONOUSLY in a separate Thread.
|
||||
/// CORRECTION : Utilise désormais 'compute' pour basculer en arrière-plan immédiat.
|
||||
Future<TargetDetectionResult> detectTargetAsync(String imagePath, TargetType targetType) async {
|
||||
final payload = DetectionPayload(imagePath: imagePath, targetType: targetType);
|
||||
// Déclenche l'exécution isolée en tâche de fond
|
||||
return await compute(runParallelTargetDetection, payload);
|
||||
}
|
||||
|
||||
/// Gardée pour rétrocompatibilité synchrone si nécessaire
|
||||
TargetDetectionResult detectTarget(String imagePath, TargetType targetType) {
|
||||
try {
|
||||
// Detect main target
|
||||
final mainTarget = _imageProcessingService.detectMainTarget(imagePath);
|
||||
|
||||
double centerX = 0.5;
|
||||
@@ -78,10 +189,8 @@ class TargetDetectionService {
|
||||
radius = mainTarget.radius;
|
||||
}
|
||||
|
||||
// Detect impacts
|
||||
final impacts = _imageProcessingService.detectImpacts(imagePath);
|
||||
|
||||
// Convert impacts to relative coordinates and calculate scores
|
||||
final detectedImpacts = impacts.map((impact) {
|
||||
final score = targetType == TargetType.concentric
|
||||
? _calculateConcentricScore(
|
||||
@@ -119,12 +228,10 @@ class TargetDetectionService {
|
||||
double centerY,
|
||||
double targetRadius,
|
||||
) {
|
||||
// Calculate distance from center (normalized to target radius)
|
||||
final dx = impactX - centerX;
|
||||
final dy = impactY - centerY;
|
||||
final distance = math.sqrt(dx * dx + dy * dy) / targetRadius;
|
||||
|
||||
// Score zones (10 zones)
|
||||
if (distance <= 0.1) return 10;
|
||||
if (distance <= 0.2) return 9;
|
||||
if (distance <= 0.3) return 8;
|
||||
@@ -135,7 +242,7 @@ class TargetDetectionService {
|
||||
if (distance <= 0.8) return 3;
|
||||
if (distance <= 0.9) return 2;
|
||||
if (distance <= 1.0) return 1;
|
||||
return 0; // Outside target
|
||||
return 0;
|
||||
}
|
||||
|
||||
int _calculateSilhouetteScore(
|
||||
@@ -144,25 +251,18 @@ class TargetDetectionService {
|
||||
double centerX,
|
||||
double centerY,
|
||||
) {
|
||||
// For silhouettes, scoring is typically based on zones
|
||||
// Head and center mass = 5, body = 4, lower = 3
|
||||
|
||||
final dx = (impactX - centerX).abs();
|
||||
final dy = impactY - centerY;
|
||||
|
||||
// Check if within silhouette bounds (approximate)
|
||||
if (dx > 0.15) return 0; // Too far left/right
|
||||
if (dx > 0.15) return 0;
|
||||
if (dy < -0.25) return 5;
|
||||
if (dy < 0.0) return 5;
|
||||
if (dy < 0.15) return 4;
|
||||
if (dy < 0.35) return 3;
|
||||
|
||||
// Vertical zones
|
||||
if (dy < -0.25) return 5; // Head zone (top)
|
||||
if (dy < 0.0) return 5; // Center mass (upper body)
|
||||
if (dy < 0.15) return 4; // Body
|
||||
if (dy < 0.35) return 3; // Lower body
|
||||
|
||||
return 0; // Outside target
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// Detect only impacts with custom settings (doesn't affect target position)
|
||||
List<DetectedImpactResult> detectImpactsOnly(
|
||||
String imagePath,
|
||||
TargetType targetType,
|
||||
@@ -173,13 +273,11 @@ class TargetDetectionService {
|
||||
ImpactDetectionSettings settings,
|
||||
) {
|
||||
try {
|
||||
// Detect impacts with custom settings
|
||||
final impacts = _imageProcessingService.detectImpactsWithSettings(
|
||||
imagePath,
|
||||
settings,
|
||||
);
|
||||
|
||||
// Convert impacts to relative coordinates and calculate scores
|
||||
return impacts.map((impact) {
|
||||
final score = targetType == TargetType.concentric
|
||||
? _calculateConcentricScoreWithRings(
|
||||
@@ -212,12 +310,10 @@ class TargetDetectionService {
|
||||
double targetRadius,
|
||||
int ringCount,
|
||||
) {
|
||||
// Calculate distance from center (normalized to target radius)
|
||||
final dx = impactX - centerX;
|
||||
final dy = impactY - centerY;
|
||||
final distance = math.sqrt(dx * dx + dy * dy) / targetRadius;
|
||||
|
||||
// Score zones based on ringCount
|
||||
for (int i = 0; i < ringCount; i++) {
|
||||
final zoneRadius = (i + 1) / ringCount;
|
||||
if (distance <= zoneRadius) {
|
||||
@@ -225,10 +321,9 @@ class TargetDetectionService {
|
||||
}
|
||||
}
|
||||
|
||||
return 0; // Outside target
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// Analyze reference impacts to learn their characteristics
|
||||
ImpactCharacteristics? analyzeReferenceImpacts(
|
||||
String imagePath,
|
||||
List<ReferenceImpact> references,
|
||||
@@ -239,7 +334,6 @@ class TargetDetectionService {
|
||||
);
|
||||
}
|
||||
|
||||
/// Detect impacts based on reference characteristics (calibrated detection)
|
||||
List<DetectedImpactResult> detectImpactsFromReferences(
|
||||
String imagePath,
|
||||
TargetType targetType,
|
||||
@@ -281,11 +375,6 @@ class TargetDetectionService {
|
||||
}
|
||||
}
|
||||
|
||||
/// Détecte les impacts en utilisant OpenCV (Hough Circles + Contours)
|
||||
///
|
||||
/// Cette méthode utilise les algorithmes OpenCV pour une détection plus robuste:
|
||||
/// - Transformation de Hough pour détecter les cercles
|
||||
/// - Analyse de contours avec filtrage par circularité
|
||||
List<DetectedImpactResult> detectImpactsWithOpenCV(
|
||||
String imagePath,
|
||||
TargetType targetType,
|
||||
@@ -326,10 +415,6 @@ class TargetDetectionService {
|
||||
}
|
||||
}
|
||||
|
||||
/// Détecte les impacts avec OpenCV en utilisant des références
|
||||
///
|
||||
/// Analyse les impacts de référence pour apprendre leurs caractéristiques
|
||||
/// puis détecte les impacts similaires dans l'image.
|
||||
List<DetectedImpactResult> detectImpactsWithOpenCVFromReferences(
|
||||
String imagePath,
|
||||
TargetType targetType,
|
||||
@@ -341,7 +426,6 @@ class TargetDetectionService {
|
||||
double tolerance = 2.0,
|
||||
}) {
|
||||
try {
|
||||
// Convertir les références au format OpenCV
|
||||
final refPoints = references.map((r) => (x: r.x, y: r.y)).toList();
|
||||
|
||||
final impacts = _opencvService.detectFromReferences(
|
||||
@@ -374,5 +458,4 @@ class TargetDetectionService {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user