diff --git a/Wpf_AiSportsMicrospace/Common/SportOperate.cs b/Wpf_AiSportsMicrospace/Common/SportOperate.cs
index d340b89..3c75f9b 100644
--- a/Wpf_AiSportsMicrospace/Common/SportOperate.cs
+++ b/Wpf_AiSportsMicrospace/Common/SportOperate.cs
@@ -65,14 +65,12 @@ namespace Wpf_AiSportsMicrospace.Common
return _webcamClient;
}
-
public int VerifyWavingAction(Human human)
{
var nose = human.Keypoints.FirstOrDefault(k => k.Name == "right_ankle");
if (nose == null)
return (int)WavingAction.None;
- // 使用 Canvas 宽度归一化
double xNorm = nose.X / 1920;
double yNorm = nose.Y / 1080;
@@ -93,10 +91,16 @@ namespace Wpf_AiSportsMicrospace.Common
ref _lastLeftWrist,
ref _leftWristDeltaX,
true);
- if (leftResult != (int)WavingAction.None) return leftResult;
+ if (leftResult != (int)WavingAction.None)
+ return leftResult;
}
- // --- 右手挥手或举手 ---
+ // --- 右手举手优先判断 ---
+ int rightHandAction = DetectRightHandRaise(human);
+ if (rightHandAction != (int)WavingAction.None)
+ return rightHandAction;
+
+ // --- 右手挥手,仅当未举手时判断 ---
if (rightWrist != null && rightElbow != null)
{
int rightWaveResult = DetectHorizontalWave(
@@ -105,51 +109,64 @@ namespace Wpf_AiSportsMicrospace.Common
ref _lastRightWrist,
ref _rightWristDeltaX,
false);
- if (rightWaveResult != (int)WavingAction.None) return rightWaveResult;
+ if (rightWaveResult != (int)WavingAction.None)
+ return rightWaveResult;
+ }
- // --- 举手逻辑 ---
- double verticalRise = rightElbow.Y - rightWrist.Y; // 手腕在肘上方 → 正值
- if (verticalRise >= 60) // 举手阈值
+ return (int)WavingAction.None;
+ }
+
+ ///
+ /// 检测右手举手状态
+ ///
+ /// 人体关键点信息
+ /// 返回 WavingAction:FirstHand / Raising / RaiseHand / None
+ public int DetectRightHandRaise(Human human)
+ {
+ var rightWrist = human.Keypoints.FirstOrDefault(k => k.Name == "right_wrist");
+ var rightElbow = human.Keypoints.FirstOrDefault(k => k.Name == "right_elbow");
+
+ if (rightWrist == null || rightElbow == null)
+ return (int)WavingAction.None;
+
+ double verticalRise = rightElbow.Y - rightWrist.Y; // 手腕在肘上方 → 正值
+ const double raiseThreshold = 60; // 举手阈值,可根据实际调整
+
+ if (verticalRise >= raiseThreshold)
+ {
+ // 初始化计时
+ if (_raiseStartTime == null) _raiseStartTime = DateTime.Now;
+ if (_wristStartTime == null) _wristStartTime = DateTime.Now;
+
+ var wristDuration = DateTime.Now - _wristStartTime.Value;
+
+ if (!_firstHandTriggered && wristDuration.TotalSeconds >= 1)
{
- // 初始化计时
- if (_raiseStartTime == null)
- _raiseStartTime = DateTime.Now;
+ _firstHandTriggered = true;
+ return (int)WavingAction.FirstHand; // 举手开始
+ }
- if (_wristStartTime == null)
- _wristStartTime = DateTime.Now;
-
- var wristDuration = DateTime.Now - _wristStartTime.Value;
-
- // 保持 >1 秒才触发一次 FirstHand
- if (!_firstHandTriggered && wristDuration.TotalSeconds >= 1)
- {
- _firstHandTriggered = true;
- return (int)WavingAction.FirstHand; // 举手开始,只触发一次
- }
-
- // 判断是否完成3秒举手
- var duration = DateTime.Now - _raiseStartTime.Value;
- if (duration.TotalSeconds >= 3)
- {
- _raiseStartTime = null;
- _wristStartTime = null;
- _firstHandTriggered = false; // 重置状态
- return (int)WavingAction.RaiseHand; // 举手完成
- }
- else
- {
- return (int)WavingAction.Raising; // 举手中
- }
+ var duration = DateTime.Now - _raiseStartTime.Value;
+ if (duration.TotalSeconds >= 3)
+ {
+ _raiseStartTime = null;
+ _wristStartTime = null;
+ _firstHandTriggered = false; // 重置状态
+ return (int)WavingAction.RaiseHand; // 举手完成
}
else
{
- // 手放下,重置计时和状态
- _raiseStartTime = null;
- _wristStartTime = null;
- _firstHandTriggered = false;
+ return (int)WavingAction.Raising; // 举手中
}
}
- return (int)WavingAction.None;
+ else
+ {
+ // 手放下,重置状态
+ _raiseStartTime = null;
+ _wristStartTime = null;
+ _firstHandTriggered = false;
+ return (int)WavingAction.None;
+ }
}
///
diff --git a/Wpf_AiSportsMicrospace/Common/Utils.cs b/Wpf_AiSportsMicrospace/Common/Utils.cs
index f316c35..1dcaf0d 100644
--- a/Wpf_AiSportsMicrospace/Common/Utils.cs
+++ b/Wpf_AiSportsMicrospace/Common/Utils.cs
@@ -11,7 +11,8 @@ namespace Wpf_AiSportsMicrospace.Common
public static class Utils
{
private static MediaPlayer _bgPlayer;
- public static void PlayBackgroundMusic(string musicFileName)
+
+ public static void PlayBackgroundMusic(string musicFileName, bool loop)
{
string projectRoot = Path.Combine(AppContext.BaseDirectory, @"..\..\..");
string musicPath = Path.Combine(projectRoot, "Resources", "Music", musicFileName);
@@ -20,11 +21,6 @@ namespace Wpf_AiSportsMicrospace.Common
{
_bgPlayer = new MediaPlayer();
_bgPlayer.Volume = 0.5;
- _bgPlayer.MediaEnded += (s, e) =>
- {
- _bgPlayer.Position = TimeSpan.Zero; // 循环播放
- _bgPlayer.Play();
- };
}
else
{
@@ -33,7 +29,25 @@ namespace Wpf_AiSportsMicrospace.Common
_bgPlayer.Open(new Uri(musicPath, UriKind.Absolute));
_bgPlayer.Play();
+
+ if (loop)
+ {
+ // 循环播放 → 直接设置 MediaEnded 事件即可
+ _bgPlayer.MediaEnded -= BgPlayer_MediaEnded; // 避免重复绑定
+ _bgPlayer.MediaEnded += BgPlayer_MediaEnded;
+ }
+ else
+ {
+ _bgPlayer.MediaEnded -= BgPlayer_MediaEnded;
+ }
}
+
+ private static void BgPlayer_MediaEnded(object sender, EventArgs e)
+ {
+ _bgPlayer.Position = TimeSpan.Zero;
+ _bgPlayer.Play();
+ }
+
public static void StopBackgroundMusic()
{
if (_bgPlayer != null)
diff --git a/Wpf_AiSportsMicrospace/MyUserControl/SportUserItem.xaml.cs b/Wpf_AiSportsMicrospace/MyUserControl/SportUserItem.xaml.cs
index df49419..acb646d 100644
--- a/Wpf_AiSportsMicrospace/MyUserControl/SportUserItem.xaml.cs
+++ b/Wpf_AiSportsMicrospace/MyUserControl/SportUserItem.xaml.cs
@@ -57,6 +57,6 @@ namespace Wpf_AiSportsMicrospace.MyUserControl
}
public static readonly DependencyProperty ImageStateProperty =
- DependencyProperty.Register(nameof(NumberText), typeof(string), typeof(SportUserItem), new PropertyMetadata("0"));
+ DependencyProperty.Register(nameof(ImageState), typeof(string), typeof(SportUserItem), new PropertyMetadata("0"));
}
}
\ No newline at end of file
diff --git a/Wpf_AiSportsMicrospace/Resources/Music/comeon.mp3 b/Wpf_AiSportsMicrospace/Resources/Music/comeon.mp3
new file mode 100644
index 0000000..4c9af9c
Binary files /dev/null and b/Wpf_AiSportsMicrospace/Resources/Music/comeon.mp3 differ
diff --git a/Wpf_AiSportsMicrospace/Resources/Music/countdown_3.mp3 b/Wpf_AiSportsMicrospace/Resources/Music/countdown_3.mp3
new file mode 100644
index 0000000..ace3e4f
Binary files /dev/null and b/Wpf_AiSportsMicrospace/Resources/Music/countdown_3.mp3 differ
diff --git a/Wpf_AiSportsMicrospace/Resources/Music/raisehand.mp3 b/Wpf_AiSportsMicrospace/Resources/Music/raisehand.mp3
new file mode 100644
index 0000000..7d7fc93
Binary files /dev/null and b/Wpf_AiSportsMicrospace/Resources/Music/raisehand.mp3 differ
diff --git a/Wpf_AiSportsMicrospace/Views/CenterHome.xaml b/Wpf_AiSportsMicrospace/Views/CenterHome.xaml
index e53443c..b660f35 100644
--- a/Wpf_AiSportsMicrospace/Views/CenterHome.xaml
+++ b/Wpf_AiSportsMicrospace/Views/CenterHome.xaml
@@ -65,6 +65,7 @@
Source="/Resources/Img/Album/change_right.png"
HorizontalAlignment="right"
VerticalAlignment="Top"
+ MouseDown="GoNew"
Width="330"
Margin="0,100,0,0"
/>
diff --git a/Wpf_AiSportsMicrospace/Views/CenterHome.xaml.cs b/Wpf_AiSportsMicrospace/Views/CenterHome.xaml.cs
index c62678d..d24c21e 100644
--- a/Wpf_AiSportsMicrospace/Views/CenterHome.xaml.cs
+++ b/Wpf_AiSportsMicrospace/Views/CenterHome.xaml.cs
@@ -33,12 +33,6 @@ namespace Wpf_AiSportsMicrospace.Views
///
public partial class CenterHome : UserControl
{
- private IHumanPredictor _humanPredictor;
- private IObjectDetector _objectDetector;
- private HumanGraphicsRenderer _humanGraphicsRenderer;
- private WebcamClient _webcamClient;
- private ConcurrentQueue _frameQueue = new();
- private CancellationTokenSource _cts = new();
private SportOperate _sportOperate;
private Main _mainWin => Application.Current.MainWindow as Main;
@@ -59,15 +53,6 @@ namespace Wpf_AiSportsMicrospace.Views
public CenterHome()
{
InitializeComponent();
- //_humanPredictor = HumanPredictorFactory.Create(HumanPredictorType.SingleHigh);
- //_humanPredictor = HumanPredictorFactory.Create(HumanPredictorType.MultiMedium);
- //_objectDetector = ObjectDetectorFactory.CreateSportGoodsDetector();
- //_humanGraphicsRenderer = new HumanGraphicsRenderer();
- //_humanGraphicsRenderer.DrawLabel = false;
-
- //_sports = SportBase.GetSports();
- //_detectQueue = new SportDetectionQueue();
-
//string imgPath = Path.Combine(projectRoot, "Resources", "Img" , _nowSelect == "test" ? "test_img" : "play_img");
// 默认选中第1张
@@ -83,7 +68,9 @@ namespace Wpf_AiSportsMicrospace.Views
private void Window_Loaded(object sender, RoutedEventArgs e)
{
_mainWin.HumanFrameUpdated += OnHumanFrameUpdated;
- Utils.PlayBackgroundMusic("homeprojectselected.mp3");
+
+ Utils.PlayBackgroundMusic("homeprojectselected.mp3", true);
+
}
private void CenterHome_Unloaded(object sender, RoutedEventArgs e)
@@ -103,27 +90,26 @@ namespace Wpf_AiSportsMicrospace.Views
switch (wavingaction)
{
case (int)WavingAction.LeftWave: // 左手挥动
- coverFlow.SlideRight();
+ Application.Current.Dispatcher.BeginInvoke(() => coverFlow.SlideRight());
break;
case (int)WavingAction.RightWave: // 右手挥动
- coverFlow.SlideLeft();
+ Application.Current.Dispatcher.BeginInvoke(() => coverFlow.SlideLeft());
break;
case (int)WavingAction.FirstHand: // 举手开始
- coverFlow.StartSelectedProgress();
+ Application.Current.Dispatcher.BeginInvoke(() => coverFlow.StartSelectedProgress());
break;
case (int)WavingAction.Raising: // 举手中,实时更新进度
break;
case (int)WavingAction.RaiseHand: // 举手完成
- _cts.Cancel();
coverFlow.ProgressCompleted += CoverFlow_ProgressCompleted;
break;
default: // 没有动作 → 取消进度
- coverFlow.CancelSelectedProgress();
+ Application.Current.Dispatcher.BeginInvoke(() => coverFlow.CancelSelectedProgress());
break;
}
}
@@ -162,91 +148,6 @@ namespace Wpf_AiSportsMicrospace.Views
//}
}
- private void StartFrameProcessing()
- {
- Task.Run(async () =>
- {
- while (!_cts.Token.IsCancellationRequested)
- {
- if (_frameQueue.TryDequeue(out var frame))
- {
- try
- {
- ProcessFrame(frame);
- }
- catch (Exception ex)
- {
- Console.WriteLine("ProcessFrame error: " + ex.Message);
- }
- }
- else
- {
- await Task.Delay(5, _cts.Token);
- }
- }
- }, _cts.Token);
- }
-
-
- private void ProcessFrame(VideoFrame frame)
- {
- try
- {
- var buffer = frame.GetImageBuffer(ImageFormat.Jpeg).ToArray();
- var humanResult = _humanPredictor.Predicting(buffer, frame.Number);
-
- var humans = humanResult?.Humans?.ToList();
- if (humans == null || humans.Count == 0)
- return;
-
- var human = humans.FirstOrDefault();
-
- if (human == null) return;
-
- //检测挥手动作
- var wavingaction = _sportOperate.VerifyWavingAction(human);
-
- switch (wavingaction)
- {
- case (int)WavingAction.LeftWave: // 左手挥动
- Dispatcher.BeginInvoke(() => coverFlow.SlideRight());
- break;
-
- case (int)WavingAction.RightWave: // 右手挥动
- Dispatcher.BeginInvoke(() => coverFlow.SlideLeft());
- break;
-
- case (int)WavingAction.FirstHand: // 举手开始
- Dispatcher.BeginInvoke(() => coverFlow.StartSelectedProgress());
- break;
-
- case (int)WavingAction.Raising: // 举手中,实时更新进度
- break;
-
- case (int)WavingAction.RaiseHand: // 举手完成
- _cts.Cancel();
- coverFlow.ProgressCompleted += CoverFlow_ProgressCompleted;
- break;
-
- default: // 没有动作 → 取消进度
- Dispatcher.BeginInvoke(() => coverFlow.CancelSelectedProgress());
- break;
- }
- }
- catch (Exception ex)
- {
- Console.WriteLine("OnFrameExtracted error: " + ex.Message);
- }
- }
-
- public void Dispose()
- {
- _cts.Cancel();
- _webcamClient?.StopExtract();
- _webcamClient = null;
- _humanPredictor.Dispose();
- }
-
public void InitImg()
{
string projectRoot = Path.Combine(AppContext.BaseDirectory, @"..\..\..");
@@ -277,5 +178,12 @@ namespace Wpf_AiSportsMicrospace.Views
var newPage = new GroupJumpRope();
mainWin?.SwitchPage(newPage, true);
}
+
+ private void GoNew(object sender, MouseButtonEventArgs e)
+ {
+ var mainWin = Application.Current.MainWindow as Main;
+ var newPage = new GroupJumpRope();
+ mainWin?.SwitchPage(newPage, true);
+ }
}
}
diff --git a/Wpf_AiSportsMicrospace/Views/Home.xaml.cs b/Wpf_AiSportsMicrospace/Views/Home.xaml.cs
index ce527e8..f336520 100644
--- a/Wpf_AiSportsMicrospace/Views/Home.xaml.cs
+++ b/Wpf_AiSportsMicrospace/Views/Home.xaml.cs
@@ -34,36 +34,16 @@ namespace Wpf_AiSportsMicrospace
///
public partial class Home : UserControl
{
- private IHumanPredictor _humanPredictor;
- private HumanGraphicsRenderer _humanGraphicsRenderer;
- private WebcamClient _webcamClient;
- private ConcurrentQueue _frameQueue = new();
- private CancellationTokenSource _cts = new();
- private SportOperate _sportOperate;
public static Uri loadingImage = new Uri("/Resources/Img/Album/1.gif", UriKind.Relative);
-
private Main _mainWin => Application.Current.MainWindow as Main;
public Home()
{
InitializeComponent();
- //_humanPredictor = HumanPredictorFactory.Create(HumanPredictorType.SingleHigh);
- //_humanGraphicsRenderer = new HumanGraphicsRenderer();
- //_humanGraphicsRenderer.DrawLabel = false;
-
- //_sports = SportBase.GetSports();
- //_detectQueue = new SportDetectionQueue();
string projectRoot = Path.Combine(AppContext.BaseDirectory, @"..\..\..");
string albumPath = Path.Combine(projectRoot, "Resources", "Img", "Album");
- // 转换为 Uri
- //coverFlow.Images.Add(new Uri(Path.Combine(albumPath, "home_play.png")));
- //coverFlow.Images.Add(new Uri(Path.Combine(albumPath, "home_test.png")));
- //coverFlow.Images.Add(new Uri(Path.Combine(albumPath, "home_history.png")));
- //coverFlow.Images.Add(new Uri(Path.Combine(albumPath, "4.jpg")));
- //coverFlow.Images.Add(new Uri(Path.Combine(albumPath, "5.jpg")));
-
coverFlow.Images.Add(new CoverFlowItem { ImageUri = new Uri(Path.Combine(albumPath, "home_test.png")), ProgressColor1 = "#215bc7", ProgressColor2 = "#fc640e" });
coverFlow.Images.Add(new CoverFlowItem { ImageUri = new Uri(Path.Combine(albumPath, "home_play.png")), ProgressColor1 = "#e73d42", ProgressColor2 = "#fd8212" });
coverFlow.Images.Add(new CoverFlowItem { ImageUri = new Uri(Path.Combine(albumPath, "home_history.png")), ProgressColor1 = "#e73d42", ProgressColor2 = "#215bc7" });
@@ -73,7 +53,7 @@ namespace Wpf_AiSportsMicrospace
Loaded += Window_Loaded;
Unloaded += Home_Unloaded;
- //AnimationBehavior.SetSourceUri(LoadingImage, loadingImage);
+ Utils.PlayBackgroundMusic("homeprojectselected.mp3", true);
}
private void Home_Unloaded(object sender, RoutedEventArgs e)
@@ -90,61 +70,29 @@ namespace Wpf_AiSportsMicrospace
//检测挥手动作
var wavingaction = _mainWin.SportOperate.VerifyWavingAction(human);
- // 把低 8 位作为动作类型,高 8 位作为进度
- //int actionType = wavingaction & 0xFF;
- //int progress = (wavingaction >> 8) & 0xFF;
-
- //switch (wavingaction)
- //{
- // case (int)WavingAction.LeftWave: // 左手挥动
- // Application.Current.Dispatcher.BeginInvoke(() => coverFlow.SlideRight());
- // break;
-
- // case (int)WavingAction.RightWave: // 右手挥动
- // Application.Current.Dispatcher.BeginInvoke(() => coverFlow.SlideLeft());
- // break;
-
- // case (int)WavingAction.FirstHand: // 举手开始
- // Application.Current.Dispatcher.BeginInvoke(() => coverFlow.StartSelectedProgress());
- // break;
-
- // case (int)WavingAction.Raising: // 举手中,实时更新进度
- // break;
-
- // case (int)WavingAction.RaiseHand: // 举手完成
- // _cts.Cancel();
- // coverFlow.ProgressCompleted += CoverFlow_ProgressCompleted;
- // break;
-
- // default: // 没有动作 → 取消进度
- // Application.Current.Dispatcher.BeginInvoke(() => coverFlow.CancelSelectedProgress());
- // break;
- //}
-
switch (wavingaction)
{
case (int)WavingAction.LeftWave: // 左手挥动
- coverFlow.SlideRight();
+ Application.Current.Dispatcher.BeginInvoke(() => coverFlow.SlideRight());
break;
case (int)WavingAction.RightWave: // 右手挥动
- coverFlow.SlideLeft();
+ Application.Current.Dispatcher.BeginInvoke(() => coverFlow.SlideLeft());
break;
case (int)WavingAction.FirstHand: // 举手开始
- coverFlow.StartSelectedProgress();
+ Application.Current.Dispatcher.BeginInvoke(() => coverFlow.StartSelectedProgress());
break;
case (int)WavingAction.Raising: // 举手中,实时更新进度
break;
case (int)WavingAction.RaiseHand: // 举手完成
- _cts.Cancel();
coverFlow.ProgressCompleted += CoverFlow_ProgressCompleted;
break;
default: // 没有动作 → 取消进度
- coverFlow.CancelSelectedProgress();
+ Application.Current.Dispatcher.BeginInvoke(() => coverFlow.CancelSelectedProgress());
break;
}
}
@@ -153,22 +101,6 @@ namespace Wpf_AiSportsMicrospace
private void Window_Loaded(object sender, RoutedEventArgs e)
{
_mainWin.HumanFrameUpdated += OnHumanFrameUpdated;
-
- //_sportOperate = new SportOperate();
- //_webcamClient = _sportOperate.CreateRTSP();
-
- //_webcamClient.OnExtractFrame += frame =>
- //{
- // if (frame != null)
- // _frameQueue.Enqueue(frame);
- //};
- //_webcamClient.StartExtract();
-
- //StartFrameProcessing();
-
- //Utils.PlayBackgroundMusic("homeprojectselected.mp3");
-
- //coverFlow.ProgressCompleted += CoverFlow_ProgressCompleted;
}
private void CoverFlow_ProgressCompleted(CoverFlowItem item)
{
@@ -182,116 +114,7 @@ namespace Wpf_AiSportsMicrospace
{
Debug.WriteLine($"停止抽帧异常: {ex.Message}");
}
-
- // 解绑事件,防止重复触发
- //coverFlow.ProgressCompleted -= CoverFlow_ProgressCompleted;
-
- // 根据图片跳转新窗口
- //string uri = item.ImageUri.ToString();
-
- //if (uri.EndsWith("1.jpg"))
- // newWindow = new GroupJumpRope();
- //else if (uri.EndsWith("2.jpg"))
- // newWindow = new GroupJumpRope();
- //else if (uri.EndsWith("3.jpg"))
- // newWindow = new GroupJumpRope();
- // 找到主窗口,切换内容到 GroupJumpRopePage
}
- private void StartFrameProcessing()
- {
- Task.Run(async () =>
- {
- while (!_cts.Token.IsCancellationRequested)
- {
- if (_frameQueue.TryDequeue(out var frame))
- {
- try
- {
- ProcessFrame(frame);
- }
- catch (Exception ex)
- {
- Console.WriteLine("ProcessFrame error: " + ex.Message);
- }
- }
- else
- {
- await Task.Delay(5, _cts.Token);
- }
- }
- }, _cts.Token);
- }
-
- private void ProcessFrame(VideoFrame frame)
- {
- try
- {
- var buffer = frame.GetImageBuffer(ImageFormat.Jpeg).ToArray();
- var humanResult = _humanPredictor.Predicting(buffer, frame.Number);
-
- var humans = humanResult?.Humans?.ToList();
- if (humans == null || humans.Count == 0)
- return;
-
- //var human = humans
- // .Where(h =>
- // h.Keypoints.Any(kp => kp.Name == "left_ankle" && kp.X < 1020 && kp.Y > 900 && kp.Y < 1020) &&
- // h.Keypoints.Any(kp => kp.Name == "right_ankle" && kp.X > 750 && kp.Y > 900 && kp.Y < 1020)
- // )
- // .FirstOrDefault();
-
- var human = humans.FirstOrDefault();
-
- if (human == null) return;
-
- //检测挥手动作
- var wavingaction = _sportOperate.VerifyWavingAction(human);
-
- // 把低 8 位作为动作类型,高 8 位作为进度
- //int actionType = wavingaction & 0xFF;
- //int progress = (wavingaction >> 8) & 0xFF;
-
- switch (wavingaction)
- {
- case (int)WavingAction.LeftWave: // 左手挥动
- Application.Current.Dispatcher.BeginInvoke(() => coverFlow.SlideRight());
- break;
-
- case (int)WavingAction.RightWave: // 右手挥动
- Application.Current.Dispatcher.BeginInvoke(() => coverFlow.SlideLeft());
- break;
-
- case (int)WavingAction.FirstHand: // 举手开始
- Application.Current.Dispatcher.BeginInvoke(() => coverFlow.StartSelectedProgress());
- break;
-
- case (int)WavingAction.Raising: // 举手中,实时更新进度
- break;
-
- case (int)WavingAction.RaiseHand: // 举手完成
- _cts.Cancel();
- coverFlow.ProgressCompleted += CoverFlow_ProgressCompleted;
- break;
-
- default: // 没有动作 → 取消进度
- Application.Current.Dispatcher.BeginInvoke(() => coverFlow.CancelSelectedProgress());
- break;
- }
- }
- catch (Exception ex)
- {
- Console.WriteLine("OnFrameExtracted error: " + ex.Message);
- }
- }
-
- public void Dispose()
- {
- _cts.Cancel();
- _webcamClient?.StopExtract();
- _webcamClient = null;
- _humanPredictor.Dispose();
- }
-
//测试点击
private void GoNew(object sender, MouseButtonEventArgs e)
{
diff --git a/Wpf_AiSportsMicrospace/Views/JumpRope/GroupJumpRope.xaml b/Wpf_AiSportsMicrospace/Views/JumpRope/GroupJumpRope.xaml
index 2a90899..ff59387 100644
--- a/Wpf_AiSportsMicrospace/Views/JumpRope/GroupJumpRope.xaml
+++ b/Wpf_AiSportsMicrospace/Views/JumpRope/GroupJumpRope.xaml
@@ -6,8 +6,8 @@
xmlns:local="clr-namespace:Wpf_AiSportsMicrospace.MyUserControl"
mc:Ignorable="d" Height="1080" Width="1920" Loaded="UserControl_Loaded" Unloaded="UserControl_Unloaded">
-
-
+
+
-
+
+
+
diff --git a/Wpf_AiSportsMicrospace/Views/JumpRope/GroupJumpRope.xaml.cs b/Wpf_AiSportsMicrospace/Views/JumpRope/GroupJumpRope.xaml.cs
index 5c3b07f..81ec7ed 100644
--- a/Wpf_AiSportsMicrospace/Views/JumpRope/GroupJumpRope.xaml.cs
+++ b/Wpf_AiSportsMicrospace/Views/JumpRope/GroupJumpRope.xaml.cs
@@ -38,122 +38,291 @@ namespace Wpf_AiSportsMicrospace.Views
///
public partial class GroupJumpRope : UserControl
{
- private IHumanPredictor _humanPredictor;
- private IObjectDetector _objectDetector;
- private HumanGraphicsRenderer _humanGraphicsRenderer;
- private WebcamClient _webcamClient;
- private ConcurrentQueue _frameQueue = new();
- private CancellationTokenSource _cts = new();
- private SportOperate _sportOperate;
-
private List sports = new();
private List circleTexts = new();
private double[] circlePositionsX = { 0.07, 0.21, 0.36, 0.50, 0.64, 0.78, 0.92 };
private Main _mainWin => Application.Current.MainWindow as Main;
- ConfigService configService = new ConfigService();
+ private List<(double XNorm, double YNorm)> circlePositions = new();
+
+ private MediaPlayer _mediaPlayer = new MediaPlayer();
+ private bool IsGameStarted = false;
public GroupJumpRope()
{
InitializeComponent();
- //_humanPredictor = HumanPredictorFactory.Create(HumanPredictorType.MultiMedium);
- //_objectDetector = ObjectDetectorFactory.CreateSportGoodsDetector();
- //_humanGraphicsRenderer = new HumanGraphicsRenderer();
- //_humanGraphicsRenderer.DrawLabel = false;
-
-
Loaded += UserControl_Loaded;
Unloaded += UserControl_Unloaded;
+
}
- private void UserControl_Loaded(object sender, RoutedEventArgs e)
+ private async void UserControl_Loaded(object sender, RoutedEventArgs e)
{
- //DrawJumpRope3DPointsWithGlow();
-
DrawCirclesWithText();
-
- _mainWin.HumanFrameUpdated += OnHumanFrameUpdated;
-
- //_sportOperate = new SportOperate();
- //_webcamClient = _sportOperate.CreateRTSP();
-
- //_webcamClient.OnExtractFrame += frame =>
- //{
- // if (frame != null)
- // _frameQueue.Enqueue(frame);
- //};
- //_webcamClient.StartExtract();
-
- //StartFrameProcessing();
+ // 播放音乐
+ PlayMusic("raisehand.mp3");
}
+
private void UserControl_Unloaded(object sender, RoutedEventArgs e)
{
_mainWin.HumanFrameUpdated -= OnHumanFrameUpdated;
}
+ private void PlayMusic(string musicFileName)
+ {
+ // 获取项目根目录
+ string projectRoot = System.IO.Path.Combine(AppContext.BaseDirectory, @"..\..\..");
+ string musicPath = System.IO.Path.Combine(projectRoot, "Resources", "Music", musicFileName);
+
+ if (!File.Exists(musicPath))
+ {
+ Console.WriteLine($"音乐文件不存在: {musicPath}");
+ return;
+ }
+
+ _mediaPlayer.Open(new Uri(musicPath, UriKind.Absolute));
+
+ // 监听播放完成事件
+ _mediaPlayer.MediaEnded += MediaPlayer_MediaEnded;
+
+ _mediaPlayer.Play();
+ }
+
+ private void MediaPlayer_MediaEnded(object sender, EventArgs e)
+ {
+ // 音乐播放完成后的逻辑
+ Console.WriteLine("音乐播放完成!");
+
+ // 可在这里绑定抽帧事件
+ _mainWin.HumanFrameUpdated += OnHumanFrameUpdated;
+ }
private void OnHumanFrameUpdated(object sender, List humans)
{
try
{
- UpdateCircleCounts(humans);
- }
- catch (Exception ex)
- {
- Console.WriteLine("OnFrameExtracted error: " + ex.Message);
- }
-
- }
-
- private void StartFrameProcessing()
- {
- Task.Run(() =>
- {
- while (!_cts.Token.IsCancellationRequested)
+ if (!IsGameStarted)
{
- if (_frameQueue.TryDequeue(out var frame))
+ var human = humans.LastOrDefault();
+ if (human == null) return;
+
+ //检测挥手动作
+ var wavingaction = DetectRightHandRaise(human);
+
+ switch (wavingaction)
{
- ProcessFrame(frame);
- }
- else
- {
- Thread.Sleep(5);
+ case (int)WavingAction.FirstHand:
+ // 第一次举手,初始化倒计时
+ StartCountdown(3);
+ break;
+
+ case (int)WavingAction.Raising:
+ // 持续倒计时中
+ UpdateCountdown();
+ break;
+
+ case (int)WavingAction.RaiseHand:
+ // 举手完成,倒计时结束
+ FinishCountdown();
+ break;
+
+ default:
+ // 没检测到动作,重置倒计时显示
+ Utils.StopBackgroundMusic();
+ countdownText.Text = "3";
+ countdownText.Visibility = Visibility.Collapsed;
+ break;
}
}
- }, _cts.Token);
- }
-
- private void ProcessFrame(VideoFrame frame)
- {
- try
- {
- var buffer = frame.GetImageBuffer(ImageFormat.Jpeg).ToArray();
- var humanResult = _humanPredictor.Predicting(buffer, frame.Number);
-
- var humans = humanResult?.Humans?.ToList();
- if (humans == null || humans.Count == 0)
- return;
-
- UpdateCircleCounts(humans);
+ else
+ {
+ UpdateCircleCounts(humans);
+ }
}
catch (Exception ex)
{
Console.WriteLine("OnFrameExtracted error: " + ex.Message);
}
+
}
- public Human LocateHuman(List humans, double begin, double end, double frameWidth)
+ private int _currentCountdown = 3;
+ private DateTime _lastUpdateTime = DateTime.Now;
+ private void StartCountdown(int start = 3)
+ {
+ _currentCountdown = start;
+ countdownText.Text = _currentCountdown.ToString();
+ countdownText.Visibility = Visibility.Visible;
+ _lastUpdateTime = DateTime.Now;
+
+ Utils.PlayBackgroundMusic("countdown_3.mp3", false);
+ }
+
+ private void UpdateCountdown()
+ {
+ if ((DateTime.Now - _lastUpdateTime).TotalSeconds >= 1)
+ {
+ _lastUpdateTime = DateTime.Now;
+ _currentCountdown--;
+
+ if (_currentCountdown > 0)
+ {
+ countdownText.Text = _currentCountdown.ToString();
+ }
+ else
+ {
+ FinishCountdown();
+ }
+ }
+ }
+
+ private async void FinishCountdown()
+ {
+ // 举手完成,显示 ✔
+ countdownText.Text = "✔";
+ countdownText.Visibility = Visibility.Collapsed;
+ IsGameStarted = true;
+ // 播放背景音乐(循环)
+ Utils.PlayBackgroundMusic("homeprojectselected1.mp3", true);
+
+ // 启动60秒倒计时(独立任务)
+ StartGameCountdown(60);
+ }
+ private async void StartGameCountdown(int seconds)
+ {
+ countdownText.Visibility = Visibility.Visible;
+
+ for (int i = seconds; i >= 0; i--)
+ {
+ countdownText.Text = i.ToString();
+ await Task.Delay(1000); // 不阻塞主线程,计数逻辑继续执行
+ }
+
+ countdownText.Visibility = Visibility.Collapsed;
+ IsGameStarted = false;
+
+ // 倒计时完成后可以触发其他逻辑,例如停止音乐
+ Utils.StopBackgroundMusic();
+ }
+
+ private DateTime? _raiseStartTime;
+ private DateTime? _wristStartTime;
+ private bool _firstHandTriggered;
+ private int _lastCountdownSecond = 3;
+ private DateTime? _countdownStartTime;
+ public int DetectRightHandRaise(Human human)
+ {
+ var rightWrist = human.Keypoints.FirstOrDefault(k => k.Name == "right_wrist");
+ var rightElbow = human.Keypoints.FirstOrDefault(k => k.Name == "right_elbow");
+
+ if (rightWrist == null || rightElbow == null)
+ return (int)WavingAction.None;
+
+ double verticalRise = rightElbow.Y - rightWrist.Y; // 手腕高于肘部 → 正值
+ const double raiseThreshold = 60; // 举手阈值,可调整
+
+ if (verticalRise >= raiseThreshold)
+ {
+ // 初始化
+ _raiseStartTime ??= DateTime.Now;
+ _wristStartTime ??= DateTime.Now;
+ _countdownStartTime ??= DateTime.Now;
+
+ var wristDuration = DateTime.Now - _wristStartTime.Value;
+
+ // 第一次举手触发
+ if (!_firstHandTriggered && wristDuration.TotalSeconds >= 1)
+ {
+ _firstHandTriggered = true;
+ _lastCountdownSecond = 3;
+ _countdownStartTime = DateTime.Now;
+ return (int)WavingAction.FirstHand;
+ }
+
+ // 倒计时逻辑
+ if (_firstHandTriggered)
+ {
+ var elapsed = DateTime.Now - _countdownStartTime.Value;
+ int currentSecond = 3 - (int)Math.Floor(elapsed.TotalSeconds);
+
+ if (currentSecond != _lastCountdownSecond && currentSecond > 0)
+ {
+ _lastCountdownSecond = currentSecond;
+ // 你可以在这里触发倒计时提示(比如语音播报或 UI 更新)
+ Console.WriteLine($"倒计时:{currentSecond}");
+ return (int)WavingAction.Raising; // 可选:倒计时状态
+ }
+
+ // 举手完成
+ if (elapsed.TotalSeconds >= 3)
+ {
+ ResetRaiseState();
+ return (int)WavingAction.RaiseHand; // 举手完成
+ }
+
+ return (int)WavingAction.Raising; // 举手中
+ }
+
+ return (int)WavingAction.Raising;
+ }
+ else
+ {
+ // 手放下重置状态
+ ResetRaiseState();
+ return (int)WavingAction.None;
+ }
+ }
+
+ private void ResetRaiseState()
+ {
+ _raiseStartTime = null;
+ _wristStartTime = null;
+ _countdownStartTime = null;
+ _firstHandTriggered = false;
+ _lastCountdownSecond = 3;
+ }
+
+ public Human LocateHuman(List humans, double begin, double end, double frameWidth, double frameHeight, int circleIndex)
{
if (humans == null || humans.Count == 0)
return null;
+ double circleX = circlePositions[circleIndex].XNorm;
+ double circleY = circlePositions[circleIndex].YNorm;
+ double radiusNormX = 0.07; // X方向半径
+ double radiusNormY = 0.1; // Y方向半径
+
foreach (var hu in humans)
{
- var nose = hu.Keypoints.FirstOrDefault(k => k.Name == "right_ankle");
- if (nose == null)
+ var rightFoot = hu.Keypoints.FirstOrDefault(k => k.Name == "right_ankle");
+ var leftFoot = hu.Keypoints.FirstOrDefault(k => k.Name == "left_ankle");
+
+ if (rightFoot == null || leftFoot == null)
continue;
- // 使用 Canvas 宽度归一化
- double xNorm = nose.X / frameWidth;
+ double xRightNorm = rightFoot.X / frameWidth;
+ double xLeftNorm = leftFoot.X / frameWidth;
- if (xNorm >= begin && xNorm <= end)
- return hu;
+ // 前后出圈检测(Y方向)
+ double yRightNorm = rightFoot.Y / frameHeight;
+ double yLeftNorm = leftFoot.Y / frameHeight;
+
+ bool outOfCircle =
+ xRightNorm < (circleX - radiusNormX) || xRightNorm > (circleX + radiusNormX) ||
+ xLeftNorm < (circleX - radiusNormX) || xLeftNorm > (circleX + radiusNormX) ||
+ yRightNorm < (circleY - radiusNormY) || yRightNorm > (circleY + radiusNormY) ||
+ yLeftNorm < (circleY - radiusNormY) || yLeftNorm > (circleY + radiusNormY);
+
+ if (outOfCircle)
+ {
+ Application.Current.Dispatcher.BeginInvoke(() =>
+ {
+ circleTexts[circleIndex].Text = "您已出圈!";
+ });
+ continue;
+ }
+
+ // X 范围匹配 → 未出圈的人
+ if (xRightNorm >= (circleX - radiusNormX) && xRightNorm <= (circleX + radiusNormX))
+ {
+ return hu; // 返回给计数逻辑
+ }
}
return null;
@@ -170,7 +339,7 @@ namespace Wpf_AiSportsMicrospace.Views
double radius = 100;
// 每个圆的位置:X 和 Y 都归一化 0~1
- var circlePositions = new List<(double XNorm, double YNorm)>
+ circlePositions = new List<(double XNorm, double YNorm)>
{
(0.07, 0.58),
(0.21, 0.88 ),
@@ -187,8 +356,11 @@ namespace Wpf_AiSportsMicrospace.Views
double y = pos.YNorm * imgHeight;
// 绘制发光圆
+ //AddGlowEllipse(x, y, overlayCanvas);
+
AddGlowEllipse(x, y, overlayCanvas);
+
// 创建文本控件
var text = new TextBlock
{
@@ -199,6 +371,7 @@ namespace Wpf_AiSportsMicrospace.Views
TextAlignment = TextAlignment.Center,
Width = radius * 2
};
+
Canvas.SetLeft(text, x - radius);
Canvas.SetTop(text, y - radius - 25);
overlayCanvas.Children.Add(text);
@@ -209,7 +382,7 @@ namespace Wpf_AiSportsMicrospace.Views
int index = circleTexts.Count - 1;
sport.OnTicked += (count, times) =>
{
- Application.Current.Dispatcher.Invoke(() =>
+ Application.Current.Dispatcher.BeginInvoke(() =>
{
circleTexts[index].Text = count.ToString();
});
@@ -227,131 +400,25 @@ namespace Wpf_AiSportsMicrospace.Views
double begin = center - range;
double end = center + range;
- var human = LocateHuman(humans, begin, end, overlayCanvas.ActualWidth);
+ var human = LocateHuman(humans, begin, end, overlayCanvas.ActualWidth, overlayCanvas.ActualHeight, i);
if (human != null)
{
sports[i].Pushing(human);
}
}
}
- private void AddGlowEllipse(double centerX, double centerY, double radius, Canvas canvas)
- {
- var ellipse = new Ellipse
- {
- Width = radius * 2,
- Height = radius,
- Stroke = Brushes.Red,
- StrokeThickness = 3,
- Opacity = 0.7
- };
-
- Canvas.SetLeft(ellipse, centerX - radius);
- Canvas.SetTop(ellipse, centerY - radius / 2);
- canvas.Children.Add(ellipse);
- }
-
- private void DrawJumpRope3DPointsWithGlow()
- {
- configService.LoadAllConfigs();
-
- ConfigSet jumpRopeConfig;
-
- double imgWidth = 1920;
- double imgHeight = 1080;
-
- if (imgWidth <= 0 || imgHeight <= 0) return;
-
- overlayCanvas.Children.Clear();
- overlayCanvas.Width = imgWidth;
- overlayCanvas.Height = imgHeight;
-
- bool needSaveConfig = false;
-
- if (configService.ConfigDic.TryGetValue("rope-skipping", out jumpRopeConfig) || jumpRopeConfig.Points.Count == 0)
- {
- jumpRopeConfig = new ConfigSet { Name = "rope-skipping" };
- needSaveConfig = true;
-
- double yOffset = -0.10;
- double frontRadius = 85;
- double backRadius = 70;
-
- double frontY = 0.70 + yOffset;
- var frontPositions = new List<(double XNorm, double YNorm)>
- {
- (0.24, frontY),
- (0.48, frontY),
- (0.72, frontY)
- };
-
- foreach (var pos in frontPositions)
- {
- double x = pos.XNorm * imgWidth;
- double y = pos.YNorm * imgHeight;
-
- jumpRopeConfig.Points.Add(new PointConfig
- {
- X = x,
- Y = y,
- Radius = frontRadius,
- XNorm = pos.XNorm,
- YNorm = pos.YNorm
- });
-
- AddGlowEllipse(x, y, overlayCanvas);
- }
-
- double backY = 0.88 + yOffset;
- var backPositions = new List<(double XNorm, double YNorm)>
- {
- (0.10, backY),
- (0.35, backY),
- (0.60, backY),
- (0.88, backY)
- };
-
- foreach (var pos in backPositions)
- {
- double x = pos.XNorm * imgWidth;
- double y = pos.YNorm * imgHeight;
-
- jumpRopeConfig.Points.Add(new PointConfig
- {
- X = x,
- Y = y,
- Radius = backRadius,
- XNorm = pos.XNorm,
- YNorm = pos.YNorm
- });
-
- AddGlowEllipse(x, y, overlayCanvas);
- }
- }
- else
- {
- foreach (var point in jumpRopeConfig.Points)
- {
- double x = point.XNorm * imgWidth;
- double y = point.YNorm * imgHeight;
- AddGlowEllipse(x, y, overlayCanvas);
- }
- }
-
- if (needSaveConfig)
- {
- configService.ConfigDic[jumpRopeConfig.Name] = jumpRopeConfig;
- configService.SaveAllConfigs();
- }
- }
-
///
/// 添加带渐变光的圆圈(中心红色,边缘蓝色)
///
- private void AddGlowEllipse(double centerX, double centerY, Canvas canvas)
+ private Ellipse AddGlowEllipse(double centerX, double centerY, Canvas canvas)
{
double radius = 70; // 统一半径
- double flattenFactor = 0.5; // 统一扁平化比例
- double opacity = 0.8; // 统一透明度
+ double flattenFactor = 0.5; // 扁平化比例
+ double opacity = 0.8; // 透明度
+
+ // 默认颜色
+ Color cColor = Color.FromArgb(220, 255, 255, 0); // 黄
+ Color eColor = Color.FromArgb(180, 0, 0, 255); // 蓝
var ellipse = new Ellipse
{
@@ -366,10 +433,10 @@ namespace Wpf_AiSportsMicrospace.Views
RadiusY = 0.5,
GradientStops = new GradientStopCollection
{
- new GradientStop(Color.FromArgb(220, 255, 255, 0), 0.0), // 中心黄
- new GradientStop(Color.FromArgb(180, 255, 255, 0), 0.4), // 中间黄
- new GradientStop(Color.FromArgb(180, 0, 0, 255), 0.7), // 边缘蓝
- new GradientStop(Color.FromArgb(0, 0, 0, 255), 1.0) // 外部透明
+ new GradientStop(cColor, 0.0), // 中心颜色
+ new GradientStop(cColor, 0.4), // 内部颜色
+ new GradientStop(eColor, 0.7), // 边缘颜色
+ new GradientStop(Color.FromArgb(0, eColor.R, eColor.G, eColor.B), 1.0) // 外部透明
}
}
};
@@ -377,8 +444,17 @@ namespace Wpf_AiSportsMicrospace.Views
// 定位到中心
Canvas.SetLeft(ellipse, centerX - radius);
Canvas.SetTop(ellipse, centerY - (radius * flattenFactor) / 2);
+
canvas.Children.Add(ellipse);
+ return ellipse; // 返回 Ellipse 对象方便后续修改或移除
}
+
+ public bool IsInsideCircle(double px, double py, double cx, double cy, double r)
+ {
+ double dx = px - cx;
+ double dy = py - cy;
+ return dx * dx + dy * dy <= r * r;
+ }
}
}
diff --git a/Wpf_AiSportsMicrospace/Views/Main.xaml.cs b/Wpf_AiSportsMicrospace/Views/Main.xaml.cs
index d20bcf7..1c7998d 100644
--- a/Wpf_AiSportsMicrospace/Views/Main.xaml.cs
+++ b/Wpf_AiSportsMicrospace/Views/Main.xaml.cs
@@ -36,31 +36,34 @@ namespace Wpf_AiSportsMicrospace.Views
{
InitializeComponent();
- var options = new InferenceOptions()
- {
- GpuEnabled = true
- };
- Yztob.AiSports.Common.SportAppSettingService.Set("inferences", options);
+ //var options = new InferenceOptions()
+ //{
+ // GpuEnabled = true
+ //};
+ //Yztob.AiSports.Common.SportAppSettingService.Set("inferences", options);
- _humanPredictor = HumanPredictorFactory.Create(HumanPredictorType.MultiMedium);
+ _humanPredictor = HumanPredictorFactory.Create(HumanPredictorType.MultiLow);
_humanGraphicsRenderer = new HumanGraphicsRenderer();
_humanGraphicsRenderer.DrawLabel = false;
- SportOperate = new SportOperate();
- WebcamClient = SportOperate.CreateRTSP();
-
// 开始抽帧线程
StartFrameProcessing();
+ //WebcamClient.OnExtractFrame += this.ProcessFrame;
+ //WebcamClient.StartExtract();//开始抽帧
+
// 默认显示首页
MainContent.Content = new Home();
}
private void StartFrameProcessing()
{
+ SportOperate = new SportOperate();
+ WebcamClient = SportOperate.CreateRTSP();
+
WebcamClient.OnExtractFrame += frame =>
{
- if (frame != null)
- _frameQueue.Enqueue(frame);
+ //if (frame != null)
+ _frameQueue.Enqueue(frame);
};
WebcamClient.StartExtract();
@@ -76,7 +79,17 @@ namespace Wpf_AiSportsMicrospace.Views
else
{
// 避免 CPU 占满
- Thread.Sleep(5);
+ //Thread.Sleep(5);
+ //if (frame == null)
+ //{
+ // WebcamClient.OnExtractFrame += frame =>
+ // {
+ // //if (frame != null)
+ // _frameQueue.Enqueue(frame);
+ // };
+ //}
+ //else
+ Thread.Sleep(5);
}
}
}, _cts.Token);
@@ -94,10 +107,11 @@ namespace Wpf_AiSportsMicrospace.Views
return;
// 触发全局事件
- Application.Current.Dispatcher.Invoke(() =>
+ Application.Current.Dispatcher.BeginInvoke(() =>
{
HumanFrameUpdated?.Invoke(this, humans);
});
+
}
catch (Exception ex)
{
diff --git a/Wpf_AiSportsMicrospace/Wpf_AiSportsMicrospace.csproj b/Wpf_AiSportsMicrospace/Wpf_AiSportsMicrospace.csproj
index 61f30af..6e0c6e9 100644
--- a/Wpf_AiSportsMicrospace/Wpf_AiSportsMicrospace.csproj
+++ b/Wpf_AiSportsMicrospace/Wpf_AiSportsMicrospace.csproj
@@ -57,10 +57,13 @@
+
+
+
@@ -246,6 +249,12 @@
Always
+
+ Always
+
+
+ Always
+
Always
@@ -258,6 +267,9 @@
Always
+
+ Always
+