diff --git a/Wpf_AiSportsMicrospace/MainWindow.xaml.cs b/Wpf_AiSportsMicrospace/MainWindow.xaml.cs index a137ef1..aca9758 100644 --- a/Wpf_AiSportsMicrospace/MainWindow.xaml.cs +++ b/Wpf_AiSportsMicrospace/MainWindow.xaml.cs @@ -113,8 +113,6 @@ public partial class MainWindow : Window //var ip = config.Ip; } - - var host = this.host.Text.Trim(); if (string.IsNullOrWhiteSpace(host)) { diff --git a/Wpf_AiSportsMicrospace/Views/Home.xaml.cs b/Wpf_AiSportsMicrospace/Views/Home.xaml.cs index edf08f7..fb39716 100644 --- a/Wpf_AiSportsMicrospace/Views/Home.xaml.cs +++ b/Wpf_AiSportsMicrospace/Views/Home.xaml.cs @@ -14,6 +14,7 @@ using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; +using Views.JumpLong; using Views.JumpRope; using Wpf_AiSportsMicrospace; using Wpf_AiSportsMicrospace.Common; @@ -150,7 +151,7 @@ namespace Wpf_AiSportsMicrospace } else { - var newPage = new TrainingRecords(); + var newPage = new StandingLeap(); mainWin?.SwitchPageWithMaskAnimation(newPage, true); } } diff --git a/Wpf_AiSportsMicrospace/Views/JumpLong/StandingLeap.xaml b/Wpf_AiSportsMicrospace/Views/JumpLong/StandingLeap.xaml new file mode 100644 index 0000000..0dde678 --- /dev/null +++ b/Wpf_AiSportsMicrospace/Views/JumpLong/StandingLeap.xaml @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/Wpf_AiSportsMicrospace/Views/JumpLong/StandingLeap.xaml.cs b/Wpf_AiSportsMicrospace/Views/JumpLong/StandingLeap.xaml.cs new file mode 100644 index 0000000..09fb312 --- /dev/null +++ b/Wpf_AiSportsMicrospace/Views/JumpLong/StandingLeap.xaml.cs @@ -0,0 +1,257 @@ +using Dto; +using Enum; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Text; +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.Navigation; +using System.Windows.Shapes; +using Wpf_AiSportsMicrospace; +using Wpf_AiSportsMicrospace.Common; +using Wpf_AiSportsMicrospace.Enum; +using Wpf_AiSportsMicrospace.MyUserControl; +using Wpf_AiSportsMicrospace.Views; +using Yztob.AiSports.Inferences.Abstractions; +using Yztob.AiSports.Inferences.Implement; +using Yztob.AiSports.Inferences.Things; +using Yztob.AiSports.Postures.Sports; +using Yztob.AiSports.Postures.Things; +using Yztob.AiSports.Sensors.Abstractions; +using Yztob.AiSports.Sensors.Things; + +namespace Views.JumpLong +{ + /// + /// StandingLeap.xaml 的交互逻辑 + /// + public partial class StandingLeap : UserControl + { + #region 私有成员 + private Main _mainWin => Application.Current.MainWindow as Main; + private IHumanPredictor _humanPredictor; + private IObjectDetector _objectDetector; + private HumanGraphicsRenderer _humanGraphicsRenderer; + private SportBase _sport; + private ApparatusLongJumpDelineation _measureApparatus; + private MediaPlayer _mediaPlayer = new MediaPlayer(); + private readonly SportDetectionQueue _detectQueue; + #endregion + + public StandingLeap() + { + InitializeComponent(); + Loaded += UserControl_Loaded; + Unloaded += UserControl_Unloaded; + + _humanPredictor = HumanPredictorFactory.Create(HumanPredictorType.SingleHigh); + _humanGraphicsRenderer = new HumanGraphicsRenderer(); + _humanGraphicsRenderer.DrawLabel = false; + _objectDetector = ObjectDetectorFactory.CreateSportGoodsDetector(); + _detectQueue = new SportDetectionQueue(); + + _sport = SportBase.Create("standing-long-jump"); + _sport.OnTicked += this.OnSportTick; + //if (_sport?.IsCounting == true) + _sport.Start(); + + _measureApparatus = new ApparatusLongJumpDelineation(); + _measureApparatus.MeasureLength = 300; + var _isCalibrated = this.ReactangleCalibrating(1920, 1080); + _sport.MeasureApparatus = _measureApparatus; + + _detectQueue.Sport = _sport; + _detectQueue.Start(); + + // 开始抽帧线程 + _mainWin.FacialRecognitionEvent += HumanPredicting; + } + + private async void UserControl_Loaded(object sender, RoutedEventArgs e) + { + // 播放音乐 + PlayMusic("raisehand.mp3"); + } + + private void UserControl_Unloaded(object sender, RoutedEventArgs e) + { + _sport.Stop(); + _humanPredictor?.Dispose(); + _objectDetector?.Dispose(); + } + + private async void HumanPredicting(VideoFrame frame) + { + try + { + var buffer = frame.GetImageBuffer(ImageFormat.Jpeg).ToArray(); + var humanResult = await Task.Run(() => _humanPredictor.Predicting(buffer, frame.Number)); + var objects = _sport.Equipment ? await Task.Run>(() => _objectDetector.Detecting(buffer)) : new List(); + if (_sport.MeasureApparatus != null) + objects.AddRange(_sport.MeasureApparatus.Apparatuses); + + //运动需要保证时序 + var human = humanResult?.Humans?.FirstOrDefault(); + _detectQueue.Enqueue(frame.Number, human, objects); + } + catch (Exception ex) + { + Console.WriteLine("ProcessFrame error: " + ex.Message); + } + } + + 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; + //_mainWin.WebcamClient.StartExtract(); + _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("音乐播放完成!"); + } + + private void OnSportTick(int counts, int times) + { + var ts = TimeSpan.FromSeconds(times); + Dispatcher.BeginInvoke(() => + { + sportCounts.Content = _sport.GetFormatCounts(); //counts.ToString(); + sportTimes.Content = _sport.GetFormatTimes();//ts.ToString(@"mm\'ss\"""); + + //触发停止 + if (!_sport.IsCounting) + { + _sport.Stop(); + _sport.Start(); + } + }); + } + private bool ReactangleCalibrating(int frameWidth, int frameHeight) + { + var boxes = new List() + { + new BoundingBox(){ + ImageWidth = frameWidth, + ImageHeight = frameHeight, + Label = new PredictionLabel(){ + Key = "begin" + }, + MaskLayer = new MaskLayer(){ + Contours = new List(){ + new SixLabors.ImageSharp.PointF(1722, 909), + new SixLabors.ImageSharp.PointF(1689, 724), + } + } + }, + new BoundingBox(){ + ImageWidth = frameWidth, + ImageHeight = frameHeight, + Label = new PredictionLabel{ + Key = "end" + }, + MaskLayer = new MaskLayer(){ + Contours = new List(){ + new SixLabors.ImageSharp.PointF(1516, 918), + new SixLabors.ImageSharp.PointF(1498, 730) + } + } + } + }; + + boxes[1].Label.Key = "take-off"; + boxes.Add(new BoundingBox() + { + ImageWidth = frameWidth, + ImageHeight = frameHeight, + Label = new PredictionLabel + { + Key = "end" + }, + MaskLayer = new MaskLayer() + { + Contours = new List(){ + new SixLabors.ImageSharp.PointF(191, 918), + new SixLabors.ImageSharp.PointF(245, 741) + } + } + }); + + return _measureApparatus.Calibrating(boxes); + } + } +} diff --git a/Wpf_AiSportsMicrospace/Views/JumpRope/GroupJumpRope.xaml.cs b/Wpf_AiSportsMicrospace/Views/JumpRope/GroupJumpRope.xaml.cs index 80d9032..51b41d2 100644 --- a/Wpf_AiSportsMicrospace/Views/JumpRope/GroupJumpRope.xaml.cs +++ b/Wpf_AiSportsMicrospace/Views/JumpRope/GroupJumpRope.xaml.cs @@ -1,4 +1,5 @@ -using Dto; +using AiSportsMicrospaceDB.DBContext; +using Dto; using Emgu.CV.Flann; using Enum; using HandyControl.Controls; @@ -37,6 +38,7 @@ using Yztob.AiSports.Inferences.Things; using Yztob.AiSports.Postures.Sports; using Yztob.AiSports.Sensors.Abstractions; using Yztob.AiSports.Sensors.Things; +using static System.Formats.Asn1.AsnWriter; namespace Wpf_AiSportsMicrospace.Views { @@ -49,7 +51,6 @@ namespace Wpf_AiSportsMicrospace.Views private MediaPlayer _mediaPlayer = new MediaPlayer(); List RankingItemList = new(); - private readonly SportDetectionQueue _detectQueue; public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { @@ -59,22 +60,23 @@ namespace Wpf_AiSportsMicrospace.Views private readonly object _updateLock = new object(); private readonly Dictionary _jumpStatus = new Dictionary(); private GroupJumpRopeContext _groupJumpRopeContext; + public GroupJumpRope() { InitializeComponent(); - _detectQueue = new SportDetectionQueue(); Loaded += UserControl_Loaded; Unloaded += UserControl_Unloaded; _groupJumpRopeContext = new GroupJumpRopeContext(); - } + private async void UserControl_Loaded(object sender, RoutedEventArgs e) { // 使用局部变量保存事件处理方法,以便解绑 - EventHandler handler = null; + Action handler = null; - handler = async (sender, buffer) => + handler = async (frame) => { + var buffer = frame.GetImageBuffer(ImageFormat.Jpeg).ToArray(); var studentList = Utils.FacialRecognition(buffer, new double[] { 0.21, 0.36, 0.50, 0.64, 0.78, 0.92 }); if (studentList != null && studentList.Count > 0) @@ -82,7 +84,6 @@ namespace Wpf_AiSportsMicrospace.Views // 解绑事件 _mainWin.FacialRecognitionEvent -= handler; } - // 这里可以继续处理 studentList }; diff --git a/Wpf_AiSportsMicrospace/Views/Main.xaml.cs b/Wpf_AiSportsMicrospace/Views/Main.xaml.cs index ae587da..a65c90c 100644 --- a/Wpf_AiSportsMicrospace/Views/Main.xaml.cs +++ b/Wpf_AiSportsMicrospace/Views/Main.xaml.cs @@ -1,4 +1,5 @@  +using SharpDX.Direct3D9; using System.Collections.Concurrent; using System.Drawing; using System.IO; @@ -38,7 +39,9 @@ namespace Wpf_AiSportsMicrospace.Views private readonly object _reconnectLock = new(); public event EventHandler> HumanFrameUpdated; - public event EventHandler FacialRecognitionEvent; + + public event Action FacialRecognitionEvent; + public Main() { @@ -106,14 +109,14 @@ namespace Wpf_AiSportsMicrospace.Views //if (frame.Number % 2 != 0) // return; - var buffer = frame.GetImageBuffer(ImageFormat.Jpeg).ToArray(); - // 触发全局事件 Application.Current.Dispatcher.BeginInvoke(() => { - FacialRecognitionEvent?.Invoke(this, buffer); + FacialRecognitionEvent?.Invoke(frame); }); + + var buffer = frame.GetImageBuffer(ImageFormat.Jpeg).ToArray(); var humanResult = _humanPredictor.Predicting(buffer, frame.Number); var humans = humanResult?.Humans?.ToList(); diff --git a/sdks/Yztob.AiSports.Inferences.dll b/sdks/Yztob.AiSports.Inferences.dll index 7b24909..f02aa46 100644 Binary files a/sdks/Yztob.AiSports.Inferences.dll and b/sdks/Yztob.AiSports.Inferences.dll differ diff --git a/sdks/Yztob.AiSports.Postures.dll b/sdks/Yztob.AiSports.Postures.dll index 3690e60..2d51421 100644 Binary files a/sdks/Yztob.AiSports.Postures.dll and b/sdks/Yztob.AiSports.Postures.dll differ diff --git a/sdks/Yztob.AiSports.Postures.xml b/sdks/Yztob.AiSports.Postures.xml index bf3a7a0..82150bd 100644 --- a/sdks/Yztob.AiSports.Postures.xml +++ b/sdks/Yztob.AiSports.Postures.xml @@ -6,7 +6,7 @@ - 人体姿态计算检测执行器 + 人体姿态检测规则执行计算器 @@ -727,6 +727,17 @@ 一些扩展方法 + + + 获取当前的人体重力线 + + 要计算的人体 + + 返回重力线的上、下起点 + [0]-为线的上端点 + [1]-为线的下端点 + + 坐标值转换成 @@ -734,6 +745,13 @@ 要转换的关键点 + + + 获取当前关键点集中的中心点位置 + + 关键点集 + + 人体姿态计算检测执行器 @@ -1326,6 +1344,70 @@ + + + 姿态分析相关工具函数集 + + + + + 获取身体关键点键-名映射字典 + + + + + + 获取相机视角名称 + + 当前视角 + + + + + 计算平面三个点的角度 + + 角度点 + 上角点 + 下角点 + + -1至少一个有一个坐标点是空的;否则返回计算角度; + + + + + 计算三个人体关键点角度 + + 角度人体关键点 + 上角人体关键点 + 下角人体关键点 + + 计算后的角度 + + + + + 断言是否有匹配 + + 实际测量的角度 + 期望的角度 + 允许的偏差 + + + + + 计算两个边界框的交并比 + + 框1 + 框2 + + + + + 计算多个关键点之间的中心点位置 + + 关键点集 + + 运动计数器计数变化委托 @@ -1752,6 +1834,9 @@ 获取或设置垫子长度,单位cm + + + @@ -2335,63 +2420,6 @@ 当前势 趋势是否变化 - - - 姿态分析相关工具函数集 - - - - - 获取身体关键点键-名映射字典 - - - - - - 获取相机视角名称 - - 当前视角 - - - - - 计算平面三个点的角度 - - 角度点 - 上角点 - 下角点 - - -1至少一个有一个坐标点是空的;否则返回计算角度; - - - - - 计算三个人体关键点角度 - - 角度人体关键点 - 上角人体关键点 - 下角人体关键点 - - 计算后的角度 - - - - - 断言是否有匹配 - - 实际测量的角度 - 期望的角度 - 允许的偏差 - - - - - 计算两个边界框的交并比 - - 框1 - 框2 - - 表示受权机器CPU部件特性图 diff --git a/sdks/Yztob.AiSports.Sensors.WinForm.dll b/sdks/Yztob.AiSports.Sensors.WinForm.dll index 5ee5135..0dd949a 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 8990204..5cf4c22 100644 --- a/sdks/Yztob.AiSports.Sensors.WinForm.xml +++ b/sdks/Yztob.AiSports.Sensors.WinForm.xml @@ -111,6 +111,12 @@ + + + 直接向预览器推送帧,进行预览回放 + + 帧图像 + 启动抽帧/预览 diff --git a/sdks/Yztob.AiSports.Sensors.dll b/sdks/Yztob.AiSports.Sensors.dll index 350b6b0..4d74540 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 0d0d629..801d5d5 100644 --- a/sdks/Yztob.AiSports.Sensors.xml +++ b/sdks/Yztob.AiSports.Sensors.xml @@ -218,12 +218,13 @@ 视频流解码器 - + 初始化解码器 设备访问url 硬件设备类型 + 视频连接超时是间 @@ -307,9 +308,21 @@ 获取FFmpeg原始帧 + + + 获取或设置帧相对流开始的展示时间戳,单位:秒 + + 通俗的理解便是,此帧在视频的第几秒出现的 + + + + 获取或设置上一帧距离这帧维持的时长 + + 即用当帧的减去上一帧的 + - 获取或设置时间戳,从UTC:2000-01-01开始的耗秒数 + 获取或设置帧提取时的系统时间戳,从UTC:2000-01-01开始的耗秒数 @@ -365,6 +378,18 @@ 获取FFmpeg原始帧 + + + 获取或设置帧相对流开始的展示时间戳,单位:秒 + + 通俗的理解便是,此帧在视频的第几秒出现的 + + + + 获取或设置上一帧距离这帧维持的时长 + + 即用当帧的减去上一帧的 + 获取或设置时间戳,从UTC:2000-01-01开始的耗秒数