diff --git a/Wpf_AiSportsMicrospace/App.xaml b/Wpf_AiSportsMicrospace/App.xaml
index 726f59f..7a315bd 100644
--- a/Wpf_AiSportsMicrospace/App.xaml
+++ b/Wpf_AiSportsMicrospace/App.xaml
@@ -2,7 +2,9 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Wpf_AiSportsMicrospace.Views"
- StartupUri="Views/Main.xaml">
+ StartupUri="Views/JumpRope/GroupJumpRope.xaml">
+
+
diff --git a/Wpf_AiSportsMicrospace/App.xaml.cs b/Wpf_AiSportsMicrospace/App.xaml.cs
index c26a4cf..c0a947f 100644
--- a/Wpf_AiSportsMicrospace/App.xaml.cs
+++ b/Wpf_AiSportsMicrospace/App.xaml.cs
@@ -1,6 +1,8 @@
using System.Configuration;
using System.Data;
using System.Windows;
+using Yztob.AiSports.Common;
+using Yztob.AiSports.Common.Implement;
namespace Wpf_AiSportsMicrospace;
@@ -9,5 +11,16 @@ namespace Wpf_AiSportsMicrospace;
///
public partial class App : Application
{
+ protected override void OnStartup(StartupEventArgs e)
+ {
+ base.OnStartup(e);
+
+#if DEBUG
+ // 初始化 DebugTracker
+ DebugTracker.Enabled = true;
+ DebugTracker.Channels.Add(new DiagnosisDebugTrackChannel());
+#endif
+
+ }
}
diff --git a/Wpf_AiSportsMicrospace/Views/Home.xaml.cs b/Wpf_AiSportsMicrospace/Views/Home.xaml.cs
index a451699..d271d44 100644
--- a/Wpf_AiSportsMicrospace/Views/Home.xaml.cs
+++ b/Wpf_AiSportsMicrospace/Views/Home.xaml.cs
@@ -122,7 +122,6 @@ namespace Wpf_AiSportsMicrospace
mainWin.SwitchPage(new GroupJumpRope(), true);
}
}
-
private void StartFrameProcessing()
{
Task.Run(() =>
@@ -135,15 +134,7 @@ namespace Wpf_AiSportsMicrospace
}
else
{
- //_webcamClient.OnExtractFrame += frame =>
- //{
- // if (frame != null)
- // _frameQueue.Enqueue(frame);
- //};
- //_webcamClient.StartExtract();
-
- _webcamClient.StartExtract();
- //Thread.Sleep(5);
+ Thread.Sleep(5);
}
}
}, _cts.Token);
diff --git a/Wpf_AiSportsMicrospace/Views/JumpRope/GroupJumpRope.xaml b/Wpf_AiSportsMicrospace/Views/JumpRope/GroupJumpRope.xaml
index 56423cc..48401f4 100644
--- a/Wpf_AiSportsMicrospace/Views/JumpRope/GroupJumpRope.xaml
+++ b/Wpf_AiSportsMicrospace/Views/JumpRope/GroupJumpRope.xaml
@@ -4,21 +4,13 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Wpf_AiSportsMicrospace.Views"
- mc:Ignorable="d" Height="500" Width="800">
-
+ mc:Ignorable="d" Height="1080" Width="1920" Loaded="UserControl_Loaded">
+
-
-
-
-
-
+
+
+
diff --git a/Wpf_AiSportsMicrospace/Views/JumpRope/GroupJumpRope.xaml.cs b/Wpf_AiSportsMicrospace/Views/JumpRope/GroupJumpRope.xaml.cs
index e8da202..b998f0c 100644
--- a/Wpf_AiSportsMicrospace/Views/JumpRope/GroupJumpRope.xaml.cs
+++ b/Wpf_AiSportsMicrospace/Views/JumpRope/GroupJumpRope.xaml.cs
@@ -1,5 +1,9 @@
-using System;
+using Emgu.CV.Flann;
+using HandyControl.Controls;
+using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
@@ -11,8 +15,20 @@ 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
{
@@ -21,17 +37,347 @@ namespace Wpf_AiSportsMicrospace.Views
///
public partial class GroupJumpRope : UserControl
{
+ private IHumanPredictor _humanPredictor;
+ private IObjectDetector _objectDetector;
+ private HumanGraphicsRenderer _humanGraphicsRenderer;
+ private WebcamClient _webcamClient;
+ private ConcurrentQueue _frameQueue = new();
+ private CancellationTokenSource _cts = new();
+ private SportOperate _sportOperate;
+ private SportBase _sport;
+ private readonly SportDetectionQueue _detectQueue;
+
+ private List sports = new();
+ private List circleTexts = new();
+ private double[] circlePositionsX = { 0.10, 0.24, 0.35, 0.48, 0.60, 0.72, 0.88 };
+
+ ConfigService configService = new ConfigService();
public GroupJumpRope()
{
InitializeComponent();
+ _humanPredictor = HumanPredictorFactory.Create(HumanPredictorType.MultiMedium);
+ _objectDetector = ObjectDetectorFactory.CreateSportGoodsDetector();
+ _humanGraphicsRenderer = new HumanGraphicsRenderer();
+ _humanGraphicsRenderer.DrawLabel = false;
- var image = new BitmapImage();
- image.BeginInit();
- image.UriSource = new Uri("../../Resources/Img/gif/1.gif", UriKind.Relative); // 替换成你的 GIF 路径
- image.EndInit();
+ _detectQueue = new SportDetectionQueue();
+ }
+ private void UserControl_Loaded(object sender, RoutedEventArgs e)
+ {
+ //DrawJumpRope3DPointsWithGlow();
- // 设置动画源
- ImageBehavior.SetAnimatedSource(GifImage, image);
+ DrawCirclesWithText();
+
+ _sportOperate = new SportOperate();
+ _webcamClient = _sportOperate.CreateRTSP();
+
+ _webcamClient.OnExtractFrame += frame =>
+ {
+ if (frame != null)
+ _frameQueue.Enqueue(frame);
+ };
+ _webcamClient.StartExtract();
+
+ StartFrameProcessing();
+ }
+
+ private void StartFrameProcessing()
+ {
+ Task.Run(() =>
+ {
+ while (!_cts.Token.IsCancellationRequested)
+ {
+ if (_frameQueue.TryDequeue(out var frame))
+ {
+ ProcessFrame(frame);
+ }
+ else
+ {
+ Thread.Sleep(5); // 空队列时避免忙等
+ }
+ }
+ }, _cts.Token);
+ }
+
+
+ private void ProcessFrame(VideoFrame frame)
+ {
+ try
+ {
+ var buffer = frame.GetImageBuffer(ImageFormat.Jpeg).ToArray();
+ var humanResult = _humanPredictor.Predicting(buffer, frame.Number);
+
+ var humans = humanResult?.Humans?.ToList();
+ if (humans == null || humans.Count == 0)
+ return;
+
+ UpdateCircleCounts(humans);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("OnFrameExtracted error: " + ex.Message);
+ }
+ }
+ public Human LocateHuman(List humans, double begin, double end, double frameWidth)
+ {
+ if (humans == null || humans.Count == 0)
+ return null;
+
+ foreach (var hu in humans)
+ {
+ var nose = hu.Keypoints.FirstOrDefault(k => k.Name == "nose");
+ if (nose == null)
+ continue;
+
+ // 使用 Canvas 宽度归一化
+ double xNorm = nose.X / frameWidth;
+
+ if (xNorm >= begin && xNorm <= end)
+ 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;
+
+ double yOffset = -0.10; // 向上移动 10% 屏幕高度
+
+ // 每个圆的位置:X 和 Y 都归一化 0~1
+ var circlePositions = new List<(double XNorm, double YNorm)>
+ {
+ (0.10, 0.88 + yOffset), // 后排
+ (0.24, 0.60 + yOffset), // 前排
+ (0.35, 0.88 + yOffset),
+ (0.48, 0.60 + yOffset),
+ (0.60, 0.88 + yOffset),
+ (0.72, 0.60 + yOffset),
+ (0.88, 0.88 + yOffset)
+ };
+
+ foreach (var pos in circlePositions)
+ {
+ double x = pos.XNorm * imgWidth;
+ double y = pos.YNorm * imgHeight;
+
+ // 绘制发光圆
+ 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.Invoke(() =>
+ {
+ circleTexts[index].Text = count.ToString();
+ });
+ };
+ sport.Start();
+ sports.Add(sport);
+ }
+ }
+
+
+ private void UpdateCircleCounts(List humans)
+ {
+ for (int i = 0; i < circlePositionsX.Length; i++)
+ {
+ double center = circlePositionsX[i];
+ double range = 0.08;
+ double begin = center - range;
+ double end = center + range;
+
+ var human = LocateHuman(humans, begin, end, overlayCanvas.ActualWidth);
+ if (human != null)
+ {
+ sports[i].Pushing(human);
+
+ //Application.Current.Dispatcher.Invoke(() =>
+ //{
+ // circleTexts[i].Text = human.Score.ToString();
+ //});
+
+ //sports[i].OnTicked = (count, times) =>
+ //{
+ // // 在 UI 线程更新文本
+ // //Application.Current.Dispatcher.Invoke(() =>
+ // //{
+ // // circleTexts[i].Text = count.ToString();
+ // //});
+ //};
+
+ }
+ }
+ }
+
+ private void AddGlowEllipse(double centerX, double centerY, double radius, Canvas canvas)
+ {
+ var ellipse = new Ellipse
+ {
+ Width = radius * 2,
+ Height = radius,
+ Stroke = Brushes.Red,
+ StrokeThickness = 3,
+ Opacity = 0.7
+ };
+
+ Canvas.SetLeft(ellipse, centerX - radius);
+ Canvas.SetTop(ellipse, centerY - radius / 2);
+ canvas.Children.Add(ellipse);
+ }
+
+ private void DrawJumpRope3DPointsWithGlow()
+ {
+ configService.LoadAllConfigs();
+
+ ConfigSet jumpRopeConfig;
+
+ double imgWidth = 1920;
+ double imgHeight = 1080;
+
+ if (imgWidth <= 0 || imgHeight <= 0) return;
+
+ overlayCanvas.Children.Clear();
+ overlayCanvas.Width = imgWidth;
+ overlayCanvas.Height = imgHeight;
+
+ bool needSaveConfig = false;
+
+ if (configService.ConfigDic.TryGetValue("rope-skipping", out jumpRopeConfig) || jumpRopeConfig.Points.Count == 0)
+ {
+ jumpRopeConfig = new ConfigSet { Name = "rope-skipping" };
+ needSaveConfig = true;
+
+ double yOffset = -0.10;
+ double frontRadius = 85;
+ double backRadius = 70;
+
+ double frontY = 0.70 + yOffset;
+ var frontPositions = new List<(double XNorm, double YNorm)>
+ {
+ (0.24, frontY),
+ (0.48, frontY),
+ (0.72, frontY)
+ };
+
+ foreach (var pos in frontPositions)
+ {
+ double x = pos.XNorm * imgWidth;
+ double y = pos.YNorm * imgHeight;
+
+ jumpRopeConfig.Points.Add(new PointConfig
+ {
+ X = x,
+ Y = y,
+ Radius = frontRadius,
+ XNorm = pos.XNorm,
+ YNorm = pos.YNorm
+ });
+
+ AddGlowEllipse(x, y, overlayCanvas);
+ }
+
+ double backY = 0.88 + yOffset;
+ var backPositions = new List<(double XNorm, double YNorm)>
+ {
+ (0.10, backY),
+ (0.35, backY),
+ (0.60, backY),
+ (0.88, backY)
+ };
+
+ foreach (var pos in backPositions)
+ {
+ double x = pos.XNorm * imgWidth;
+ double y = pos.YNorm * imgHeight;
+
+ jumpRopeConfig.Points.Add(new PointConfig
+ {
+ X = x,
+ Y = y,
+ Radius = backRadius,
+ XNorm = pos.XNorm,
+ YNorm = pos.YNorm
+ });
+
+ AddGlowEllipse(x, y, overlayCanvas);
+ }
+ }
+ else
+ {
+ foreach (var point in jumpRopeConfig.Points)
+ {
+ double x = point.XNorm * imgWidth;
+ double y = point.YNorm * imgHeight;
+ AddGlowEllipse(x, y, overlayCanvas);
+ }
+ }
+
+ if (needSaveConfig)
+ {
+ configService.ConfigDic[jumpRopeConfig.Name] = jumpRopeConfig;
+ configService.SaveAllConfigs();
+ }
+ }
+
+ ///
+ /// 添加带渐变光的圆圈(中心红色,边缘蓝色)
+ ///
+ private void AddGlowEllipse(double centerX, double centerY, Canvas canvas)
+ {
+ double radius = 70; // 统一半径
+ double flattenFactor = 0.5; // 统一扁平化比例
+ double opacity = 0.8; // 统一透明度
+
+ 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(Color.FromArgb(220, 255, 255, 0), 0.0), // 中心黄
+ new GradientStop(Color.FromArgb(180, 255, 255, 0), 0.4), // 中间黄
+ new GradientStop(Color.FromArgb(180, 0, 0, 255), 0.7), // 边缘蓝
+ new GradientStop(Color.FromArgb(0, 0, 0, 255), 1.0) // 外部透明
+ }
+ }
+ };
+
+ // 定位到中心
+ Canvas.SetLeft(ellipse, centerX - radius);
+ Canvas.SetTop(ellipse, centerY - (radius * flattenFactor) / 2);
+ canvas.Children.Add(ellipse);
}
}
}
diff --git a/sdks/Yztob.AiSports.Common.dll b/sdks/Yztob.AiSports.Common.dll
index 534f254..98ab8c8 100644
Binary files a/sdks/Yztob.AiSports.Common.dll and b/sdks/Yztob.AiSports.Common.dll differ
diff --git a/sdks/Yztob.AiSports.Inferences.dll b/sdks/Yztob.AiSports.Inferences.dll
index 610e1d0..7b24909 100644
Binary files a/sdks/Yztob.AiSports.Inferences.dll and b/sdks/Yztob.AiSports.Inferences.dll differ
diff --git a/sdks/Yztob.AiSports.Inferences.xml b/sdks/Yztob.AiSports.Inferences.xml
index 0f8bf63..f4bc390 100644
--- a/sdks/Yztob.AiSports.Inferences.xml
+++ b/sdks/Yztob.AiSports.Inferences.xml
@@ -52,7 +52,7 @@
单人检测-高精度,3D结构
-
+
多人检测-低精度低速率,2D结构
diff --git a/sdks/Yztob.AiSports.Postures.dll b/sdks/Yztob.AiSports.Postures.dll
index 6902d42..3690e60 100644
Binary files a/sdks/Yztob.AiSports.Postures.dll and b/sdks/Yztob.AiSports.Postures.dll differ
diff --git a/sdks/Yztob.AiSports.Sensors.WinForm.dll b/sdks/Yztob.AiSports.Sensors.WinForm.dll
index a7a482d..5ee5135 100644
Binary files a/sdks/Yztob.AiSports.Sensors.WinForm.dll and b/sdks/Yztob.AiSports.Sensors.WinForm.dll differ
diff --git a/sdks/Yztob.AiSports.Sensors.WinForm.xml b/sdks/Yztob.AiSports.Sensors.WinForm.xml
index e6ddd23..8990204 100644
--- a/sdks/Yztob.AiSports.Sensors.WinForm.xml
+++ b/sdks/Yztob.AiSports.Sensors.WinForm.xml
@@ -55,6 +55,11 @@
获取或设置自应用大小,保持同比缩放
+
+
+ 获取或设置是否展示帧序号、帧率
+
+
获取当前是否正在播放
@@ -75,9 +80,14 @@
获取当画面缩放后X轴的偏移量,即黑边大小
+
+
+ 获取或设置视频流拉取帧率
+
+
- 获取或设置抽帧响应
+ 获取或设置抽帧响应,处理的是未解析的原始帧
diff --git a/sdks/Yztob.AiSports.Sensors.dll b/sdks/Yztob.AiSports.Sensors.dll
index c90d584..350b6b0 100644
Binary files a/sdks/Yztob.AiSports.Sensors.dll and b/sdks/Yztob.AiSports.Sensors.dll differ
diff --git a/sdks/Yztob.AiSports.Sensors.xml b/sdks/Yztob.AiSports.Sensors.xml
index cab0d31..0d0d629 100644
--- a/sdks/Yztob.AiSports.Sensors.xml
+++ b/sdks/Yztob.AiSports.Sensors.xml
@@ -312,6 +312,12 @@
获取或设置时间戳,从UTC:2000-01-01开始的耗秒数
+
+
+ 获取当前时间戳
+
+
+
将当前帧转换成指定格式图像,并返回图像缓冲区数组