2026-03-13 16:26:55 +08:00

587 lines
22 KiB
C#

using AiSportsMicrospaceDB.DBContext;
using AiSportsMicrospaceDB.Entities;
using Common;
using Dto;
using Enum;
using HandyControl.Controls;
using Microsoft.Extensions.DependencyInjection;
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 WpfAnimatedGif;
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;
private JumpLongPoints _Points;
private int _sportType = 0; // 0:未开始 1:运动中 2:运动结束展示排行榜
#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();
_detectQueue.Start();
_measureApparatus = new ApparatusLongJumpDelineation();
_measureApparatus.MeasureLength = 300;
}
private async void UserControl_Loaded(object sender, RoutedEventArgs e)
{
// 播放音乐
PlayMusic("jumphand.mp3");
var app = (App)Application.Current;
using var scope = App.AppHost.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
_Points = db.JumpLongPoints.FirstOrDefault(x => x.Code == AppSettings.Code);
_mainWin.HumanFrameUpdated += OnFrameUpdated;
_sport = SportBase.Create("standing-long-jump");
_sport.OnTicked += this.OnSportTick;
_sport.Start();
// 开始跳远抽帧线程
_mainWin.FacialRecognitionEvent += this.HumanPredicting;
var _isCalibrated = this.ReactangleCalibrating(1920, 1080);
_sport.MeasureApparatus = _measureApparatus;
_detectQueue.Sport = _sport;
}
//举手检测
private void OnFrameUpdated(object sender, List<Human> humans)
{
try
{
if (humans == null || humans.Count == 0) return;
//举手检测 未开始 :退出& 开始 运动中:不检测 2.结束:继续测试 & 退出
if (_sportType != 1)
{
//左手返回
int leftWaving = DetectLeftHandRaise(humans);
if (leftWaving == 5)
{
//_mainWin.HumanFrameUpdated -= this.OnFrameUpdated;
//_mainWin.WebcamClient.StopExtract();
// 举左手逻辑,例如结束动画或退出
var newPage = new Home();
_mainWin?.SwitchPageWithMaskAnimation(newPage, true);
return; // 提前退出
}
//右手判断
int rightWaving = DetectRightHandRaise(humans);
if (rightWaving >= 3 && _sportType == 0) //举手完成 开始跳远检测
{
TipBox.Opacity = 0;
_sportType = 1;
StartSport();
}
}
}
catch (Exception ex)
{
Console.WriteLine("OnFrameExtracted error: " + ex.Message);
}
}
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);
if (!File.Exists(musicPath))
{
Console.WriteLine($"音乐文件不存在: {musicPath}");
return;
}
_mediaPlayer.Open(new Uri(musicPath, UriKind.Absolute));
ShowCenterTip(TimeSpan.FromSeconds(3));
// 监听播放完成事件
_mediaPlayer.MediaEnded += MediaPlayer_MediaEnded;
//_mainWin.WebcamClient.StartExtract();
_mediaPlayer.Play();
}
private void ShowCenterTip(TimeSpan duration)
{
// 渐变出现动画
var fadeInAnimation = new DoubleAnimation
{
From = 0,
To = 1,
Duration = TimeSpan.FromSeconds(1.5)
};
TipBox.BeginAnimation(UIElement.OpacityProperty, fadeInAnimation);
//// 定时移除,并且渐变消失
//Task.Delay(duration).ContinueWith(_ =>
//{
// Dispatcher.Invoke(() =>
// {
// // 渐变消失动画
// var fadeOutAnimation = new DoubleAnimation
// {
// From = 1,
// To = 0,
// Duration = TimeSpan.FromSeconds(1.5)
// };
// TipBox.BeginAnimation(UIElement.OpacityProperty, fadeOutAnimation);
// });;
//});
}
private void MediaPlayer_MediaEnded(object sender, EventArgs e)
{
// 音乐播放完成后的逻辑
Console.WriteLine("音乐播放完成!");
}
private void OnSportTick(int counts, int times)
{
var ts = TimeSpan.FromSeconds(times);
_ = Dispatcher.BeginInvoke(async () =>
{
//sportCounts.Content = _sport.GetFormatCounts(); //counts.ToString();
//sportTimes.Content = _sport.GetFormatTimes();//ts.ToString(@"mm\'ss\""");
var counts = _sport.GetFormatCounts();
if (double.Parse(counts[..^1]) > 0 && _sportType == 1) //正向跳远时候&&处于测试阶段时候触发更新
{
ImageBehavior.SetRepeatBehavior(centerImg4, new RepeatBehavior(2));
ImageBehavior.SetRepeatBehavior(centerImg4, new RepeatBehavior(1));
ImageBehavior.GetAnimationController(centerImg4).Play();
await Task.Delay(2100);
_sport.Stop();//关闭运动
//_mainWin.FacialRecognitionEvent -= this.HumanPredicting;
count.Text = counts;
time.Text = _sport.GetFormatTimes();
ShowRope();
if (!_sport.IsCounting)
{
_sport.Stop();
_sport.Start();
}
}
});
}
//展示成绩
private async void ShowRope()
{
// 渐变出现动画
var fadeInAnimation = new DoubleAnimation
{
From = 0,
To = 1,
Duration = TimeSpan.FromSeconds(1.5)
};
var fadeInAnimation1 = new DoubleAnimation
{
From = 1,
To = 0,
Duration = TimeSpan.FromSeconds(1.5)
};
//videoImage.Opacity = 0;
//centerImg1.Opacity = 1;
//centerImg2.Opacity = 1;
//centerImg3.Opacity = 1;
_sportType = 2;
RankingGrid.BeginAnimation(UIElement.OpacityProperty, fadeInAnimation);
ImageBehavior.GetAnimationController(centerImg4).Pause();
//_mainWin.WebcamClient.OnExtractFrame -= this.OnFrameExtracted;
for (int i = 12; i >= 0; i--)
{
countDown.Text = i.ToString() + "s";
if (i == 8)
{
centerImg4.Visibility = Visibility.Hidden;
centerImg3.Visibility = Visibility.Visible;
}
if (i == 1)
{
RankingGrid.BeginAnimation(UIElement.OpacityProperty, fadeInAnimation1);
_sportType = 0;
PlayMusic("raisehand.mp3");
}
await Task.Delay(1000);
}
// 关闭跳远抽帧线程
}
private bool ReactangleCalibrating(int frameWidth, int frameHeight)
{
var mainWin = Application.Current.MainWindow as Main;
var app = (App)Application.Current;
using var scope = App.AppHost.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var points = db.JumpLongPoints.FirstOrDefault(x => x.Code == AppSettings.Code);
var boxes = new List<BoundingBox>()
{
new(){
ImageWidth = frameWidth,
ImageHeight = frameHeight,
Label = new PredictionLabel(){
Key = "begin"
},
MaskLayer = new MaskLayer(){
Contours = [
new ((float)points.BottomPoint3.X, (float)points.BottomPoint3.Y),
new ((float)points.TopPoint3.X, (float)points.TopPoint3.Y),
]
}
},
new(){
ImageWidth = frameWidth,
ImageHeight = frameHeight,
Label = new PredictionLabel{
Key = "end"
},
MaskLayer = new MaskLayer(){
Contours = [
new((float)points.BottomPoint2.X, (float)points.BottomPoint2.Y),
new((float)points.TopPoint2.X, (float)points.TopPoint2.Y),
]
}
}
};
boxes[1].Label.Key = "take-off";
boxes.Add(new BoundingBox()
{
ImageWidth = frameWidth,
ImageHeight = frameHeight,
Label = new PredictionLabel
{
Key = "end"
},
MaskLayer = new MaskLayer()
{
Contours = [
new ((float)points.BottomPoint1.X, (float)points.BottomPoint1.Y),
new ((float)points.TopPoint1.X, (float)points.TopPoint1.Y),
]
}
});
return _measureApparatus.Calibrating(boxes);
}
public int DetectLeftHandRaise(List<Human> humans)
{
if (humans == null || humans.Count == 0)
return (int)WavingAction.None;
foreach (var human in humans)
{
if (human?.Keypoints == null)
continue;
// --- 筛选右脚踝坐标 ---
var rightAnkle = human.Keypoints.FirstOrDefault(k => k.Name == "right_ankle");
if (rightAnkle == null)
continue;
//double xNorm = rightAnkle.X / 1920;
//double yNorm = rightAnkle.Y / 1080;
var app = (App)Application.Current;
using var scope = App.AppHost.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var points = db.JumpLongPoints.FirstOrDefault(x => x.Code == AppSettings.Code);
// 检测起跳区域的人
var isinx = (rightAnkle.X < points.TopPoint3.X && rightAnkle.X > points.TopPoint2.X) || (rightAnkle.X < points.BottomPoint3.X && rightAnkle.X > points.BottomPoint2.X);
if (points.TopPoint3.X < points.TopPoint2.X)
{
isinx = (rightAnkle.X > points.TopPoint3.X && rightAnkle.X < points.TopPoint2.X) || (rightAnkle.X > points.BottomPoint3.X && rightAnkle.X < points.BottomPoint2.X);
}
var isiny = (rightAnkle.Y > points.TopPoint3.Y && rightAnkle.X < points.BottomPoint3.Y) || (rightAnkle.Y < points.TopPoint2.Y && rightAnkle.X > points.BottomPoint2.Y);
if (!isinx || !isiny) continue;
// 获取左手关键点
var leftWrist = human.Keypoints.FirstOrDefault(k => k.Name == "left_wrist");
var leftElbow = human.Keypoints.FirstOrDefault(k => k.Name == "left_elbow");
if (leftWrist == null || leftElbow == null)
continue;
const double raiseThreshold = 60; // 举手阈值
// 判断左手是否举起
double verticalRise = leftElbow.Y - leftWrist.Y;
if (verticalRise >= raiseThreshold)
{
return (int)WavingAction.RaiseHand; // 一旦检测到左手举起,立即返回
}
}
return (int)WavingAction.None; // 没有检测到举手
}
private DateTime? _raiseStartTime;
private bool _firstHandTriggered;
private int _lastCountdownSecond = 3;
private DateTime? _countdownStartTime;
public int DetectRightHandRaise(List<Human> humans)
{
if (humans == null || humans.Count == 0)
return (int)WavingAction.None;
foreach (var human in humans)
{
if (human?.Keypoints == null)
continue;
// --- 筛选右脚踝坐标 ---
var rightAnkle = human.Keypoints.FirstOrDefault(k => k.Name == "right_ankle");
if (rightAnkle == null)
continue;
// 检测起跳区域的人
var isinx = (rightAnkle.X < _Points.TopPoint3.X && rightAnkle.X > _Points.TopPoint2.X) || (rightAnkle.X < _Points.BottomPoint3.X && rightAnkle.X > _Points.BottomPoint2.X);
if (_Points.TopPoint3.X < _Points.TopPoint2.X)
{
isinx = (rightAnkle.X > _Points.TopPoint3.X && rightAnkle.X < _Points.TopPoint2.X) || (rightAnkle.X > _Points.BottomPoint3.X && rightAnkle.X < _Points.BottomPoint2.X);
}
var isiny = (rightAnkle.Y > _Points.TopPoint3.Y && rightAnkle.X < _Points.BottomPoint3.Y) || (rightAnkle.Y < _Points.TopPoint2.Y && rightAnkle.X > _Points.BottomPoint2.Y);
if (!isinx || !isiny) continue;
var rightWrist = human.Keypoints.FirstOrDefault(k => k.Name == "right_wrist");
var rightElbow = human.Keypoints.FirstOrDefault(k => k.Name == "right_elbow");
const double raiseThreshold = 60; // 举手阈值
const double holdDuration = 1; // 持续时间(秒)
const int countdownSeconds = 3; // 倒计时长度
bool handRaised = false;
if (rightWrist != null && rightElbow != null)
{
double verticalRise = rightElbow.Y - rightWrist.Y;
handRaised = verticalRise >= raiseThreshold;
}
// ---------- 第一步:持续举手触发倒计时 ----------
if (!_firstHandTriggered)
{
if (handRaised)
{
_raiseStartTime ??= DateTime.Now;
var holdElapsed = (DateTime.Now - _raiseStartTime.Value).TotalSeconds;
if (holdElapsed >= holdDuration)
{
// 举手达到指定时间,启动倒计时
_firstHandTriggered = true;
ResetRaiseState();
return (int)WavingAction.FirstHand;
}
}
else
{
// 手放下,重置举手开始时间
_raiseStartTime = DateTime.Now;
}
continue; // 本人未触发,检测下一个人
}
// ---------- 第二步:倒计时逻辑 ----------
var countdownElapsed = (DateTime.Now - _countdownStartTime.Value).TotalSeconds;
int currentSecond = countdownSeconds - (int)Math.Floor(countdownElapsed);
if (currentSecond > 0 && currentSecond != _lastCountdownSecond)
{
_lastCountdownSecond = currentSecond;
Console.WriteLine($"倒计时:{currentSecond}");
return (int)WavingAction.Raising; // 倒计时中
}
if (countdownElapsed >= countdownSeconds)
{
ResetRaiseState();
return (int)WavingAction.RaiseHand; // 举手完成
}
return (int)WavingAction.Raising;
}
// 没有任何中心区域内的人举手
return (int)WavingAction.None;
}
private void ResetRaiseState()
{
_raiseStartTime = null;
_countdownStartTime = null;
_firstHandTriggered = false;
_lastCountdownSecond = 3;
}
private void StartSport()
{
Utils.PlayBackgroundMusic("countdown_3.mp3", false);
_mainWin.ShowCountDownAnimation();
centerImg3.Visibility = Visibility.Hidden;
centerImg4.Visibility = Visibility.Visible;
//if (_sport?.IsCounting == true)
//_mainWin.WebcamClient.OnExtractFrame += this.OnFrameExtracted;
}
private WriteableBitmap _videoBitmap;
private int _lastFrameNumber = -1;
private async void OnFrameExtracted(VideoFrame frame)
{
if (frame == null) return;
// 跳帧:每秒只渲染 10~15 帧,降低 CPU 占用
if (frame.Number % 2 != 0) return;
if (_lastFrameNumber != -1 && frame.Number - _lastFrameNumber < 2) return;
_lastFrameNumber = (int)frame.Number;
//获得帧二进制流,保存图像、人体识别竺
var buffer = frame.GetImageBuffer(Yztob.AiSports.Sensors.Things.ImageFormat.Jpeg).ToArray();
//await File.WriteAllBytesAsync($"./temps/{frame.Number}.jpg", buffer);
// === 显示到 WPF ===
BitmapImage bitmap = null;
await Task.Run(() =>
{
using var ms = new MemoryStream(buffer);
bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.StreamSource = ms;
bitmap.EndInit();
bitmap.Freeze(); // 跨线程安全
});
// UI 线程显示
Application.Current.Dispatcher.Invoke(() =>
{
if (videoImage == null) return;
if (_videoBitmap == null ||
_videoBitmap.PixelWidth != bitmap.PixelWidth ||
_videoBitmap.PixelHeight != bitmap.PixelHeight)
{
_videoBitmap = new WriteableBitmap(bitmap.PixelWidth, bitmap.PixelHeight,
96, 96, PixelFormats.Bgra32, null);
videoImage.Source = _videoBitmap;
}
_videoBitmap.Lock();
bitmap.CopyPixels(new Int32Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight),
_videoBitmap.BackBuffer,
_videoBitmap.BackBufferStride * bitmap.PixelHeight,
_videoBitmap.BackBufferStride);
_videoBitmap.AddDirtyRect(new Int32Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight));
_videoBitmap.Unlock();
videoImage.Opacity = 1;
centerImg1.Opacity = 0;
centerImg2.Opacity = 0;
centerImg3.Opacity = 0;
});
}
}
}