desactivation de tous les scripts et ia, supression du modele yolo

This commit is contained in:
streaper2
2026-04-28 16:52:20 +02:00
parent e32833e366
commit fba3b41f2f
14 changed files with 382 additions and 285 deletions

View File

@@ -223,6 +223,16 @@ class AnalysisProvider extends ChangeNotifier {
notifyListeners();
}
/// Update a shot's score manually
void updateShotScore(String shotId, int newScore) {
final index = _shots.indexWhere((shot) => shot.id == shotId);
if (index == -1) return;
_shots[index] = _shots[index].copyWith(score: newScore);
_recalculateScores();
notifyListeners();
}
/// Auto-detect impacts using image processing
Future<int> autoDetectImpacts({
int darkThreshold = 80,
@@ -293,6 +303,8 @@ class AnalysisProvider extends ChangeNotifier {
double param2 = 30,
int minRadius = 5,
int maxRadius = 50,
int minSize = 5,
int maxSize = 1000,
int blurSize = 5,
bool useContourDetection = true,
double minCircularity = 0.6,
@@ -351,6 +363,47 @@ class AnalysisProvider extends ChangeNotifier {
return detectedImpacts.length;
}
/// Auto-detect impacts using YOLOv8 model
Future<int> autoDetectImpactsWithYOLO({bool clearExisting = false}) async {
if (_imagePath == null || _targetType == null) return 0;
try {
final detectedImpacts = await _detectionService.detectImpactsWithYOLO(
_imagePath!,
_targetType!,
_targetCenterX,
_targetCenterY,
_targetRadius,
_ringCount,
);
if (clearExisting) {
_shots.clear();
}
// Add detected impacts as shots
for (final impact in detectedImpacts) {
final shot = Shot(
id: _uuid.v4(),
x: impact.x,
y: impact.y,
score: impact.suggestedScore,
sessionId: '',
);
_shots.add(shot);
}
_recalculateScores();
_recalculateGrouping();
notifyListeners();
return detectedImpacts.length;
} catch (e) {
print('Error in YOLO auto-detection: $e');
return 0;
}
}
/// Detect impacts with OpenCV using reference points
Future<int> detectFromReferencesWithOpenCV({
double tolerance = 2.0,

View File

@@ -274,68 +274,7 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
),
child: Column(
children: [
// Auto-calibrate button
SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
onPressed: () async {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Row(
children: [
SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
),
),
SizedBox(width: 12),
Text('Auto-calibration en cours...'),
],
),
duration: Duration(seconds: 2),
),
);
final success = await provider
.autoCalibrateTarget();
if (context.mounted) {
ScaffoldMessenger.of(
context,
).hideCurrentSnackBar();
if (success) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Cible calibrée automatiquement',
),
backgroundColor: AppTheme.successColor,
),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Échec de la calibration auto',
),
backgroundColor: AppTheme.errorColor,
),
);
}
}
},
icon: const Icon(Icons.auto_fix_high),
label: const Text('Auto-Calibrer la Cible'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.deepPurple,
foregroundColor: Colors.white,
),
),
),
const SizedBox(height: 16),
// Ring count slider
Row(
children: [
@@ -387,57 +326,6 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
),
],
),
// Target size slider
Row(
children: [
const Icon(
Icons.zoom_out_map,
color: Colors.white,
size: 20,
),
const SizedBox(width: 8),
const Text(
'Taille:',
style: TextStyle(color: Colors.white),
),
Expanded(
child: Slider(
value: provider.targetRadius.clamp(0.05, 3.0),
min: 0.05,
max: 3.0,
label:
'${(provider.targetRadius * 100).toStringAsFixed(0)}%',
activeColor: AppTheme.warningColor,
onChanged: (value) {
provider.adjustTargetPosition(
provider.targetCenterX,
provider.targetCenterY,
provider.targetInnerRadius,
value,
ringCount: provider.ringCount,
);
},
),
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 4,
),
decoration: BoxDecoration(
color: AppTheme.warningColor,
borderRadius: BorderRadius.circular(12),
),
child: Text(
'${(provider.targetRadius * 100).toStringAsFixed(0)}%',
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
],
),
const Divider(color: Colors.white24, height: 16),
// Distortion correction row
/*Row(
@@ -793,6 +681,7 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
),
// Overlay qui se transforme avec l'image
TargetOverlay(
showRings: true,
shots: provider.shots,
targetCenterX: provider.targetCenterX,
targetCenterY: provider.targetCenterY,
@@ -879,6 +768,7 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
),
// Overlay qui se transforme avec l'image
TargetOverlay(
showRings: true,
shots: provider.shots,
targetCenterX: provider.targetCenterX,
targetCenterY: provider.targetCenterY,
@@ -991,107 +881,7 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
}
Widget _buildActionButtons(BuildContext context, AnalysisProvider provider) {
return Column(
children: [
// Reference-based detection section
if (_isSelectingReferences) ...[
Card(
color: Colors.deepPurple.withValues(alpha: 0.1),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(Icons.touch_app, color: Colors.deepPurple),
const SizedBox(width: 8),
Text(
'${provider.referenceImpacts.length} reference(s) selectionnee(s)',
style: const TextStyle(fontWeight: FontWeight.bold),
),
],
),
const SizedBox(height: 8),
const Text(
'Touchez l\'image pour marquer 3-4 impacts de reference. '
'L\'algorithme apprendra leurs caracteristiques pour detecter les autres.',
style: TextStyle(fontSize: 12, color: Colors.grey),
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: () {
setState(() => _isSelectingReferences = false);
provider.clearReferenceImpacts();
},
child: const Text('Annuler'),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton.icon(
onPressed: provider.referenceImpacts.length >= 2
? () => _showCalibratedDetectionDialog(
context,
provider,
)
: null,
icon: const Icon(Icons.auto_fix_high),
label: const Text('Detecter'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.deepPurple,
foregroundColor: Colors.white,
),
),
),
],
),
],
),
),
),
const SizedBox(height: 12),
] else ...[
// désactiver le temps de l'amelioration du scripts d'auto-detection
// Auto-detect buttons row
// Row(
// children: [
// Expanded(
// child: ElevatedButton.icon(
// onPressed: () => _showAutoDetectDialog(context, provider),
// icon: const Icon(Icons.auto_fix_high),
// label: const Text('Auto-Detection'),
// style: ElevatedButton.styleFrom(
// backgroundColor: AppTheme.primaryColor,
// foregroundColor: Colors.white,
// padding: const EdgeInsets.symmetric(vertical: 12),
// ),
// ),
// ),
// const SizedBox(width: 12),
// Expanded(
// child: ElevatedButton.icon(
// onPressed: () => setState(() => _isSelectingReferences = true),
// icon: const Icon(Icons.touch_app),
// label: const Text('Par Reference'),
// style: ElevatedButton.styleFrom(
// backgroundColor: Colors.deepPurple,
// foregroundColor: Colors.white,
// padding: const EdgeInsets.symmetric(vertical: 12),
// ),
// ),
// ),
// ],
// ),
const SizedBox(height: 12),
],
// Manual actions
],
);
return const SizedBox.shrink();
}
void _showHelpDialog(BuildContext context) {
@@ -1129,20 +919,67 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
AnalysisProvider provider,
String shotId,
) {
final shot = provider.shots.firstWhere((s) => s.id == shotId);
showModalBottomSheet(
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
builder: (context) => SafeArea(
child: Wrap(
children: [
ListTile(
leading: const Icon(Icons.delete, color: AppTheme.errorColor),
title: const Text('Supprimer cet impact'),
onTap: () {
provider.removeShot(shotId);
Navigator.pop(context);
},
),
],
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Modifier l\'impact',
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 20),
// Dropdown for score
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('Valeur de l\'impact : ', style: TextStyle(fontSize: 16)),
const SizedBox(width: 10),
DropdownButton<int>(
value: shot.score.clamp(0, 10),
items: List.generate(11, (i) => i).map((i) {
return DropdownMenuItem<int>(
value: i,
child: Text(i == 10 && provider.targetType == TargetType.concentric ? '10 (X)' : '$i'),
);
}).toList(),
onChanged: (newScore) {
if (newScore != null) {
provider.updateShotScore(shotId, newScore);
Navigator.pop(context);
}
},
),
],
),
const SizedBox(height: 20),
// Delete button at the bottom
SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
onPressed: () {
provider.removeShot(shotId);
Navigator.pop(context);
},
icon: const Icon(Icons.delete),
label: const Text('SUPPRIMER L\'IMPACT'),
style: ElevatedButton.styleFrom(
backgroundColor: AppTheme.errorColor,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 12),
),
),
),
],
),
),
),
);
@@ -1187,7 +1024,7 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
);
}
/*
void _showAutoDetectDialog(BuildContext context, AnalysisProvider provider) {
// Detection settings
bool clearExisting = true;
@@ -1197,9 +1034,6 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
int maxImpactSize = 500;
double minFillRatio = 0.5;
// NOTE: OpenCV désactivé - problèmes de build Windows
// Utilisation de la détection classique uniquement
showDialog(
context: context,
builder: (context) => StatefulBuilder(
@@ -1216,11 +1050,78 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// YOLO option button
Card(
color: AppTheme.primaryColor.withAlpha(25),
child: ListTile(
leading: const Icon(Icons.psychology, color: AppTheme.primaryColor),
title: const Text('IA Detection (YOLOv8)', style: TextStyle(fontWeight: FontWeight.bold)),
subtitle: const Text('Détection intelligente via modèle entraîné'),
onTap: () async {
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Row(
children: [
SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
),
),
SizedBox(width: 12),
Text('Détection IA en cours...'),
],
),
duration: Duration(seconds: 10),
),
);
final count = await provider.autoDetectImpactsWithYOLO(
clearExisting: clearExisting,
);
if (context.mounted) {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
count > 0
? '$count impact(s) détecté(s) par l\'IA'
: 'Aucun impact détecté par l\'IA.',
),
backgroundColor: count > 0
? AppTheme.successColor
: AppTheme.warningColor,
),
);
}
},
),
),
const Padding(
padding: EdgeInsets.symmetric(vertical: 12),
child: Row(
children: [
Expanded(child: Divider()),
Padding(
padding: EdgeInsets.symmetric(horizontal: 8),
child: Text('OU', style: TextStyle(color: Colors.grey, fontSize: 12)),
),
Expanded(child: Divider()),
],
),
),
const Text(
'Ajustez les parametres de detection:',
'Détection Classique (Paramétrable):',
style: TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
const SizedBox(height: 8),
// Dark threshold slider
Text('Seuil de detection (zones sombres): $darkThreshold'),
@@ -1379,14 +1280,13 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
}
},
icon: const Icon(Icons.search),
label: const Text('Detecter'),
label: const Text('Détecter (Classique)'),
),
],
),
),
);
}
*/
void _showCalibratedDetectionDialog(
BuildContext context,

View File

@@ -25,6 +25,7 @@ class TargetOverlay extends StatelessWidget {
final double? groupingDiameter;
final List<Shot>? referenceImpacts;
final double zoomScale;
final bool showRings;
const TargetOverlay({
super.key,
@@ -42,6 +43,7 @@ class TargetOverlay extends StatelessWidget {
this.groupingDiameter,
this.referenceImpacts,
this.zoomScale = 1.0,
this.showRings = false,
});
@override
@@ -72,6 +74,7 @@ class TargetOverlay extends StatelessWidget {
groupingDiameter: groupingDiameter,
referenceImpacts: referenceImpacts,
zoomScale: zoomScale,
showRings: showRings,
),
child: Stack(
children: shots.map((shot) {
@@ -132,6 +135,7 @@ class _TargetOverlayPainter extends CustomPainter {
final double? groupingDiameter;
final List<Shot>? referenceImpacts;
final double zoomScale;
final bool showRings;
_TargetOverlayPainter({
required this.shots,
@@ -146,12 +150,15 @@ class _TargetOverlayPainter extends CustomPainter {
this.groupingDiameter,
this.referenceImpacts,
this.zoomScale = 1.0,
this.showRings = false,
});
@override
void paint(Canvas canvas, Size size) {
// Draw target center indicator
_drawTargetCenter(canvas, size);
if (showRings) {
_drawTargetCenter(canvas, size);
}
// Draw grouping circle
if (groupingCenterX != null && groupingCenterY != null && groupingDiameter != null && shots.length > 1) {
@@ -371,6 +378,7 @@ class _TargetOverlayPainter extends CustomPainter {
groupingCenterY != oldDelegate.groupingCenterY ||
groupingDiameter != oldDelegate.groupingDiameter ||
referenceImpacts != oldDelegate.referenceImpacts ||
zoomScale != oldDelegate.zoomScale;
zoomScale != oldDelegate.zoomScale ||
showRings != oldDelegate.showRings;
}
}

View File

@@ -392,16 +392,21 @@ class TargetDetectionService {
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);
// Use YOLO-detected score (valeur) if available, otherwise calculate it
int score = impact.suggestedScore;
if (score <= 0) {
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,

View File

@@ -7,25 +7,41 @@ import 'target_detection_service.dart';
class YOLOImpactDetectionService {
Interpreter? _interpreter;
int _inputSize = 640;
List<int> _outputShape = [1, 17, 8400];
int _numClasses = 13;
static const String modelPath = 'assets/models/yolov11n_impact.tflite';
static const String modelPath = 'assets/models/yolov8n_32.tflite';
static const String labelsPath = 'assets/models/labels.txt';
Future<void> init() async {
if (_interpreter != null) return;
try {
// Try loading the specific YOLOv11 model first, fallback to v8 if not found
try {
_interpreter = await Interpreter.fromAsset(modelPath);
} catch (e) {
print('YOLOv11 model not found at $modelPath, trying YOLOv8 fallback');
_interpreter = await Interpreter.fromAsset(
'assets/models/yolov8n_impact.tflite',
);
_interpreter = await Interpreter.fromAsset(modelPath);
// Get model metadata
final inputTensors = _interpreter!.getInputTensors();
if (inputTensors.isNotEmpty) {
// [1, 640, 640, 3] or [1, 3, 640, 640]
final shape = inputTensors[0].shape;
if (shape.length == 4) {
_inputSize = shape[1] == 3 ? shape[2] : shape[1];
}
}
final outputTensors = _interpreter!.getOutputTensors();
if (outputTensors.isNotEmpty) {
_outputShape = outputTensors[0].shape;
// Output is usually [1, 4 + num_classes, num_boxes]
if (_outputShape.length == 3) {
_numClasses = _outputShape[1] - 4;
}
}
print('YOLO Interpreter loaded successfully');
print('Model Input Size: $_inputSize');
print('Model Output Shape: $_outputShape (Classes: $_numClasses)');
} catch (e) {
print('Error loading YOLO model: $e');
}
@@ -37,31 +53,35 @@ class YOLOImpactDetectionService {
try {
final bytes = File(imagePath).readAsBytesSync();
final originalImage = img.decodeImage(bytes);
if (originalImage == null) return [];
final decodedImage = img.decodeImage(bytes);
if (decodedImage == null) return [];
final originalImage = img.bakeOrientation(decodedImage);
// YOLOv8/v11 usually takes 640x640
const int inputSize = 640;
final resizedImage = img.copyResize(
originalImage,
width: inputSize,
height: inputSize,
width: _inputSize,
height: _inputSize,
);
// Prepare input tensor
var input = _imageToByteListFloat32(resizedImage, inputSize);
// Raw YOLO output shape usually [1, 4 + num_classes, 8400]
// For single class "impact", it's [1, 5, 8400]
var output = List<double>.filled(1 * 5 * 8400, 0).reshape([1, 5, 8400]);
var input = _imageToByteListFloat32(resizedImage, _inputSize);
// Allocate output buffer dynamically
var output = List<double>.filled(
_outputShape.fold(1, (a, b) => a * b),
0
).reshape(_outputShape);
_interpreter!.run(input, output);
return _processOutput(
final results = _processOutput(
output[0],
originalImage.width,
originalImage.height,
);
print('YOLO Detection result count: ${results.length}');
return results;
} catch (e) {
print('Error during YOLO inference: $e');
return [];
@@ -76,33 +96,55 @@ class YOLOImpactDetectionService {
final List<_Detection> candidates = [];
const double threshold = 0.25;
// output is [5, 8400] -> [x, y, w, h, conf]
for (int i = 0; i < 8400; i++) {
final double confidence = output[4][i];
if (confidence > threshold) {
// output is typically [4 + numClasses, numBoxes]
final int rows = output.length;
final int numBoxes = output[0].length;
for (int i = 0; i < numBoxes; i++) {
double maxConfidence = 0;
int bestClass = 4;
// Find best class among all classes (indices 4 to rows-1)
for (int c = 4; c < rows; c++) {
if (output[c][i] > maxConfidence) {
maxConfidence = output[c][i];
bestClass = c;
}
}
if (maxConfidence > threshold) {
final int classIndex = bestClass - 4;
candidates.add(
_Detection(
x: output[0][i],
y: output[1][i],
// Use dynamic mapping: 0,1,2,3 are typically x,y,w,h
// We'll keep the current mapping for now as it matches user's previous model
y: output[0][i],
x: output[1][i],
w: output[2][i],
h: output[3][i],
confidence: confidence,
confidence: maxConfidence,
classIndex: classIndex,
),
);
}
}
// Apply Non-Max Suppression (NMS)
final List<_Detection> suppressed = _nms(candidates);
return suppressed
.map(
(det) => DetectedImpactResult(
x: det.x / 640.0,
y: det.y / 640.0,
radius: 5.0,
suggestedScore: 0,
),
(det) {
// Score = ClassIndex (0 to 10) for impact models
int score = det.classIndex;
return DetectedImpactResult(
x: det.x,
y: det.y,
radius: 5.0,
suggestedScore: score,
);
},
)
.toList();
}
@@ -110,7 +152,6 @@ class YOLOImpactDetectionService {
List<_Detection> _nms(List<_Detection> detections) {
if (detections.isEmpty) return [];
// Sort by confidence descending
detections.sort((a, b) => b.confidence.compareTo(a.confidence));
final List<_Detection> selected = [];
@@ -164,11 +205,13 @@ class YOLOImpactDetectionService {
class _Detection {
final double x, y, w, h, confidence;
final int classIndex;
_Detection({
required this.x,
required this.y,
required this.w,
required this.h,
required this.confidence,
required this.classIndex,
});
}