chore: suppression du code mort (détection auto, distorsion, ML Kit)
- Supprime 5 services inatteignables depuis l'UI (~3 000 lignes) : distortion_correction, image_processing, target_detection, opencv_impact_detection, target_rectify - AnalysisProvider allégé (835 -> ~360 lignes) : retrait de la détection par références, de la détection auto d'impacts, du workflow distorsion et du doublon moveShot - Retire les dépendances inutilisées google_mlkit_object_detection et google_mlkit_document_scanner du pubspec - Le bouton ↻ du Plotting efface désormais tous les impacts en un clic (clearShots) sans relancer la détection auto ni toucher la calibration - Nettoie les paramètres morts de TargetOverlay (referenceImpacts, onAddShot), le flag _isSelectingReferences, _buildActionButtons vide et le résidu _detectionTimer de capture_screen - Supprime le dossier tests/ (brouillons d'expérimentation OpenCV) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
/// Gestionnaire d'état pour l'analyse des cibles (ChangeNotifier).
|
/// Gestionnaire d'état pour l'analyse des cibles (ChangeNotifier).
|
||||||
///
|
///
|
||||||
/// Gère le workflow complet d'analyse : chargement d'image, détection de cible,
|
/// Gère le workflow complet d'analyse : chargement d'image, gestion des
|
||||||
/// gestion des impacts (manuels et automatiques), calcul des scores,
|
/// impacts placés manuellement, calcul des scores, analyse de groupement
|
||||||
/// analyse de groupement et sauvegarde des sessions.
|
/// et sauvegarde des sessions.
|
||||||
library;
|
library;
|
||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
@@ -13,37 +13,25 @@ import '../../data/models/target_analysis.dart';
|
|||||||
import '../../data/models/shot.dart';
|
import '../../data/models/shot.dart';
|
||||||
import '../../data/models/target_type.dart';
|
import '../../data/models/target_type.dart';
|
||||||
import '../../data/repositories/session_repository.dart';
|
import '../../data/repositories/session_repository.dart';
|
||||||
import '../../services/target_detection_service.dart';
|
|
||||||
import '../../services/score_calculator_service.dart';
|
import '../../services/score_calculator_service.dart';
|
||||||
import '../../services/grouping_analyzer_service.dart';
|
import '../../services/grouping_analyzer_service.dart';
|
||||||
import '../../services/distortion_correction_service.dart';
|
|
||||||
import '../../services/opencv_target_service.dart';
|
|
||||||
import '../../services/ai_export_service.dart';
|
import '../../services/ai_export_service.dart';
|
||||||
|
|
||||||
enum AnalysisState { initial, loading, success, error }
|
enum AnalysisState { initial, loading, success, error }
|
||||||
|
|
||||||
class AnalysisProvider extends ChangeNotifier {
|
class AnalysisProvider extends ChangeNotifier {
|
||||||
final TargetDetectionService _detectionService;
|
|
||||||
final ScoreCalculatorService _scoreCalculatorService;
|
final ScoreCalculatorService _scoreCalculatorService;
|
||||||
final GroupingAnalyzerService _groupingAnalyzerService;
|
final GroupingAnalyzerService _groupingAnalyzerService;
|
||||||
final SessionRepository _sessionRepository;
|
final SessionRepository _sessionRepository;
|
||||||
final DistortionCorrectionService _distortionService;
|
|
||||||
final OpenCVTargetService _opencvTargetService;
|
|
||||||
final Uuid _uuid = const Uuid();
|
final Uuid _uuid = const Uuid();
|
||||||
|
|
||||||
AnalysisProvider({
|
AnalysisProvider({
|
||||||
required TargetDetectionService detectionService,
|
|
||||||
required ScoreCalculatorService scoreCalculatorService,
|
required ScoreCalculatorService scoreCalculatorService,
|
||||||
required GroupingAnalyzerService groupingAnalyzerService,
|
required GroupingAnalyzerService groupingAnalyzerService,
|
||||||
required SessionRepository sessionRepository,
|
required SessionRepository sessionRepository,
|
||||||
DistortionCorrectionService? distortionService,
|
}) : _scoreCalculatorService = scoreCalculatorService,
|
||||||
OpenCVTargetService? opencvTargetService,
|
|
||||||
}) : _detectionService = detectionService,
|
|
||||||
_scoreCalculatorService = scoreCalculatorService,
|
|
||||||
_groupingAnalyzerService = groupingAnalyzerService,
|
_groupingAnalyzerService = groupingAnalyzerService,
|
||||||
_sessionRepository = sessionRepository,
|
_sessionRepository = sessionRepository;
|
||||||
_distortionService = distortionService ?? DistortionCorrectionService(),
|
|
||||||
_opencvTargetService = opencvTargetService ?? OpenCVTargetService();
|
|
||||||
|
|
||||||
AnalysisState _state = AnalysisState.initial;
|
AnalysisState _state = AnalysisState.initial;
|
||||||
String? _errorMessage;
|
String? _errorMessage;
|
||||||
@@ -53,7 +41,7 @@ class AnalysisProvider extends ChangeNotifier {
|
|||||||
// AJOUT PROTECTION DU PLOTTING : Stockage permanent de la rotation du Crop
|
// AJOUT PROTECTION DU PLOTTING : Stockage permanent de la rotation du Crop
|
||||||
double _cropRotation = 0.0;
|
double _cropRotation = 0.0;
|
||||||
|
|
||||||
// Target detection results
|
// Target calibration
|
||||||
double _targetCenterX = 0.5;
|
double _targetCenterX = 0.5;
|
||||||
double _targetCenterY = 0.5;
|
double _targetCenterY = 0.5;
|
||||||
double _targetRadius = 0.4;
|
double _targetRadius = 0.4;
|
||||||
@@ -71,15 +59,6 @@ class AnalysisProvider extends ChangeNotifier {
|
|||||||
// Grouping results
|
// Grouping results
|
||||||
GroupingResult? _groupingResult;
|
GroupingResult? _groupingResult;
|
||||||
|
|
||||||
// Reference-based detection
|
|
||||||
List<Shot> _referenceImpacts = [];
|
|
||||||
ImpactCharacteristics? _learnedCharacteristics;
|
|
||||||
|
|
||||||
// Distortion correction
|
|
||||||
bool _distortionCorrectionEnabled = false;
|
|
||||||
DistortionParameters? _distortionParams;
|
|
||||||
String? _correctedImagePath;
|
|
||||||
|
|
||||||
// Getters
|
// Getters
|
||||||
AnalysisState get state => _state;
|
AnalysisState get state => _state;
|
||||||
String? get errorMessage => _errorMessage;
|
String? get errorMessage => _errorMessage;
|
||||||
@@ -100,21 +79,6 @@ class AnalysisProvider extends ChangeNotifier {
|
|||||||
|
|
||||||
int get totalScore => _scoreResult?.totalScore ?? 0;
|
int get totalScore => _scoreResult?.totalScore ?? 0;
|
||||||
int get shotCount => _shots.length;
|
int get shotCount => _shots.length;
|
||||||
List<Shot> get referenceImpacts => List.unmodifiable(_referenceImpacts);
|
|
||||||
ImpactCharacteristics? get learnedCharacteristics => _learnedCharacteristics;
|
|
||||||
bool get hasLearnedCharacteristics => _learnedCharacteristics != null;
|
|
||||||
|
|
||||||
// Distortion correction getters
|
|
||||||
bool get distortionCorrectionEnabled => _distortionCorrectionEnabled;
|
|
||||||
DistortionParameters? get distortionParams => _distortionParams;
|
|
||||||
String? get correctedImagePath => _correctedImagePath;
|
|
||||||
bool get hasDistortion => _distortionParams?.needsCorrection ?? false;
|
|
||||||
|
|
||||||
/// Retourne le chemin de l'image à afficher (corrigée si activée, originale sinon)
|
|
||||||
String? get displayImagePath =>
|
|
||||||
_distortionCorrectionEnabled && _correctedImagePath != null
|
|
||||||
? _correctedImagePath
|
|
||||||
: _imagePath;
|
|
||||||
|
|
||||||
/// Modifie et mémorise la rotation de l'image pour le Plotting
|
/// Modifie et mémorise la rotation de l'image pour le Plotting
|
||||||
void setCropRotation(double rotation) {
|
void setCropRotation(double rotation) {
|
||||||
@@ -122,14 +86,11 @@ class AnalysisProvider extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Analyze an image
|
/// Charge l'image et initialise les paramètres de cible par défaut.
|
||||||
///
|
/// Le placement des impacts et la calibration se font ensuite manuellement.
|
||||||
/// [autoAnalyze] determines if we should run automatic detection immediately.
|
|
||||||
/// If false, only the image is loaded and default target parameters are set.
|
|
||||||
Future<void> analyzeImage(
|
Future<void> analyzeImage(
|
||||||
String imagePath,
|
String imagePath,
|
||||||
TargetType targetType, {
|
TargetType targetType, {
|
||||||
bool autoAnalyze = true,
|
|
||||||
Offset? manualCenter,
|
Offset? manualCenter,
|
||||||
}) async {
|
}) async {
|
||||||
_state = AnalysisState.loading;
|
_state = AnalysisState.loading;
|
||||||
@@ -147,55 +108,13 @@ class AnalysisProvider extends ChangeNotifier {
|
|||||||
_imageAspectRatio = frame.image.width / frame.image.height;
|
_imageAspectRatio = frame.image.width / frame.image.height;
|
||||||
frame.image.dispose();
|
frame.image.dispose();
|
||||||
|
|
||||||
if (!autoAnalyze) {
|
|
||||||
// Just setup default values without running detection
|
|
||||||
_targetCenterX = manualCenter?.dx ?? 0.5;
|
_targetCenterX = manualCenter?.dx ?? 0.5;
|
||||||
_targetCenterY = manualCenter?.dy ?? 0.5;
|
_targetCenterY = manualCenter?.dy ?? 0.5;
|
||||||
_targetRadius = 0.4;
|
_targetRadius = 0.4;
|
||||||
_targetInnerRadius = 0.04;
|
_targetInnerRadius = 0.04;
|
||||||
|
|
||||||
// Initialize empty shots list
|
|
||||||
_shots = [];
|
_shots = [];
|
||||||
|
|
||||||
_state = AnalysisState.success;
|
|
||||||
notifyListeners();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final result = await _detectionService.detectTargetAsync(
|
|
||||||
imagePath,
|
|
||||||
targetType,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!result.success) {
|
|
||||||
_state = AnalysisState.error;
|
|
||||||
_errorMessage = result.errorMessage;
|
|
||||||
notifyListeners();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_targetCenterX = result.centerX;
|
|
||||||
_targetCenterY = result.centerY;
|
|
||||||
_targetRadius = result.radius;
|
|
||||||
_targetInnerRadius = result.radius * 0.1;
|
|
||||||
|
|
||||||
// Create shots from detected impacts
|
|
||||||
_shots = result.impacts.map((impact) {
|
|
||||||
return Shot(
|
|
||||||
id: _uuid.v4(),
|
|
||||||
x: impact.x,
|
|
||||||
y: impact.y,
|
|
||||||
score: impact.suggestedScore,
|
|
||||||
analysisId: '',
|
|
||||||
);
|
|
||||||
}).toList();
|
|
||||||
|
|
||||||
// Calculate scores
|
|
||||||
_recalculateScores();
|
|
||||||
|
|
||||||
// Calculate grouping
|
|
||||||
_recalculateGrouping();
|
|
||||||
|
|
||||||
_state = AnalysisState.success;
|
_state = AnalysisState.success;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -216,22 +135,18 @@ class AnalysisProvider extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove a shot
|
/// Efface tous les impacts en un clic (bouton ↻ de l'écran Plotting).
|
||||||
void removeShot(String shotId) {
|
/// La calibration (centre, rayon, anneaux) n'est pas touchée.
|
||||||
_shots.removeWhere((shot) => shot.id == shotId);
|
void clearShots() {
|
||||||
|
_shots.clear();
|
||||||
_recalculateScores();
|
_recalculateScores();
|
||||||
_recalculateGrouping();
|
_recalculateGrouping();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Move a shot to a new position
|
/// Remove a shot
|
||||||
void moveShot(String shotId, double newX, double newY) {
|
void removeShot(String shotId) {
|
||||||
final index = _shots.indexWhere((shot) => shot.id == shotId);
|
_shots.removeWhere((shot) => shot.id == shotId);
|
||||||
if (index == -1) return;
|
|
||||||
|
|
||||||
final newScore = _calculateShotScore(newX, newY);
|
|
||||||
_shots[index] = _shots[index].copyWith(x: newX, y: newY, score: newScore);
|
|
||||||
|
|
||||||
_recalculateScores();
|
_recalculateScores();
|
||||||
_recalculateGrouping();
|
_recalculateGrouping();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
@@ -247,265 +162,6 @@ class AnalysisProvider extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Auto-detect impacts using image processing
|
|
||||||
Future<int> autoDetectImpacts({
|
|
||||||
int darkThreshold = 80,
|
|
||||||
int minImpactSize = 20,
|
|
||||||
int maxImpactSize = 500,
|
|
||||||
double minCircularity = 0.6,
|
|
||||||
double minFillRatio = 0.5,
|
|
||||||
bool clearExisting = false,
|
|
||||||
}) async {
|
|
||||||
if (_imagePath == null || _targetType == null) return 0;
|
|
||||||
|
|
||||||
final settings = ImpactDetectionSettings(
|
|
||||||
darkThreshold: darkThreshold,
|
|
||||||
minImpactSize: minImpactSize,
|
|
||||||
maxImpactSize: maxImpactSize,
|
|
||||||
minCircularity: minCircularity,
|
|
||||||
minFillRatio: minFillRatio,
|
|
||||||
);
|
|
||||||
|
|
||||||
final detectedImpacts = _detectionService.detectImpactsOnly(
|
|
||||||
_imagePath!,
|
|
||||||
_targetType!,
|
|
||||||
_targetCenterX,
|
|
||||||
_targetCenterY,
|
|
||||||
_targetRadius,
|
|
||||||
_ringCount,
|
|
||||||
settings,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (clearExisting) {
|
|
||||||
_shots.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add detected impacts as shots
|
|
||||||
for (final impact in detectedImpacts) {
|
|
||||||
final score = _calculateShotScore(impact.x, impact.y);
|
|
||||||
final shot = Shot(
|
|
||||||
id: _uuid.v4(),
|
|
||||||
x: impact.x,
|
|
||||||
y: impact.y,
|
|
||||||
score: score,
|
|
||||||
analysisId: '',
|
|
||||||
);
|
|
||||||
_shots.add(shot);
|
|
||||||
}
|
|
||||||
|
|
||||||
_recalculateScores();
|
|
||||||
_recalculateGrouping();
|
|
||||||
notifyListeners();
|
|
||||||
|
|
||||||
return detectedImpacts.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Auto-detect impacts using OpenCV (Hough Circles + Contours)
|
|
||||||
Future<int> autoDetectImpactsWithOpenCV({
|
|
||||||
double cannyThreshold1 = 50,
|
|
||||||
double cannyThreshold2 = 150,
|
|
||||||
double minDist = 20,
|
|
||||||
double param1 = 100,
|
|
||||||
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,
|
|
||||||
double minContourArea = 50,
|
|
||||||
double maxContourArea = 5000,
|
|
||||||
bool clearExisting = false,
|
|
||||||
}) async {
|
|
||||||
if (_imagePath == null || _targetType == null) return 0;
|
|
||||||
|
|
||||||
final settings = OpenCVDetectionSettings(
|
|
||||||
cannyThreshold1: cannyThreshold1,
|
|
||||||
cannyThreshold2: cannyThreshold2,
|
|
||||||
minDist: minDist,
|
|
||||||
param1: param1,
|
|
||||||
param2: param2,
|
|
||||||
minRadius: minRadius,
|
|
||||||
maxRadius: maxRadius,
|
|
||||||
blurSize: blurSize,
|
|
||||||
useContourDetection: useContourDetection,
|
|
||||||
minCircularity: minCircularity,
|
|
||||||
minContourArea: minContourArea,
|
|
||||||
maxContourArea: maxContourArea,
|
|
||||||
);
|
|
||||||
|
|
||||||
final detectedImpacts = _detectionService.detectImpactsWithOpenCV(
|
|
||||||
_imagePath!,
|
|
||||||
_targetType!,
|
|
||||||
_targetCenterX,
|
|
||||||
_targetCenterY,
|
|
||||||
_targetRadius,
|
|
||||||
_ringCount,
|
|
||||||
settings: settings,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (clearExisting) {
|
|
||||||
_shots.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add detected impacts as shots
|
|
||||||
for (final impact in detectedImpacts) {
|
|
||||||
final score = _calculateShotScore(impact.x, impact.y);
|
|
||||||
final shot = Shot(
|
|
||||||
id: _uuid.v4(),
|
|
||||||
x: impact.x,
|
|
||||||
y: impact.y,
|
|
||||||
score: score,
|
|
||||||
analysisId: '',
|
|
||||||
);
|
|
||||||
_shots.add(shot);
|
|
||||||
}
|
|
||||||
|
|
||||||
_recalculateScores();
|
|
||||||
_recalculateGrouping();
|
|
||||||
notifyListeners();
|
|
||||||
|
|
||||||
return detectedImpacts.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Detect impacts with OpenCV using reference points
|
|
||||||
Future<int> detectFromReferencesWithOpenCV({
|
|
||||||
double tolerance = 2.0,
|
|
||||||
bool clearExisting = false,
|
|
||||||
}) async {
|
|
||||||
if (_imagePath == null ||
|
|
||||||
_targetType == null ||
|
|
||||||
_referenceImpacts.length < 2) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convertir les références
|
|
||||||
final references = _referenceImpacts
|
|
||||||
.map((shot) => ReferenceImpact(x: shot.x, y: shot.y))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
final detectedImpacts = _detectionService
|
|
||||||
.detectImpactsWithOpenCVFromReferences(
|
|
||||||
_imagePath!,
|
|
||||||
_targetType!,
|
|
||||||
_targetCenterX,
|
|
||||||
_targetCenterY,
|
|
||||||
_targetRadius,
|
|
||||||
_ringCount,
|
|
||||||
references,
|
|
||||||
tolerance: tolerance,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (clearExisting) {
|
|
||||||
_shots.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add detected impacts as shots
|
|
||||||
for (final impact in detectedImpacts) {
|
|
||||||
final score = _calculateShotScore(impact.x, impact.y);
|
|
||||||
final shot = Shot(
|
|
||||||
id: _uuid.v4(),
|
|
||||||
x: impact.x,
|
|
||||||
y: impact.y,
|
|
||||||
score: score,
|
|
||||||
analysisId: '',
|
|
||||||
);
|
|
||||||
_shots.add(shot);
|
|
||||||
}
|
|
||||||
|
|
||||||
_recalculateScores();
|
|
||||||
_recalculateGrouping();
|
|
||||||
notifyListeners();
|
|
||||||
|
|
||||||
return detectedImpacts.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a reference impact for calibrated detection
|
|
||||||
void addReferenceImpact(double x, double y) {
|
|
||||||
final score = _calculateShotScore(x, y);
|
|
||||||
final shot = Shot(id: _uuid.v4(), x: x, y: y, score: score, analysisId: '');
|
|
||||||
_referenceImpacts.add(shot);
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove a reference impact
|
|
||||||
void removeReferenceImpact(String shotId) {
|
|
||||||
_referenceImpacts.removeWhere((shot) => shot.id == shotId);
|
|
||||||
_learnedCharacteristics = null;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clear all reference impacts
|
|
||||||
void clearReferenceImpacts() {
|
|
||||||
_referenceImpacts.clear();
|
|
||||||
_learnedCharacteristics = null;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Learn characteristics from reference impacts
|
|
||||||
bool learnFromReferences() {
|
|
||||||
if (_imagePath == null || _referenceImpacts.length < 2) return false;
|
|
||||||
|
|
||||||
final references = _referenceImpacts
|
|
||||||
.map((shot) => ReferenceImpact(x: shot.x, y: shot.y))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
_learnedCharacteristics = _detectionService.analyzeReferenceImpacts(
|
|
||||||
_imagePath!,
|
|
||||||
references,
|
|
||||||
);
|
|
||||||
|
|
||||||
notifyListeners();
|
|
||||||
return _learnedCharacteristics != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Auto-detect impacts using learned reference characteristics
|
|
||||||
Future<int> detectFromReferences({
|
|
||||||
double tolerance = 2.0,
|
|
||||||
bool clearExisting = false,
|
|
||||||
}) async {
|
|
||||||
if (_imagePath == null ||
|
|
||||||
_targetType == null ||
|
|
||||||
_learnedCharacteristics == null) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
final detectedImpacts = _detectionService.detectImpactsFromReferences(
|
|
||||||
_imagePath!,
|
|
||||||
_targetType!,
|
|
||||||
_targetCenterX,
|
|
||||||
_targetCenterY,
|
|
||||||
_targetRadius,
|
|
||||||
_ringCount,
|
|
||||||
_learnedCharacteristics!,
|
|
||||||
tolerance: tolerance,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (clearExisting) {
|
|
||||||
_shots.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add detected impacts as shots
|
|
||||||
for (final impact in detectedImpacts) {
|
|
||||||
final score = _calculateShotScore(impact.x, impact.y);
|
|
||||||
final shot = Shot(
|
|
||||||
id: _uuid.v4(),
|
|
||||||
x: impact.x,
|
|
||||||
y: impact.y,
|
|
||||||
score: score,
|
|
||||||
analysisId: '',
|
|
||||||
);
|
|
||||||
_shots.add(shot);
|
|
||||||
}
|
|
||||||
|
|
||||||
_recalculateScores();
|
|
||||||
_recalculateGrouping();
|
|
||||||
notifyListeners();
|
|
||||||
|
|
||||||
return detectedImpacts.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adjust target position
|
/// Adjust target position
|
||||||
void adjustTargetPosition(
|
void adjustTargetPosition(
|
||||||
double centerX,
|
double centerX,
|
||||||
@@ -539,118 +195,6 @@ class AnalysisProvider extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Auto-calibrate target using OpenCV
|
|
||||||
Future<bool> autoCalibrateTarget() async {
|
|
||||||
if (_imagePath == null) return false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 1. Attempt to correct perspective/distortion first
|
|
||||||
final correctedPath = await _distortionService
|
|
||||||
.correctPerspectiveWithConcentricMesh(_imagePath!);
|
|
||||||
|
|
||||||
if (correctedPath != _imagePath) {
|
|
||||||
_imagePath = correctedPath;
|
|
||||||
_correctedImagePath = correctedPath;
|
|
||||||
_distortionCorrectionEnabled = true;
|
|
||||||
_imageAspectRatio = 1.0;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Detect the target on the straight/corrected image
|
|
||||||
final result = await _opencvTargetService.detectTarget(_imagePath!);
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
adjustTargetPosition(
|
|
||||||
result.centerX,
|
|
||||||
result.centerY,
|
|
||||||
result.radius * 0.1,
|
|
||||||
result.radius,
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
} catch (e) {
|
|
||||||
debugPrint('Auto-calibration error: $e');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calcule les paramètres de distorsion basés sur la calibration actuelle
|
|
||||||
void calculateDistortion() {
|
|
||||||
_distortionParams = _distortionService.calculateDistortionFromCalibration(
|
|
||||||
targetCenterX: _targetCenterX,
|
|
||||||
targetCenterY: _targetCenterY,
|
|
||||||
targetRadius: _targetRadius,
|
|
||||||
imageAspectRatio: _imageAspectRatio,
|
|
||||||
);
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Applique la correction de distorsion à l'image
|
|
||||||
/// Crée une nouvelle image corrigée et la sauvegarde
|
|
||||||
Future<void> applyDistortionCorrection() async {
|
|
||||||
if (_imagePath == null || _distortionParams == null) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
_correctedImagePath = await _distortionService.applyCorrection(
|
|
||||||
_imagePath!,
|
|
||||||
_distortionParams!,
|
|
||||||
);
|
|
||||||
_distortionCorrectionEnabled = true;
|
|
||||||
notifyListeners();
|
|
||||||
} catch (e) {
|
|
||||||
_errorMessage = 'Erreur lors de la correction: $e';
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Active ou désactive l'affichage de l'image corrigée
|
|
||||||
void setDistortionCorrectionEnabled(bool enabled) {
|
|
||||||
if (enabled && _correctedImagePath == null && _distortionParams != null) {
|
|
||||||
// Si on active mais pas encore d'image corrigée, la créer
|
|
||||||
applyDistortionCorrection();
|
|
||||||
} else {
|
|
||||||
_distortionCorrectionEnabled = enabled;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calcule ET applique la correction pour un feedback immédiat
|
|
||||||
Future<void> calculateAndApplyDistortion() async {
|
|
||||||
// 1. Calcul des paramètres (votre code actuel)
|
|
||||||
_distortionParams = _distortionService.calculateDistortionFromCalibration(
|
|
||||||
targetCenterX: _targetCenterX,
|
|
||||||
targetCenterY: _targetCenterY,
|
|
||||||
targetRadius: _targetRadius,
|
|
||||||
imageAspectRatio: _imageAspectRatio,
|
|
||||||
);
|
|
||||||
|
|
||||||
// 2. Vérification si une correction est réellement nécessaire
|
|
||||||
if (_distortionParams != null && _distortionParams!.needsCorrection) {
|
|
||||||
// 3. Application immédiate de la transformation (méthode asynchrone)
|
|
||||||
await applyDistortionCorrection();
|
|
||||||
} else {
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> runFullDistortionWorkflow() async {
|
|
||||||
_state = AnalysisState.loading;
|
|
||||||
notifyListeners();
|
|
||||||
|
|
||||||
try {
|
|
||||||
calculateDistortion();
|
|
||||||
await applyDistortionCorrection();
|
|
||||||
_distortionCorrectionEnabled = true;
|
|
||||||
_state = AnalysisState.success;
|
|
||||||
} catch (e) {
|
|
||||||
_errorMessage = "Erreur de rendu : $e";
|
|
||||||
_state = AnalysisState.error;
|
|
||||||
} finally {
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int _calculateShotScore(double x, double y) {
|
int _calculateShotScore(double x, double y) {
|
||||||
if (_targetType == TargetType.concentric) {
|
if (_targetType == TargetType.concentric) {
|
||||||
return _scoreCalculatorService.calculateConcentricScore(
|
return _scoreCalculatorService.calculateConcentricScore(
|
||||||
@@ -807,11 +351,6 @@ class AnalysisProvider extends ChangeNotifier {
|
|||||||
_shots = [];
|
_shots = [];
|
||||||
_scoreResult = null;
|
_scoreResult = null;
|
||||||
_groupingResult = null;
|
_groupingResult = null;
|
||||||
_referenceImpacts = [];
|
|
||||||
_learnedCharacteristics = null;
|
|
||||||
_distortionCorrectionEnabled = false;
|
|
||||||
_distortionParams = null;
|
|
||||||
_correctedImagePath = null;
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import '../../core/theme/app_theme.dart';
|
|||||||
import '../../data/models/target_type.dart';
|
import '../../data/models/target_type.dart';
|
||||||
import '../../data/models/shot.dart';
|
import '../../data/models/shot.dart';
|
||||||
import '../../data/repositories/session_repository.dart';
|
import '../../data/repositories/session_repository.dart';
|
||||||
import '../../services/target_detection_service.dart';
|
|
||||||
import '../../services/score_calculator_service.dart';
|
import '../../services/score_calculator_service.dart';
|
||||||
import '../../services/grouping_analyzer_service.dart';
|
import '../../services/grouping_analyzer_service.dart';
|
||||||
import '../../services/wallet_identity_service.dart';
|
import '../../services/wallet_identity_service.dart';
|
||||||
@@ -61,7 +60,6 @@ class AnalysisScreen extends StatelessWidget {
|
|||||||
return ChangeNotifierProvider(
|
return ChangeNotifierProvider(
|
||||||
create: (context) {
|
create: (context) {
|
||||||
final p = AnalysisProvider(
|
final p = AnalysisProvider(
|
||||||
detectionService: context.read<TargetDetectionService>(),
|
|
||||||
scoreCalculatorService: context.read<ScoreCalculatorService>(),
|
scoreCalculatorService: context.read<ScoreCalculatorService>(),
|
||||||
groupingAnalyzerService: context.read<GroupingAnalyzerService>(),
|
groupingAnalyzerService: context.read<GroupingAnalyzerService>(),
|
||||||
sessionRepository: context.read<SessionRepository>(),
|
sessionRepository: context.read<SessionRepository>(),
|
||||||
@@ -73,7 +71,6 @@ class AnalysisScreen extends StatelessWidget {
|
|||||||
p.analyzeImage(
|
p.analyzeImage(
|
||||||
imagePath,
|
imagePath,
|
||||||
targetType,
|
targetType,
|
||||||
autoAnalyze: false,
|
|
||||||
manualCenter: manualCenterOffset,
|
manualCenter: manualCenterOffset,
|
||||||
);
|
);
|
||||||
return p;
|
return p;
|
||||||
@@ -111,7 +108,6 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
|
|||||||
|
|
||||||
// Forcé à TRUE pour démarrer sur l'ajustement des cercles
|
// Forcé à TRUE pour démarrer sur l'ajustement des cercles
|
||||||
bool _isCalibrating = true;
|
bool _isCalibrating = true;
|
||||||
bool _isSelectingReferences = false;
|
|
||||||
bool _isAtBottom = false;
|
bool _isAtBottom = false;
|
||||||
|
|
||||||
final ScrollController _scrollController = ScrollController();
|
final ScrollController _scrollController = ScrollController();
|
||||||
@@ -192,7 +188,6 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
|
|||||||
if (validated == true) {
|
if (validated == true) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isCalibrating = false;
|
_isCalibrating = false;
|
||||||
_isSelectingReferences = false;
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
_enterCalibration();
|
_enterCalibration();
|
||||||
@@ -250,17 +245,17 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
if (!_isCalibrating && !_isSelectingReferences)
|
// Remise à zéro des impacts : efface tous les impacts en un clic,
|
||||||
|
// sans modifier la calibration (centre, rayon, anneaux).
|
||||||
|
if (!_isCalibrating)
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.refresh),
|
icon: const Icon(Icons.refresh),
|
||||||
onPressed: () => provider.analyzeImage(
|
tooltip: 'Effacer tous les impacts',
|
||||||
context.read<AnalysisProvider>().imagePath!,
|
onPressed: () => provider.clearShots(),
|
||||||
context.read<AnalysisProvider>().targetType!,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
// Nuage d'export vers le backend IA : visible uniquement si l'analyse
|
// Nuage d'export vers le backend IA : visible uniquement si l'analyse
|
||||||
// a réussi ET que l'utilisateur a activé l'option dans les Paramètres.
|
// a réussi ET que l'utilisateur a activé l'option dans les Paramètres.
|
||||||
if (!_isCalibrating && !_isSelectingReferences)
|
if (!_isCalibrating)
|
||||||
FutureBuilder<bool>(
|
FutureBuilder<bool>(
|
||||||
future: WalletIdentityService().isUploadEnabled(),
|
future: WalletIdentityService().isUploadEnabled(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
@@ -470,8 +465,6 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
|
|||||||
targetCenterX: provider.targetCenterX,
|
targetCenterX: provider.targetCenterX,
|
||||||
targetCenterY: provider.targetCenterY,
|
targetCenterY: provider.targetCenterY,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
|
||||||
_buildActionButtons(context, provider),
|
|
||||||
const SizedBox(height: 50),
|
const SizedBox(height: 50),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -647,10 +640,6 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildActionButtons(BuildContext context, AnalysisProvider provider) {
|
|
||||||
return const Column(children: [Row(children: [])]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _showShotDetails(
|
void _showShotDetails(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
AnalysisProvider provider,
|
AnalysisProvider provider,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
/// Overlay visuel de la cible.
|
/// Overlay visuel de la cible.
|
||||||
///
|
///
|
||||||
/// Dessine les anneaux de la cible, les impacts détectés, le cercle de groupement
|
/// Dessine les anneaux de la cible, les impacts et le cercle de groupement.
|
||||||
/// et les impacts de référence. Gère uniquement la SÉLECTION d'impacts existants
|
/// Gère uniquement la SÉLECTION d'impacts existants (tap sur un impact).
|
||||||
/// (tap sur un impact). L'AJOUT d'un impact est délégué à l'écran parent pour
|
/// L'AJOUT d'un impact est délégué à l'écran parent pour éviter tout conflit
|
||||||
/// éviter tout conflit de gestes avec le zoom/pan de l'InteractiveViewer.
|
/// de gestes avec le zoom/pan de l'InteractiveViewer.
|
||||||
library;
|
library;
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -20,11 +20,9 @@ class TargetOverlay extends StatelessWidget {
|
|||||||
final int ringCount;
|
final int ringCount;
|
||||||
final List<double>? ringRadii;
|
final List<double>? ringRadii;
|
||||||
final void Function(Shot shot)? onShotTapped;
|
final void Function(Shot shot)? onShotTapped;
|
||||||
final void Function(double x, double y)? onAddShot;
|
|
||||||
final double? groupingCenterX;
|
final double? groupingCenterX;
|
||||||
final double? groupingCenterY;
|
final double? groupingCenterY;
|
||||||
final double? groupingDiameter;
|
final double? groupingDiameter;
|
||||||
final List<Shot>? referenceImpacts;
|
|
||||||
final double zoomScale;
|
final double zoomScale;
|
||||||
final bool showRings;
|
final bool showRings;
|
||||||
|
|
||||||
@@ -38,11 +36,9 @@ class TargetOverlay extends StatelessWidget {
|
|||||||
this.ringCount = 10,
|
this.ringCount = 10,
|
||||||
this.ringRadii,
|
this.ringRadii,
|
||||||
this.onShotTapped,
|
this.onShotTapped,
|
||||||
this.onAddShot,
|
|
||||||
this.groupingCenterX,
|
this.groupingCenterX,
|
||||||
this.groupingCenterY,
|
this.groupingCenterY,
|
||||||
this.groupingDiameter,
|
this.groupingDiameter,
|
||||||
this.referenceImpacts,
|
|
||||||
this.zoomScale = 1.0,
|
this.zoomScale = 1.0,
|
||||||
this.showRings = false,
|
this.showRings = false,
|
||||||
});
|
});
|
||||||
@@ -72,7 +68,6 @@ class TargetOverlay extends StatelessWidget {
|
|||||||
groupingCenterX: groupingCenterX,
|
groupingCenterX: groupingCenterX,
|
||||||
groupingCenterY: groupingCenterY,
|
groupingCenterY: groupingCenterY,
|
||||||
groupingDiameter: groupingDiameter,
|
groupingDiameter: groupingDiameter,
|
||||||
referenceImpacts: referenceImpacts,
|
|
||||||
zoomScale: zoomScale,
|
zoomScale: zoomScale,
|
||||||
showRings: showRings,
|
showRings: showRings,
|
||||||
),
|
),
|
||||||
@@ -127,7 +122,6 @@ class _TargetOverlayPainter extends CustomPainter {
|
|||||||
final double? groupingCenterX;
|
final double? groupingCenterX;
|
||||||
final double? groupingCenterY;
|
final double? groupingCenterY;
|
||||||
final double? groupingDiameter;
|
final double? groupingDiameter;
|
||||||
final List<Shot>? referenceImpacts;
|
|
||||||
final double zoomScale;
|
final double zoomScale;
|
||||||
final bool showRings;
|
final bool showRings;
|
||||||
|
|
||||||
@@ -142,7 +136,6 @@ class _TargetOverlayPainter extends CustomPainter {
|
|||||||
this.groupingCenterX,
|
this.groupingCenterX,
|
||||||
this.groupingCenterY,
|
this.groupingCenterY,
|
||||||
this.groupingDiameter,
|
this.groupingDiameter,
|
||||||
this.referenceImpacts,
|
|
||||||
this.zoomScale = 1.0,
|
this.zoomScale = 1.0,
|
||||||
this.showRings = false,
|
this.showRings = false,
|
||||||
});
|
});
|
||||||
@@ -163,13 +156,6 @@ class _TargetOverlayPainter extends CustomPainter {
|
|||||||
for (final shot in shots) {
|
for (final shot in shots) {
|
||||||
_drawImpact(canvas, size, shot);
|
_drawImpact(canvas, size, shot);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw reference impacts (with different color)
|
|
||||||
if (referenceImpacts != null) {
|
|
||||||
for (final ref in referenceImpacts!) {
|
|
||||||
_drawReferenceImpact(canvas, size, ref);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _drawTargetCenter(Canvas canvas, Size size) {
|
void _drawTargetCenter(Canvas canvas, Size size) {
|
||||||
@@ -319,48 +305,6 @@ class _TargetOverlayPainter extends CustomPainter {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _drawReferenceImpact(Canvas canvas, Size size, Shot ref) {
|
|
||||||
final x = ref.x * size.width;
|
|
||||||
final y = ref.y * size.height;
|
|
||||||
|
|
||||||
// Tailles fixes divisées par le zoom pour rester constantes à l'écran
|
|
||||||
final outerRadius = 12 / zoomScale;
|
|
||||||
final innerRadius = 10 / zoomScale;
|
|
||||||
final strokeWidth = 3 / zoomScale;
|
|
||||||
final fontSize = 12 / zoomScale;
|
|
||||||
|
|
||||||
// Draw outer circle (white outline for visibility)
|
|
||||||
final outlinePaint = Paint()
|
|
||||||
..color = Colors.white
|
|
||||||
..style = PaintingStyle.stroke
|
|
||||||
..strokeWidth = strokeWidth;
|
|
||||||
canvas.drawCircle(Offset(x, y), outerRadius, outlinePaint);
|
|
||||||
|
|
||||||
// Draw reference marker (purple)
|
|
||||||
final refPaint = Paint()
|
|
||||||
..color = Colors.deepPurple
|
|
||||||
..style = PaintingStyle.fill;
|
|
||||||
canvas.drawCircle(Offset(x, y), innerRadius, refPaint);
|
|
||||||
|
|
||||||
// Draw "R" to indicate reference
|
|
||||||
final textPainter = TextPainter(
|
|
||||||
text: TextSpan(
|
|
||||||
text: 'R',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: fontSize,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
textDirection: TextDirection.ltr,
|
|
||||||
);
|
|
||||||
textPainter.layout();
|
|
||||||
textPainter.paint(
|
|
||||||
canvas,
|
|
||||||
Offset(x - textPainter.width / 2, y - textPainter.height / 2),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool shouldRepaint(covariant _TargetOverlayPainter oldDelegate) {
|
bool shouldRepaint(covariant _TargetOverlayPainter oldDelegate) {
|
||||||
return shots != oldDelegate.shots ||
|
return shots != oldDelegate.shots ||
|
||||||
@@ -372,7 +316,6 @@ class _TargetOverlayPainter extends CustomPainter {
|
|||||||
groupingCenterX != oldDelegate.groupingCenterX ||
|
groupingCenterX != oldDelegate.groupingCenterX ||
|
||||||
groupingCenterY != oldDelegate.groupingCenterY ||
|
groupingCenterY != oldDelegate.groupingCenterY ||
|
||||||
groupingDiameter != oldDelegate.groupingDiameter ||
|
groupingDiameter != oldDelegate.groupingDiameter ||
|
||||||
referenceImpacts != oldDelegate.referenceImpacts ||
|
|
||||||
zoomScale != oldDelegate.zoomScale ||
|
zoomScale != oldDelegate.zoomScale ||
|
||||||
showRings != oldDelegate.showRings;
|
showRings != oldDelegate.showRings;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,7 +128,6 @@ class _CaptureScreenState extends State<CaptureScreen>
|
|||||||
|
|
||||||
// Détection OpenCV (cible circulaire) — on garde le résultat COMPLET
|
// Détection OpenCV (cible circulaire) — on garde le résultat COMPLET
|
||||||
TargetDetectionResult? _targetResult; // NOUVEAU : centre + rayon de la cible
|
TargetDetectionResult? _targetResult; // NOUVEAU : centre + rayon de la cible
|
||||||
Timer? _detectionTimer;
|
|
||||||
bool _isAnalyzingFrame = false;
|
bool _isAnalyzingFrame = false;
|
||||||
|
|
||||||
// NOUVEAU : Données IMU en temps réel
|
// NOUVEAU : Données IMU en temps réel
|
||||||
@@ -148,7 +147,6 @@ class _CaptureScreenState extends State<CaptureScreen>
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
_cameraController?.dispose();
|
_cameraController?.dispose();
|
||||||
_scanAnimationController.dispose();
|
_scanAnimationController.dispose();
|
||||||
_detectionTimer?.cancel();
|
|
||||||
_parallelismSubscription?.cancel(); // NOUVEAU
|
_parallelismSubscription?.cancel(); // NOUVEAU
|
||||||
_parallelismService.dispose(); // NOUVEAU
|
_parallelismService.dispose(); // NOUVEAU
|
||||||
super.dispose();
|
super.dispose();
|
||||||
@@ -300,9 +298,6 @@ class _CaptureScreenState extends State<CaptureScreen>
|
|||||||
// Détection OpenCV périodique (inchangée)
|
// Détection OpenCV périodique (inchangée)
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
void _startAlignmentDetection() {
|
void _startAlignmentDetection() {
|
||||||
_detectionTimer?.cancel();
|
|
||||||
_detectionTimer = null;
|
|
||||||
|
|
||||||
DateTime? lastAnalysis;
|
DateTime? lastAnalysis;
|
||||||
|
|
||||||
_cameraController!.startImageStream((CameraImage cameraImage) async {
|
_cameraController!.startImageStream((CameraImage cameraImage) async {
|
||||||
@@ -362,8 +357,6 @@ class _CaptureScreenState extends State<CaptureScreen>
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _stopAlignmentDetection() {
|
void _stopAlignmentDetection() {
|
||||||
_detectionTimer?.cancel();
|
|
||||||
_detectionTimer = null;
|
|
||||||
try {
|
try {
|
||||||
if (_cameraController != null &&
|
if (_cameraController != null &&
|
||||||
_cameraController!.value.isStreamingImages) {
|
_cameraController!.value.isStreamingImages) {
|
||||||
|
|||||||
@@ -7,10 +7,8 @@ import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
|||||||
import 'app.dart';
|
import 'app.dart';
|
||||||
import 'core/theme/theme_provider.dart';
|
import 'core/theme/theme_provider.dart';
|
||||||
import 'data/repositories/session_repository.dart';
|
import 'data/repositories/session_repository.dart';
|
||||||
import 'services/target_detection_service.dart';
|
|
||||||
import 'services/score_calculator_service.dart';
|
import 'services/score_calculator_service.dart';
|
||||||
import 'services/grouping_analyzer_service.dart';
|
import 'services/grouping_analyzer_service.dart';
|
||||||
import 'services/image_processing_service.dart';
|
|
||||||
import 'features/session/session_provider.dart';
|
import 'features/session/session_provider.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
@@ -32,14 +30,6 @@ void main() async {
|
|||||||
runApp(
|
runApp(
|
||||||
MultiProvider(
|
MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
Provider<ImageProcessingService>(
|
|
||||||
create: (_) => ImageProcessingService(),
|
|
||||||
),
|
|
||||||
Provider<TargetDetectionService>(
|
|
||||||
create: (context) => TargetDetectionService(
|
|
||||||
imageProcessingService: context.read<ImageProcessingService>(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Provider<ScoreCalculatorService>(
|
Provider<ScoreCalculatorService>(
|
||||||
create: (_) => ScoreCalculatorService(),
|
create: (_) => ScoreCalculatorService(),
|
||||||
),
|
),
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,228 +0,0 @@
|
|||||||
/// Service de détection d'impacts utilisant OpenCV.
|
|
||||||
library;
|
|
||||||
|
|
||||||
import 'dart:math' as math;
|
|
||||||
import 'package:opencv_dart/opencv_dart.dart' as cv;
|
|
||||||
|
|
||||||
/// Paramètres de détection d'impacts OpenCV
|
|
||||||
class OpenCVDetectionSettings {
|
|
||||||
/// Seuil Canny bas pour la détection de contours
|
|
||||||
final double cannyThreshold1;
|
|
||||||
|
|
||||||
/// Seuil Canny haut pour la détection de contours
|
|
||||||
final double cannyThreshold2;
|
|
||||||
|
|
||||||
/// Distance minimale entre les centres des cercles détectés
|
|
||||||
final double minDist;
|
|
||||||
|
|
||||||
/// Paramètre 1 de HoughCircles (seuil Canny interne)
|
|
||||||
final double param1;
|
|
||||||
|
|
||||||
/// Paramètre 2 de HoughCircles (seuil d'accumulation)
|
|
||||||
final double param2;
|
|
||||||
|
|
||||||
/// Rayon minimum des cercles en pixels
|
|
||||||
final int minRadius;
|
|
||||||
|
|
||||||
/// Rayon maximum des cercles en pixels
|
|
||||||
final int maxRadius;
|
|
||||||
|
|
||||||
/// Taille du flou gaussien (doit être impair)
|
|
||||||
final int blurSize;
|
|
||||||
|
|
||||||
/// Utiliser la détection de contours en plus de Hough
|
|
||||||
final bool useContourDetection;
|
|
||||||
|
|
||||||
/// Circularité minimale pour la détection par contours (0-1)
|
|
||||||
final double minCircularity;
|
|
||||||
|
|
||||||
/// Surface minimale des contours
|
|
||||||
final double minContourArea;
|
|
||||||
|
|
||||||
/// Surface maximale des contours
|
|
||||||
final double maxContourArea;
|
|
||||||
|
|
||||||
const OpenCVDetectionSettings({
|
|
||||||
this.cannyThreshold1 = 50,
|
|
||||||
this.cannyThreshold2 = 150,
|
|
||||||
this.minDist = 20,
|
|
||||||
this.param1 = 100,
|
|
||||||
this.param2 = 30,
|
|
||||||
this.minRadius = 5,
|
|
||||||
this.maxRadius = 50,
|
|
||||||
this.blurSize = 5,
|
|
||||||
this.useContourDetection = true,
|
|
||||||
this.minCircularity = 0.6,
|
|
||||||
this.minContourArea = 50,
|
|
||||||
this.maxContourArea = 5000,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Résultat de détection d'impact
|
|
||||||
class OpenCVDetectedImpact {
|
|
||||||
/// Position X normalisée (0-1)
|
|
||||||
final double x;
|
|
||||||
|
|
||||||
/// Position Y normalisée (0-1)
|
|
||||||
final double y;
|
|
||||||
|
|
||||||
/// Rayon en pixels
|
|
||||||
final double radius;
|
|
||||||
|
|
||||||
/// Score de confiance (0-1)
|
|
||||||
final double confidence;
|
|
||||||
|
|
||||||
/// Méthode de détection utilisée
|
|
||||||
final String method;
|
|
||||||
|
|
||||||
const OpenCVDetectedImpact({
|
|
||||||
required this.x,
|
|
||||||
required this.y,
|
|
||||||
required this.radius,
|
|
||||||
this.confidence = 1.0,
|
|
||||||
this.method = 'unknown',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Service de détection d'impacts utilisant OpenCV
|
|
||||||
class OpenCVImpactDetectionService {
|
|
||||||
/// Détecte les impacts dans une image en utilisant OpenCV
|
|
||||||
List<OpenCVDetectedImpact> detectImpacts(
|
|
||||||
String imagePath, {
|
|
||||||
OpenCVDetectionSettings settings = const OpenCVDetectionSettings(),
|
|
||||||
}) {
|
|
||||||
try {
|
|
||||||
final img = cv.imread(imagePath, flags: cv.IMREAD_COLOR);
|
|
||||||
if (img.isEmpty) return [];
|
|
||||||
|
|
||||||
final gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY);
|
|
||||||
|
|
||||||
// Apply blur to reduce noise
|
|
||||||
final blurKSize = (settings.blurSize, settings.blurSize);
|
|
||||||
final blurred = cv.gaussianBlur(gray, blurKSize, 2, sigmaY: 2);
|
|
||||||
|
|
||||||
final List<OpenCVDetectedImpact> detectedImpacts = [];
|
|
||||||
|
|
||||||
final circles = cv.HoughCircles(
|
|
||||||
blurred,
|
|
||||||
cv.HOUGH_GRADIENT,
|
|
||||||
1,
|
|
||||||
settings.minDist,
|
|
||||||
param1: settings.param1,
|
|
||||||
param2: settings.param2,
|
|
||||||
minRadius: settings.minRadius,
|
|
||||||
maxRadius: settings.maxRadius,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (circles.rows > 0 && circles.cols > 0) {
|
|
||||||
// Mat shape: (1, N, 3) usually for HoughCircles (CV_32FC3)
|
|
||||||
// We use at<Vec3f> directly.
|
|
||||||
|
|
||||||
for (int i = 0; i < circles.cols; i++) {
|
|
||||||
final vec = circles.at<cv.Vec3f>(0, i);
|
|
||||||
final x = vec.val1;
|
|
||||||
final y = vec.val2;
|
|
||||||
final r = vec.val3;
|
|
||||||
|
|
||||||
detectedImpacts.add(
|
|
||||||
OpenCVDetectedImpact(
|
|
||||||
x: x / img.cols,
|
|
||||||
y: y / img.rows,
|
|
||||||
radius: r,
|
|
||||||
confidence: 0.8,
|
|
||||||
method: 'hough',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Contour Detection (if enabled)
|
|
||||||
if (settings.useContourDetection) {
|
|
||||||
// Canny edge detection
|
|
||||||
final edges = cv.canny(
|
|
||||||
blurred,
|
|
||||||
settings.cannyThreshold1,
|
|
||||||
settings.cannyThreshold2,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Find contours
|
|
||||||
final contoursResult = cv.findContours(
|
|
||||||
edges,
|
|
||||||
cv.RETR_EXTERNAL,
|
|
||||||
cv.CHAIN_APPROX_SIMPLE,
|
|
||||||
);
|
|
||||||
|
|
||||||
final contours = contoursResult.$1;
|
|
||||||
// hierarchy is $2
|
|
||||||
|
|
||||||
for (int i = 0; i < contours.length; i++) {
|
|
||||||
final contour = contours[i];
|
|
||||||
|
|
||||||
// Filter by area
|
|
||||||
final area = cv.contourArea(contour);
|
|
||||||
if (area < settings.minContourArea ||
|
|
||||||
area > settings.maxContourArea) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter by circularity
|
|
||||||
final perimeter = cv.arcLength(contour, true);
|
|
||||||
if (perimeter == 0) continue;
|
|
||||||
final circularity = 4 * math.pi * area / (perimeter * perimeter);
|
|
||||||
|
|
||||||
if (circularity < settings.minCircularity) continue;
|
|
||||||
|
|
||||||
// Get bounding circle
|
|
||||||
final enclosingCircle = cv.minEnclosingCircle(contour);
|
|
||||||
final center = enclosingCircle.$1;
|
|
||||||
final radius = enclosingCircle.$2;
|
|
||||||
|
|
||||||
// Avoid duplicates (simple distance check against Hough results)
|
|
||||||
bool isDuplicate = false;
|
|
||||||
for (final existing in detectedImpacts) {
|
|
||||||
final dx = existing.x * img.cols - center.x;
|
|
||||||
final dy = existing.y * img.rows - center.y;
|
|
||||||
final dist = math.sqrt(dx * dx + dy * dy);
|
|
||||||
if (dist < radius) {
|
|
||||||
isDuplicate = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isDuplicate) {
|
|
||||||
detectedImpacts.add(
|
|
||||||
OpenCVDetectedImpact(
|
|
||||||
x: center.x / img.cols,
|
|
||||||
y: center.y / img.rows,
|
|
||||||
radius: radius,
|
|
||||||
confidence: circularity, // Use circularity as confidence
|
|
||||||
method: 'contour',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return detectedImpacts;
|
|
||||||
} catch (e) {
|
|
||||||
// print('OpenCV Error: $e');
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Détecte les impacts en utilisant une image de référence
|
|
||||||
List<OpenCVDetectedImpact> detectFromReferences(
|
|
||||||
String imagePath,
|
|
||||||
List<({double x, double y})> referencePoints, {
|
|
||||||
double tolerance = 2.0,
|
|
||||||
}) {
|
|
||||||
// Basic implementation: use average color/brightness of reference points
|
|
||||||
// This is a placeholder for a more complex template matching or feature matching
|
|
||||||
|
|
||||||
// For now, we can just run the standard detection but filter results
|
|
||||||
// based on properties of the reference points (e.g. size/radius if we had it).
|
|
||||||
|
|
||||||
// Returning standard detection for now to enable the feature.
|
|
||||||
return detectImpacts(imagePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,461 +0,0 @@
|
|||||||
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 [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,259 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:math' as math;
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:opencv_dart/opencv_dart.dart' as cv;
|
|
||||||
|
|
||||||
/// Résultat d'une tentative de redressement de cible.
|
|
||||||
class RectifyResult {
|
|
||||||
/// Chemin du fichier image redressé (ou original si échec).
|
|
||||||
final String outputPath;
|
|
||||||
|
|
||||||
/// true si une cible a été détectée et redressée, false si on a renvoyé
|
|
||||||
/// l'image d'origine sans transformation.
|
|
||||||
final bool rectified;
|
|
||||||
|
|
||||||
/// Angle d'inclinaison estimé de la cible AVANT redressement, en degrés.
|
|
||||||
/// (0 = déjà de face). Utile pour informer l'utilisateur.
|
|
||||||
final double estimatedTiltDegrees;
|
|
||||||
|
|
||||||
/// Message de diagnostic (utile pour debug / affichage).
|
|
||||||
final String message;
|
|
||||||
|
|
||||||
const RectifyResult({
|
|
||||||
required this.outputPath,
|
|
||||||
required this.rectified,
|
|
||||||
required this.estimatedTiltDegrees,
|
|
||||||
required this.message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Service qui redresse une cible RONDE (cercles concentriques) photographiée
|
|
||||||
/// de biais, en la ramenant parfaitement de face.
|
|
||||||
///
|
|
||||||
/// Principe :
|
|
||||||
/// 1. Détecter le plus grand contour ~circulaire de l'image.
|
|
||||||
/// 2. Ajuster une ELLIPSE sur ce contour (fitEllipse).
|
|
||||||
/// 3. Un cercle vu en perspective devient une ellipse : on calcule la
|
|
||||||
/// transformation de perspective qui remappe cette ellipse vers un
|
|
||||||
/// CERCLE parfait, et on l'applique à toute l'image.
|
|
||||||
/// 4. La cible apparaît alors de face.
|
|
||||||
///
|
|
||||||
/// L'image résultat est carrée et centrée sur la cible.
|
|
||||||
class TargetRectifyService {
|
|
||||||
/// Taille (en pixels) du côté de l'image carrée de sortie.
|
|
||||||
final int outputSize;
|
|
||||||
|
|
||||||
/// Marge autour de la cible dans l'image de sortie (1.0 = cible pile au bord,
|
|
||||||
/// 1.3 = 30 % de marge autour). Garde un peu de contexte.
|
|
||||||
final double marginFactor;
|
|
||||||
|
|
||||||
/// En dessous de cet écart d'axes (ratio petit/grand axe proche de 1),
|
|
||||||
/// la cible est considérée déjà de face → pas de warp inutile.
|
|
||||||
final double minTiltRatioToRectify;
|
|
||||||
|
|
||||||
TargetRectifyService({
|
|
||||||
this.outputSize = 1024,
|
|
||||||
this.marginFactor = 1.25,
|
|
||||||
this.minTiltRatioToRectify = 0.985,
|
|
||||||
});
|
|
||||||
|
|
||||||
/// Redresse l'image située à [inputPath]. Écrit le résultat dans
|
|
||||||
/// [outputPath] et renvoie un [RectifyResult].
|
|
||||||
///
|
|
||||||
/// Ne bloque jamais : en cas d'échec de détection, renvoie l'image
|
|
||||||
/// d'origine (rectified = false) pour ne pas perdre la photo du tireur.
|
|
||||||
Future<RectifyResult> rectify({
|
|
||||||
required String inputPath,
|
|
||||||
required String outputPath,
|
|
||||||
}) async {
|
|
||||||
cv.Mat? src;
|
|
||||||
cv.Mat? gray;
|
|
||||||
cv.Mat? blurred;
|
|
||||||
cv.Mat? edges;
|
|
||||||
try {
|
|
||||||
src = cv.imread(inputPath, flags: cv.IMREAD_COLOR);
|
|
||||||
if (src.isEmpty) {
|
|
||||||
return RectifyResult(
|
|
||||||
outputPath: inputPath,
|
|
||||||
rectified: false,
|
|
||||||
estimatedTiltDegrees: 0,
|
|
||||||
message: 'Image illisible',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── 1. Prétraitement ────────────────────────────────────────────────
|
|
||||||
gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY);
|
|
||||||
blurred = cv.gaussianBlur(gray, (5, 5), 2, sigmaY: 2);
|
|
||||||
edges = cv.canny(blurred, 60, 160);
|
|
||||||
|
|
||||||
// Dilatation légère pour fermer les contours brisés
|
|
||||||
final cv.Mat kernel = cv.getStructuringElement(
|
|
||||||
cv.MORPH_ELLIPSE,
|
|
||||||
(3, 3),
|
|
||||||
);
|
|
||||||
final cv.Mat dilated = cv.dilate(edges, kernel);
|
|
||||||
|
|
||||||
// ── 2. Recherche du meilleur contour elliptique ──────────────────────
|
|
||||||
final (contours, _) = cv.findContours(
|
|
||||||
dilated,
|
|
||||||
cv.RETR_EXTERNAL,
|
|
||||||
cv.CHAIN_APPROX_SIMPLE,
|
|
||||||
);
|
|
||||||
|
|
||||||
final double imgArea = (src.width * src.height).toDouble();
|
|
||||||
cv.RotatedRect? bestEllipse;
|
|
||||||
double bestScore = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < contours.length; i++) {
|
|
||||||
final c = contours[i];
|
|
||||||
if (c.length < 5) continue; // fitEllipse exige >= 5 points
|
|
||||||
|
|
||||||
final double area = cv.contourArea(c);
|
|
||||||
// On ignore les contours minuscules et ceux qui couvrent presque tout
|
|
||||||
if (area < imgArea * 0.03 || area > imgArea * 0.97) continue;
|
|
||||||
|
|
||||||
final cv.RotatedRect e = cv.fitEllipse(c);
|
|
||||||
final ep = e.points; // 4 sommets
|
|
||||||
if (ep.length < 4) continue;
|
|
||||||
final double ecx = (ep[0].x + ep[1].x + ep[2].x + ep[3].x) / 4.0;
|
|
||||||
final double ecy = (ep[0].y + ep[1].y + ep[2].y + ep[3].y) / 4.0;
|
|
||||||
final double mAx = (ep[0].x + ep[1].x) / 2.0;
|
|
||||||
final double mAy = (ep[0].y + ep[1].y) / 2.0;
|
|
||||||
final double mBx = (ep[1].x + ep[2].x) / 2.0;
|
|
||||||
final double mBy = (ep[1].y + ep[2].y) / 2.0;
|
|
||||||
final double w =
|
|
||||||
2 * math.sqrt(math.pow(mAx - ecx, 2) + math.pow(mAy - ecy, 2));
|
|
||||||
final double h =
|
|
||||||
2 * math.sqrt(math.pow(mBx - ecx, 2) + math.pow(mBy - ecy, 2));
|
|
||||||
if (w <= 1 || h <= 1) continue;
|
|
||||||
|
|
||||||
// À quel point le contour ressemble-t-il vraiment à son ellipse ?
|
|
||||||
// On compare l'aire du contour à l'aire de l'ellipse ajustée.
|
|
||||||
final double ellipseArea = math.pi * (w / 2) * (h / 2);
|
|
||||||
if (ellipseArea <= 0) continue;
|
|
||||||
final double fitRatio = area / ellipseArea; // ~1 si bon ajustement
|
|
||||||
if (fitRatio < 0.7 || fitRatio > 1.3) continue;
|
|
||||||
|
|
||||||
// Score = taille de l'ellipse (on veut la cible la plus grande)
|
|
||||||
final double score = ellipseArea;
|
|
||||||
if (score > bestScore) {
|
|
||||||
bestScore = score;
|
|
||||||
bestEllipse = e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bestEllipse == null) {
|
|
||||||
return RectifyResult(
|
|
||||||
outputPath: inputPath,
|
|
||||||
rectified: false,
|
|
||||||
estimatedTiltDegrees: 0,
|
|
||||||
message: 'Aucune cible circulaire détectée',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── 3. Extraire les 4 sommets de l'ellipse (robuste à la version d'API) ─
|
|
||||||
// RotatedRect.points renvoie les 4 coins de la boîte englobant l'ellipse.
|
|
||||||
// On en dérive nous-mêmes le centre, les demi-axes et l'orientation, ce
|
|
||||||
// qui évite de dépendre de la forme exacte de `.size` / `.center`
|
|
||||||
// (record vs objet) qui varie selon les versions d'opencv_dart.
|
|
||||||
final pts = bestEllipse.points; // List<Point2f> de 4 sommets
|
|
||||||
|
|
||||||
double px(int i) => pts[i].x;
|
|
||||||
double py(int i) => pts[i].y;
|
|
||||||
|
|
||||||
// Centre = moyenne des 4 sommets
|
|
||||||
final double cx = (px(0) + px(1) + px(2) + px(3)) / 4.0;
|
|
||||||
final double cy = (py(0) + py(1) + py(2) + py(3)) / 4.0;
|
|
||||||
|
|
||||||
// Milieux de deux côtés adjacents → extrémités des deux demi-axes
|
|
||||||
// Côté 0-1 et côté 1-2 (ordre des sommets d'un RotatedRect)
|
|
||||||
final double m01x = (px(0) + px(1)) / 2.0;
|
|
||||||
final double m01y = (py(0) + py(1)) / 2.0;
|
|
||||||
final double m12x = (px(1) + px(2)) / 2.0;
|
|
||||||
final double m12y = (py(1) + py(2)) / 2.0;
|
|
||||||
|
|
||||||
// Demi-axes = distance centre → milieu de chaque côté
|
|
||||||
final double axisA =
|
|
||||||
math.sqrt(math.pow(m01x - cx, 2) + math.pow(m01y - cy, 2));
|
|
||||||
final double axisB =
|
|
||||||
math.sqrt(math.pow(m12x - cx, 2) + math.pow(m12y - cy, 2));
|
|
||||||
|
|
||||||
final double majorAxis = math.max(axisA, axisB);
|
|
||||||
final double minorAxis = math.min(axisA, axisB);
|
|
||||||
if (majorAxis <= 1) {
|
|
||||||
return RectifyResult(
|
|
||||||
outputPath: inputPath,
|
|
||||||
rectified: false,
|
|
||||||
estimatedTiltDegrees: 0,
|
|
||||||
message: 'Ellipse dégénérée',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final double axisRatio = minorAxis / majorAxis; // 1 = cercle parfait
|
|
||||||
final double tiltDeg =
|
|
||||||
math.acos(axisRatio.clamp(0.0, 1.0)) * (180.0 / math.pi);
|
|
||||||
|
|
||||||
// Déjà quasiment de face → on ne touche pas (évite le flou inutile)
|
|
||||||
if (axisRatio >= minTiltRatioToRectify) {
|
|
||||||
cv.imwrite(outputPath, src);
|
|
||||||
return RectifyResult(
|
|
||||||
outputPath: outputPath,
|
|
||||||
rectified: false,
|
|
||||||
estimatedTiltDegrees: tiltDeg,
|
|
||||||
message: 'Cible déjà de face',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── 4. Construire la transformation de perspective ────────────────────
|
|
||||||
// Source : extrémités des deux axes de l'ellipse (4 points).
|
|
||||||
// Destination : extrémités des axes d'un cercle parfait centré.
|
|
||||||
// On obtient les extrémités en prolongeant centre→milieu-de-côté.
|
|
||||||
final srcPts = cv.VecPoint.fromList([
|
|
||||||
cv.Point((cx + (m01x - cx)).round(), (cy + (m01y - cy)).round()),
|
|
||||||
cv.Point((cx - (m01x - cx)).round(), (cy - (m01y - cy)).round()),
|
|
||||||
cv.Point((cx + (m12x - cx)).round(), (cy + (m12y - cy)).round()),
|
|
||||||
cv.Point((cx - (m12x - cx)).round(), (cy - (m12y - cy)).round()),
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Cible : cercle parfait centré, rayon R, dans une image carrée.
|
|
||||||
// L'axe "A" (m01) devient l'axe horizontal, l'axe "B" (m12) le vertical.
|
|
||||||
final double out = outputSize.toDouble();
|
|
||||||
final double center = out / 2;
|
|
||||||
final double radius = (out / 2) / marginFactor;
|
|
||||||
final dstPts = cv.VecPoint.fromList([
|
|
||||||
cv.Point((center + radius).round(), center.round()),
|
|
||||||
cv.Point((center - radius).round(), center.round()),
|
|
||||||
cv.Point(center.round(), (center + radius).round()),
|
|
||||||
cv.Point(center.round(), (center - radius).round()),
|
|
||||||
]);
|
|
||||||
|
|
||||||
final cv.Mat transform = cv.getPerspectiveTransform(srcPts, dstPts);
|
|
||||||
|
|
||||||
final cv.Mat warped = cv.warpPerspective(
|
|
||||||
src,
|
|
||||||
transform,
|
|
||||||
(outputSize, outputSize),
|
|
||||||
flags: cv.INTER_LINEAR,
|
|
||||||
borderMode: cv.BORDER_CONSTANT,
|
|
||||||
);
|
|
||||||
|
|
||||||
cv.imwrite(outputPath, warped);
|
|
||||||
|
|
||||||
return RectifyResult(
|
|
||||||
outputPath: outputPath,
|
|
||||||
rectified: true,
|
|
||||||
estimatedTiltDegrees: tiltDeg,
|
|
||||||
message: 'Cible redressée (inclinaison ${tiltDeg.toStringAsFixed(1)}°)',
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
debugPrint('TargetRectify erreur: $e');
|
|
||||||
// En cas de pépin, on renvoie l'original pour ne jamais perdre la photo
|
|
||||||
return RectifyResult(
|
|
||||||
outputPath: inputPath,
|
|
||||||
rectified: false,
|
|
||||||
estimatedTiltDegrees: 0,
|
|
||||||
message: 'Erreur de traitement: $e',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
24
pubspec.lock
24
pubspec.lock
@@ -293,30 +293,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.3"
|
version: "2.1.3"
|
||||||
google_mlkit_commons:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: google_mlkit_commons
|
|
||||||
sha256: "3e69fea4211727732cc385104e675ad1e40b29f12edd492ee52fa108423a6124"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.11.1"
|
|
||||||
google_mlkit_document_scanner:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: google_mlkit_document_scanner
|
|
||||||
sha256: "67428ddb853880c8185049a5834cd328e6420921a74786f6aadee0b76f8536bd"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.2.1"
|
|
||||||
google_mlkit_object_detection:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: google_mlkit_object_detection
|
|
||||||
sha256: "9dd35886972e18747e22098f8ebee78d30716a99a789bb2e3a65a24229e031e7"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.15.1"
|
|
||||||
hooks:
|
hooks:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -38,11 +38,9 @@ dependencies:
|
|||||||
cupertino_icons: ^1.0.8
|
cupertino_icons: ^1.0.8
|
||||||
sensors_plus: ^4.0.2
|
sensors_plus: ^4.0.2
|
||||||
opencv_dart: ^2.1.0
|
opencv_dart: ^2.1.0
|
||||||
google_mlkit_object_detection: ^0.15.0
|
|
||||||
|
|
||||||
# Image capture from camera/gallery
|
# Image capture from camera/gallery
|
||||||
image_picker: ^1.2.1
|
image_picker: ^1.2.1
|
||||||
google_mlkit_document_scanner: ^0.2.0
|
|
||||||
|
|
||||||
# Local database for history
|
# Local database for history
|
||||||
sqflite: ^2.3.2
|
sqflite: ^2.3.2
|
||||||
@@ -73,9 +71,6 @@ dependencies:
|
|||||||
crypto: ^3.0.7
|
crypto: ^3.0.7
|
||||||
camera: ^0.12.0+1
|
camera: ^0.12.0+1
|
||||||
|
|
||||||
# Machine Learning for YOLOv8
|
|
||||||
# tflite_flutter: ^0.11.0
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
@@ -100,7 +95,6 @@ flutter:
|
|||||||
|
|
||||||
# To add assets to your application, add an assets section, like this:
|
# To add assets to your application, add an assets section, like this:
|
||||||
# assets:
|
# assets:
|
||||||
# - assets/models/yolov8n_32.tflite
|
|
||||||
# - images/a_dot_burr.jpeg
|
# - images/a_dot_burr.jpeg
|
||||||
# - images/a_dot_ham.jpeg
|
# - images/a_dot_ham.jpeg
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
import 'package:opencv_dart/opencv_dart.dart' as cv;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
var p1 = cv.VecPoint.fromList([cv.Point(0, 0), cv.Point(1, 1)]);
|
|
||||||
var p2 = cv.VecPoint2f.fromList([cv.Point2f(0, 0), cv.Point2f(1, 1)]);
|
|
||||||
|
|
||||||
// Is it p1.mat ?
|
|
||||||
// Or is it cv.findHomography(p1, p1) but actually needs specific types ?
|
|
||||||
cv.Mat mat1 = cv.Mat.fromVec(p1);
|
|
||||||
cv.Mat mat2 = cv.Mat.fromVec(p2);
|
|
||||||
cv.findHomography(mat1, mat2);
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import 'package:opencv_dart/opencv_dart.dart' as cv;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
print(cv.approxPolyDP);
|
|
||||||
print(cv.arcLength);
|
|
||||||
print(cv.contourArea);
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import 'package:opencv_dart/opencv_dart.dart' as cv;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
print(cv.findHomography);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user