diff --git a/Wpf_AiSportsMicrospace/Dto/GroupJumpRopeState.cs b/Wpf_AiSportsMicrospace/Dto/GroupJumpRopeState.cs new file mode 100644 index 0000000..9ba854e --- /dev/null +++ b/Wpf_AiSportsMicrospace/Dto/GroupJumpRopeState.cs @@ -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 UserList { get; private set; } + public List UserNumberList { get; private set; } + public List Sports { get; private set; } // ✅ 新增运动对象 + public Dictionary LastJumpUpdateTime { get; private set; } + public List 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(); + UserNumberList = new List(); + Sports = new List(); // ✅ 初始化 + LastJumpUpdateTime = new Dictionary(); + } + + public void Safe(Action action) + { + lock (_lock) + action(); + } + + public T Safe(Func func) + { + lock (_lock) + return func(); + } + } +} diff --git a/Wpf_AiSportsMicrospace/Views/JumpRope/GroupJumpRope.xaml.cs b/Wpf_AiSportsMicrospace/Views/JumpRope/GroupJumpRope.xaml.cs index 3dff199..52eec7e 100644 --- a/Wpf_AiSportsMicrospace/Views/JumpRope/GroupJumpRope.xaml.cs +++ b/Wpf_AiSportsMicrospace/Views/JumpRope/GroupJumpRope.xaml.cs @@ -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 /// public partial class GroupJumpRope : UserControl { - private List sports = new(); - private List circleTexts = new(); - private List userList = new(); - private List 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 _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 _lastJumpUpdateTime = new(); // 用于存储最后更新时间 - private Dictionary _lastUIUpdateTime = new(); // 用于防抖处理 - public Human LocateHuman(List humans, double frameWidth, double frameHeight, int circleIndex) + private Dictionary _lastJumpUpdateTime = new(); + private Dictionary _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 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 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 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); - } - } - } - /// /// 添加带渐变光的圆圈(中心红色,边缘蓝色) @@ -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 _uiUpdateCache + = new ConcurrentDictionary(); + + 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 防抖时间 + } + }