立定跳远

This commit is contained in:
tanglong 2025-11-18 11:30:48 +08:00
parent bb2c3bd008
commit d8a17e013a
13 changed files with 408 additions and 74 deletions

View File

@ -113,8 +113,6 @@ public partial class MainWindow : Window
//var ip = config.Ip;
}
var host = this.host.Text.Trim();
if (string.IsNullOrWhiteSpace(host))
{

View File

@ -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);
}
}

View File

@ -0,0 +1,15 @@
<UserControl x:Class="Views.JumpLong.StandingLeap"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Views.JumpLong"
mc:Ignorable="d"
Height="1080" Width="1920" Loaded="UserControl_Loaded" Unloaded="UserControl_Unloaded">
<Grid>
<Grid Height="1080" Width="1920" x:Name="userBox">
<Label x:Name="sportCounts" Height="50" Width="200" HorizontalAlignment="Left" BorderBrush="Black" BorderThickness="2"></Label>
<Label x:Name="sportTimes" Height="50" Width="200" HorizontalAlignment="Center" BorderBrush="Black" BorderThickness="2" ></Label>
</Grid>
</Grid>
</UserControl>

View File

@ -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
{
/// <summary>
/// StandingLeap.xaml 的交互逻辑
/// </summary>
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<HumanInferenceResult>(() => _humanPredictor.Predicting(buffer, frame.Number));
var objects = _sport.Equipment ? await Task.Run<List<BoundingBox>>(() => _objectDetector.Detecting(buffer)) : new List<BoundingBox>();
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<BoundingBox>()
{
new BoundingBox(){
ImageWidth = frameWidth,
ImageHeight = frameHeight,
Label = new PredictionLabel(){
Key = "begin"
},
MaskLayer = new MaskLayer(){
Contours = new List<SixLabors.ImageSharp.PointF>(){
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<SixLabors.ImageSharp.PointF>(){
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<SixLabors.ImageSharp.PointF>(){
new SixLabors.ImageSharp.PointF(191, 918),
new SixLabors.ImageSharp.PointF(245, 741)
}
}
});
return _measureApparatus.Calibrating(boxes);
}
}
}

View File

@ -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<RankItem> 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<int, (string lastNumber, DateTime lastChangeTime, string currentState)> _jumpStatus = new Dictionary<int, (string, DateTime, string)>();
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<byte[]> handler = null;
Action<VideoFrame> 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
};

View File

@ -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<List<Human>> HumanFrameUpdated;
public event EventHandler<Byte[]> FacialRecognitionEvent;
public event Action<VideoFrame> 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();

Binary file not shown.

Binary file not shown.

View File

@ -6,7 +6,7 @@
<members>
<member name="T:Yztob.AiSports.Postures.Abstractions.ICalculator">
<summary>
人体姿态计算检测执行器
人体姿态检测规则执行计算
</summary>
</member>
<member name="P:Yztob.AiSports.Postures.Abstractions.ICalculator.PointThreshold">
@ -727,6 +727,17 @@
一些扩展方法
</summary>
</member>
<member name="M:Yztob.AiSports.Postures.Extensions.GravityLine(Yztob.AiSports.Inferences.Things.Human)">
<summary>
获取当前<seealso cref="T:Yztob.AiSports.Inferences.Things.Human"/>的人体重力线
</summary>
<param name="human">要计算的人体</param>
<returns>
返回重力线的上、下起点
[0]-为线的上端点
[1]-为线的下端点
</returns>
</member>
<member name="M:Yztob.AiSports.Postures.Extensions.AsPointF(Yztob.AiSports.Inferences.Things.Keypoint)">
<summary>
<paramref name="keypoint"/>坐标值转换成<seealso cref="T:System.Drawing.PointF"/>
@ -734,6 +745,13 @@
<param name="keypoint">要转换的关键点</param>
<returns></returns>
</member>
<member name="M:Yztob.AiSports.Postures.Extensions.ToCenterPoint(System.Collections.Generic.IEnumerable{Yztob.AiSports.Inferences.Things.Keypoint})">
<summary>
获取当前关键点集中的中心点位置
</summary>
<param name="keypoints">关键点集</param>
<returns></returns>
</member>
<member name="T:Yztob.AiSports.Postures.Implement.Calculator">
<summary>
人体姿态计算检测执行器
@ -1326,6 +1344,70 @@
</summary>
<returns></returns>
</member>
<member name="T:Yztob.AiSports.Postures.PostureUtility">
<summary>
姿态分析相关工具函数集
</summary>
</member>
<member name="M:Yztob.AiSports.Postures.PostureUtility.GetBodyKeyNameMaps">
<summary>
获取身体关键点键-名映射字典
</summary>
<returns></returns>
</member>
<member name="M:Yztob.AiSports.Postures.PostureUtility.GetCameraViewName(Yztob.AiSports.Postures.Things.CameraViews)">
<summary>
获取相机视角名称
</summary>
<param name="view">当前视角</param>
<returns></returns>
</member>
<member name="M:Yztob.AiSports.Postures.PostureUtility.Angle(System.Nullable{System.Drawing.PointF},System.Nullable{System.Drawing.PointF},System.Nullable{System.Drawing.PointF})">
<summary>
计算平面三个点的角度
</summary>
<param name="center">角度点</param>
<param name="second">上角点</param>
<param name="third">下角点</param>
<returns>
-1至少一个有一个坐标点是空的否则返回计算角度
</returns>
</member>
<member name="M:Yztob.AiSports.Postures.PostureUtility.Angle(Yztob.AiSports.Inferences.Things.Keypoint,Yztob.AiSports.Inferences.Things.Keypoint,Yztob.AiSports.Inferences.Things.Keypoint)">
<summary>
计算三个人体关键点角度
</summary>
<param name="center">角度人体关键点</param>
<param name="second">上角人体关键点</param>
<param name="third">下角人体关键点</param>
<returns>
计算后的角度
</returns>
</member>
<member name="M:Yztob.AiSports.Postures.PostureUtility.AssertAngle(System.Single,System.Single,System.Single)">
<summary>
断言<paramref name="angle"/>是否有<paramref name="expect"/>匹配
</summary>
<param name="angle">实际测量的角度</param>
<param name="expect">期望的角度</param>
<param name="offset">允许的偏差</param>
<returns></returns>
</member>
<member name="M:Yztob.AiSports.Postures.PostureUtility.IoU(Yztob.AiSports.Inferences.Things.BoundingBox,Yztob.AiSports.Inferences.Things.BoundingBox)">
<summary>
计算两个边界框的交并比
</summary>
<param name="box1">框1</param>
<param name="box2">框2</param>
<returns></returns>
</member>
<member name="M:Yztob.AiSports.Postures.PostureUtility.CenterPoint(System.Collections.Generic.IEnumerable{Yztob.AiSports.Inferences.Things.Keypoint})">
<summary>
计算多个关键点之间的中心点位置
</summary>
<param name="keypoints">关键点集</param>
<returns></returns>
</member>
<member name="T:Yztob.AiSports.Postures.Sports.CounterTicked">
<summary>
运动计数器计数变化委托
@ -1752,6 +1834,9 @@
获取或设置垫子长度单位cm
</summary>
</member>
<member name="M:Yztob.AiSports.Postures.Sports.SportStandingLongJump.Pushing(Yztob.AiSports.Inferences.Things.Human)">
<inheritdoc />
</member>
<member name="M:Yztob.AiSports.Postures.Sports.SportStandingLongJump.Pushing(Yztob.AiSports.Inferences.Things.Human,System.Collections.Generic.IEnumerable{Yztob.AiSports.Inferences.Things.BoundingBox})">
<inheritdoc />
</member>
@ -2335,63 +2420,6 @@
<param name="trend">当前势</param>
<param name="isChanged">趋势是否变化</param>
</member>
<member name="T:Yztob.AiSports.Postures.Utility">
<summary>
姿态分析相关工具函数集
</summary>
</member>
<member name="M:Yztob.AiSports.Postures.Utility.GetBodyKeyNameMaps">
<summary>
获取身体关键点键-名映射字典
</summary>
<returns></returns>
</member>
<member name="M:Yztob.AiSports.Postures.Utility.GetCameraViewName(Yztob.AiSports.Postures.Things.CameraViews)">
<summary>
获取相机视角名称
</summary>
<param name="view">当前视角</param>
<returns></returns>
</member>
<member name="M:Yztob.AiSports.Postures.Utility.Angle(System.Nullable{System.Drawing.PointF},System.Nullable{System.Drawing.PointF},System.Nullable{System.Drawing.PointF})">
<summary>
计算平面三个点的角度
</summary>
<param name="center">角度点</param>
<param name="second">上角点</param>
<param name="third">下角点</param>
<returns>
-1至少一个有一个坐标点是空的否则返回计算角度
</returns>
</member>
<member name="M:Yztob.AiSports.Postures.Utility.Angle(Yztob.AiSports.Inferences.Things.Keypoint,Yztob.AiSports.Inferences.Things.Keypoint,Yztob.AiSports.Inferences.Things.Keypoint)">
<summary>
计算三个人体关键点角度
</summary>
<param name="center">角度人体关键点</param>
<param name="second">上角人体关键点</param>
<param name="third">下角人体关键点</param>
<returns>
计算后的角度
</returns>
</member>
<member name="M:Yztob.AiSports.Postures.Utility.AssertAngle(System.Single,System.Single,System.Single)">
<summary>
断言<paramref name="angle"/>是否有<paramref name="expect"/>匹配
</summary>
<param name="angle">实际测量的角度</param>
<param name="expect">期望的角度</param>
<param name="offset">允许的偏差</param>
<returns></returns>
</member>
<member name="M:Yztob.AiSports.Postures.Utility.IoU(Yztob.AiSports.Inferences.Things.BoundingBox,Yztob.AiSports.Inferences.Things.BoundingBox)">
<summary>
计算两个边界框的交并比
</summary>
<param name="box1">框1</param>
<param name="box2">框2</param>
<returns></returns>
</member>
<member name="T:Yztob.AiSports.Common.Licenses.FeatureCpuPartMap">
<summary>
表示受权机器CPU部件特性图

View File

@ -111,6 +111,12 @@
<member name="M:Yztob.AiSports.Sensors.WinForm.RTSPPreview.Dispose">
<inheritdoc />
</member>
<member name="M:Yztob.AiSports.Sensors.WinForm.RTSPPreview.Pushing(Yztob.AiSports.Sensors.Things.VideoFrameWithBuffer)">
<summary>
直接向预览器推送帧,进行预览回放
</summary>
<param name="frame">帧图像</param>
</member>
<member name="M:Yztob.AiSports.Sensors.WinForm.RTSPPreview.Play">
<summary>
启动抽帧/预览

Binary file not shown.

View File

@ -218,12 +218,13 @@
视频流解码器
</summary>
</member>
<member name="M:Yztob.AiSports.Sensors.Implement.VideoStreamDecoder.#ctor(System.String,FFmpeg.AutoGen.Abstractions.AVHWDeviceType)">
<member name="M:Yztob.AiSports.Sensors.Implement.VideoStreamDecoder.#ctor(System.String,FFmpeg.AutoGen.Abstractions.AVHWDeviceType,System.Int32)">
<summary>
初始化解码器
</summary>
<param name="url">设备访问url</param>
<param name="HWDeviceType">硬件设备类型</param>
<param name="timeout">视频连接超时是间</param>
</member>
<member name="P:Yztob.AiSports.Sensors.Implement.VideoStreamDecoder.CodecName">
<summary>
@ -307,9 +308,21 @@
获取FFmpeg原始帧
</summary>
</member>
<member name="P:Yztob.AiSports.Sensors.Things.VideoFrame.PresentTimestamp">
<summary>
获取或设置帧相对流开始的展示时间戳,单位:秒
</summary>
<remarks>通俗的理解便是,此帧在视频的第几秒出现的</remarks>
</member>
<member name="P:Yztob.AiSports.Sensors.Things.VideoFrame.PreserveDuration">
<summary>
获取或设置上一帧距离这帧维持的时长
</summary>
<remarks>即用当帧的<see cref="P:Yztob.AiSports.Sensors.Things.VideoFrame.PresentTimestamp"/>减去上一帧的<see cref="P:Yztob.AiSports.Sensors.Things.VideoFrame.PresentTimestamp"/></remarks>
</member>
<member name="P:Yztob.AiSports.Sensors.Things.VideoFrame.Timestamp">
<summary>
获取或设置时间戳从UTC:2000-01-01开始的耗秒数
获取或设置帧提取时的系统时间戳从UTC:2000-01-01开始的耗秒数
</summary>
</member>
<member name="M:Yztob.AiSports.Sensors.Things.VideoFrame.GetTimestamp">
@ -365,6 +378,18 @@
获取FFmpeg原始帧
</summary>
</member>
<member name="P:Yztob.AiSports.Sensors.Things.VideoFrameWithBuffer.PresentTimestamp">
<summary>
获取或设置帧相对流开始的展示时间戳,单位:秒
</summary>
<remarks>通俗的理解便是,此帧在视频的第几秒出现的</remarks>
</member>
<member name="P:Yztob.AiSports.Sensors.Things.VideoFrameWithBuffer.PreserveDuration">
<summary>
获取或设置上一帧距离这帧维持的时长
</summary>
<remarks>即用当帧的<see cref="P:Yztob.AiSports.Sensors.Things.VideoFrameWithBuffer.PresentTimestamp"/>减去上一帧的<see cref="P:Yztob.AiSports.Sensors.Things.VideoFrameWithBuffer.PresentTimestamp"/></remarks>
</member>
<member name="P:Yztob.AiSports.Sensors.Things.VideoFrameWithBuffer.Timestamp">
<summary>
获取或设置时间戳从UTC:2000-01-01开始的耗秒数