258 lines
7.1 KiB
Dart
258 lines
7.1 KiB
Dart
import 'dart:math' as math;
|
|
import '../data/models/target_type.dart';
|
|
import 'image_processing_service.dart';
|
|
|
|
export 'image_processing_service.dart' show ImpactDetectionSettings, ReferenceImpact, ImpactCharacteristics;
|
|
|
|
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;
|
|
|
|
TargetDetectionService({
|
|
ImageProcessingService? imageProcessingService,
|
|
}) : _imageProcessingService = imageProcessingService ?? ImageProcessingService();
|
|
|
|
/// 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 [];
|
|
}
|
|
}
|
|
}
|