461 lines
13 KiB
Dart
461 lines
13 KiB
Dart
import 'dart:math' as math;
|
|
import 'package:flutter/foundation.dart';
|
|
import '../data/models/target_type.dart';
|
|
import 'image_processing_service.dart';
|
|
import 'opencv_impact_detection_service.dart';
|
|
export 'image_processing_service.dart'
|
|
show ImpactDetectionSettings, ReferenceImpact, ImpactCharacteristics;
|
|
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)
|
|
final double radius; // Relative (0-1)
|
|
final List<DetectedImpactResult> impacts;
|
|
final bool success;
|
|
final String? errorMessage;
|
|
|
|
TargetDetectionResult({
|
|
required this.centerX,
|
|
required this.centerY,
|
|
required this.radius,
|
|
required this.impacts,
|
|
this.success = true,
|
|
this.errorMessage,
|
|
});
|
|
|
|
factory TargetDetectionResult.error(String message) {
|
|
return TargetDetectionResult(
|
|
centerX: 0.5,
|
|
centerY: 0.5,
|
|
radius: 0.4,
|
|
impacts: [],
|
|
success: false,
|
|
errorMessage: message,
|
|
);
|
|
}
|
|
}
|
|
|
|
class DetectedImpactResult {
|
|
final double x; // Relative (0-1)
|
|
final double y; // Relative (0-1)
|
|
final double radius; // Absolute pixels
|
|
final int suggestedScore;
|
|
|
|
DetectedImpactResult({
|
|
required this.x,
|
|
required this.y,
|
|
required this.radius,
|
|
required this.suggestedScore,
|
|
});
|
|
}
|
|
|
|
class TargetDetectionService {
|
|
final ImageProcessingService _imageProcessingService;
|
|
final OpenCVImpactDetectionService _opencvService;
|
|
|
|
TargetDetectionService({
|
|
ImageProcessingService? imageProcessingService,
|
|
OpenCVImpactDetectionService? opencvService,
|
|
}) : _imageProcessingService =
|
|
imageProcessingService ?? ImageProcessingService(),
|
|
_opencvService = opencvService ?? OpenCVImpactDetectionService();
|
|
|
|
/// 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 {
|
|
final mainTarget = _imageProcessingService.detectMainTarget(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;
|
|
}
|
|
|
|
final impacts = _imageProcessingService.detectImpacts(imagePath);
|
|
|
|
final detectedImpacts = impacts.map((impact) {
|
|
final score = targetType == TargetType.concentric
|
|
? _calculateConcentricScore(
|
|
impact.x,
|
|
impact.y,
|
|
centerX,
|
|
centerY,
|
|
radius,
|
|
)
|
|
: _calculateSilhouetteScore(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: $e');
|
|
}
|
|
}
|
|
|
|
int _calculateConcentricScore(
|
|
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 _calculateSilhouetteScore(
|
|
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;
|
|
}
|
|
|
|
List<DetectedImpactResult> detectImpactsOnly(
|
|
String imagePath,
|
|
TargetType targetType,
|
|
double centerX,
|
|
double centerY,
|
|
double radius,
|
|
int ringCount,
|
|
ImpactDetectionSettings settings,
|
|
) {
|
|
try {
|
|
final impacts = _imageProcessingService.detectImpactsWithSettings(
|
|
imagePath,
|
|
settings,
|
|
);
|
|
|
|
return impacts.map((impact) {
|
|
final score = targetType == TargetType.concentric
|
|
? _calculateConcentricScoreWithRings(
|
|
impact.x,
|
|
impact.y,
|
|
centerX,
|
|
centerY,
|
|
radius,
|
|
ringCount,
|
|
)
|
|
: _calculateSilhouetteScore(impact.x, impact.y, centerX, centerY);
|
|
|
|
return DetectedImpactResult(
|
|
x: impact.x,
|
|
y: impact.y,
|
|
radius: impact.radius,
|
|
suggestedScore: score,
|
|
);
|
|
}).toList();
|
|
} catch (e) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
int _calculateConcentricScoreWithRings(
|
|
double impactX,
|
|
double impactY,
|
|
double centerX,
|
|
double centerY,
|
|
double targetRadius,
|
|
int ringCount,
|
|
) {
|
|
final dx = impactX - centerX;
|
|
final dy = impactY - centerY;
|
|
final distance = math.sqrt(dx * dx + dy * dy) / targetRadius;
|
|
|
|
for (int i = 0; i < ringCount; i++) {
|
|
final zoneRadius = (i + 1) / ringCount;
|
|
if (distance <= zoneRadius) {
|
|
return 10 - i;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
ImpactCharacteristics? analyzeReferenceImpacts(
|
|
String imagePath,
|
|
List<ReferenceImpact> references,
|
|
) {
|
|
return _imageProcessingService.analyzeReferenceImpacts(
|
|
imagePath,
|
|
references,
|
|
);
|
|
}
|
|
|
|
List<DetectedImpactResult> detectImpactsFromReferences(
|
|
String imagePath,
|
|
TargetType targetType,
|
|
double centerX,
|
|
double centerY,
|
|
double radius,
|
|
int ringCount,
|
|
ImpactCharacteristics characteristics, {
|
|
double tolerance = 2.0,
|
|
}) {
|
|
try {
|
|
final impacts = _imageProcessingService.detectImpactsFromReferences(
|
|
imagePath,
|
|
characteristics,
|
|
tolerance: tolerance,
|
|
);
|
|
|
|
return impacts.map((impact) {
|
|
final score = targetType == TargetType.concentric
|
|
? _calculateConcentricScoreWithRings(
|
|
impact.x,
|
|
impact.y,
|
|
centerX,
|
|
centerY,
|
|
radius,
|
|
ringCount,
|
|
)
|
|
: _calculateSilhouetteScore(impact.x, impact.y, centerX, centerY);
|
|
|
|
return DetectedImpactResult(
|
|
x: impact.x,
|
|
y: impact.y,
|
|
radius: impact.radius,
|
|
suggestedScore: score,
|
|
);
|
|
}).toList();
|
|
} catch (e) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
List<DetectedImpactResult> detectImpactsWithOpenCV(
|
|
String imagePath,
|
|
TargetType targetType,
|
|
double centerX,
|
|
double centerY,
|
|
double radius,
|
|
int ringCount, {
|
|
OpenCVDetectionSettings? settings,
|
|
}) {
|
|
try {
|
|
final impacts = _opencvService.detectImpacts(
|
|
imagePath,
|
|
settings: settings ?? const OpenCVDetectionSettings(),
|
|
);
|
|
|
|
return impacts.map((impact) {
|
|
final score = targetType == TargetType.concentric
|
|
? _calculateConcentricScoreWithRings(
|
|
impact.x,
|
|
impact.y,
|
|
centerX,
|
|
centerY,
|
|
radius,
|
|
ringCount,
|
|
)
|
|
: _calculateSilhouetteScore(impact.x, impact.y, centerX, centerY);
|
|
|
|
return DetectedImpactResult(
|
|
x: impact.x,
|
|
y: impact.y,
|
|
radius: impact.radius,
|
|
suggestedScore: score,
|
|
);
|
|
}).toList();
|
|
} catch (e) {
|
|
debugPrint('Erreur détection OpenCV: $e');
|
|
return [];
|
|
}
|
|
}
|
|
|
|
List<DetectedImpactResult> detectImpactsWithOpenCVFromReferences(
|
|
String imagePath,
|
|
TargetType targetType,
|
|
double centerX,
|
|
double centerY,
|
|
double radius,
|
|
int ringCount,
|
|
List<ReferenceImpact> references, {
|
|
double tolerance = 2.0,
|
|
}) {
|
|
try {
|
|
final refPoints = references.map((r) => (x: r.x, y: r.y)).toList();
|
|
|
|
final impacts = _opencvService.detectFromReferences(
|
|
imagePath,
|
|
refPoints,
|
|
tolerance: tolerance,
|
|
);
|
|
|
|
return impacts.map((impact) {
|
|
final score = targetType == TargetType.concentric
|
|
? _calculateConcentricScoreWithRings(
|
|
impact.x,
|
|
impact.y,
|
|
centerX,
|
|
centerY,
|
|
radius,
|
|
ringCount,
|
|
)
|
|
: _calculateSilhouetteScore(impact.x, impact.y, centerX, centerY);
|
|
|
|
return DetectedImpactResult(
|
|
x: impact.x,
|
|
y: impact.y,
|
|
radius: impact.radius,
|
|
suggestedScore: score,
|
|
);
|
|
}).toList();
|
|
} catch (e) {
|
|
debugPrint('Erreur détection OpenCV depuis références: $e');
|
|
return [];
|
|
}
|
|
}
|
|
} |