2025-10-14 09:46:14 +08:00

490 lines
18 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Dto;
using Emgu.CV.Features2D;
using Emgu.CV.Flann;
using HandyControl.Controls;
using SharpDX.Direct3D9;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
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
{
/// <summary>
/// GroupJumpRope.xaml 的交互逻辑
/// </summary>
public partial class GroupJumpRope : UserControl
{
private GroupJumpRopeState _groupJumpRopeState = new();
private Main _mainWin => Application.Current.MainWindow as Main;
private MediaPlayer _mediaPlayer = new MediaPlayer();
private bool IsGameStarted = false;
public GroupJumpRope()
{
InitializeComponent();
Loaded += UserControl_Loaded;
Unloaded += UserControl_Unloaded;
_groupJumpRopeState = new GroupJumpRopeState();
}
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;
_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<Human> humans)
{
try
{
if (!IsGameStarted)
{
if (humans == null || humans.Count == 0) return;
int wavingaction = 0;
foreach (var human in humans)
{
wavingaction = DetectRightHandRaise(human);
if (wavingaction < 3)
continue;
switch (wavingaction)
{
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";
countdownGrid.Visibility = Visibility.Hidden;
break;
}
}
}
else
{
UpdateCircleCounts(humans);
}
}
catch (Exception ex)
{
Console.WriteLine("OnFrameExtracted error: " + ex.Message);
}
}
private int _currentCountdown = 3;
private DateTime _lastUpdateTime = DateTime.Now;
private void StartCountdown(int start = 3)
{
_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;
IsGameStarted = true;
// 播放背景音乐(循环)
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;
_groupJumpRopeState.UserList.ForEach(x =>
{
x.ImageState = "1";
});
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");
const double raiseThreshold = 60; // 举手阈值
const double holdDuration = 1; // 持续 2 秒触发倒计时
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)
{
// 举手达到 2 秒,启动倒计时
_firstHandTriggered = true;
_countdownStartTime = DateTime.Now;
_lastCountdownSecond = countdownSeconds;
return (int)WavingAction.FirstHand;
}
}
else
{
// 手放下,重置举手开始时间
_raiseStartTime = DateTime.Now;
}
return (int)WavingAction.None; // 倒计时未开始
}
// ---------- 第二步:倒计时逻辑(独立于手状态) ----------
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; // 倒计时中
}
private void ResetRaiseState()
{
_raiseStartTime = null;
_wristStartTime = null;
_countdownStartTime = null;
_firstHandTriggered = false;
_lastCountdownSecond = 3;
}
private void DrawCirclesWithText()
{
userBox.Children.Clear();
_groupJumpRopeState.Sports.Clear();
_groupJumpRopeState.UserList.Clear();
double imgWidth = userBox.ActualWidth;
double imgHeight = userBox.ActualHeight;
for (int i = 0; i < _groupJumpRopeState.CirclePositions.Count; i++)
{
var pos = _groupJumpRopeState.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) =>
{
currentItem.NumberText = count.ToString();
if (currentItem.ImageState != "2")
currentItem.ImageState = "2";
};
sport.PointThreshold = 0.03f;
sport.Start();
_groupJumpRopeState.Sports.Add(sport);
_groupJumpRopeState.UserList.Add(userItem);
}
}
public Human LocateHuman(List<Human> humans, double frameWidth, double frameHeight, int index)
{
if (humans == null || humans.Count == 0)
return null;
return _groupJumpRopeState.Safe(() =>
{
var circle = _groupJumpRopeState.CirclePositions[index];
var user = _groupJumpRopeState.UserList[index];
var state = _groupJumpRopeState.CircleStates[index];
double radiusX = 0.073;
double radiusY = 0.135;
Human inCircleHuman = null;
bool isOut = false;
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 xR = rightFoot.X / frameWidth;
double xL = leftFoot.X / frameWidth;
double yR = rightFoot.Y / frameHeight;
double yL = leftFoot.Y / frameHeight;
bool outOfCircle = xR < circle.XNorm - radiusX || xR > circle.XNorm + radiusX ||
xL < circle.XNorm - radiusX || xL > circle.XNorm + radiusX ||
yR < circle.YNorm - radiusY || yR > circle.YNorm + radiusY ||
yL < circle.YNorm - radiusY || yL > circle.YNorm + radiusY;
if (outOfCircle) isOut = true;
else if (inCircleHuman == null) inCircleHuman = hu;
}
var now = DateTime.Now;
// 更新状态
if (isOut)
{
if (state.ImageState != "3" && (now - state.LastUIUpdateTime).TotalMilliseconds > 500)
{
Application.Current.Dispatcher.Invoke(() => user.ImageState = "3");
state.ImageState = "3";
state.LastUIUpdateTime = now;
}
}
else
{
if (state.LastNumberText == user.NumberText)
{
var elapsed = (now - state.LastJumpTime).TotalSeconds;
if (elapsed > 2 && (now - state.LastUIUpdateTime).TotalMilliseconds > 500)
{
Application.Current.Dispatcher.Invoke(() => user.ImageState = "1");
state.ImageState = "1";
state.LastUIUpdateTime = now;
}
}
else
{
state.LastNumberText = user.NumberText;
state.LastJumpTime = now;
if (state.ImageState != "2" && (now - state.LastUIUpdateTime).TotalMilliseconds > 500)
{
Application.Current.Dispatcher.Invoke(() => user.ImageState = "2");
state.ImageState = "2";
state.LastUIUpdateTime = now;
}
}
}
return inCircleHuman;
});
}
private void UpdateCircleCounts(List<Human> humans)
{
for (int i = 0; i < _groupJumpRopeState.CirclePositions.Count; i++)
{
var human = LocateHuman(humans, userBox.ActualWidth, userBox.ActualHeight, i);
if (human != null)
{
// 推动运动对象
_groupJumpRopeState.Safe(() => _groupJumpRopeState.Sports[i].Pushing(human));
}
}
}
/// <summary>
/// 添加带渐变光的圆圈(中心红色,边缘蓝色)
/// </summary>
private SportUserItem AddUserItem(double centerX, double centerY, int index)
{
var userItem = new SportUserItem();
userItem.Width = 270;
userItem.Height = 560;
userItem.DisplayText = _groupJumpRopeState.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);
userBox.Children.Add(userItem);
_groupJumpRopeState.UserList.Add(userItem);
return userItem; //
}
}
}