/// 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 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 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 directly. for (int i = 0; i < circles.cols; i++) { final vec = circles.at(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 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); } }