This commit is contained in:
tanglong 2025-10-14 09:08:47 +08:00
parent 694b046e50
commit 458cbe176c
2 changed files with 187 additions and 110 deletions

View File

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Text;
using Wpf_AiSportsMicrospace.MyUserControl;
using Yztob.AiSports.Postures.Sports;
namespace Dto
{
public class GroupJumpRopeState
{
private readonly object _lock = new();
public List<(double XNorm, double YNorm)> CirclePositions { get; private set; }
public List<SportUserItem> UserList { get; private set; }
public List<string> UserNumberList { get; private set; }
public List<SportBase> Sports { get; private set; } // ✅ 新增运动对象
public Dictionary<int, DateTime> LastJumpUpdateTime { get; private set; }
public List<string> UseNameList = ["四号位", "一号位", "五号位", "二号位", "六号位", "三号位", "七号位",];
public GroupJumpRopeState()
{
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)
};
UserList = new List<SportUserItem>();
UserNumberList = new List<string>();
Sports = new List<SportBase>(); // ✅ 初始化
LastJumpUpdateTime = new Dictionary<int, DateTime>();
}
public void Safe(Action action)
{
lock (_lock)
action();
}
public T Safe<T>(Func<T> func)
{
lock (_lock)
return func();
}
}
}

View File

@ -1,4 +1,6 @@
using Emgu.CV.Flann;
using Dto;
using Emgu.CV.Features2D;
using Emgu.CV.Flann;
using HandyControl.Controls;
using SharpDX.Direct3D9;
using System;
@ -41,28 +43,23 @@ namespace Wpf_AiSportsMicrospace.Views
/// </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", "0"];
private double[] circlePositionsX = { 0.07, 0.21, 0.36, 0.50, 0.64, 0.78, 0.92 };
private GroupJumpRopeState _groupJumpRopeState = new();
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;
_groupJumpRopeState = new GroupJumpRopeState();
}
private async void UserControl_Loaded(object sender, RoutedEventArgs e)
{
DrawCirclesWithText();
// 播放音乐
PlayMusic("raisehand.mp3");
}
@ -261,7 +258,7 @@ namespace Wpf_AiSportsMicrospace.Views
countdownGrid.Visibility = Visibility.Hidden;
userList.ForEach(x =>
_groupJumpRopeState.UserList.ForEach(x =>
{
x.ImageState = "1";
});
@ -345,159 +342,148 @@ namespace Wpf_AiSportsMicrospace.Views
_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)
private Dictionary<int, DateTime> _lastJumpUpdateTime = new();
private Dictionary<int, DateTime> _lastUIUpdateTime = new();
// 状态缓存类
class CircleState
{
public string LastNumberText = "";
public DateTime LastActiveTime = DateTime.MinValue; // 最近跳动时间
public DateTime LastUIUpdateTime = DateTime.MinValue; // 最近UI更新时间
}
private CircleState[] _circleStates;
private void InitializeCircleStates()
{
int count = _groupJumpRopeState.CirclePositions.Count;
_circleStates = new CircleState[count];
for (int i = 0; i < count; i++)
_circleStates[i] = new CircleState();
}
// 安全访问状态并更新
private Human LocateHumanSafe(List<Human> humans, double frameWidth, double frameHeight, int index)
{
var state = _circleStates[index];
var user = _groupJumpRopeState.UserList[index];
if (humans == null || humans.Count == 0)
{
// 没人时,判断是否切回静止
if ((DateTime.Now - state.LastActiveTime).TotalSeconds >= 2)
user.ImageState = "1";
return null;
}
double circleX = circlePositions[circleIndex].XNorm;
double circleY = circlePositions[circleIndex].YNorm;
double radiusNormX = 0.073;
double radiusNormY = 0.135;
var circle = _groupJumpRopeState.CirclePositions[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;
var right = hu.Keypoints.FirstOrDefault(k => k.Name == "right_ankle");
var left = hu.Keypoints.FirstOrDefault(k => k.Name == "left_ankle");
if (right == null || left == null) continue;
double xRightNorm = rightFoot.X / frameWidth;
double xLeftNorm = leftFoot.X / frameWidth;
double yRightNorm = rightFoot.Y / frameHeight;
double yLeftNorm = leftFoot.Y / frameHeight;
double xR = right.X / frameWidth;
double yR = right.Y / frameHeight;
double xL = left.X / frameWidth;
double yL = left.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);
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;
}
}
// 决定目标状态
string targetState;
DateTime now = DateTime.Now;
if (isOut)
userList[circleIndex].ImageState = "3";
{
targetState = "3"; // 出圈
}
else if (inCircleHuman != null)
{
targetState = "2"; // 运动中
state.LastActiveTime = now;
}
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;
}
// 圈内无人,根据上次活跃时间判断是否切回静止
targetState = (now - state.LastActiveTime).TotalSeconds >= 2 ? "1" : "2";
}
// UI 防抖:每 500ms 更新一次
if ((now - state.LastUIUpdateTime).TotalMilliseconds > 500 && user.ImageState != targetState)
{
user.ImageState = targetState;
state.LastUIUpdateTime = now;
}
return inCircleHuman;
}
private void UpdateCircleCounts(List<Human> humans)
{
double frameWidth = userBox.ActualWidth;
double frameHeight = userBox.ActualHeight;
for (int i = 0; i < _groupJumpRopeState.CirclePositions.Count; i++)
{
var human = LocateHumanSafe(humans, frameWidth, frameHeight, i);
if (human != null)
{
_groupJumpRopeState.Sports[i].Pushing(human);
}
}
}
private void DrawCirclesWithText()
{
userBox.Children.Clear();
sports.Clear();
circleTexts.Clear();
userList.Clear(); // 清空用户控件列表
_groupJumpRopeState.Sports.Clear();
_groupJumpRopeState.UserNumberList.Clear();
_groupJumpRopeState.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.58 ),
(0.50, 0.88),
(0.64, 0.58 ),
(0.78, 0.88),
(0.92, 0.58 )
};
for (int i = 0; i < circlePositions.Count; i++)
for (int i = 0; i < _groupJumpRopeState.CirclePositions.Count; i++)
{
var pos = circlePositions[i];
var pos = _groupJumpRopeState.CirclePositions[i];
double x = pos.XNorm * imgWidth;
double y = pos.YNorm * imgHeight;
// 绘制发光圆
//AddGlowEllipse(x, y, overlayCanvas);
var userItem = AddUserItem(x, y, i);
// 绑定运动对象
_groupJumpRopeState.UserList.Add(userItem);
_groupJumpRopeState.UserNumberList.Add("0");
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);
_groupJumpRopeState.Sports.Add(sport);
}
InitializeCircleStates();
}
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>
/// 添加带渐变光的圆圈(中心红色,边缘蓝色)
@ -507,14 +493,55 @@ namespace Wpf_AiSportsMicrospace.Views
var userItem = new SportUserItem();
userItem.Width = 270;
userItem.Height = 560;
userItem.DisplayText = _useName[index];
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);
userList.Add(userItem);
_groupJumpRopeState.UserList.Add(userItem);
return userItem; //
}
private readonly ConcurrentDictionary<int, (string lastState, DateTime lastUpdate)> _uiUpdateCache
= new ConcurrentDictionary<int, (string lastState, DateTime lastUpdate)>();
private void SetImageStateSafe(int index, string state)
{
if (index < 0 || index >= _groupJumpRopeState.UserList.Count) return;
var now = DateTime.Now;
if (_uiUpdateCache.TryGetValue(index, out var cache))
{
// 如果状态没变或更新太频繁(<300ms跳过
if (cache.lastState == state && (now - cache.lastUpdate).TotalMilliseconds < 300)
return;
}
_uiUpdateCache[index] = (state, now);
if (Application.Current.Dispatcher.CheckAccess())
{
_groupJumpRopeState.UserList[index].ImageState = state;
}
else
{
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
if (index >= 0 && index < _groupJumpRopeState.UserList.Count)
_groupJumpRopeState.UserList[index].ImageState = state;
}));
}
}
}
class CircleState
{
public string ImageState = "1"; // 当前显示状态
public string LastNumberText = ""; // 上一次的 NumberText
public DateTime LastActiveTime = DateTime.MinValue; // 上次活动时间(运动中)
public DateTime LastUIUpdateTime = DateTime.MinValue; // UI 防抖时间
}
}