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 +