|
@@ -1,7 +1,6 @@
|
|
|
import 'dart:async';
|
|
import 'dart:async';
|
|
|
|
|
|
|
|
import 'package:body_detection/models/pose_landmark.dart';
|
|
import 'package:body_detection/models/pose_landmark.dart';
|
|
|
-import 'package:body_detection/models/pose_landmark_type.dart';
|
|
|
|
|
import 'package:physigo/exercises/exercises_validation/models/exercise.dart';
|
|
import 'package:physigo/exercises/exercises_validation/models/exercise.dart';
|
|
|
import 'package:rxdart/rxdart.dart';
|
|
import 'package:rxdart/rxdart.dart';
|
|
|
import 'package:body_detection/body_detection.dart';
|
|
import 'package:body_detection/body_detection.dart';
|
|
@@ -11,7 +10,7 @@ import 'package:flutter/material.dart';
|
|
|
|
|
|
|
|
import 'pose_painter.dart';
|
|
import 'pose_painter.dart';
|
|
|
|
|
|
|
|
-typedef MeanFilteredData = Iterable<List<double>>;
|
|
|
|
|
|
|
+typedef MeanFilteredData = List<List<double>>;
|
|
|
typedef LandmarkVariations = List<List<double>>;
|
|
typedef LandmarkVariations = List<List<double>>;
|
|
|
|
|
|
|
|
enum StepExercise {
|
|
enum StepExercise {
|
|
@@ -21,9 +20,6 @@ enum StepExercise {
|
|
|
end,
|
|
end,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// TODO: authorized joint in exercise
|
|
|
|
|
-// TODO: correct axis depending on phone orientation. Determine axis based on skeleton(shoulder line, ...)
|
|
|
|
|
-
|
|
|
|
|
class PoseDetector extends StatefulWidget {
|
|
class PoseDetector extends StatefulWidget {
|
|
|
final Exercise exercise;
|
|
final Exercise exercise;
|
|
|
const PoseDetector({required this.exercise, Key? key}) : super(key: key);
|
|
const PoseDetector({required this.exercise, Key? key}) : super(key: key);
|
|
@@ -33,11 +29,11 @@ class PoseDetector extends StatefulWidget {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
class _PoseDetectorState extends State<PoseDetector> {
|
|
class _PoseDetectorState extends State<PoseDetector> {
|
|
|
- static const buffer = 5;
|
|
|
|
|
|
|
+ static const meanFilterBuffer = 5;
|
|
|
final List<StreamSubscription> _streamSubscriptions = [];
|
|
final List<StreamSubscription> _streamSubscriptions = [];
|
|
|
final List<StreamController> _streamControllers = [];
|
|
final List<StreamController> _streamControllers = [];
|
|
|
- final StreamController<Pose> _streamController = StreamController.broadcast();
|
|
|
|
|
- final StreamController<StepExercise> _stepExerciseStream = StreamController.broadcast();
|
|
|
|
|
|
|
+ final StreamController<Pose> _poseController = StreamController.broadcast();
|
|
|
|
|
+ final StreamController<StepExercise> _stepExerciseController = StreamController.broadcast();
|
|
|
Image? _cameraImage;
|
|
Image? _cameraImage;
|
|
|
Pose? _detectedPose;
|
|
Pose? _detectedPose;
|
|
|
Size _imageSize = Size.zero;
|
|
Size _imageSize = Size.zero;
|
|
@@ -48,28 +44,28 @@ class _PoseDetectorState extends State<PoseDetector> {
|
|
|
@override
|
|
@override
|
|
|
initState() {
|
|
initState() {
|
|
|
super.initState();
|
|
super.initState();
|
|
|
- _streamControllers.add(_streamController);
|
|
|
|
|
- _streamControllers.add(_stepExerciseStream);
|
|
|
|
|
|
|
+ _streamControllers.add(_poseController);
|
|
|
|
|
+ _streamControllers.add(_stepExerciseController);
|
|
|
_startCamera = _startCameraStream();
|
|
_startCamera = _startCameraStream();
|
|
|
- _meanFilterStream = _getMeanFilterStream(_streamController.stream);
|
|
|
|
|
|
|
+ _meanFilterStream = _getMeanFilterStream(_poseController.stream);
|
|
|
_streamSubscriptions.add(
|
|
_streamSubscriptions.add(
|
|
|
- CombineLatestStream.combine2(_stepExerciseStream.stream, _meanFilterStream, (a, b) => [a, b]).listen((value) {
|
|
|
|
|
- StepExercise stepExercise = value.first as StepExercise;
|
|
|
|
|
- MeanFilteredData meanFilteredData = value.last as MeanFilteredData;
|
|
|
|
|
|
|
+ CombineLatestStream.combine2(_stepExerciseController.stream, _meanFilterStream, (a, b) => [a, b]).listen((value) {
|
|
|
|
|
+ final stepExercise = value.first as StepExercise;
|
|
|
|
|
+ final meanFilteredData = value.last as MeanFilteredData;
|
|
|
final isStartOfExerciseMovement = widget.exercise.isAtStartMovement(meanFilteredData);
|
|
final isStartOfExerciseMovement = widget.exercise.isAtStartMovement(meanFilteredData);
|
|
|
final isEndOfExerciseMovement = widget.exercise.isAtEndMovement(meanFilteredData);
|
|
final isEndOfExerciseMovement = widget.exercise.isAtEndMovement(meanFilteredData);
|
|
|
if (stepExercise == StepExercise.notInPlace && isStartOfExerciseMovement) {
|
|
if (stepExercise == StepExercise.notInPlace && isStartOfExerciseMovement) {
|
|
|
- _stepExerciseStream.add(StepExercise.ready);
|
|
|
|
|
|
|
+ _stepExerciseController.add(StepExercise.ready);
|
|
|
}
|
|
}
|
|
|
if ((stepExercise == StepExercise.ready || stepExercise == StepExercise.start) && isEndOfExerciseMovement) {
|
|
if ((stepExercise == StepExercise.ready || stepExercise == StepExercise.start) && isEndOfExerciseMovement) {
|
|
|
- _stepExerciseStream.add(StepExercise.end);
|
|
|
|
|
|
|
+ _stepExerciseController.add(StepExercise.end);
|
|
|
}
|
|
}
|
|
|
if (stepExercise == StepExercise.end && isStartOfExerciseMovement) {
|
|
if (stepExercise == StepExercise.end && isStartOfExerciseMovement) {
|
|
|
- _stepExerciseStream.add(StepExercise.start);
|
|
|
|
|
|
|
+ _stepExerciseController.add(StepExercise.start);
|
|
|
}
|
|
}
|
|
|
}));
|
|
}));
|
|
|
- _stepExerciseStream.add(StepExercise.notInPlace);
|
|
|
|
|
- _repCounter = _stepExerciseStream.stream
|
|
|
|
|
|
|
+ _stepExerciseController.add(StepExercise.notInPlace);
|
|
|
|
|
+ _repCounter = _stepExerciseController.stream
|
|
|
.where((event) => event == StepExercise.start)
|
|
.where((event) => event == StepExercise.start)
|
|
|
.scan((int accumulated, value, index) => accumulated + 1, 0);
|
|
.scan((int accumulated, value, index) => accumulated + 1, 0);
|
|
|
}
|
|
}
|
|
@@ -77,22 +73,22 @@ class _PoseDetectorState extends State<PoseDetector> {
|
|
|
Stream<MeanFilteredData> _getMeanFilterStream(Stream<Pose> stream) {
|
|
Stream<MeanFilteredData> _getMeanFilterStream(Stream<Pose> stream) {
|
|
|
return stream
|
|
return stream
|
|
|
.where((pose) => pose.landmarks.isNotEmpty)
|
|
.where((pose) => pose.landmarks.isNotEmpty)
|
|
|
- .map((pose) => pose.landmarks.where((landmark) => authorizedType.contains(landmark.type)).toList())
|
|
|
|
|
|
|
+ .map((pose) => pose.landmarks.where((landmark) => Exercise.authorizedType.contains(landmark.type)).toList())
|
|
|
// Get last [buffer] poses
|
|
// Get last [buffer] poses
|
|
|
- .bufferCount(buffer, 1)
|
|
|
|
|
|
|
+ .bufferCount(meanFilterBuffer, 1)
|
|
|
// Swap matrix [buffer] * [authorizedType.length]
|
|
// Swap matrix [buffer] * [authorizedType.length]
|
|
|
.map(_swapMatrixDimensions)
|
|
.map(_swapMatrixDimensions)
|
|
|
// For every landmarks, get meanFilter of size [buffer]
|
|
// For every landmarks, get meanFilter of size [buffer]
|
|
|
- .map((filteredLandmarks) => filteredLandmarks.map(_meanFilter));
|
|
|
|
|
|
|
+ .map((filteredLandmarks) => filteredLandmarks.map(_meanFilter).toList());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
List<double> _meanFilter(List<PoseLandmark> landmarks) {
|
|
List<double> _meanFilter(List<PoseLandmark> landmarks) {
|
|
|
return landmarks
|
|
return landmarks
|
|
|
.map((landmark) => landmark.position)
|
|
.map((landmark) => landmark.position)
|
|
|
.map((position) => [
|
|
.map((position) => [
|
|
|
- position.x / buffer,
|
|
|
|
|
- position.y / buffer,
|
|
|
|
|
- position.z / buffer,
|
|
|
|
|
|
|
+ position.x / meanFilterBuffer,
|
|
|
|
|
+ position.y / meanFilterBuffer,
|
|
|
|
|
+ position.z / meanFilterBuffer,
|
|
|
])
|
|
])
|
|
|
.reduce((value, element) => [
|
|
.reduce((value, element) => [
|
|
|
value[0] + element[0],
|
|
value[0] + element[0],
|
|
@@ -147,7 +143,7 @@ class _PoseDetectorState extends State<PoseDetector> {
|
|
|
|
|
|
|
|
void _handlePose(Pose? pose) {
|
|
void _handlePose(Pose? pose) {
|
|
|
if (!mounted) return;
|
|
if (!mounted) return;
|
|
|
- if (pose != null) _streamController.add(pose);
|
|
|
|
|
|
|
+ if (pose != null) _poseController.add(pose);
|
|
|
setState(() {
|
|
setState(() {
|
|
|
_detectedPose = pose;
|
|
_detectedPose = pose;
|
|
|
});
|
|
});
|
|
@@ -185,7 +181,7 @@ class _PoseDetectorState extends State<PoseDetector> {
|
|
|
),
|
|
),
|
|
|
),
|
|
),
|
|
|
StreamBuilder<StepExercise>(
|
|
StreamBuilder<StepExercise>(
|
|
|
- stream: _stepExerciseStream.stream,
|
|
|
|
|
|
|
+ stream: _stepExerciseController.stream,
|
|
|
builder: (context, snapshot) {
|
|
builder: (context, snapshot) {
|
|
|
Color color;
|
|
Color color;
|
|
|
if (!snapshot.hasData) {
|
|
if (!snapshot.hasData) {
|
|
@@ -202,7 +198,7 @@ class _PoseDetectorState extends State<PoseDetector> {
|
|
|
color = Colors.blue;
|
|
color = Colors.blue;
|
|
|
break;
|
|
break;
|
|
|
case StepExercise.end:
|
|
case StepExercise.end:
|
|
|
- color = Colors.red;
|
|
|
|
|
|
|
+ color = Colors.yellow;
|
|
|
break;
|
|
break;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -224,11 +220,11 @@ class _PoseDetectorState extends State<PoseDetector> {
|
|
|
children: [
|
|
children: [
|
|
|
Text(
|
|
Text(
|
|
|
"${repCounter % widget.exercise.reps}/${widget.exercise.reps}",
|
|
"${repCounter % widget.exercise.reps}/${widget.exercise.reps}",
|
|
|
- style: TextStyle(fontSize: 40),
|
|
|
|
|
|
|
+ style: const TextStyle(fontSize: 40),
|
|
|
),
|
|
),
|
|
|
Text(
|
|
Text(
|
|
|
"${repCounter ~/ widget.exercise.reps}/${widget.exercise.series}",
|
|
"${repCounter ~/ widget.exercise.reps}/${widget.exercise.series}",
|
|
|
- style: TextStyle(fontSize: 40),
|
|
|
|
|
|
|
+ style: const TextStyle(fontSize: 40),
|
|
|
),
|
|
),
|
|
|
],
|
|
],
|
|
|
);
|
|
);
|
|
@@ -240,33 +236,4 @@ class _PoseDetectorState extends State<PoseDetector> {
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- static const authorizedType = [
|
|
|
|
|
- PoseLandmarkType.nose,
|
|
|
|
|
- PoseLandmarkType.leftShoulder,
|
|
|
|
|
- PoseLandmarkType.rightShoulder,
|
|
|
|
|
- PoseLandmarkType.leftElbow,
|
|
|
|
|
- PoseLandmarkType.rightElbow,
|
|
|
|
|
- PoseLandmarkType.leftWrist,
|
|
|
|
|
- PoseLandmarkType.rightWrist,
|
|
|
|
|
- PoseLandmarkType.leftHip,
|
|
|
|
|
- PoseLandmarkType.rightHip,
|
|
|
|
|
- PoseLandmarkType.leftKnee,
|
|
|
|
|
- PoseLandmarkType.rightKnee,
|
|
|
|
|
- PoseLandmarkType.leftAnkle,
|
|
|
|
|
- PoseLandmarkType.rightAnkle,
|
|
|
|
|
- ];
|
|
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-/*
|
|
|
|
|
-
|
|
|
|
|
-GETTING IN POSITION:
|
|
|
|
|
- CHECK IF EVERY NECESSARY JOINT ARE ON SCREEN (reliability > 0.8)
|
|
|
|
|
- CHECK IF START POSITION IS OKAY (for squat, if knee, hip, shoulder aligned)
|
|
|
|
|
-
|
|
|
|
|
-COUTING REPETITION:
|
|
|
|
|
- FROM BEGINNING TO END:
|
|
|
|
|
- - BEGINNING: defined by start position, get position of interesting joint
|
|
|
|
|
- - END: defined by positions/distance interesting joints (knee and hip same level for squat,
|
|
|
|
|
- elbow and should same level for push up)
|
|
|
|
|
- */
|
|
|