diff --git a/Wpf_AiSportsMicrospace/App.xaml b/Wpf_AiSportsMicrospace/App.xaml index 89298c0..a5b322f 100644 --- a/Wpf_AiSportsMicrospace/App.xaml +++ b/Wpf_AiSportsMicrospace/App.xaml @@ -29,11 +29,6 @@ - - - diff --git a/Wpf_AiSportsMicrospace/Resources/Img/test_img/jump_long/jumping.gif b/Wpf_AiSportsMicrospace/Resources/Img/test_img/jump_long/jumping.gif new file mode 100644 index 0000000..4ff993a Binary files /dev/null and b/Wpf_AiSportsMicrospace/Resources/Img/test_img/jump_long/jumping.gif differ diff --git a/Wpf_AiSportsMicrospace/Resources/Img/test_img/jump_long/right_hand.gif b/Wpf_AiSportsMicrospace/Resources/Img/test_img/jump_long/right_hand.gif new file mode 100644 index 0000000..fcd87f3 Binary files /dev/null and b/Wpf_AiSportsMicrospace/Resources/Img/test_img/jump_long/right_hand.gif differ diff --git a/Wpf_AiSportsMicrospace/Resources/Img/test_img/jump_long/rotate_img.png b/Wpf_AiSportsMicrospace/Resources/Img/test_img/jump_long/rotate_img.png new file mode 100644 index 0000000..2b1b145 Binary files /dev/null and b/Wpf_AiSportsMicrospace/Resources/Img/test_img/jump_long/rotate_img.png differ diff --git a/Wpf_AiSportsMicrospace/Views/JumpLong/StandingLeap.xaml b/Wpf_AiSportsMicrospace/Views/JumpLong/StandingLeap.xaml index 8a098bf..edaf4f7 100644 --- a/Wpf_AiSportsMicrospace/Views/JumpLong/StandingLeap.xaml +++ b/Wpf_AiSportsMicrospace/Views/JumpLong/StandingLeap.xaml @@ -12,18 +12,25 @@ - - - - - + + + + + - - - - + + + + + + + + + + + + + + + - - + + @@ -67,17 +93,21 @@ - + - - + + + + + + diff --git a/Wpf_AiSportsMicrospace/Views/JumpLong/StandingLeap.xaml.cs b/Wpf_AiSportsMicrospace/Views/JumpLong/StandingLeap.xaml.cs index ef5a214..8eeabb1 100644 --- a/Wpf_AiSportsMicrospace/Views/JumpLong/StandingLeap.xaml.cs +++ b/Wpf_AiSportsMicrospace/Views/JumpLong/StandingLeap.xaml.cs @@ -46,6 +46,9 @@ namespace Views.JumpLong private ApparatusLongJumpDelineation _measureApparatus; private MediaPlayer _mediaPlayer = new MediaPlayer(); private readonly SportDetectionQueue _detectQueue; + + private int _sportType = 0; // 0:未开始 1:运动中 2:运动结束展示排行榜 + #endregion public StandingLeap() @@ -53,7 +56,6 @@ namespace Views.JumpLong InitializeComponent(); Loaded += UserControl_Loaded; Unloaded += UserControl_Unloaded; - _humanPredictor = HumanPredictorFactory.Create(HumanPredictorType.SingleHigh); _humanGraphicsRenderer = new HumanGraphicsRenderer(); _humanGraphicsRenderer.DrawLabel = false; @@ -62,19 +64,13 @@ namespace Views.JumpLong _sport = SportBase.Create("standing-long-jump"); _sport.OnTicked += this.OnSportTick; - //if (_sport?.IsCounting == true) - _sport.Start(); - + _mainWin.HumanFrameUpdated += this.OnFrameUpdated; _measureApparatus = new ApparatusLongJumpDelineation(); _measureApparatus.MeasureLength = 300; var _isCalibrated = this.ReactangleCalibrating(1920, 1080); _sport.MeasureApparatus = _measureApparatus; - _detectQueue.Sport = _sport; - _detectQueue.Start(); - // 开始抽帧线程 - _mainWin.FacialRecognitionEvent += HumanPredicting; } private async void UserControl_Loaded(object sender, RoutedEventArgs e) @@ -83,6 +79,47 @@ namespace Views.JumpLong PlayMusic("raisehand.mp3"); } + + //举手检测 + private void OnFrameUpdated(object sender, List humans) + { + try + { + if (humans == null || humans.Count == 0) return; + + //举手检测 未开始 :退出& 开始 运动中:不检测 2.结束:继续测试 & 退出 + if (_sportType != 1) + { + //左手返回 + int leftWaving = DetectLeftHandRaise(humans); + if (leftWaving == 5) + { + _mainWin.HumanFrameUpdated -= this.OnFrameUpdated; + _mainWin.WebcamClient.StopExtract(); + + // 举左手逻辑,例如结束动画或退出 + var newPage = new Home(); + _mainWin?.SwitchPageWithMaskAnimation(newPage, true); + return; // 提前退出 + } + + //右手判断 + int rightWaving = DetectRightHandRaise(humans); + if (rightWaving >= 3 && _sportType == 0) //举手完成 开始跳远检测 + { + Utils.PlayBackgroundMusic("countdown_3.mp3", false); + _sportType = 1; + StartSport(); + } + } + } + catch (Exception ex) + { + Console.WriteLine("OnFrameExtracted error: " + ex.Message); + } + } + + private void UserControl_Unloaded(object sender, RoutedEventArgs e) { _sport.Stop(); @@ -110,6 +147,7 @@ namespace Views.JumpLong } } + //播放音乐 private void PlayMusic(string musicFileName) { // 获取项目根目录 @@ -158,6 +196,7 @@ namespace Views.JumpLong }); }); + } private void MediaPlayer_MediaEnded(object sender, EventArgs e) { @@ -168,24 +207,29 @@ namespace Views.JumpLong private void OnSportTick(int counts, int times) { var ts = TimeSpan.FromSeconds(times); - Dispatcher.BeginInvoke(() => + _ = Dispatcher.BeginInvoke(() => { //sportCounts.Content = _sport.GetFormatCounts(); //counts.ToString(); //sportTimes.Content = _sport.GetFormatTimes();//ts.ToString(@"mm\'ss\"""); - count.Text = _sport.GetFormatCounts(); - time.Text = _sport.GetFormatTimes(); - ShowRope(); - //触发停止 - if (!_sport.IsCounting) + var counts = _sport.GetFormatCounts(); + if (double.Parse(counts[..^1]) > 0 && _sportType == 1) //正向跳远时候&&处于测试阶段时候触发更新 { - _sport.Stop(); - _sport.Start(); + _sport.Stop();//关闭运动 + _mainWin.FacialRecognitionEvent -= this.HumanPredicting; + count.Text = counts; + time.Text = _sport.GetFormatTimes(); + ShowRope(); + if (!_sport.IsCounting) + { + _sport.Stop(); + _sport.Start(); + } } }); } //展示成绩 - private void ShowRope() + private async void ShowRope() { // 渐变出现动画 var fadeInAnimation = new DoubleAnimation @@ -194,18 +238,56 @@ namespace Views.JumpLong To = 1, Duration = TimeSpan.FromSeconds(1.5) }; + var fadeInAnimation1 = new DoubleAnimation + { + From = 1, + To = 0, + Duration = TimeSpan.FromSeconds(1.5) + }; + //videoImage.Opacity = 0; + //centerImg1.Opacity = 1; + //centerImg2.Opacity = 1; + //centerImg3.Opacity = 1; + _sportType = 2; RankingGrid.BeginAnimation(UIElement.OpacityProperty, fadeInAnimation); - - + //_mainWin.WebcamClient.OnExtractFrame -= this.OnFrameExtracted; + for (int i = 12; i >= 0; i--) + { + countDown.Text = i.ToString() + "s"; + if (i == 1) + { + RankingGrid.BeginAnimation(UIElement.OpacityProperty, fadeInAnimation1); + _sportType = 0; + PlayMusic("raisehand.mp3"); + } + await Task.Delay(1000); + } + // 关闭跳远抽帧线程 } - private bool ReactangleCalibrating(int frameWidth, int frameHeight) + + //关闭成绩展示 显示跳远页面 + private void closRope() + { + //关闭动画 + //var fadeInAnimation = new DoubleAnimation + //{ + // From = 1, + // To = 0, + // Duration = TimeSpan.FromSeconds(1) + //}; + // RankingGrid.BeginAnimation(UIElement.OpacityProperty, fadeInAnimation); + RankingGrid.Opacity = 0; + _sportType = 0; + PlayMusic("raisehand.mp3"); + } + private bool ReactangleCalibrating(int frameWidth, int frameHeight) { var mainWin = Application.Current.MainWindow as Main; var app = (App)Application.Current; using var scope = App.AppHost.Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); - var points = db.JumpLongPoints.FirstOrDefault(); + var points = db.JumpLongPoints.FirstOrDefault(); var boxes = new List() { new BoundingBox(){ @@ -256,5 +338,245 @@ namespace Views.JumpLong return _measureApparatus.Calibrating(boxes); } + + + 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; + + + var app = (App)Application.Current; + using var scope = App.AppHost.Services.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + var points = db.JumpLongPoints.FirstOrDefault(); + + // 检测起跳区域的人 + + var isinx = (rightAnkle.X < points.TopPoint3.X && rightAnkle.X > points.TopPoint2.X) || (rightAnkle.X < points.BottomPoint3.X && rightAnkle.X > points.BottomPoint2.X); + if (points.TopPoint3.X < points.TopPoint2.X) + { + isinx = (rightAnkle.X > points.TopPoint3.X && rightAnkle.X < points.TopPoint2.X) || (rightAnkle.X > points.BottomPoint3.X && rightAnkle.X < points.BottomPoint2.X); + } + var isiny = (rightAnkle.Y > points.TopPoint3.Y && rightAnkle.X < points.BottomPoint3.Y) || (rightAnkle.Y < points.TopPoint2.Y && rightAnkle.X > points.BottomPoint2.Y); + if (!isinx || !isiny) 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 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; + + + var app = (App)Application.Current; + using var scope = App.AppHost.Services.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + var points = db.JumpLongPoints.FirstOrDefault(); + + // 检测起跳区域的人 + + var isinx = (rightAnkle.X < points.TopPoint3.X && rightAnkle.X > points.TopPoint2.X) || (rightAnkle.X < points.BottomPoint3.X && rightAnkle.X > points.BottomPoint2.X); + if (points.TopPoint3.X < points.TopPoint2.X) + { + isinx = (rightAnkle.X > points.TopPoint3.X && rightAnkle.X < points.TopPoint2.X) || (rightAnkle.X > points.BottomPoint3.X && rightAnkle.X < points.BottomPoint2.X); + } + var isiny = (rightAnkle.Y > points.TopPoint3.Y && rightAnkle.X < points.BottomPoint3.Y) || (rightAnkle.Y < points.TopPoint2.Y && rightAnkle.X > points.BottomPoint2.Y); + if (!isinx || !isiny) 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; + ResetRaiseState(); + 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; + } + + private void ResetRaiseState() + { + _raiseStartTime = null; + _countdownStartTime = null; + _firstHandTriggered = false; + _lastCountdownSecond = 3; + } + + private void StartSport() + { + + + //if (_sport?.IsCounting == true) + //_mainWin.WebcamClient.OnExtractFrame += this.OnFrameExtracted; + _sport.Start(); + _detectQueue.Start(); + + // 开始跳远抽帧线程 + _mainWin.FacialRecognitionEvent += this.HumanPredicting; + } + + private WriteableBitmap _videoBitmap; + private int _lastFrameNumber = -1; + private async void OnFrameExtracted(VideoFrame frame) + { + if (frame == null) return; + + // 跳帧:每秒只渲染 10~15 帧,降低 CPU 占用 + + if (frame.Number % 2 != 0) return; + + if (_lastFrameNumber != -1 && frame.Number - _lastFrameNumber < 2) return; + _lastFrameNumber = (int)frame.Number; + + //获得帧二进制流,保存图像、人体识别竺 + var buffer = frame.GetImageBuffer(Yztob.AiSports.Sensors.Things.ImageFormat.Jpeg).ToArray(); + //await File.WriteAllBytesAsync($"./temps/{frame.Number}.jpg", buffer); + + // === 显示到 WPF === + BitmapImage bitmap = null; + await Task.Run(() => + { + using var ms = new MemoryStream(buffer); + bitmap = new BitmapImage(); + bitmap.BeginInit(); + bitmap.CacheOption = BitmapCacheOption.OnLoad; + bitmap.StreamSource = ms; + bitmap.EndInit(); + bitmap.Freeze(); // 跨线程安全 + }); + + // UI 线程显示 + Application.Current.Dispatcher.Invoke(() => + { + if (videoImage == null) return; + + if (_videoBitmap == null || + _videoBitmap.PixelWidth != bitmap.PixelWidth || + _videoBitmap.PixelHeight != bitmap.PixelHeight) + { + _videoBitmap = new WriteableBitmap(bitmap.PixelWidth, bitmap.PixelHeight, + 96, 96, PixelFormats.Bgra32, null); + videoImage.Source = _videoBitmap; + } + + _videoBitmap.Lock(); + bitmap.CopyPixels(new Int32Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight), + _videoBitmap.BackBuffer, + _videoBitmap.BackBufferStride * bitmap.PixelHeight, + _videoBitmap.BackBufferStride); + _videoBitmap.AddDirtyRect(new Int32Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight)); + _videoBitmap.Unlock(); + + videoImage.Opacity = 1; + centerImg1.Opacity = 0; + centerImg2.Opacity = 0; + centerImg3.Opacity = 0; + }); + } } } diff --git a/Wpf_AiSportsMicrospace/Wpf_AiSportsMicrospace.csproj b/Wpf_AiSportsMicrospace/Wpf_AiSportsMicrospace.csproj index 99c0124..62dd210 100644 --- a/Wpf_AiSportsMicrospace/Wpf_AiSportsMicrospace.csproj +++ b/Wpf_AiSportsMicrospace/Wpf_AiSportsMicrospace.csproj @@ -68,8 +68,11 @@ + + + @@ -119,12 +122,21 @@ Always + + Always + Always Always + + Always + + + Always + Always