522 lines
18 KiB
C#
522 lines
18 KiB
C#
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 List<SportBase> sports = new();
|
||
private List<TextBlock> circleTexts = new();
|
||
private List<SportUserItem> userList = new();
|
||
private List<string> userNumberList = ["0", "0", "0", "0", "0", "0"];
|
||
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;
|
||
private List<(double XNorm, double YNorm)> circlePositions = new();
|
||
|
||
private MediaPlayer _mediaPlayer = new MediaPlayer();
|
||
private bool IsGameStarted = false;
|
||
List<string> _useName = ["一号位", "四号位", "二号位", "五号位", "三号位", "六号位",];
|
||
public GroupJumpRope()
|
||
{
|
||
InitializeComponent();
|
||
Loaded += UserControl_Loaded;
|
||
Unloaded += UserControl_Unloaded;
|
||
|
||
}
|
||
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;
|
||
|
||
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 Dictionary<int, DateTime> _lastJumpUpdateTime = new(); // 用于存储最后更新时间
|
||
private Dictionary<int, DateTime> _lastUIUpdateTime = new(); // 用于防抖处理
|
||
public Human LocateHuman(List<Human> humans, 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.073;
|
||
double radiusNormY = 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 xRightNorm = rightFoot.X / frameWidth;
|
||
double xLeftNorm = leftFoot.X / frameWidth;
|
||
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)
|
||
{
|
||
isOut = true;
|
||
}
|
||
else if (inCircleHuman == null)
|
||
{
|
||
inCircleHuman = hu;
|
||
}
|
||
|
||
}
|
||
|
||
if (isOut)
|
||
userList[circleIndex].ImageState = "3";
|
||
else
|
||
{
|
||
//if (userList[circleIndex].ImageState == "3")
|
||
//{
|
||
// userList[circleIndex].ImageState = "1";
|
||
//}
|
||
|
||
// 检查当前编号是否未变化超过 2 秒
|
||
if (userNumberList[circleIndex] == userList[circleIndex].NumberText)
|
||
{
|
||
// 获取上次变化时间
|
||
if (!_lastJumpUpdateTime.TryGetValue(circleIndex, out var lastTime))
|
||
lastTime = DateTime.Now; // 默认当前时间
|
||
|
||
var elapsed = (DateTime.Now - lastTime).TotalSeconds;
|
||
|
||
if (elapsed > 1)
|
||
{
|
||
userList[circleIndex].ImageState = "1";
|
||
_lastJumpUpdateTime[circleIndex] = DateTime.Now;
|
||
}
|
||
// 否则:2 秒内仍在变化,不更新状态
|
||
}
|
||
else
|
||
{
|
||
// 编号发生变化,更新记录,并保持状态不变
|
||
userNumberList[circleIndex] = userList[circleIndex].NumberText;
|
||
_lastJumpUpdateTime[circleIndex] = DateTime.Now;
|
||
}
|
||
}
|
||
return inCircleHuman;
|
||
}
|
||
|
||
|
||
private void DrawCirclesWithText()
|
||
{
|
||
userBox.Children.Clear();
|
||
sports.Clear();
|
||
circleTexts.Clear();
|
||
userList.Clear(); // 清空用户控件列表
|
||
|
||
double imgWidth = userBox.ActualWidth;
|
||
double imgHeight = userBox.ActualHeight;
|
||
double radius = 100;
|
||
|
||
// 每个圆的位置:X 和 Y 都归一化 0~1
|
||
circlePositions = new List<(double XNorm, double YNorm)>
|
||
{
|
||
//(0.07, 0.58),
|
||
(0.21, 0.88 ),
|
||
(0.36, 0.68 ),
|
||
(0.50, 0.88),
|
||
(0.64, 0.68 ),
|
||
(0.78, 0.88),
|
||
(0.92, 0.68 )
|
||
};
|
||
|
||
for (int i = 0; i < circlePositions.Count; i++)
|
||
{
|
||
var pos = circlePositions[i];
|
||
double x = pos.XNorm * imgWidth;
|
||
double y = pos.YNorm * imgHeight;
|
||
|
||
// 绘制发光圆
|
||
//AddGlowEllipse(x, y, overlayCanvas);
|
||
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();
|
||
sports.Add(sport);
|
||
}
|
||
}
|
||
private void UpdateCircleCounts(List<Human> humans)
|
||
{
|
||
// 在后台线程运行检测逻辑
|
||
//Parallel.For(0, circlePositions.Count, i =>
|
||
//{
|
||
// var human = LocateHuman(humans, userBox.ActualWidth, userBox.ActualHeight, i);
|
||
// sports[i].Pushing(human);
|
||
//});
|
||
|
||
for (int i = 0; i < circlePositions.Count; i++)
|
||
{
|
||
var human = LocateHuman(humans, userBox.ActualWidth, userBox.ActualHeight, i);
|
||
if (human != null)
|
||
{
|
||
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 = _useName[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);
|
||
userList.Add(userItem);
|
||
return userItem; //
|
||
}
|
||
}
|
||
}
|