--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Tizen.Multimedia;
+using Tizen.Multimedia.Vision;
+
+namespace Fitness.Services
+{
+ /// <summary>
+ /// Common base class for all exercise services.
+ /// </summary>
+ public abstract class BaseExerciseService : INotifyPropertyChanged
+ {
+ private readonly PoseDetector poseDetector = new PoseDetector();
+ private int isInferencing = 0;
+ private Landmark[,] poseLandmarks;
+
+ /// <summary>
+ /// Occurs when a property value changes.
+ /// </summary>
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ /// <summary>
+ /// Gets the pose landmark property.
+ /// </summary>
+ public Landmark[,] PoseLandmarks
+ {
+ get => poseLandmarks;
+ private set
+ {
+ if (value != poseLandmarks)
+ {
+ poseLandmarks = value;
+ RaisePropertyChanged();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Detects performing the exercise based on given preview image data.
+ /// </summary>
+ /// <param name="previewFrame">Preview image data.</param>
+ /// <returns>A task that represents the exercise detection.</returns>
+ public async Task DetectExercise(Tizen.Multimedia.PreviewFrame previewFrame)
+ {
+ var stopwatch = new System.Diagnostics.Stopwatch();
+ stopwatch.Start();
+
+ try
+ {
+ if (Interlocked.Exchange(ref isInferencing, 1) == 0)
+ {
+ var plane = previewFrame.Plane as TriplePlane;
+ if (plane is null)
+ {
+ throw new System.Exception($"Unsupported plane type: {previewFrame.Plane.GetType()}");
+ }
+
+ uint width = (uint)previewFrame.Resolution.Width;
+ uint height = (uint)previewFrame.Resolution.Height;
+
+ var landmarks = await poseDetector.Detect(plane, height, width);
+
+ DetectExercise(landmarks);
+ NUIContext.InvokeOnMainThread(() =>
+ {
+ PoseLandmarks = landmarks;
+ });
+ }
+ }
+ catch (System.Exception exception)
+ {
+ Services.Logger.Error(exception.Message + System.Environment.NewLine + exception.StackTrace);
+ }
+ finally
+ {
+ stopwatch.Stop();
+ Services.Logger.Debug($"total time {stopwatch.ElapsedMilliseconds} msec");
+
+ Interlocked.Exchange(ref isInferencing, 0);
+ }
+ }
+
+ /// <summary>
+ /// Detects performing the exercise based on given landmarks.
+ /// </summary>
+ /// <param name="landmarks">Body landmarks.</param>
+ protected virtual void DetectExercise(Landmark[,] landmarks)
+ {
+ }
+
+ /// <summary>
+ /// Raises PropertyChanged event.
+ /// </summary>
+ /// <param name="propertyName">Property name.</param>
+ protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+ }
+}
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Tizen.Multimedia;
+using Tizen.Multimedia.Vision;
+
+namespace Fitness.Services
+{
+ /// <summary>
+ /// Interface for exercise service.
+ /// </summary>
+ public interface IExerciseService
+ {
+ /// <summary>
+ /// Gets the pose landmark property.
+ /// </summary>
+ Landmark[,] PoseLandmarks { get; }
+
+ /// <summary>
+ /// Gets or sets the hold time threshold specifying the time of a single exercise.
+ /// </summary>
+ long HoldTimeThreshold { get; set; }
+
+ /// <summary>
+ /// Gets the property specifying the time of the exercise performed - values ranging from 0 to 5.
+ /// </summary>
+ int Hold { get; }
+
+ /// <summary>
+ /// Gets the property specifying the number of repetitions of the exercise.
+ /// </summary>
+ int Count { get; }
+
+ /// <summary>
+ /// Gets the property specifying the correctness of the exercise - values ranging from 0 to 100.
+ /// </summary>
+ int Score { get; }
+
+ /// <summary>
+ /// Detects performing the exercise based on given preview image data.
+ /// </summary>
+ /// <param name="preview">Preview image data.</param>
+ /// <returns>A task that represents the exercise detection.</returns>
+ Task DetectExercise(PreviewFrame preview);
+ }
+}
--- /dev/null
+using System.Threading;
+using System.Threading.Tasks;
+using Tizen.Applications;
+using Tizen.Multimedia.Vision;
+
+namespace Fitness.Services
+{
+ public class PoseDetector
+ {
+ private InferenceModelConfiguration pdConfig;
+
+ public PoseDetector()
+ {
+ pdConfig = new InferenceModelConfiguration();
+ pdConfig.WeightFilePath = Application.Current.DirectoryInfo.Resource + "tflite/pld_tflite_model.tflite";
+ pdConfig.MeanValue = 0.0;
+ pdConfig.StdValue = 1.0;
+ pdConfig.Backend = InferenceBackendType.TFLite;
+ pdConfig.Device = InferenceTargetDevice.CPU;
+ pdConfig.DataType = InferenceDataType.Float32;
+ pdConfig.TensorSize = new Tizen.Multimedia.Size(192, 192);
+ pdConfig.TensorChannels = 3;
+ pdConfig.InputNodeName = "image";
+ pdConfig.OutputNodeName = new string[]
+ {
+ "Convolutional_Pose_Machine/stage_5_out",
+ };
+ pdConfig.LoadInferenceModel();
+ }
+
+ public enum BodyPart
+ {
+ Head,
+ Neck,
+ RightUpArm,
+ RightLowArm,
+ RightHand,
+ LeftUpArm,
+ LeftLowArm,
+ LeftHand,
+ RightUpLeg,
+ RightLowLeg,
+ RightFoot,
+ LeftUpLeg,
+ LeftLowLeg,
+ LeftFoot,
+ }
+
+ public Task<Landmark[,]> Detect(Tizen.Multimedia.TriplePlane plane, uint height, uint width)
+ {
+ byte[] rgbframe = YUV420ToRGB(plane.Y, plane.U, plane.V, (int)height, (int)width);
+
+ MediaVisionSource source = new MediaVisionSource(rgbframe, width, height, Tizen.Multimedia.ColorSpace.Rgb888);
+ return Tizen.Multimedia.Vision.PoseLandmarkDetector.DetectAsync(source, pdConfig);
+ }
+
+ // https://stackoverflow.com/questions/16107165/convert-from-yuv-420-to-imagebgr-byte
+ private byte[] YUV420ToRGB(byte[] planeY, byte[] planeU, byte[] planeV, int height, int width)
+ {
+ byte[] rgb = new byte[height * width * 3];
+ int indexRGB = 0;
+ int indexY = 0;
+ int indexUV = 0;
+ for (int r = 0; r < height; r++)
+ {
+ indexRGB = r * width * 3;
+ indexY = r * width;
+ indexUV = (r / 2) * width / 2;
+
+ // process two pixels at a time
+ for (int c = 0; c < width; c += 2)
+ {
+ int c1 = planeY[indexY + c] - 16;
+ int c2 = planeY[indexY + c + 1] - 16;
+ int d1 = planeU[indexUV + (c / 2)] - 128;
+ int e1 = planeV[indexUV + (c / 2)] - 128;
+
+ int r1 = ((298 * c1) + (409 * e1) + 128) >> 8;
+ int g1 = ((298 * c1) - (100 * d1) - (208 * e1) + 128) >> 8;
+ int b1 = ((298 * c1) + (516 * d1) + 128) >> 8;
+ int r2 = ((298 * c2) + (409 * e1) + 128) >> 8;
+ int g2 = ((298 * c2) - (100 * d1) - (208 * e1) + 128) >> 8;
+ int b2 = ((298 * c2) + (516 * d1) + 128) >> 8;
+
+ rgb[indexRGB + (c * 3) + 0] = (byte)System.Math.Clamp(r1, 0, 255);
+ rgb[indexRGB + (c * 3) + 1] = (byte)System.Math.Clamp(g1, 0, 255);
+ rgb[indexRGB + (c * 3) + 2] = (byte)System.Math.Clamp(b1, 0, 255);
+ rgb[indexRGB + (c * 3) + 3] = (byte)System.Math.Clamp(r2, 0, 255);
+ rgb[indexRGB + (c * 3) + 4] = (byte)System.Math.Clamp(g2, 0, 255);
+ rgb[indexRGB + (c * 3) + 5] = (byte)System.Math.Clamp(b2, 0, 255);
+ }
+ }
+
+ return rgb;
+ }
+ }
+}
--- /dev/null
+using System.Collections.Generic;
+using System.Numerics;
+using Tizen.Multimedia;
+
+namespace Fitness.Services
+{
+ /// <summary>
+ /// SquatDetector.
+ /// </summary>
+ public static class SquatDetector
+ {
+ // SquatLowerBody is calculation result of squat picture from
+ // https://review.tizen.org/gerrit/gitweb?p=test/tct/native/api.git;a=blob;f=src/utc/capi-media-vision/res/inference/images/poseLandmark.jpg;h=4bead08b8dc3687f228b5ab885e7136b26331cfb;hb=refs/heads/tizen
+ // Each below vector is derived from two points respectively,
+ // 0. RightUpLeg => RightLowLeg,
+ // 1. RightLowLeg => RightFoot,
+ // 2. LeftUpLeg => LeftLowLeg,
+ // 4. LeftLowLeg => LeftFoot
+ private static readonly List<Vector2> SquatLowerBody = new List<Vector2>(new[]
+ {
+ new Vector2(0.6666667F, -0.33333334F),
+ new Vector2(0F, -1F),
+ new Vector2(-0.6666667F, -0.33333334F),
+ new Vector2(0F, -1F),
+ });
+
+ /// <summary>
+ /// Return Average of lower body <see href="https://en.wikipedia.org/wiki/Cosine_similarity">Cosine Similarity</see> between input user pose and ground truth pose.
+ /// </summary>
+ /// <param name="userBody">user body detection result.</param>
+ /// <returns>Float score value (-1.0 ~ 1.0).</returns>
+ public static float Similarity(List<Point> userBody)
+ {
+ if (userBody.Count != System.Enum.GetNames(typeof(PoseDetector.BodyPart)).Length)
+ {
+ throw new System.ArgumentException("Must have all points which represent user body", "userBody");
+ }
+
+ List<Vector2> userSquat = new List<Vector2>(new[]
+ {
+ L1UnitVectorize(userBody[(int)PoseDetector.BodyPart.RightUpLeg], userBody[(int)PoseDetector.BodyPart.RightLowLeg]),
+ L1UnitVectorize(userBody[(int)PoseDetector.BodyPart.RightLowLeg], userBody[(int)PoseDetector.BodyPart.RightFoot]),
+ L1UnitVectorize(userBody[(int)PoseDetector.BodyPart.LeftUpLeg], userBody[(int)PoseDetector.BodyPart.LeftLowLeg]),
+ L1UnitVectorize(userBody[(int)PoseDetector.BodyPart.LeftLowLeg], userBody[(int)PoseDetector.BodyPart.LeftFoot]),
+ });
+
+ float score = 0F;
+
+ for (int i = 0; i < SquatLowerBody.Count; i++)
+ {
+ score += Vector2.Dot(SquatLowerBody[i], userSquat[i]) / SquatLowerBody[i].Length() / userSquat[i].Length();
+ }
+
+ return score / SquatLowerBody.Count;
+ }
+
+ private static Vector2 L1UnitVectorize(Point from, Point to)
+ {
+ int vx = from.X - to.X;
+ int vy = from.Y - to.Y;
+ float parent = System.Math.Abs(vx) + System.Math.Abs(vy);
+ return new Vector2(vx / parent, vy / parent);
+ }
+ }
+}
--- /dev/null
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Linq;
+using System.Timers;
+using Tizen.Multimedia.Vision;
+
+namespace Fitness.Services
+{
+ /// <summary>
+ /// Class handling the squat exercise detection.
+ /// </summary>
+ public class SquatService : BaseExerciseService, IExerciseService
+ {
+ private const float SquatDetectedThreshold = 0.92f;
+ private const int HoldCount = 5;
+ private int hold;
+ private int count;
+ private int score;
+ private int newHold;
+ private int newCount;
+ private int newScore;
+ private float fscore = 0;
+ private Stopwatch stopwatch = new Stopwatch();
+ private System.Timers.Timer holdTimer;
+ private long holdTimeThreshold;
+
+ public int Hold
+ {
+ get => hold;
+ set
+ {
+ if (value != hold)
+ {
+ hold = value;
+ RaisePropertyChanged();
+ }
+ }
+ }
+
+ public int Count
+ {
+ get => count;
+ set
+ {
+ if (value != count)
+ {
+ count = value;
+ RaisePropertyChanged();
+ }
+ }
+ }
+
+ public int Score
+ {
+ get => score;
+ set
+ {
+ if (value != score)
+ {
+ score = value;
+ RaisePropertyChanged();
+ }
+ }
+ }
+
+ public long HoldTimeThreshold
+ {
+ get => holdTimeThreshold;
+ set
+ {
+ if (value != holdTimeThreshold)
+ {
+ holdTimeThreshold = value;
+ InitializeTimer();
+ }
+ }
+ }
+
+ protected override void DetectExercise(Landmark[,] landmarks)
+ {
+ int numberOfBodyParts = landmarks.GetLength(1);
+ var range = Enumerable.Range(0, numberOfBodyParts);
+
+ var locations = range.Select(x => landmarks[0, x].Location).ToList();
+ var squatSimilarity = SquatDetector.Similarity(locations);
+ if (float.IsNaN(squatSimilarity))
+ {
+ squatSimilarity = 0.0f;
+ }
+
+ DetectSquat(squatSimilarity);
+
+ NUIContext.InvokeOnMainThread(() =>
+ {
+ Hold = newHold;
+ Count = newCount;
+ Score = newScore;
+ });
+ }
+
+ private void InitializeTimer()
+ {
+ long interval = holdTimeThreshold / HoldCount;
+ holdTimer = new System.Timers.Timer(interval);
+ holdTimer.Elapsed += HoldTimer_Elapsed;
+ holdTimer.AutoReset = true;
+ holdTimer.Enabled = true;
+ }
+
+ private void HoldTimer_Elapsed(object sender, ElapsedEventArgs e)
+ {
+ if (Hold < HoldCount && fscore >= SquatDetectedThreshold)
+ {
+ newHold++;
+ }
+ }
+
+ private void DetectSquat(float squatSimilarity)
+ {
+ var previousScore = fscore;
+ fscore = squatSimilarity;
+ newScore = (int)System.Math.Round((float)(100 * fscore));
+
+ if (previousScore < SquatDetectedThreshold)
+ {
+ if (fscore >= SquatDetectedThreshold)
+ {
+ stopwatch.Start();
+ holdTimer.Start();
+ }
+ }
+ else if (fscore < SquatDetectedThreshold)
+ {
+ stopwatch.Stop();
+ holdTimer.Stop();
+
+ if (stopwatch.ElapsedMilliseconds >= holdTimeThreshold)
+ {
+ newCount++;
+ }
+
+ newHold = 0;
+ stopwatch.Reset();
+ }
+ }
+ }
+}
+++ /dev/null
-using System.Threading;
-using System.Threading.Tasks;
-using Tizen.Applications;
-using Tizen.Multimedia.Vision;
-
-namespace Fitness.Services
-{
- public class PoseDetector
- {
- private InferenceModelConfiguration pdConfig;
-
- public PoseDetector()
- {
- pdConfig = new InferenceModelConfiguration();
- pdConfig.WeightFilePath = Application.Current.DirectoryInfo.Resource + "tflite/pld_tflite_model.tflite";
- pdConfig.MeanValue = 0.0;
- pdConfig.StdValue = 1.0;
- pdConfig.Backend = InferenceBackendType.TFLite;
- pdConfig.Device = InferenceTargetDevice.CPU;
- pdConfig.DataType = InferenceDataType.Float32;
- pdConfig.TensorSize = new Tizen.Multimedia.Size(192, 192);
- pdConfig.TensorChannels = 3;
- pdConfig.InputNodeName = "image";
- pdConfig.OutputNodeName = new string[]
- {
- "Convolutional_Pose_Machine/stage_5_out",
- };
- pdConfig.LoadInferenceModel();
- }
-
- public enum BodyPart
- {
- Head,
- Neck,
- RightUpArm,
- RightLowArm,
- RightHand,
- LeftUpArm,
- LeftLowArm,
- LeftHand,
- RightUpLeg,
- RightLowLeg,
- RightFoot,
- LeftUpLeg,
- LeftLowLeg,
- LeftFoot,
- }
-
- public Task<Landmark[,]> Detect(Tizen.Multimedia.TriplePlane plane, uint height, uint width)
- {
- byte[] rgbframe = YUV420ToRGB(plane.Y, plane.U, plane.V, (int)height, (int)width);
-
- MediaVisionSource source = new MediaVisionSource(rgbframe, width, height, Tizen.Multimedia.ColorSpace.Rgb888);
- return Tizen.Multimedia.Vision.PoseLandmarkDetector.DetectAsync(source, pdConfig);
- }
-
- // https://stackoverflow.com/questions/16107165/convert-from-yuv-420-to-imagebgr-byte
- private byte[] YUV420ToRGB(byte[] planeY, byte[] planeU, byte[] planeV, int height, int width)
- {
- byte[] rgb = new byte[height * width * 3];
- int indexRGB = 0;
- int indexY = 0;
- int indexUV = 0;
- for (int r = 0; r < height; r++)
- {
- indexRGB = r * width * 3;
- indexY = r * width;
- indexUV = (r / 2) * width / 2;
-
- // process two pixels at a time
- for (int c = 0; c < width; c += 2)
- {
- int c1 = planeY[indexY + c] - 16;
- int c2 = planeY[indexY + c + 1] - 16;
- int d1 = planeU[indexUV + (c / 2)] - 128;
- int e1 = planeV[indexUV + (c / 2)] - 128;
-
- int r1 = ((298 * c1) + (409 * e1) + 128) >> 8;
- int g1 = ((298 * c1) - (100 * d1) - (208 * e1) + 128) >> 8;
- int b1 = ((298 * c1) + (516 * d1) + 128) >> 8;
- int r2 = ((298 * c2) + (409 * e1) + 128) >> 8;
- int g2 = ((298 * c2) - (100 * d1) - (208 * e1) + 128) >> 8;
- int b2 = ((298 * c2) + (516 * d1) + 128) >> 8;
-
- rgb[indexRGB + (c * 3) + 0] = (byte)System.Math.Clamp(r1, 0, 255);
- rgb[indexRGB + (c * 3) + 1] = (byte)System.Math.Clamp(g1, 0, 255);
- rgb[indexRGB + (c * 3) + 2] = (byte)System.Math.Clamp(b1, 0, 255);
- rgb[indexRGB + (c * 3) + 3] = (byte)System.Math.Clamp(r2, 0, 255);
- rgb[indexRGB + (c * 3) + 4] = (byte)System.Math.Clamp(g2, 0, 255);
- rgb[indexRGB + (c * 3) + 5] = (byte)System.Math.Clamp(b2, 0, 255);
- }
- }
-
- return rgb;
- }
- }
-}
+++ /dev/null
-using System.Collections.Generic;
-using System.Numerics;
-using Tizen.Multimedia;
-
-namespace Fitness.Services
-{
- /// <summary>
- /// SquatDetector.
- /// </summary>
- public static class SquatDetector
- {
- // SquatLowerBody is calculation result of squat picture from
- // https://review.tizen.org/gerrit/gitweb?p=test/tct/native/api.git;a=blob;f=src/utc/capi-media-vision/res/inference/images/poseLandmark.jpg;h=4bead08b8dc3687f228b5ab885e7136b26331cfb;hb=refs/heads/tizen
- // Each below vector is derived from two points respectively,
- // 0. RightUpLeg => RightLowLeg,
- // 1. RightLowLeg => RightFoot,
- // 2. LeftUpLeg => LeftLowLeg,
- // 4. LeftLowLeg => LeftFoot
- private static readonly List<Vector2> SquatLowerBody = new List<Vector2>(new[]
- {
- new Vector2(0.6666667F, -0.33333334F),
- new Vector2(0F, -1F),
- new Vector2(-0.6666667F, -0.33333334F),
- new Vector2(0F, -1F),
- });
-
- /// <summary>
- /// Return Average of lower body <see href="https://en.wikipedia.org/wiki/Cosine_similarity">Cosine Similarity</see> between input user pose and ground truth pose.
- /// </summary>
- /// <param name="userBody">user body detection result.</param>
- /// <returns>Float score value (-1.0 ~ 1.0).</returns>
- public static float Similarity(List<Point> userBody)
- {
- if (userBody.Count != System.Enum.GetNames(typeof(PoseDetector.BodyPart)).Length)
- {
- throw new System.ArgumentException("Must have all points which represent user body", "userBody");
- }
-
- List<Vector2> userSquat = new List<Vector2>(new[]
- {
- L1UnitVectorize(userBody[(int)PoseDetector.BodyPart.RightUpLeg], userBody[(int)PoseDetector.BodyPart.RightLowLeg]),
- L1UnitVectorize(userBody[(int)PoseDetector.BodyPart.RightLowLeg], userBody[(int)PoseDetector.BodyPart.RightFoot]),
- L1UnitVectorize(userBody[(int)PoseDetector.BodyPart.LeftUpLeg], userBody[(int)PoseDetector.BodyPart.LeftLowLeg]),
- L1UnitVectorize(userBody[(int)PoseDetector.BodyPart.LeftLowLeg], userBody[(int)PoseDetector.BodyPart.LeftFoot]),
- });
-
- float score = 0F;
-
- for (int i = 0; i < SquatLowerBody.Count; i++)
- {
- score += Vector2.Dot(SquatLowerBody[i], userSquat[i]) / SquatLowerBody[i].Length() / userSquat[i].Length();
- }
-
- return score / SquatLowerBody.Count;
- }
-
- private static Vector2 L1UnitVectorize(Point from, Point to)
- {
- int vx = from.X - to.X;
- int vy = from.Y - to.Y;
- float parent = System.Math.Abs(vx) + System.Math.Abs(vy);
- return new Vector2(vx / parent, vy / parent);
- }
- }
-}
+++ /dev/null
-using System.ComponentModel;
-using System.Diagnostics;
-using System.Linq;
-using System.Runtime.CompilerServices;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Timers;
-using Tizen.Multimedia;
-using Tizen.Multimedia.Vision;
-
-namespace Fitness.Services
-{
- /// <summary>
- /// Class handling the squat exercise detection.
- /// </summary>
- public class SquatService : INotifyPropertyChanged
- {
- private const float SquatDetectedThreshold = 0.92f;
- private const int HoldCount = 5;
- private readonly PoseDetector poseDetector = new PoseDetector();
- private int hold;
- private int count;
- private int score;
- private int newHold;
- private int newCount;
- private int newScore;
- private float fscore = 0;
- private Stopwatch stopwatch = new Stopwatch();
- private System.Timers.Timer holdTimer;
- private long holdTimeThreshold;
- private int isInferencing = 0;
- private Landmark[,] poseLandmarks;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="SquatService"/> class.
- /// </summary>
- public SquatService()
- {
- }
-
- /// <summary>
- /// Occurs when a property value changes.
- /// </summary>
- public event PropertyChangedEventHandler PropertyChanged;
-
- /// <summary>
- /// Gets or sets the pose landmark property.
- /// </summary>
- public Landmark[,] PoseLandmarks
- {
- get => poseLandmarks;
- set
- {
- if (value != poseLandmarks)
- {
- poseLandmarks = value;
- RaisePropertyChanged();
- }
- }
- }
-
- /// <summary>
- /// Gets or sets the hold property specifying the time of the exercise performed - values ranging from 0 to 5.
- /// </summary>
- public int Hold
- {
- get => hold;
- set
- {
- if (value != hold)
- {
- hold = value;
- RaisePropertyChanged();
- }
- }
- }
-
- /// <summary>
- /// Gets or sets the count property specifying the number of repetitions of the exercise.
- /// </summary>
- public int Count
- {
- get => count;
- set
- {
- if (value != count)
- {
- count = value;
- RaisePropertyChanged();
- }
- }
- }
-
- /// <summary>
- /// Gets or sets the score property specifying the correctness of the exercise - values ranging from 0 to 100.
- /// </summary>
- public int Score
- {
- get => score;
- set
- {
- if (value != score)
- {
- score = value;
- RaisePropertyChanged();
- }
- }
- }
-
- /// <summary>
- /// Gets or sets the hold time threshold specifying the time of a single exercise.
- /// </summary>
- public long HoldTimeThreshold
- {
- get => holdTimeThreshold;
- set
- {
- if (value != holdTimeThreshold)
- {
- holdTimeThreshold = value;
- InitializeTimer();
- }
- }
- }
-
- /// <summary>
- /// Detects squat exercise based on given preview image data.
- /// </summary>
- /// <param name="previewFrame">Preview image data.</param>
- /// <returns>A task that represents the squat detection.</returns>
- public async Task DetectSquat(Tizen.Multimedia.PreviewFrame previewFrame)
- {
- var stopwatch = new System.Diagnostics.Stopwatch();
- stopwatch.Start();
-
- try
- {
- if (Interlocked.Exchange(ref isInferencing, 1) == 0)
- {
- var plane = previewFrame.Plane as TriplePlane;
- if (plane == null)
- {
- throw new System.Exception($"Unsupported plane type: {previewFrame.Plane.GetType()}");
- }
-
- uint width = (uint)previewFrame.Resolution.Width;
- uint height = (uint)previewFrame.Resolution.Height;
-
- var landmarks = await poseDetector.Detect(plane, height, width);
-
- int numberOfBodyParts = landmarks.GetLength(1);
- var range = Enumerable.Range(0, numberOfBodyParts);
-
- var locations = range.Select(x => landmarks[0, x].Location).ToList();
- var squatSimilarity = SquatDetector.Similarity(locations);
- if (float.IsNaN(squatSimilarity))
- {
- squatSimilarity = 0.0f;
- }
-
- DetectSquat(squatSimilarity);
-
- NUIContext.InvokeOnMainThread(() =>
- {
- PoseLandmarks = landmarks;
- Hold = newHold;
- Count = newCount;
- Score = newScore;
- });
- }
- }
- catch (System.Exception exception)
- {
- Services.Logger.Error(exception.Message + System.Environment.NewLine + exception.StackTrace);
- }
- finally
- {
- stopwatch.Stop();
- Services.Logger.Debug($"total time {stopwatch.ElapsedMilliseconds} msec");
-
- Interlocked.Exchange(ref isInferencing, 0);
- }
- }
-
- private void InitializeTimer()
- {
- long interval = holdTimeThreshold / HoldCount;
- holdTimer = new System.Timers.Timer(interval);
- holdTimer.Elapsed += HoldTimer_Elapsed;
- holdTimer.AutoReset = true;
- holdTimer.Enabled = true;
- }
-
- private void HoldTimer_Elapsed(object sender, ElapsedEventArgs e)
- {
- if ((Hold < HoldCount) && (fscore >= SquatDetectedThreshold))
- {
- newHold++;
- }
- }
-
- private void DetectSquat(float squatSimilarity)
- {
- var previousScore = fscore;
- fscore = squatSimilarity;
- newScore = (int)System.Math.Round((float)(100 * fscore));
-
- if (previousScore < SquatDetectedThreshold)
- {
- if (fscore >= SquatDetectedThreshold)
- {
- stopwatch.Start();
- holdTimer.Start();
- }
- }
- else
- {
- if (fscore < SquatDetectedThreshold)
- {
- stopwatch.Stop();
- holdTimer.Stop();
-
- if (stopwatch.ElapsedMilliseconds >= holdTimeThreshold)
- {
- newCount++;
- }
-
- newHold = 0;
- stopwatch.Reset();
- }
- }
- }
-
- private void RaisePropertyChanged([CallerMemberName] string propertyName = null)
- {
- PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
- }
- }
-}
using System;
using System.Collections.Generic;
+using System.IO;
using Fitness.Models;
using Fitness.ViewModels;
using Tizen.Applications;
Difficulty = DifficultyLevel.Easy,
Duration = new TimeSpan(0, 4, 30),
Favourite = false,
- VideoUrl = Application.Current.DirectoryInfo.Resource + "media/PlieSquat.avi",
- ThumbnailUrl = Application.Current.DirectoryInfo.Resource + "media/PlieSquat.png",
+ VideoUrl = Path.Combine(Application.Current.DirectoryInfo.Resource, "media/PlieSquat.avi"),
+ ThumbnailUrl = Path.Combine(Application.Current.DirectoryInfo.Resource, "media/PlieSquat.png"),
Id = "1",
},
new WorkoutViewModel
public class ExercisingViewModel : ChangeWorkoutViewModel
{
private WorkoutState state;
- private SquatService squatService;
+ private IExerciseService squatService;
public ExercisingViewModel(WorkoutViewModel workoutViewModel)
{
/// <summary>
/// Gets or sets the <see cref="SquatService"/> property.
/// </summary>
- public SquatService SquatService
+ public IExerciseService SquatService
{
get => squatService;
set
/// </summary>
public class ScanningViewModel : BaseViewModel
{
- private SquatService squatService;
+ private IExerciseService squatService;
/// <summary>
/// Initializes a new instance of the <see cref="ScanningViewModel"/> class.
/// <summary>
/// Gets or sets the <see cref="SquatService"/> property.
/// </summary>
- public SquatService SquatService
+ public IExerciseService SquatService
{
get => squatService;
set
{
if (BindingContext is ViewModels.ExercisingViewModel viewModel)
{
- _ = viewModel.SquatService.DetectSquat(e.Preview);
+ _ = viewModel.SquatService.DetectExercise(e.Preview);
}
}
{
if (BindingContext is ViewModels.ScanningViewModel viewModel)
{
- _ = viewModel.SquatService.DetectSquat(e.Preview);
+ _ = viewModel.SquatService.DetectExercise(e.Preview);
}
}
}