feat: target rectify service pour perfectionner la prise de photo avec detection

This commit is contained in:
qguillaume
2026-06-05 22:27:53 +02:00
parent c49c8b8c73
commit 9f9f9beb84
3 changed files with 361 additions and 58 deletions

View File

@@ -19,6 +19,7 @@ import '../../services/image_crop_service.dart';
import '../../services/opencv_target_service.dart';
import '../../services/parallelism_service.dart'; // NOUVEAU
import '../../services/object_detection_service.dart'; // NOUVEAU : ML Kit
import '../../services/target_rectify_service.dart'; // NOUVEAU : redressement
import 'package:image/image.dart' as img;
import 'package:path_provider/path_provider.dart';
@@ -69,6 +70,9 @@ class _CaptureScreenState extends State<CaptureScreen>
StreamSubscription<List<DetectedObject2D>>? _objectSubscription;
List<DetectedObject2D> _detectedObjects = [];
// NOUVEAU : Service de redressement de cible (warp perspective)
final TargetRectifyService _rectifyService = TargetRectifyService();
@override
void initState() {
super.initState();
@@ -506,14 +510,15 @@ class _CaptureScreenState extends State<CaptureScreen>
),
),
// NOUVEAU : 1.bis Cadre dessiné autour de l'objet détecté
if (_primaryObject != null)
// NOUVEAU : 1.bis Cadre dessiné autour de CHAQUE objet détecté
if (_detectedObjects.isNotEmpty)
Center(
child: AspectRatio(
aspectRatio: 3 / 4,
child: CustomPaint(
painter: _ObjectBoxPainter(
object: _primaryObject!,
objects: _detectedObjects,
primary: _primaryObject,
color: frameColor,
),
),
@@ -792,8 +797,39 @@ class _CaptureScreenState extends State<CaptureScreen>
final XFile photo = await _cameraController!.takePicture();
// NOUVEAU : redressement automatique de la cible (warp perspective).
// On écrit le résultat dans un fichier dédié, à côté de l'original.
String finalPath = photo.path;
try {
final tempDir = await getTemporaryDirectory();
final rectifiedPath =
'${tempDir.path}/rectified_${DateTime.now().millisecondsSinceEpoch}.jpg';
final result = await _rectifyService.rectify(
inputPath: photo.path,
outputPath: rectifiedPath,
);
finalPath = result.outputPath;
if (mounted && result.rectified) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
duration: const Duration(seconds: 2),
content: Text(
'Cible redressée automatiquement '
'(inclinaison ${result.estimatedTiltDegrees.toStringAsFixed(1)}° corrigée)',
),
),
);
}
} catch (e) {
debugPrint('Redressement ignoré: $e');
finalPath = photo.path; // on garde la photo originale en secours
}
setState(() {
_selectedImagePath = photo.path;
_selectedImagePath = finalPath;
_showLiveCamera = false;
});
@@ -902,58 +938,66 @@ class _CaptureScreenState extends State<CaptureScreen>
}
// ─────────────────────────────────────────────────────────────────────────
// NOUVEAU : Painter qui dessine la boîte englobante de l'objet détecté.
// Les coordonnées de l'objet sont normalisées (0..1) → on les multiplie
// par la taille réelle de la zone de dessin (qui suit l'AspectRatio 3/4).
// Painter qui dessine la boîte englobante de CHAQUE objet détecté.
// L'objet principal (le plus grand et centré) est mis en évidence avec
// la couleur d'état (vert/orange) ; les autres en blanc translucide.
// ─────────────────────────────────────────────────────────────────────────
class _ObjectBoxPainter extends CustomPainter {
final DetectedObject2D object;
final List<DetectedObject2D> objects;
final DetectedObject2D? primary;
final Color color;
_ObjectBoxPainter({required this.object, required this.color});
_ObjectBoxPainter({
required this.objects,
required this.primary,
required this.color,
});
@override
void paint(Canvas canvas, Size size) {
final Paint stroke = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 3.0
..color = color;
for (final obj in objects) {
final bool isPrimary = identical(obj, primary);
final Color boxColor =
isPrimary ? color : Colors.white.withValues(alpha: 0.6);
final Rect rect = Rect.fromLTRB(
object.left * size.width,
object.top * size.height,
object.right * size.width,
object.bottom * size.height,
);
final Paint stroke = Paint()
..style = PaintingStyle.stroke
..strokeWidth = isPrimary ? 3.0 : 1.5
..color = boxColor;
// Boîte à coins arrondis
final RRect rrect =
RRect.fromRectAndRadius(rect, const Radius.circular(8));
canvas.drawRRect(rrect, stroke);
// Petit label si disponible
if (object.label.isNotEmpty) {
final textPainter = TextPainter(
text: TextSpan(
text: ' ${object.label} '
'${(object.confidence * 100).toStringAsFixed(0)}% ',
style: TextStyle(
color: Colors.black,
fontSize: 12,
fontWeight: FontWeight.bold,
backgroundColor: color,
),
),
textDirection: TextDirection.ltr,
)..layout();
textPainter.paint(
canvas,
Offset(rect.left, (rect.top - 18).clamp(0.0, size.height)),
final Rect rect = Rect.fromLTRB(
obj.left * size.width,
obj.top * size.height,
obj.right * size.width,
obj.bottom * size.height,
);
final RRect rrect =
RRect.fromRectAndRadius(rect, const Radius.circular(8));
canvas.drawRRect(rrect, stroke);
if (obj.label.isNotEmpty) {
final textPainter = TextPainter(
text: TextSpan(
text: ' ${obj.label} '
'${(obj.confidence * 100).toStringAsFixed(0)}% ',
style: TextStyle(
color: Colors.black,
fontSize: 12,
fontWeight: FontWeight.bold,
backgroundColor: boxColor,
),
),
textDirection: TextDirection.ltr,
)..layout();
textPainter.paint(
canvas,
Offset(rect.left, (rect.top - 18).clamp(0.0, size.height)),
);
}
}
}
@override
bool shouldRepaint(_ObjectBoxPainter old) =>
old.object != object || old.color != color;
}
old.objects != objects || old.color != color || old.primary != primary;
}