pose_detector.dart 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. import 'dart:async';
  2. import 'package:body_detection/models/pose_landmark.dart';
  3. import 'package:physigo/exercises/exercises_validation/models/exercise.dart';
  4. import 'package:rxdart/rxdart.dart';
  5. import 'package:body_detection/body_detection.dart';
  6. import 'package:body_detection/models/image_result.dart';
  7. import 'package:body_detection/models/pose.dart';
  8. import 'package:flutter/material.dart';
  9. import 'pose_painter.dart';
  10. typedef MeanFilteredData = List<List<double>>;
  11. typedef LandmarkVariations = List<List<double>>;
  12. enum StepExercise {
  13. notInPlace,
  14. ready,
  15. start,
  16. end,
  17. }
  18. class PoseDetector extends StatefulWidget {
  19. final Exercise exercise;
  20. const PoseDetector({required this.exercise, Key? key}) : super(key: key);
  21. @override
  22. State<PoseDetector> createState() => _PoseDetectorState();
  23. }
  24. class _PoseDetectorState extends State<PoseDetector> {
  25. static const meanFilterBuffer = 5;
  26. final List<StreamSubscription> _streamSubscriptions = [];
  27. final List<StreamController> _streamControllers = [];
  28. final StreamController<Pose> _poseController = StreamController.broadcast();
  29. final StreamController<StepExercise> _stepExerciseController = StreamController.broadcast();
  30. Image? _cameraImage;
  31. Pose? _detectedPose;
  32. Size _imageSize = Size.zero;
  33. late Future<void> _startCamera;
  34. late Stream<MeanFilteredData> _meanFilterStream;
  35. late Stream<int> _repCounter;
  36. @override
  37. initState() {
  38. super.initState();
  39. _streamControllers.add(_poseController);
  40. _streamControllers.add(_stepExerciseController);
  41. _startCamera = _startCameraStream();
  42. _meanFilterStream = _getMeanFilterStream(_poseController.stream);
  43. _streamSubscriptions.add(
  44. CombineLatestStream.combine2(_stepExerciseController.stream, _meanFilterStream, (a, b) => [a, b]).listen((value) {
  45. final stepExercise = value.first as StepExercise;
  46. final meanFilteredData = value.last as MeanFilteredData;
  47. final isStartOfExerciseMovement = widget.exercise.isAtStartMovement(meanFilteredData);
  48. final isEndOfExerciseMovement = widget.exercise.isAtEndMovement(meanFilteredData);
  49. if (stepExercise == StepExercise.notInPlace && isStartOfExerciseMovement) {
  50. _stepExerciseController.add(StepExercise.ready);
  51. }
  52. if ((stepExercise == StepExercise.ready || stepExercise == StepExercise.start) && isEndOfExerciseMovement) {
  53. _stepExerciseController.add(StepExercise.end);
  54. }
  55. if (stepExercise == StepExercise.end && isStartOfExerciseMovement) {
  56. _stepExerciseController.add(StepExercise.start);
  57. }
  58. }));
  59. _stepExerciseController.add(StepExercise.notInPlace);
  60. _repCounter = _stepExerciseController.stream
  61. .where((event) => event == StepExercise.start)
  62. .scan((int accumulated, value, index) => accumulated + 1, 0);
  63. }
  64. Stream<MeanFilteredData> _getMeanFilterStream(Stream<Pose> stream) {
  65. return stream
  66. .where((pose) => pose.landmarks.isNotEmpty)
  67. .map((pose) => pose.landmarks.where((landmark) => Exercise.authorizedType.contains(landmark.type)).toList())
  68. // Get last [buffer] poses
  69. .bufferCount(meanFilterBuffer, 1)
  70. // Swap matrix [buffer] * [authorizedType.length]
  71. .map(_swapMatrixDimensions)
  72. // For every landmarks, get meanFilter of size [buffer]
  73. .map((filteredLandmarks) => filteredLandmarks.map(_meanFilter).toList());
  74. }
  75. List<double> _meanFilter(List<PoseLandmark> landmarks) {
  76. return landmarks
  77. .map((landmark) => landmark.position)
  78. .map((position) => [
  79. position.x / meanFilterBuffer,
  80. position.y / meanFilterBuffer,
  81. position.z / meanFilterBuffer,
  82. ])
  83. .reduce((value, element) => [
  84. value[0] + element[0],
  85. value[1] + element[1],
  86. value[2] + element[2],
  87. ]);
  88. }
  89. List<List<T>> _swapMatrixDimensions<T>(List<List<T>> matrix) {
  90. final height = matrix.length;
  91. final width = matrix[0].length;
  92. List<List<T>> newMatrix = [];
  93. for (int col = 0; col < width; col++) {
  94. List<T> newRow = [];
  95. for (int row = 0; row < height; row++) {
  96. newRow.add(matrix[row][col]);
  97. }
  98. newMatrix.add(newRow);
  99. }
  100. return newMatrix;
  101. }
  102. Future<void> _startCameraStream() async {
  103. await BodyDetection.startCameraStream(onFrameAvailable: _handleCameraImage, onPoseAvailable: _handlePose);
  104. await BodyDetection.enablePoseDetection();
  105. }
  106. Future<void> _stopCameraStream() async {
  107. await BodyDetection.disablePoseDetection();
  108. await BodyDetection.stopCameraStream();
  109. }
  110. void _handleCameraImage(ImageResult result) {
  111. if (!mounted) return;
  112. // To avoid a memory leak issue.
  113. // https://github.com/flutter/flutter/issues/60160
  114. PaintingBinding.instance?.imageCache?.clear();
  115. PaintingBinding.instance?.imageCache?.clearLiveImages();
  116. final image = Image.memory(
  117. result.bytes,
  118. gaplessPlayback: true,
  119. fit: BoxFit.contain,
  120. );
  121. setState(() {
  122. _cameraImage = image;
  123. _imageSize = result.size;
  124. });
  125. }
  126. void _handlePose(Pose? pose) {
  127. if (!mounted) return;
  128. if (pose != null) _poseController.add(pose);
  129. setState(() {
  130. _detectedPose = pose;
  131. });
  132. }
  133. @override
  134. void dispose() {
  135. _stopCameraStream();
  136. for (final ss in _streamSubscriptions) {
  137. ss.cancel();
  138. }
  139. for (final sc in _streamControllers) {
  140. sc.close();
  141. }
  142. super.dispose();
  143. }
  144. @override
  145. Widget build(BuildContext context) {
  146. return FutureBuilder<void>(
  147. future: _startCamera,
  148. builder: (context, snapshot) {
  149. if (snapshot.connectionState == ConnectionState.waiting) {
  150. return const Center(child: CircularProgressIndicator());
  151. }
  152. return Column(
  153. children: [
  154. Center(
  155. child: CustomPaint(
  156. child: _cameraImage,
  157. foregroundPainter: PosePainter(
  158. pose: _detectedPose,
  159. imageSize: _imageSize,
  160. ),
  161. ),
  162. ),
  163. StreamBuilder<StepExercise>(
  164. stream: _stepExerciseController.stream,
  165. builder: (context, snapshot) {
  166. Color color;
  167. if (!snapshot.hasData) {
  168. color = Colors.black;
  169. } else {
  170. switch (snapshot.data!) {
  171. case StepExercise.notInPlace:
  172. color = Colors.black;
  173. break;
  174. case StepExercise.ready:
  175. color = Colors.green;
  176. break;
  177. case StepExercise.start:
  178. color = Colors.blue;
  179. break;
  180. case StepExercise.end:
  181. color = Colors.yellow;
  182. break;
  183. }
  184. }
  185. return Container(
  186. height: 100,
  187. width: 100,
  188. color: color,
  189. );
  190. },
  191. ),
  192. StreamBuilder<int>(
  193. stream: _repCounter,
  194. builder: (context, snapshot) {
  195. var repCounter = 0;
  196. if (snapshot.hasData) {
  197. repCounter = snapshot.data!;
  198. }
  199. return Column(
  200. children: [
  201. Text(
  202. "${repCounter % widget.exercise.reps}/${widget.exercise.reps}",
  203. style: const TextStyle(fontSize: 40),
  204. ),
  205. Text(
  206. "${repCounter ~/ widget.exercise.reps}/${widget.exercise.series}",
  207. style: const TextStyle(fontSize: 40),
  208. ),
  209. ],
  210. );
  211. },
  212. )
  213. ],
  214. );
  215. },
  216. );
  217. }
  218. }