From 61b5fcfe226aec23b16372e5eaf26603ca7a0221 Mon Sep 17 00:00:00 2001 From: qguillaume Date: Fri, 5 Jun 2026 21:30:59 +0200 Subject: [PATCH] fix: ajout untrack files --- ...otlin-compiler-11570733270002131811.salive | 0 lib/services/object_detection_service.dart | 186 ++++++++++++++++++ 2 files changed, 186 insertions(+) create mode 100644 android/.kotlin/sessions/kotlin-compiler-11570733270002131811.salive create mode 100644 lib/services/object_detection_service.dart diff --git a/android/.kotlin/sessions/kotlin-compiler-11570733270002131811.salive b/android/.kotlin/sessions/kotlin-compiler-11570733270002131811.salive new file mode 100644 index 00000000..e69de29b diff --git a/lib/services/object_detection_service.dart b/lib/services/object_detection_service.dart new file mode 100644 index 00000000..d21a5962 --- /dev/null +++ b/lib/services/object_detection_service.dart @@ -0,0 +1,186 @@ +import 'dart:async'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:camera/camera.dart'; +import 'package:google_mlkit_object_detection/google_mlkit_object_detection.dart'; + +/// Un objet détecté, exprimé en coordonnées NORMALISÉES (0..1) par rapport +/// à l'image source. Indépendant de la résolution réelle de la caméra. +class DetectedObject2D { + /// Boîte englobante normalisée (left, top, right, bottom dans [0..1]). + final double left; + final double top; + final double right; + final double bottom; + + /// Label le plus probable (ex: "Fruit", "Plant"...) — peut être vide. + final String label; + + /// Confiance du label [0..1]. + final double confidence; + + const DetectedObject2D({ + required this.left, + required this.top, + required this.right, + required this.bottom, + required this.label, + required this.confidence, + }); + + double get centerX => (left + right) / 2; + double get centerY => (top + bottom) / 2; + double get width => right - left; + double get height => bottom - top; + double get area => width * height; +} + +/// Service de détection d'objets en temps réel basé sur Google ML Kit. +/// +/// Traite directement les frames `CameraImage` du flux caméra (pas de +/// passage par un fichier JPEG, donc beaucoup plus rapide qu'OpenCV ici). +/// +/// Usage : +/// final svc = ObjectDetectionService(); +/// svc.start(); +/// cameraController.startImageStream((frame) => +/// svc.processCameraImage(frame, cameraDescription, rotation)); +/// svc.stream.listen((objects) { ... }); +class ObjectDetectionService { + ObjectDetector? _detector; + final StreamController> _controller = + StreamController>.broadcast(); + + bool _isBusy = false; + bool _started = false; + + Stream> get stream => _controller.stream; + + void start() { + if (_started) return; + _started = true; + + // Mode STREAM : optimisé pour le flux vidéo temps réel. + // classifyObjects: true => on récupère un label grossier + confiance. + // multipleObjects: false => on suit l'objet principal (plus stable). + final options = ObjectDetectorOptions( + mode: DetectionMode.stream, + classifyObjects: true, + multipleObjects: false, + ); + _detector = ObjectDetector(options: options); + } + + /// À appeler depuis `startImageStream`. + Future processCameraImage( + CameraImage image, + CameraDescription camera, + DeviceOrientation deviceOrientation, + ) async { + if (!_started || _detector == null) return; + if (_isBusy) return; // On saute la frame si la précédente n'est pas finie + _isBusy = true; + + try { + final inputImage = _toInputImage(image, camera, deviceOrientation); + if (inputImage == null) { + _isBusy = false; + return; + } + + final objects = await _detector!.processImage(inputImage); + + final int imgW = image.width; + final int imgH = image.height; + + final List results = objects.map((o) { + final rect = o.boundingBox; + String label = ''; + double conf = 0; + if (o.labels.isNotEmpty) { + final best = o.labels.reduce( + (a, b) => a.confidence >= b.confidence ? a : b, + ); + label = best.text; + conf = best.confidence; + } + return DetectedObject2D( + left: (rect.left / imgW).clamp(0.0, 1.0), + top: (rect.top / imgH).clamp(0.0, 1.0), + right: (rect.right / imgW).clamp(0.0, 1.0), + bottom: (rect.bottom / imgH).clamp(0.0, 1.0), + label: label, + confidence: conf, + ); + }).toList(); + + if (!_controller.isClosed) _controller.add(results); + } catch (e) { + debugPrint('ObjectDetection erreur: $e'); + } finally { + _isBusy = false; + } + } + + /// Convertit une CameraImage en InputImage ML Kit. + InputImage? _toInputImage( + CameraImage image, + CameraDescription camera, + DeviceOrientation deviceOrientation, + ) { + // Rotation : combine l'orientation du capteur et celle de l'appareil. + final sensorOrientation = camera.sensorOrientation; + InputImageRotation? rotation; + + if (defaultTargetPlatform == TargetPlatform.iOS) { + rotation = InputImageRotationValue.fromRawValue(sensorOrientation); + } else { + // Android : table d'orientation + var rotationCompensation = _orientations[deviceOrientation] ?? 0; + if (camera.lensDirection == CameraLensDirection.front) { + rotationCompensation = + (sensorOrientation + rotationCompensation) % 360; + } else { + rotationCompensation = + (sensorOrientation - rotationCompensation + 360) % 360; + } + rotation = InputImageRotationValue.fromRawValue(rotationCompensation); + } + if (rotation == null) return null; + + final format = InputImageFormatValue.fromRawValue(image.format.raw); + if (format == null) return null; + + // ML Kit attend un seul plan contigu (NV21 sur Android, BGRA sur iOS). + if (image.planes.isEmpty) return null; + final plane = image.planes.first; + + return InputImage.fromBytes( + bytes: plane.bytes, + metadata: InputImageMetadata( + size: Size(image.width.toDouble(), image.height.toDouble()), + rotation: rotation, + format: format, + bytesPerRow: plane.bytesPerRow, + ), + ); + } + + static const Map _orientations = { + DeviceOrientation.portraitUp: 0, + DeviceOrientation.landscapeLeft: 90, + DeviceOrientation.portraitDown: 180, + DeviceOrientation.landscapeRight: 270, + }; + + void stop() { + _started = false; + } + + void dispose() { + stop(); + _detector?.close(); + _detector = null; + _controller.close(); + } +} \ No newline at end of file