--- /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.Linq;
-using System.Threading;
-using System.Threading.Tasks;
using System.Windows.Input;
using Fitness.Services;
-using Tizen.Multimedia;
-using Tizen.Multimedia.Vision;
using Tizen.NUI.Binding;
namespace Fitness.ViewModels
{
+ /// <summary>
+ /// Class handling the scanning view.
+ /// </summary>
public class ScanningViewModel : BaseViewModel
{
- private const float SquatDetectedThreshold = 0.92f;
-
- private readonly PoseDetector poseDetector = new PoseDetector();
- private int isInferencing = 0;
-
- private Landmark[,] poseLandmarks;
- private string poseScores;
- private string squatSimilarity;
+ private SquatService squatService;
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ScanningViewModel"/> class.
+ /// </summary>
public ScanningViewModel()
{
CloseScanningView = new Command(() =>
{
NavigationService.Instance.Pop();
});
+ squatService = new SquatService()
+ {
+ HoldTimeThreshold = 1200,
+ };
}
/// <summary>
/// </summary>
public string Title { get; private set; } = "ScanningView";
- public Landmark[,] PoseLandmarks
- {
- get => poseLandmarks;
- set
- {
- if (value != poseLandmarks)
- {
- poseLandmarks = value;
- RaisePropertyChanged();
- }
- }
- }
-
- public string PoseScores
- {
- get => poseScores;
- set
- {
- if (value != poseScores)
- {
- poseScores = value;
- RaisePropertyChanged();
- }
- }
- }
-
- public string SquatSimilarity
+ /// <summary>
+ /// Gets or sets the <see cref="SquatService"/> property.
+ /// </summary>
+ public SquatService SquatService
{
- get => squatSimilarity;
+ get => squatService;
set
{
- if (value != squatSimilarity)
+ if (value != squatService)
{
- squatSimilarity = value;
+ squatService = value;
RaisePropertyChanged();
}
}
/// Gets EndWorkout Command.
/// </summary>
public ICommand CloseScanningView { get; private set; }
-
- public async Task DetectPose(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 scores = range.Select(x => landmarks[0, x].Score);
- var trimmedScores = scores.Select(score => score.ToString(".000"));
- var averageScore = scores.Average().ToString("0.000");
-
- var locations = range.Select(x => landmarks[0, x].Location).ToList();
- var squatSimilarity = SquatDetector.Similarity(locations);
- if (float.IsNaN(squatSimilarity))
- {
- squatSimilarity = 0.0f;
- }
-
- var squatDetection = squatSimilarity >= SquatDetectedThreshold ? "SQUAT DETECTED" : "no squat";
-
- NUIContext.InvokeOnMainThread(() =>
- {
- PoseLandmarks = landmarks;
- PoseScores = $"scores {string.Join(", ", trimmedScores)} average {averageScore}";
- SquatSimilarity = $"{squatSimilarity.ToString("0.000")} {squatDetection}";
- });
- }
- }
- 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);
- }
- }
}
}
{
if (BindingContext is ViewModels.ScanningViewModel viewModel)
{
- _ = viewModel.DetectPose(e.Preview);
+ _ = viewModel.SquatService.DetectSquat(e.Preview);
}
}
}
<ctrl:Overlay WidthSpecification="{Static LayoutParamPolicies.MatchParent}"
HeightSpecification="{Static LayoutParamPolicies.MatchParent}"
BindingContext="{Binding Source={x:Reference Root}, Path=BindingContext}"
- PoseLandmarks="{Binding PoseLandmarks}">
+ PoseLandmarks="{Binding SquatService.PoseLandmarks}">
<x:Arguments>
<Size2D>1280, 960</Size2D>
</x:Arguments>
</View.Layout>
<TextLabel BindingContext="{Binding Source={x:Reference Root}, Path=BindingContext}"
- Text="{Binding PoseScores}"
- PixelSize="30"
- TextColor="Black"/>
-
+ Text="{Binding SquatService.Hold, StringFormat='Hold: {0}'}"
+ PixelSize="40"
+ TextColor="Black" />
+ <TextLabel BindingContext="{Binding Source={x:Reference Root}, Path=BindingContext}"
+ Text="{Binding SquatService.Count, StringFormat='Count: {0}'}"
+ PixelSize="40"
+ TextColor="Black" />
<TextLabel BindingContext="{Binding Source={x:Reference Root}, Path=BindingContext}"
- Text="{Binding SquatSimilarity}"
+ Text="{Binding SquatService.Score, StringFormat='Score: {0}'}"
PixelSize="40"
TextColor="Black"/>