Léo Salé 3 лет назад
Родитель
Сommit
d26f6e2bc3

+ 1 - 1
app/lib/exercises/exercises_validation/exercise_validation_page.dart

@@ -17,7 +17,7 @@ class ExerciseValidationPage extends StatelessWidget {
           if (snapshot.hasError) {
             return Center(child: Text(snapshot.error.toString()));
           }
-          return PoseDetector();
+          return const PoseDetector();
         }),
       ),
     );

+ 17 - 6
app/lib/exercises/exercises_validation/widgets/pose_detector.dart

@@ -2,8 +2,11 @@ import 'dart:async';
 
 import 'package:body_detection/body_detection.dart';
 import 'package:body_detection/models/image_result.dart';
+import 'package:body_detection/models/pose.dart';
 import 'package:flutter/material.dart';
 
+import 'pose_painter.dart';
+
 class PoseDetector extends StatefulWidget {
   const PoseDetector({Key? key}) : super(key: key);
 
@@ -13,6 +16,7 @@ class PoseDetector extends StatefulWidget {
 
 class _PoseDetectorState extends State<PoseDetector> {
   Image? _cameraImage;
+  Pose? _detectedPose;
   Size _imageSize = Size.zero;
   late Future<void> _startCamera;
 
@@ -23,19 +27,16 @@ class _PoseDetectorState extends State<PoseDetector> {
   }
 
   Future<void> _startCameraStream() async {
-    await BodyDetection.startCameraStream(
-      onFrameAvailable: _handleCameraImage,
-    );
+    await BodyDetection.startCameraStream(onFrameAvailable: _handleCameraImage, onPoseAvailable: _handlePose);
+    await BodyDetection.enablePoseDetection();
   }
 
   Future<void> _stopCameraStream() async {
+    await BodyDetection.disablePoseDetection();
     await BodyDetection.stopCameraStream();
   }
 
   void _handleCameraImage(ImageResult result) {
-    // Ignore callback if navigated out of the page.
-    if (!mounted) return;
-
     // To avoid a memory leak issue.
     // https://github.com/flutter/flutter/issues/60160
     PaintingBinding.instance?.imageCache?.clear();
@@ -53,6 +54,12 @@ class _PoseDetectorState extends State<PoseDetector> {
     });
   }
 
+  void _handlePose(Pose? pose) {
+    setState(() {
+      _detectedPose = pose;
+    });
+  }
+
   @override
   void dispose() {
     _stopCameraStream();
@@ -71,6 +78,10 @@ class _PoseDetectorState extends State<PoseDetector> {
           child: CustomPaint(
             size: _imageSize,
             child: _cameraImage,
+            foregroundPainter: PosePainter(
+              pose: _detectedPose,
+              imageSize: _imageSize,
+            ),
           ),
         );
       },

+ 109 - 0
app/lib/exercises/exercises_validation/widgets/pose_painter.dart

@@ -0,0 +1,109 @@
+import 'package:body_detection/models/pose.dart';
+import 'package:body_detection/models/pose_landmark.dart';
+import 'package:body_detection/models/pose_landmark_type.dart';
+import 'package:flutter/widgets.dart';
+
+class PosePainter extends CustomPainter {
+  final Pose? pose;
+  final Size imageSize;
+  PosePainter({required this.pose, required this.imageSize, Key? key});
+
+  final pointPaint = Paint()..color = const Color.fromRGBO(255, 255, 255, 0.8);
+  final leftPointPaint = Paint()..color = const Color.fromRGBO(223, 157, 80, 1);
+  final rightPointPaint = Paint()..color = const Color.fromRGBO(100, 208, 218, 1);
+  final linePaint = Paint()
+    ..color = const Color.fromARGB(228, 0, 0, 0)
+    ..strokeWidth = 3;
+  final maskPaint = Paint()..colorFilter = const ColorFilter.mode(Color.fromRGBO(0, 0, 255, 0.5), BlendMode.srcOut);
+
+  @override
+  void paint(Canvas canvas, Size size) {
+    if (pose == null) return;
+
+    final double hRatio = imageSize.width == 0 ? 1 : size.width / imageSize.width;
+    final double vRatio = imageSize.height == 0 ? 1 : size.height / imageSize.height;
+
+    offsetForPart(PoseLandmark part) => Offset(part.position.x * hRatio, part.position.y * vRatio);
+
+    // Landmark connections
+    final landmarksByType = {for (final it in pose!.landmarks) it.type: it};
+    for (final connection in _connections) {
+      final point1 = offsetForPart(landmarksByType[connection[0]]!);
+      final point2 = offsetForPart(landmarksByType[connection[1]]!);
+      canvas.drawLine(point1, point2, linePaint);
+    }
+
+    for (final part in pose!.landmarks) {
+      // Landmark points
+      canvas.drawCircle(offsetForPart(part), 5, pointPaint);
+      if (part.type.isLeftSide) {
+        canvas.drawCircle(offsetForPart(part), 3, leftPointPaint);
+      } else if (part.type.isRightSide) {
+        canvas.drawCircle(offsetForPart(part), 3, rightPointPaint);
+      }
+
+      // Landmark labels
+      TextSpan span = TextSpan(
+        text: part.type.toString().substring(16),
+        style: const TextStyle(
+          color: Color.fromRGBO(0, 128, 255, 1),
+          fontSize: 10,
+          shadows: [
+            Shadow(
+              color: Color.fromRGBO(255, 255, 255, 1),
+              offset: Offset(1, 1),
+              blurRadius: 1,
+            ),
+          ],
+        ),
+      );
+      TextPainter tp = TextPainter(text: span, textAlign: TextAlign.left);
+      tp.textDirection = TextDirection.ltr;
+      tp.layout();
+      tp.paint(canvas, offsetForPart(part));
+    }
+  }
+
+  @override
+  bool shouldRepaint(PosePainter oldDelegate) {
+    return oldDelegate.pose != pose || oldDelegate.imageSize != imageSize;
+  }
+
+  static const List<List<PoseLandmarkType>> _connections = [
+    [PoseLandmarkType.leftEar, PoseLandmarkType.leftEyeOuter],
+    [PoseLandmarkType.leftEyeOuter, PoseLandmarkType.leftEye],
+    [PoseLandmarkType.leftEye, PoseLandmarkType.leftEyeInner],
+    [PoseLandmarkType.leftEyeInner, PoseLandmarkType.nose],
+    [PoseLandmarkType.nose, PoseLandmarkType.rightEyeInner],
+    [PoseLandmarkType.rightEyeInner, PoseLandmarkType.rightEye],
+    [PoseLandmarkType.rightEye, PoseLandmarkType.rightEyeOuter],
+    [PoseLandmarkType.rightEyeOuter, PoseLandmarkType.rightEar],
+    [PoseLandmarkType.mouthLeft, PoseLandmarkType.mouthRight],
+    [PoseLandmarkType.leftShoulder, PoseLandmarkType.rightShoulder],
+    [PoseLandmarkType.leftShoulder, PoseLandmarkType.leftHip],
+    [PoseLandmarkType.rightShoulder, PoseLandmarkType.rightHip],
+    [PoseLandmarkType.rightShoulder, PoseLandmarkType.rightElbow],
+    [PoseLandmarkType.rightWrist, PoseLandmarkType.rightElbow],
+    [PoseLandmarkType.rightWrist, PoseLandmarkType.rightThumb],
+    [PoseLandmarkType.rightWrist, PoseLandmarkType.rightIndexFinger],
+    [PoseLandmarkType.rightWrist, PoseLandmarkType.rightPinkyFinger],
+    [PoseLandmarkType.leftHip, PoseLandmarkType.rightHip],
+    [PoseLandmarkType.leftHip, PoseLandmarkType.leftKnee],
+    [PoseLandmarkType.rightHip, PoseLandmarkType.rightKnee],
+    [PoseLandmarkType.rightKnee, PoseLandmarkType.rightAnkle],
+    [PoseLandmarkType.leftKnee, PoseLandmarkType.leftAnkle],
+    [PoseLandmarkType.leftElbow, PoseLandmarkType.leftShoulder],
+    [PoseLandmarkType.leftWrist, PoseLandmarkType.leftElbow],
+    [PoseLandmarkType.leftWrist, PoseLandmarkType.leftThumb],
+    [PoseLandmarkType.leftWrist, PoseLandmarkType.leftIndexFinger],
+    [PoseLandmarkType.leftWrist, PoseLandmarkType.leftPinkyFinger],
+    [PoseLandmarkType.leftAnkle, PoseLandmarkType.leftHeel],
+    [PoseLandmarkType.leftAnkle, PoseLandmarkType.leftToe],
+    [PoseLandmarkType.rightAnkle, PoseLandmarkType.rightHeel],
+    [PoseLandmarkType.rightAnkle, PoseLandmarkType.rightToe],
+    [PoseLandmarkType.rightHeel, PoseLandmarkType.rightToe],
+    [PoseLandmarkType.leftHeel, PoseLandmarkType.leftToe],
+    [PoseLandmarkType.rightIndexFinger, PoseLandmarkType.rightPinkyFinger],
+    [PoseLandmarkType.leftIndexFinger, PoseLandmarkType.leftPinkyFinger],
+  ];
+}