419 lines
12 KiB
Dart
419 lines
12 KiB
Dart
import 'dart:math' as math;
|
|
import '../data/models/target_type.dart';
|
|
import 'image_processing_service.dart';
|
|
import 'opencv_impact_detection_service.dart';
|
|
import 'yolo_impact_detection_service.dart';
|
|
|
|
export 'image_processing_service.dart'
|
|
show ImpactDetectionSettings, ReferenceImpact, ImpactCharacteristics;
|
|
export 'opencv_impact_detection_service.dart'
|
|
show OpenCVDetectionSettings, OpenCVDetectedImpact;
|
|
|
|
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;
|
|
final YOLOImpactDetectionService _yoloService;
|
|
|
|
TargetDetectionService({
|
|
ImageProcessingService? imageProcessingService,
|
|
OpenCVImpactDetectionService? opencvService,
|
|
YOLOImpactDetectionService? yoloService,
|
|
}) : _imageProcessingService =
|
|
imageProcessingService ?? ImageProcessingService(),
|
|
_opencvService = opencvService ?? OpenCVImpactDetectionService(),
|
|
_yoloService = yoloService ?? YOLOImpactDetectionService();
|
|
|
|
/// Detect target and impacts from an image file
|
|
TargetDetectionResult detectTarget(String imagePath, TargetType targetType) {
|
|
try {
|
|
// Detect main target
|
|
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;
|
|
}
|
|
|
|
// 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(
|
|
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,
|
|
) {
|
|
// 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;
|
|
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; // Outside target
|
|
}
|
|
|
|
int _calculateSilhouetteScore(
|
|
double impactX,
|
|
double impactY,
|
|
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
|
|
|
|
// 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
|
|
}
|
|
|
|
/// Detect only impacts with custom settings (doesn't affect target position)
|
|
List<DetectedImpactResult> detectImpactsOnly(
|
|
String imagePath,
|
|
TargetType targetType,
|
|
double centerX,
|
|
double centerY,
|
|
double radius,
|
|
int ringCount,
|
|
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(
|
|
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,
|
|
) {
|
|
// 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) {
|
|
return 10 - i;
|
|
}
|
|
}
|
|
|
|
return 0; // Outside target
|
|
}
|
|
|
|
/// Analyze reference impacts to learn their characteristics
|
|
ImpactCharacteristics? analyzeReferenceImpacts(
|
|
String imagePath,
|
|
List<ReferenceImpact> references,
|
|
) {
|
|
return _imageProcessingService.analyzeReferenceImpacts(
|
|
imagePath,
|
|
references,
|
|
);
|
|
}
|
|
|
|
/// Detect impacts based on reference characteristics (calibrated detection)
|
|
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 [];
|
|
}
|
|
}
|
|
|
|
/// 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,
|
|
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) {
|
|
print('Erreur détection OpenCV: $e');
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/// 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,
|
|
double centerX,
|
|
double centerY,
|
|
double radius,
|
|
int ringCount,
|
|
List<ReferenceImpact> references, {
|
|
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(
|
|
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) {
|
|
print('Erreur détection OpenCV depuis références: $e');
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/// Détecte les impacts en utilisant YOLOv8
|
|
Future<List<DetectedImpactResult>> detectImpactsWithYOLO(
|
|
String imagePath,
|
|
TargetType targetType,
|
|
double centerX,
|
|
double centerY,
|
|
double radius,
|
|
int ringCount,
|
|
) async {
|
|
try {
|
|
final impacts = await _yoloService.detectImpacts(imagePath);
|
|
|
|
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) {
|
|
print('Erreur détection YOLOv8: $e');
|
|
return [];
|
|
}
|
|
}
|
|
}
|