2025-10-12 17:21:15 +08:00

417 lines
15 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 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.Imaging;
using System.Windows.Shapes;
using System.Windows.Threading;
using Wpf_AiSportsMicrospace.Common;
using Wpf_AiSportsMicrospace.Dto;
using Wpf_AiSportsMicrospace.Enum;
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 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 bool IsGameStarted = false;
public GroupJumpRope()
{
InitializeComponent();
Loaded += UserControl_Loaded;
Unloaded += UserControl_Unloaded;
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
DrawCirclesWithText();
_mainWin.HumanFrameUpdated += OnHumanFrameUpdated;
Utils.PlayBackgroundMusic("raisehand.mp3", false);
}
private void UserControl_Unloaded(object sender, RoutedEventArgs e)
{
_mainWin.HumanFrameUpdated -= OnHumanFrameUpdated;
}
private DateTime? _waitStartTime = null;
private void OnHumanFrameUpdated(object sender, List<Human> humans)
{
try
{
if (!IsGameStarted)
{
var human = humans.LastOrDefault();
if (human == null) return;
//检测挥手动作
var wavingaction = DetectRightHandRaise(human);
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";
countdownText.Visibility = Visibility.Collapsed;
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();
countdownText.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 void FinishCountdown()
{
countdownText.Text = "✔";
countdownText.Visibility = Visibility.Collapsed;
IsGameStarted = true;
// 你也可以在这里触发其他动作,例如:
// 播放音效、触发事件、执行下一步逻辑
Utils.PlayBackgroundMusic("homeprojectselected1.mp3", 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<Human> 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 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;
// 前后出圈检测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;
}
private void DrawCirclesWithText()
{
overlayCanvas.Children.Clear();
sports.Clear();
circleTexts.Clear();
double imgWidth = overlayCanvas.ActualWidth;
double imgHeight = overlayCanvas.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.58 ),
(0.50, 0.88),
(0.64, 0.58 ),
(0.78, 0.88),
(0.92, 0.58 )
};
foreach (var pos in circlePositions)
{
double x = pos.XNorm * imgWidth;
double y = pos.YNorm * imgHeight;
// 绘制发光圆
//AddGlowEllipse(x, y, overlayCanvas);
AddGlowEllipse(x, y, overlayCanvas);
// 创建文本控件
var text = new TextBlock
{
Text = "0",
Foreground = Brushes.Red,
FontWeight = FontWeights.Bold,
FontSize = 50,
TextAlignment = TextAlignment.Center,
Width = radius * 2
};
Canvas.SetLeft(text, x - radius);
Canvas.SetTop(text, y - radius - 25);
overlayCanvas.Children.Add(text);
circleTexts.Add(text);
// 绑定运动对象
var sport = SportBase.Create("rope-skipping");
int index = circleTexts.Count - 1;
sport.OnTicked += (count, times) =>
{
Application.Current.Dispatcher.BeginInvoke(() =>
{
circleTexts[index].Text = count.ToString();
});
};
sport.Start();
sports.Add(sport);
}
}
private void UpdateCircleCounts(List<Human> humans)
{
for (int i = 0; i < circlePositionsX.Length; i++)
{
double center = circlePositionsX[i];
double range = 0.07;
double begin = center - range;
double end = center + range;
var human = LocateHuman(humans, begin, end, overlayCanvas.ActualWidth, overlayCanvas.ActualHeight, i);
if (human != null)
{
sports[i].Pushing(human);
}
}
}
/// <summary>
/// 添加带渐变光的圆圈(中心红色,边缘蓝色)
/// </summary>
private Ellipse AddGlowEllipse(double centerX, double centerY, Canvas canvas)
{
double radius = 70; // 统一半径
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
{
Width = radius * 2,
Height = radius * flattenFactor,
Opacity = opacity,
Fill = new RadialGradientBrush
{
GradientOrigin = new Point(0.5, 0.5),
Center = new Point(0.5, 0.5),
RadiusX = 0.5,
RadiusY = 0.5,
GradientStops = new GradientStopCollection
{
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) // 外部透明
}
}
};
// 定位到中心
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;
}
}
}