From 0c8107299429e72333e327a8de9ae5d81d897e2b Mon Sep 17 00:00:00 2001 From: tanglong <842690096@qq.com> Date: Sun, 12 Oct 2025 16:13:44 +0800 Subject: [PATCH] =?UTF-8?q?=E5=87=BA=E5=9C=88=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Wpf_AiSportsMicrospace/Common/SportOperate.cs | 99 ++-- Wpf_AiSportsMicrospace/Views/CenterHome.xaml | 1 + .../Views/CenterHome.xaml.cs | 116 +---- Wpf_AiSportsMicrospace/Views/Home.xaml.cs | 151 +----- .../Views/JumpRope/GroupJumpRope.xaml | 11 + .../Views/JumpRope/GroupJumpRope.xaml.cs | 445 ++++++++++-------- Wpf_AiSportsMicrospace/Views/Main.xaml.cs | 30 +- 7 files changed, 360 insertions(+), 493 deletions(-) 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/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..0be02e2 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张 @@ -103,27 +88,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 +146,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 +176,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..f5f7cac 100644 --- a/Wpf_AiSportsMicrospace/Views/Home.xaml.cs +++ b/Wpf_AiSportsMicrospace/Views/Home.xaml.cs @@ -90,61 +90,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 +121,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) { @@ -197,101 +149,6 @@ namespace Wpf_AiSportsMicrospace // 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 959f92a..d705809 100644 --- a/Wpf_AiSportsMicrospace/Views/JumpRope/GroupJumpRope.xaml +++ b/Wpf_AiSportsMicrospace/Views/JumpRope/GroupJumpRope.xaml @@ -11,6 +11,17 @@ + + diff --git a/Wpf_AiSportsMicrospace/Views/JumpRope/GroupJumpRope.xaml.cs b/Wpf_AiSportsMicrospace/Views/JumpRope/GroupJumpRope.xaml.cs index 5c3b07f..a055d15 100644 --- a/Wpf_AiSportsMicrospace/Views/JumpRope/GroupJumpRope.xaml.cs +++ b/Wpf_AiSportsMicrospace/Views/JumpRope/GroupJumpRope.xaml.cs @@ -38,122 +38,276 @@ 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 DispatcherTimer _countdownTimer; + private int _countdownValue; + private bool _isCountingDown = false; + private SportOperate sportOperate; + private List<(double XNorm, double YNorm)> circlePositions = new(); + + 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; + + _countdownTimer = new DispatcherTimer(); + _countdownTimer.Interval = TimeSpan.FromSeconds(1); + _countdownTimer.Tick += CountdownTimer_Tick; + + sportOperate = new SportOperate(); } private 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(); } private void UserControl_Unloaded(object sender, RoutedEventArgs e) { _mainWin.HumanFrameUpdated -= OnHumanFrameUpdated; } + private void CountdownTimer_Tick(object sender, EventArgs e) + { + if (_countdownValue > 0) + { + countdownText.Text = _countdownValue.ToString(); + _countdownValue--; + } + else + { + _countdownTimer.Stop(); + countdownText.Visibility = Visibility.Collapsed; + _isCountingDown = false; + IsGameStarted = true; // 倒计时结束,游戏正式开始 + } + } + private void StartCountdown() + { + if (_isCountingDown) return; // 避免重复启动 + _isCountingDown = true; + _countdownValue = 3; // 从3开始倒计时 + countdownText.Visibility = Visibility.Visible; + _countdownTimer.Start(); + } + + private DateTime? _waitStartTime = null; 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: + // 没检测到动作,重置倒计时显示 + 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; + } + + private void UpdateCountdown() + { + if ((DateTime.Now - _lastUpdateTime).TotalSeconds >= 1) + { + _lastUpdateTime = DateTime.Now; + _currentCountdown--; + + if (_currentCountdown > 0) + { + countdownText.Text = _currentCountdown.ToString(); + } + else + { + FinishCountdown(); + } + } + } + + private void FinishCountdown() + { + countdownText.Text = "✔"; + countdownText.Visibility = Visibility.Collapsed; + + IsGameStarted = true; + + // 你也可以在这里触发其他动作,例如: + // 播放音效、触发事件、执行下一步逻辑 + } + + + 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)) + { + IsGameStarted = true; + return hu; // 返回给计数逻辑 + } } return null; @@ -170,7 +324,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 +341,11 @@ namespace Wpf_AiSportsMicrospace.Views double y = pos.YNorm * imgHeight; // 绘制发光圆 + //AddGlowEllipse(x, y, overlayCanvas); + AddGlowEllipse(x, y, overlayCanvas); + // 创建文本控件 var text = new TextBlock { @@ -199,6 +356,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 +367,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 +385,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 +418,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 +429,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..1519121 100644 --- a/Wpf_AiSportsMicrospace/Views/Main.xaml.cs +++ b/Wpf_AiSportsMicrospace/Views/Main.xaml.cs @@ -42,25 +42,28 @@ namespace Wpf_AiSportsMicrospace.Views }; 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) {