import 'dart:math' as math; import 'package:opencv_dart/opencv_dart.dart' as cv; class TargetDetectionResult { final double centerX; final double centerY; final double radius; final bool success; TargetDetectionResult({ required this.centerX, required this.centerY, required this.radius, this.success = true, }); factory TargetDetectionResult.failure() { return TargetDetectionResult( centerX: 0.5, centerY: 0.5, radius: 0.4, success: false, ); } } class OpenCVTargetService { /// Detect the main target (center and radius) from an image file Future detectTarget(String imagePath) async { try { // Read image final img = cv.imread(imagePath, flags: cv.IMREAD_COLOR); if (img.isEmpty) { return TargetDetectionResult.failure(); } // Convert to grayscale final gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY); // Apply Gaussian blur to reduce noise final blurred = cv.gaussianBlur(gray, (9, 9), 2, sigmaY: 2); // Detect circles using Hough Transform // Parameters need to be tuned for the specific target type final circles = cv.HoughCircles( blurred, cv.HOUGH_GRADIENT, 1, // dp: Inverse ratio of the accumulator resolution to the image resolution (img.rows / 8) .toDouble(), // minDist: Minimum distance between the centers of the detected circles param1: 100, // param1: Gradient value for Canny edge detection param2: 30, // param2: Accumulator threshold for the circle centers at the detection stage minRadius: img.cols ~/ 20, // minRadius maxRadius: img.cols ~/ 2, // maxRadius ); // HoughCircles returns a Mat in opencv_dart? Or a specific object? // Checking common bindings: usually returns a Mat (1, N, 3) of floats. // If circles is empty or null, return failure. if (circles.isEmpty) { // Try with different parameters if first attempt fails (more lenient) final looseCircles = cv.HoughCircles( blurred, cv.HOUGH_GRADIENT, 1, (img.rows / 8).toDouble(), param1: 50, param2: 20, minRadius: img.cols ~/ 20, maxRadius: img.cols ~/ 2, ); if (looseCircles.isEmpty) { return TargetDetectionResult.failure(); } return _findBestCircle(looseCircles, img.cols, img.rows); } return _findBestCircle(circles, img.cols, img.rows); } catch (e) { // print('Error detecting target with OpenCV: $e'); return TargetDetectionResult.failure(); } } TargetDetectionResult _findBestCircle(cv.Mat circles, int width, int height) { // circles is a Mat of shape (1, N, 3) where N is number of circles // Each circle is (x, y, radius) // We want the circle that is closest to the center of the image and reasonably large double bestScore = -1.0; double bestX = 0.5; double bestY = 0.5; double bestRadius = 0.4; // The shape is typically (1, N, 3) for HoughCircles // We need to access the data. // Assuming we can iterate. // In opencv_dart 1.0+, Mat might not be directly iterable like a list. // We can use circles.at(0, i) if available or similar. // Or we might need to interpret the memory. // For now, let's assume a simplified access pattern or that we can get a list. // If this fails to compile, we will fix it based on the error. // Attempting to access knowing standard layout: // circles.rows is 1, circles.cols is N. final int numCircles = circles.cols; for (int i = 0; i < numCircles; i++) { // Use the generic 'at' or specific getter if known. // Assuming Vec3f is returned as a specific type or List // Note: in many dart bindings, we might get a list of points directly. // But HoughCircles typically returns Mat. // Let's try to use `at(0, i)` which is common in C++ and some bindings. // If not, we might need `ptr` access. final vec = circles.at(0, i); final double x = vec.val1; final double y = vec.val2; final double r = vec.val3; final relX = x / width; final relY = y / height; final relR = r / math.min(width, height); // Score based on centrality and size final distFromCenter = math.sqrt( math.pow(relX - 0.5, 2) + math.pow(relY - 0.5, 2), ); final sizeScore = relR; // Larger is usually better for the main target outer ring // We penalize distance from center final score = sizeScore - (distFromCenter * 0.5); if (score > bestScore) { bestScore = score; bestX = relX; bestY = relY; bestRadius = relR; } } return TargetDetectionResult( centerX: bestX, centerY: bestY, radius: bestRadius, ); } }