using Dto; using Emgu.CV.Flann; using Enum; using HandyControl.Controls; using SharpDX.Direct3D9; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Globalization; using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Media.Imaging; using System.Windows.Media.Media3D; using System.Windows.Shapes; using System.Windows.Threading; using Wpf_AiSportsMicrospace.Common; using Wpf_AiSportsMicrospace.Dto; using Wpf_AiSportsMicrospace.Enum; using Wpf_AiSportsMicrospace.MyUserControl; using Wpf_AiSportsMicrospace.Service; using WpfAnimatedGif; using Yztob.AiSports.Common; using Yztob.AiSports.Common.Implement; using Yztob.AiSports.Inferences.Abstractions; using Yztob.AiSports.Inferences.Things; using Yztob.AiSports.Postures.Sports; using Yztob.AiSports.Sensors.Abstractions; using Yztob.AiSports.Sensors.Things; namespace Wpf_AiSportsMicrospace.Views { /// /// GroupJumpRope.xaml 的交互逻辑 /// public partial class GroupJumpRope : UserControl { private Main _mainWin => Application.Current.MainWindow as Main; private MediaPlayer _mediaPlayer = new MediaPlayer(); List RankingItemList = new(); private readonly SportDetectionQueue _detectQueue; public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } private GameState _currentGameState = GameState.NotStarted; private readonly object _updateLock = new object(); private readonly Dictionary _jumpStatus = new Dictionary(); private GroupJumpRopeContext _groupJumpRopeContext; public GroupJumpRope() { InitializeComponent(); _detectQueue = new SportDetectionQueue(); Loaded += UserControl_Loaded; Unloaded += UserControl_Unloaded; _groupJumpRopeContext = new GroupJumpRopeContext(); } private async void UserControl_Loaded(object sender, RoutedEventArgs e) { DrawCirclesWithText(); // 播放音乐 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); string imgPath = System.IO.Path.Combine(projectRoot, "Resources", "Img", "提示图.png"); if (!File.Exists(musicPath)) { Console.WriteLine($"音乐文件不存在: {musicPath}"); return; } _mediaPlayer.Open(new Uri(musicPath, UriKind.Absolute)); ShowCenterTip(imgPath, TimeSpan.FromSeconds(3)); // 监听播放完成事件 _mediaPlayer.MediaEnded += MediaPlayer_MediaEnded; //_mainWin.WebcamClient.StartExtract(); _mediaPlayer.Play(); } private void ShowCenterTip(string imagePath, TimeSpan duration) { var tipImage = new Image { Source = new BitmapImage(new Uri(imagePath, UriKind.Absolute)), HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center, Opacity = 0, Margin = new Thickness(0, -100, 0, 0), }; // 增加图片的大小,调整比例 tipImage.Width = 1920 * 0.9; // 宽度为 Canvas 宽度的 90% tipImage.Height = 1080 * 0.6; // 高度为 Canvas 高度的 60% // 将图片添加到 Overlay Canvas userBox.Children.Add(tipImage); // 渐变出现动画 var fadeInAnimation = new DoubleAnimation { From = 0, To = 1, Duration = TimeSpan.FromSeconds(1.5) }; tipImage.BeginAnimation(UIElement.OpacityProperty, fadeInAnimation); // 定时移除,并且渐变消失 Task.Delay(duration).ContinueWith(_ => { Dispatcher.Invoke(() => { // 渐变消失动画 var fadeOutAnimation = new DoubleAnimation { From = 1, To = 0, Duration = TimeSpan.FromSeconds(1.5) }; tipImage.BeginAnimation(UIElement.OpacityProperty, fadeOutAnimation); // 完成后移除图片 fadeOutAnimation.Completed += (s, e) => { userBox.Children.Remove(tipImage); }; }); }); } private void MediaPlayer_MediaEnded(object sender, EventArgs e) { // 音乐播放完成后的逻辑 Console.WriteLine("音乐播放完成!"); // 可在这里绑定抽帧事件 _mainWin.HumanFrameUpdated += OnHumanFrameUpdated; } private void OnHumanFrameUpdated(object sender, List humans) { try { if (humans == null || humans.Count == 0) return; switch (_currentGameState) { case GameState.NotStarted: // 未开始 int rightWaving = DetectRightHandRaise(humans); if (rightWaving >= 3) { switch (rightWaving) { case (int)WavingAction.FirstHand: StartCountdown(3); break; case (int)WavingAction.Raising: UpdateCountdown(); break; case (int)WavingAction.RaiseHand: FinishCountdown(); _currentGameState = GameState.Running; // 倒计时完成 → 游戏开始 break; } } break; case GameState.Running: // 游戏进行中 UpdateCircleCounts(humans); // 可选:判断游戏结束条件,将状态切换到 Finished // if (CheckGameFinished()) _currentGameState = GameState.Finished; break; case GameState.Finished: // 游戏完成 int leftWaving = DetectLeftHandRaise(humans); if (leftWaving == 5) { _mainWin.HumanFrameUpdated -= OnHumanFrameUpdated; _mainWin.WebcamClient.StopExtract(); // 举左手逻辑,例如结束动画或退出 var newPage = new Home(); _mainWin?.SwitchPageWithMaskAnimation(newPage, true); } break; } } catch (Exception ex) { Console.WriteLine("OnFrameExtracted error: " + ex.Message); } } private int _currentCountdown = 3; private DateTime _lastUpdateTime = DateTime.Now; private void StartCountdown(int start = 3) { _mainWin.ShowCountDownAnimation(); //_currentCountdown = start; //countdownText.Text = _currentCountdown.ToString(); //countdownGrid.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 = "GO!"; countdownGrid.Visibility = Visibility.Hidden; // 播放背景音乐(循环) Utils.PlayBackgroundMusic("homeprojectselected1.mp3", true); // 启动60秒倒计时(独立任务) StartGameCountdown(60); } private async void StartGameCountdown(int seconds) { countdownGrid.Visibility = Visibility.Visible; for (int i = seconds; i >= 0; i--) { countdownText.Text = i.ToString(); await Task.Delay(1000); } countdownGrid.Visibility = Visibility.Hidden; _groupJumpRopeContext.UserList.ForEach(x => { x.ImageState = "1"; }); _mainWin.HumanFrameUpdated -= OnHumanFrameUpdated; _mainWin.WebcamClient.StopExtract(); RankingItemList = _groupJumpRopeContext.UpdateRankList(); ShowRankingBoard(RankingItemList); Utils.StopBackgroundMusic(); _currentGameState = GameState.Finished; _mainWin.HumanFrameUpdated += OnHumanFrameUpdated; _mainWin.WebcamClient.StartExtract(); } private DateTime? _raiseStartTime; private bool _firstHandTriggered; private int _lastCountdownSecond = 3; private DateTime? _countdownStartTime; public int DetectRightHandRaise(List humans) { if (humans == null || humans.Count == 0) return (int)WavingAction.None; foreach (var human in humans) { if (human?.Keypoints == null) continue; // --- 筛选右脚踝坐标 --- var rightAnkle = human.Keypoints.FirstOrDefault(k => k.Name == "right_ankle"); if (rightAnkle == null) continue; double xNorm = rightAnkle.X / 1920; double yNorm = rightAnkle.Y / 1080; // 仅检测中心区域内的人 if (!(xNorm >= 0.44 && xNorm <= 0.57 && yNorm >= 0.81)) continue; // --- 获取右臂关键点 --- var rightWrist = human.Keypoints.FirstOrDefault(k => k.Name == "right_wrist"); var rightElbow = human.Keypoints.FirstOrDefault(k => k.Name == "right_elbow"); const double raiseThreshold = 60; // 举手阈值 const double holdDuration = 1; // 持续时间(秒) const int countdownSeconds = 3; // 倒计时长度 bool handRaised = false; if (rightWrist != null && rightElbow != null) { double verticalRise = rightElbow.Y - rightWrist.Y; handRaised = verticalRise >= raiseThreshold; } // ---------- 第一步:持续举手触发倒计时 ---------- if (!_firstHandTriggered) { if (handRaised) { _raiseStartTime ??= DateTime.Now; var holdElapsed = (DateTime.Now - _raiseStartTime.Value).TotalSeconds; if (holdElapsed >= holdDuration) { // 举手达到指定时间,启动倒计时 _firstHandTriggered = true; _countdownStartTime = DateTime.Now; _lastCountdownSecond = countdownSeconds; return (int)WavingAction.FirstHand; } } else { // 手放下,重置举手开始时间 _raiseStartTime = DateTime.Now; } continue; // 本人未触发,检测下一个人 } // ---------- 第二步:倒计时逻辑 ---------- var countdownElapsed = (DateTime.Now - _countdownStartTime.Value).TotalSeconds; int currentSecond = countdownSeconds - (int)Math.Floor(countdownElapsed); if (currentSecond > 0 && currentSecond != _lastCountdownSecond) { _lastCountdownSecond = currentSecond; Console.WriteLine($"倒计时:{currentSecond}"); return (int)WavingAction.Raising; // 倒计时中 } if (countdownElapsed >= countdownSeconds) { ResetRaiseState(); return (int)WavingAction.RaiseHand; // 举手完成 } return (int)WavingAction.Raising; } // 没有任何中心区域内的人举手 return (int)WavingAction.None; } public int DetectLeftHandRaise(List humans) { if (humans == null || humans.Count == 0) return (int)WavingAction.None; foreach (var human in humans) { if (human?.Keypoints == null) continue; // --- 筛选右脚踝坐标 --- var rightAnkle = human.Keypoints.FirstOrDefault(k => k.Name == "right_ankle"); if (rightAnkle == null) continue; double xNorm = rightAnkle.X / 1920; double yNorm = rightAnkle.Y / 1080; // 仅检测中心区域内的人 if (!(xNorm >= 0.44 && xNorm <= 0.57 && yNorm >= 0.81)) continue; // 获取左手关键点 var leftWrist = human.Keypoints.FirstOrDefault(k => k.Name == "left_wrist"); var leftElbow = human.Keypoints.FirstOrDefault(k => k.Name == "left_elbow"); if (leftWrist == null || leftElbow == null) continue; const double raiseThreshold = 60; // 举手阈值 // 判断左手是否举起 double verticalRise = leftElbow.Y - leftWrist.Y; if (verticalRise >= raiseThreshold) { return (int)WavingAction.RaiseHand; // 一旦检测到左手举起,立即返回 } } return (int)WavingAction.None; // 没有检测到举手 } private void ResetRaiseState() { _raiseStartTime = null; _countdownStartTime = null; _firstHandTriggered = false; _lastCountdownSecond = 3; } private void DrawCirclesWithText() { userBox.Children.Clear(); _groupJumpRopeContext.Sports.Clear(); _groupJumpRopeContext.UserList.Clear(); _groupJumpRopeContext.UserNumberList.Clear(); double imgWidth = userBox.ActualWidth; double imgHeight = userBox.ActualHeight; double radius = 100; for (int i = 0; i < _groupJumpRopeContext.CirclePositions.Count; i++) { var pos = _groupJumpRopeContext.CirclePositions[i]; double x = pos.XNorm * imgWidth; double y = pos.YNorm * imgHeight; // 绘制发光圆 var userItem = AddUserItem(x, y, i); // 绑定运动对象 var sport = SportBase.Create("rope-skipping"); int indexCopy = i; var currentItem = userItem; // 订阅事件 sport.OnTicked += (count, times) => { // 更新UI userItem.NumberText = count.ToString(); // 更新数字源 _groupJumpRopeContext.UserNumberList[indexCopy] = count.ToString(); // 改变状态为“跳绳中” if (userItem.ImageState != "2") userItem.ImageState = "2"; }; sport.Start(); _groupJumpRopeContext.Sports.Add(sport); } } private void UpdateCircleCounts(List humans) { double radiusNormX = 0.07; double radiusNormY = 0.14; for (int i = 0; i < _groupJumpRopeContext.CirclePositions.Count; i++) { var circleX = _groupJumpRopeContext.CirclePositions[i].XNorm; var circleY = _groupJumpRopeContext.CirclePositions[i].YNorm; Human humanInCircle = null; // 找圈内的人 foreach (var hu in humans) { 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; double xRightNorm = rightFoot.X / userBox.ActualWidth; double xLeftNorm = leftFoot.X / userBox.ActualWidth; double yRightNorm = rightFoot.Y / userBox.ActualHeight; double yLeftNorm = leftFoot.Y / userBox.ActualHeight; 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) { humanInCircle = hu; break; // 每圈只处理一个人 } } // 根据是否有人和跳绳数字判断状态 bool hasHuman = humanInCircle != null; lock (_updateLock) { _groupJumpRopeContext.UserList[i].ImageState = GetJumpState(i, hasHuman); } // 推送计数 if (hasHuman) _groupJumpRopeContext.Sports[i].Pushing(humanInCircle); } } private string GetJumpState(int circleIndex, bool humanInCircle) { if (!humanInCircle) { // 无人 → 出圈 return "3"; } string currentNumber = _groupJumpRopeContext.UserNumberList[circleIndex]; if (!_jumpStatus.ContainsKey(circleIndex)) { _jumpStatus[circleIndex] = (currentNumber, DateTime.Now, "1"); // 初始状态先为停止 return "1"; } var (lastNumber, lastChangeTime, lastState) = _jumpStatus[circleIndex]; if (currentNumber != lastNumber) { // 数字变化 → 跳绳中 _jumpStatus[circleIndex] = (currentNumber, DateTime.Now, "2"); return "2"; } // 数字未变化,判断是否超过 2 秒 double elapsed = (DateTime.Now - lastChangeTime).TotalSeconds; if (elapsed >= 0.8) { // 超过 2 秒未变化 → 停止 _jumpStatus[circleIndex] = (currentNumber, lastChangeTime, "1"); return "1"; } // 维持上次状态 return lastState; } private SportUserItem AddUserItem(double centerX, double centerY, int index) { var userItem = new SportUserItem(); userItem.Width = 270; userItem.Height = 560; userItem.DisplayText = _groupJumpRopeContext.UseNameList[index]; userItem.VerticalAlignment = VerticalAlignment.Top; userItem.HorizontalAlignment = HorizontalAlignment.Left; userItem.ImageState = "1"; //userItem.Margin = new Thickness(centerX - (index == 0 ? 80 : index == 6 ? 190 : 135), centerY - 540, 0, 0); userItem.Margin = new Thickness(centerX - 265, centerY - 500, 0, 0); userBox.Children.Add(userItem); _groupJumpRopeContext.UserList.Add(userItem); _groupJumpRopeContext.UserNumberList.Add("0"); return userItem; // } public void ShowRankingBoard(List list) { var scoreGrid = FindName("ScoreGrid") as Grid; name1.Text = list[0].Name; name2.Text = list[1].Name; name3.Text = list[2].Name; number1.Text = list[0].Number.ToString(); number2.Text = list[1].Number.ToString(); number3.Text = list[2].Number.ToString(); foreach (var item in list) { var grid = new Grid { Width = 1344, Height = 71, HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Top, Margin = new Thickness(0, 69 * item.Rank, 0, 0) }; var border = new Border { Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#fff")) }; grid.Children.Add(border); var rankText = new TextBlock { Text = item.Rank.ToString(), FontSize = 24, FontWeight = FontWeights.Bold, Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#999999")), HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Center, TextAlignment = TextAlignment.Center, Width = 200 }; grid.Children.Add(rankText); var positionText = new TextBlock { Text = item.Name, FontSize = 24, FontWeight = FontWeights.Bold, Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#999999")), HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center }; grid.Children.Add(positionText); var scoreText = new TextBlock { Text = item.Number.ToString(), FontSize = 24, FontWeight = FontWeights.Bold, Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#999999")), HorizontalAlignment = HorizontalAlignment.Right, VerticalAlignment = VerticalAlignment.Center, TextAlignment = TextAlignment.Center, Width = 200 }; grid.Children.Add(scoreText); ScoreGrid.Children.Add(grid); } showElement("RankingGrid"); //var rankingGrid = FindName("RankingGrid") as Grid; //if (rankingGrid == null) return; //rankingGrid.Visibility = Visibility.Visible; //var fadeIn = new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(600)); //rankingGrid.BeginAnimation(UIElement.OpacityProperty, fadeIn); } /* * 控制元素隐藏(渐隐动画 * time: 动画持续时间(毫秒) * name: 元素名称 */ public void HideElement(string name , int time) { var Element = FindName(name) as Grid; if (Element == null) return; var fadeOut = new DoubleAnimation(1, 0, TimeSpan.FromMilliseconds(time)); fadeOut.Completed += (s, e) => Element.Visibility = Visibility.Collapsed; Element.BeginAnimation(UIElement.OpacityProperty, fadeOut); } /* * 控制元素限时(渐隐动画 * time: 动画持续时间(毫秒) * name: 元素名称 */ public void showElement(string name, int time = 600 ) { var Element = FindName(name) as FrameworkElement; if (Element == null) return; Element.Visibility = Visibility.Visible; var fadeIn = new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(time)); Element.BeginAnimation(UIElement.OpacityProperty, fadeIn); } } }