出圈检测
This commit is contained in:
parent
589678b324
commit
0c81072994
@ -65,14 +65,12 @@ namespace Wpf_AiSportsMicrospace.Common
|
|||||||
|
|
||||||
return _webcamClient;
|
return _webcamClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int VerifyWavingAction(Human human)
|
public int VerifyWavingAction(Human human)
|
||||||
{
|
{
|
||||||
var nose = human.Keypoints.FirstOrDefault(k => k.Name == "right_ankle");
|
var nose = human.Keypoints.FirstOrDefault(k => k.Name == "right_ankle");
|
||||||
if (nose == null)
|
if (nose == null)
|
||||||
return (int)WavingAction.None;
|
return (int)WavingAction.None;
|
||||||
|
|
||||||
// 使用 Canvas 宽度归一化
|
|
||||||
double xNorm = nose.X / 1920;
|
double xNorm = nose.X / 1920;
|
||||||
double yNorm = nose.Y / 1080;
|
double yNorm = nose.Y / 1080;
|
||||||
|
|
||||||
@ -93,10 +91,16 @@ namespace Wpf_AiSportsMicrospace.Common
|
|||||||
ref _lastLeftWrist,
|
ref _lastLeftWrist,
|
||||||
ref _leftWristDeltaX,
|
ref _leftWristDeltaX,
|
||||||
true);
|
true);
|
||||||
if (leftResult != (int)WavingAction.None) return leftResult;
|
if (leftResult != (int)WavingAction.None)
|
||||||
|
return leftResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 右手挥手或举手 ---
|
// --- 右手举手优先判断 ---
|
||||||
|
int rightHandAction = DetectRightHandRaise(human);
|
||||||
|
if (rightHandAction != (int)WavingAction.None)
|
||||||
|
return rightHandAction;
|
||||||
|
|
||||||
|
// --- 右手挥手,仅当未举手时判断 ---
|
||||||
if (rightWrist != null && rightElbow != null)
|
if (rightWrist != null && rightElbow != null)
|
||||||
{
|
{
|
||||||
int rightWaveResult = DetectHorizontalWave(
|
int rightWaveResult = DetectHorizontalWave(
|
||||||
@ -105,51 +109,64 @@ namespace Wpf_AiSportsMicrospace.Common
|
|||||||
ref _lastRightWrist,
|
ref _lastRightWrist,
|
||||||
ref _rightWristDeltaX,
|
ref _rightWristDeltaX,
|
||||||
false);
|
false);
|
||||||
if (rightWaveResult != (int)WavingAction.None) return rightWaveResult;
|
if (rightWaveResult != (int)WavingAction.None)
|
||||||
|
return rightWaveResult;
|
||||||
|
}
|
||||||
|
|
||||||
// --- 举手逻辑 ---
|
return (int)WavingAction.None;
|
||||||
double verticalRise = rightElbow.Y - rightWrist.Y; // 手腕在肘上方 → 正值
|
}
|
||||||
if (verticalRise >= 60) // 举手阈值
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检测右手举手状态
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="human">人体关键点信息</param>
|
||||||
|
/// <returns>返回 WavingAction:FirstHand / Raising / RaiseHand / None</returns>
|
||||||
|
public int DetectRightHandRaise(Human human)
|
||||||
|
{
|
||||||
|
var rightWrist = human.Keypoints.FirstOrDefault(k => k.Name == "right_wrist");
|
||||||
|
var rightElbow = human.Keypoints.FirstOrDefault(k => k.Name == "right_elbow");
|
||||||
|
|
||||||
|
if (rightWrist == null || rightElbow == null)
|
||||||
|
return (int)WavingAction.None;
|
||||||
|
|
||||||
|
double verticalRise = rightElbow.Y - rightWrist.Y; // 手腕在肘上方 → 正值
|
||||||
|
const double raiseThreshold = 60; // 举手阈值,可根据实际调整
|
||||||
|
|
||||||
|
if (verticalRise >= raiseThreshold)
|
||||||
|
{
|
||||||
|
// 初始化计时
|
||||||
|
if (_raiseStartTime == null) _raiseStartTime = DateTime.Now;
|
||||||
|
if (_wristStartTime == null) _wristStartTime = DateTime.Now;
|
||||||
|
|
||||||
|
var wristDuration = DateTime.Now - _wristStartTime.Value;
|
||||||
|
|
||||||
|
if (!_firstHandTriggered && wristDuration.TotalSeconds >= 1)
|
||||||
{
|
{
|
||||||
// 初始化计时
|
_firstHandTriggered = true;
|
||||||
if (_raiseStartTime == null)
|
return (int)WavingAction.FirstHand; // 举手开始
|
||||||
_raiseStartTime = DateTime.Now;
|
}
|
||||||
|
|
||||||
if (_wristStartTime == null)
|
var duration = DateTime.Now - _raiseStartTime.Value;
|
||||||
_wristStartTime = DateTime.Now;
|
if (duration.TotalSeconds >= 3)
|
||||||
|
{
|
||||||
var wristDuration = DateTime.Now - _wristStartTime.Value;
|
_raiseStartTime = null;
|
||||||
|
_wristStartTime = null;
|
||||||
// 保持 >1 秒才触发一次 FirstHand
|
_firstHandTriggered = false; // 重置状态
|
||||||
if (!_firstHandTriggered && wristDuration.TotalSeconds >= 1)
|
return (int)WavingAction.RaiseHand; // 举手完成
|
||||||
{
|
|
||||||
_firstHandTriggered = true;
|
|
||||||
return (int)WavingAction.FirstHand; // 举手开始,只触发一次
|
|
||||||
}
|
|
||||||
|
|
||||||
// 判断是否完成3秒举手
|
|
||||||
var duration = DateTime.Now - _raiseStartTime.Value;
|
|
||||||
if (duration.TotalSeconds >= 3)
|
|
||||||
{
|
|
||||||
_raiseStartTime = null;
|
|
||||||
_wristStartTime = null;
|
|
||||||
_firstHandTriggered = false; // 重置状态
|
|
||||||
return (int)WavingAction.RaiseHand; // 举手完成
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return (int)WavingAction.Raising; // 举手中
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 手放下,重置计时和状态
|
return (int)WavingAction.Raising; // 举手中
|
||||||
_raiseStartTime = null;
|
|
||||||
_wristStartTime = null;
|
|
||||||
_firstHandTriggered = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (int)WavingAction.None;
|
else
|
||||||
|
{
|
||||||
|
// 手放下,重置状态
|
||||||
|
_raiseStartTime = null;
|
||||||
|
_wristStartTime = null;
|
||||||
|
_firstHandTriggered = false;
|
||||||
|
return (int)WavingAction.None;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -65,6 +65,7 @@
|
|||||||
Source="/Resources/Img/Album/change_right.png"
|
Source="/Resources/Img/Album/change_right.png"
|
||||||
HorizontalAlignment="right"
|
HorizontalAlignment="right"
|
||||||
VerticalAlignment="Top"
|
VerticalAlignment="Top"
|
||||||
|
MouseDown="GoNew"
|
||||||
Width="330"
|
Width="330"
|
||||||
Margin="0,100,0,0"
|
Margin="0,100,0,0"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -33,12 +33,6 @@ namespace Wpf_AiSportsMicrospace.Views
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class CenterHome : UserControl
|
public partial class CenterHome : UserControl
|
||||||
{
|
{
|
||||||
private IHumanPredictor _humanPredictor;
|
|
||||||
private IObjectDetector _objectDetector;
|
|
||||||
private HumanGraphicsRenderer _humanGraphicsRenderer;
|
|
||||||
private WebcamClient _webcamClient;
|
|
||||||
private ConcurrentQueue<VideoFrame> _frameQueue = new();
|
|
||||||
private CancellationTokenSource _cts = new();
|
|
||||||
private SportOperate _sportOperate;
|
private SportOperate _sportOperate;
|
||||||
private Main _mainWin => Application.Current.MainWindow as Main;
|
private Main _mainWin => Application.Current.MainWindow as Main;
|
||||||
|
|
||||||
@ -59,15 +53,6 @@ namespace Wpf_AiSportsMicrospace.Views
|
|||||||
public CenterHome()
|
public CenterHome()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
//_humanPredictor = HumanPredictorFactory.Create(HumanPredictorType.SingleHigh);
|
|
||||||
//_humanPredictor = HumanPredictorFactory.Create(HumanPredictorType.MultiMedium);
|
|
||||||
//_objectDetector = ObjectDetectorFactory.CreateSportGoodsDetector();
|
|
||||||
//_humanGraphicsRenderer = new HumanGraphicsRenderer();
|
|
||||||
//_humanGraphicsRenderer.DrawLabel = false;
|
|
||||||
|
|
||||||
//_sports = SportBase.GetSports();
|
|
||||||
//_detectQueue = new SportDetectionQueue();
|
|
||||||
|
|
||||||
//string imgPath = Path.Combine(projectRoot, "Resources", "Img" , _nowSelect == "test" ? "test_img" : "play_img");
|
//string imgPath = Path.Combine(projectRoot, "Resources", "Img" , _nowSelect == "test" ? "test_img" : "play_img");
|
||||||
|
|
||||||
// 默认选中第1张
|
// 默认选中第1张
|
||||||
@ -103,27 +88,26 @@ namespace Wpf_AiSportsMicrospace.Views
|
|||||||
switch (wavingaction)
|
switch (wavingaction)
|
||||||
{
|
{
|
||||||
case (int)WavingAction.LeftWave: // 左手挥动
|
case (int)WavingAction.LeftWave: // 左手挥动
|
||||||
coverFlow.SlideRight();
|
Application.Current.Dispatcher.BeginInvoke(() => coverFlow.SlideRight());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case (int)WavingAction.RightWave: // 右手挥动
|
case (int)WavingAction.RightWave: // 右手挥动
|
||||||
coverFlow.SlideLeft();
|
Application.Current.Dispatcher.BeginInvoke(() => coverFlow.SlideLeft());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case (int)WavingAction.FirstHand: // 举手开始
|
case (int)WavingAction.FirstHand: // 举手开始
|
||||||
coverFlow.StartSelectedProgress();
|
Application.Current.Dispatcher.BeginInvoke(() => coverFlow.StartSelectedProgress());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case (int)WavingAction.Raising: // 举手中,实时更新进度
|
case (int)WavingAction.Raising: // 举手中,实时更新进度
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case (int)WavingAction.RaiseHand: // 举手完成
|
case (int)WavingAction.RaiseHand: // 举手完成
|
||||||
_cts.Cancel();
|
|
||||||
coverFlow.ProgressCompleted += CoverFlow_ProgressCompleted;
|
coverFlow.ProgressCompleted += CoverFlow_ProgressCompleted;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default: // 没有动作 → 取消进度
|
default: // 没有动作 → 取消进度
|
||||||
coverFlow.CancelSelectedProgress();
|
Application.Current.Dispatcher.BeginInvoke(() => coverFlow.CancelSelectedProgress());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -162,91 +146,6 @@ namespace Wpf_AiSportsMicrospace.Views
|
|||||||
//}
|
//}
|
||||||
|
|
||||||
}
|
}
|
||||||
private void StartFrameProcessing()
|
|
||||||
{
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
while (!_cts.Token.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
if (_frameQueue.TryDequeue(out var frame))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ProcessFrame(frame);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine("ProcessFrame error: " + ex.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await Task.Delay(5, _cts.Token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, _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;
|
|
||||||
|
|
||||||
var human = humans.FirstOrDefault();
|
|
||||||
|
|
||||||
if (human == null) return;
|
|
||||||
|
|
||||||
//检测挥手动作
|
|
||||||
var wavingaction = _sportOperate.VerifyWavingAction(human);
|
|
||||||
|
|
||||||
switch (wavingaction)
|
|
||||||
{
|
|
||||||
case (int)WavingAction.LeftWave: // 左手挥动
|
|
||||||
Dispatcher.BeginInvoke(() => coverFlow.SlideRight());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case (int)WavingAction.RightWave: // 右手挥动
|
|
||||||
Dispatcher.BeginInvoke(() => coverFlow.SlideLeft());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case (int)WavingAction.FirstHand: // 举手开始
|
|
||||||
Dispatcher.BeginInvoke(() => coverFlow.StartSelectedProgress());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case (int)WavingAction.Raising: // 举手中,实时更新进度
|
|
||||||
break;
|
|
||||||
|
|
||||||
case (int)WavingAction.RaiseHand: // 举手完成
|
|
||||||
_cts.Cancel();
|
|
||||||
coverFlow.ProgressCompleted += CoverFlow_ProgressCompleted;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default: // 没有动作 → 取消进度
|
|
||||||
Dispatcher.BeginInvoke(() => coverFlow.CancelSelectedProgress());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine("OnFrameExtracted error: " + ex.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_cts.Cancel();
|
|
||||||
_webcamClient?.StopExtract();
|
|
||||||
_webcamClient = null;
|
|
||||||
_humanPredictor.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void InitImg()
|
public void InitImg()
|
||||||
{
|
{
|
||||||
string projectRoot = Path.Combine(AppContext.BaseDirectory, @"..\..\..");
|
string projectRoot = Path.Combine(AppContext.BaseDirectory, @"..\..\..");
|
||||||
@ -277,5 +176,12 @@ namespace Wpf_AiSportsMicrospace.Views
|
|||||||
var newPage = new GroupJumpRope();
|
var newPage = new GroupJumpRope();
|
||||||
mainWin?.SwitchPage(newPage, true);
|
mainWin?.SwitchPage(newPage, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void GoNew(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
var mainWin = Application.Current.MainWindow as Main;
|
||||||
|
var newPage = new GroupJumpRope();
|
||||||
|
mainWin?.SwitchPage(newPage, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -90,61 +90,29 @@ namespace Wpf_AiSportsMicrospace
|
|||||||
//检测挥手动作
|
//检测挥手动作
|
||||||
var wavingaction = _mainWin.SportOperate.VerifyWavingAction(human);
|
var wavingaction = _mainWin.SportOperate.VerifyWavingAction(human);
|
||||||
|
|
||||||
// 把低 8 位作为动作类型,高 8 位作为进度
|
|
||||||
//int actionType = wavingaction & 0xFF;
|
|
||||||
//int progress = (wavingaction >> 8) & 0xFF;
|
|
||||||
|
|
||||||
//switch (wavingaction)
|
|
||||||
//{
|
|
||||||
// case (int)WavingAction.LeftWave: // 左手挥动
|
|
||||||
// Application.Current.Dispatcher.BeginInvoke(() => coverFlow.SlideRight());
|
|
||||||
// break;
|
|
||||||
|
|
||||||
// case (int)WavingAction.RightWave: // 右手挥动
|
|
||||||
// Application.Current.Dispatcher.BeginInvoke(() => coverFlow.SlideLeft());
|
|
||||||
// break;
|
|
||||||
|
|
||||||
// case (int)WavingAction.FirstHand: // 举手开始
|
|
||||||
// Application.Current.Dispatcher.BeginInvoke(() => coverFlow.StartSelectedProgress());
|
|
||||||
// break;
|
|
||||||
|
|
||||||
// case (int)WavingAction.Raising: // 举手中,实时更新进度
|
|
||||||
// break;
|
|
||||||
|
|
||||||
// case (int)WavingAction.RaiseHand: // 举手完成
|
|
||||||
// _cts.Cancel();
|
|
||||||
// coverFlow.ProgressCompleted += CoverFlow_ProgressCompleted;
|
|
||||||
// break;
|
|
||||||
|
|
||||||
// default: // 没有动作 → 取消进度
|
|
||||||
// Application.Current.Dispatcher.BeginInvoke(() => coverFlow.CancelSelectedProgress());
|
|
||||||
// break;
|
|
||||||
//}
|
|
||||||
|
|
||||||
switch (wavingaction)
|
switch (wavingaction)
|
||||||
{
|
{
|
||||||
case (int)WavingAction.LeftWave: // 左手挥动
|
case (int)WavingAction.LeftWave: // 左手挥动
|
||||||
coverFlow.SlideRight();
|
Application.Current.Dispatcher.BeginInvoke(() => coverFlow.SlideRight());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case (int)WavingAction.RightWave: // 右手挥动
|
case (int)WavingAction.RightWave: // 右手挥动
|
||||||
coverFlow.SlideLeft();
|
Application.Current.Dispatcher.BeginInvoke(() => coverFlow.SlideLeft());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case (int)WavingAction.FirstHand: // 举手开始
|
case (int)WavingAction.FirstHand: // 举手开始
|
||||||
coverFlow.StartSelectedProgress();
|
Application.Current.Dispatcher.BeginInvoke(() => coverFlow.StartSelectedProgress());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case (int)WavingAction.Raising: // 举手中,实时更新进度
|
case (int)WavingAction.Raising: // 举手中,实时更新进度
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case (int)WavingAction.RaiseHand: // 举手完成
|
case (int)WavingAction.RaiseHand: // 举手完成
|
||||||
_cts.Cancel();
|
|
||||||
coverFlow.ProgressCompleted += CoverFlow_ProgressCompleted;
|
coverFlow.ProgressCompleted += CoverFlow_ProgressCompleted;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default: // 没有动作 → 取消进度
|
default: // 没有动作 → 取消进度
|
||||||
coverFlow.CancelSelectedProgress();
|
Application.Current.Dispatcher.BeginInvoke(() => coverFlow.CancelSelectedProgress());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -153,22 +121,6 @@ namespace Wpf_AiSportsMicrospace
|
|||||||
private void Window_Loaded(object sender, RoutedEventArgs e)
|
private void Window_Loaded(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
_mainWin.HumanFrameUpdated += OnHumanFrameUpdated;
|
_mainWin.HumanFrameUpdated += OnHumanFrameUpdated;
|
||||||
|
|
||||||
//_sportOperate = new SportOperate();
|
|
||||||
//_webcamClient = _sportOperate.CreateRTSP();
|
|
||||||
|
|
||||||
//_webcamClient.OnExtractFrame += frame =>
|
|
||||||
//{
|
|
||||||
// if (frame != null)
|
|
||||||
// _frameQueue.Enqueue(frame);
|
|
||||||
//};
|
|
||||||
//_webcamClient.StartExtract();
|
|
||||||
|
|
||||||
//StartFrameProcessing();
|
|
||||||
|
|
||||||
//Utils.PlayBackgroundMusic("homeprojectselected.mp3");
|
|
||||||
|
|
||||||
//coverFlow.ProgressCompleted += CoverFlow_ProgressCompleted;
|
|
||||||
}
|
}
|
||||||
private void CoverFlow_ProgressCompleted(CoverFlowItem item)
|
private void CoverFlow_ProgressCompleted(CoverFlowItem item)
|
||||||
{
|
{
|
||||||
@ -197,101 +149,6 @@ namespace Wpf_AiSportsMicrospace
|
|||||||
// newWindow = new GroupJumpRope();
|
// newWindow = new GroupJumpRope();
|
||||||
// 找到主窗口,切换内容到 GroupJumpRopePage
|
// 找到主窗口,切换内容到 GroupJumpRopePage
|
||||||
}
|
}
|
||||||
private void StartFrameProcessing()
|
|
||||||
{
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
while (!_cts.Token.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
if (_frameQueue.TryDequeue(out var frame))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ProcessFrame(frame);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine("ProcessFrame error: " + ex.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await Task.Delay(5, _cts.Token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, _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;
|
|
||||||
|
|
||||||
//var human = humans
|
|
||||||
// .Where(h =>
|
|
||||||
// h.Keypoints.Any(kp => kp.Name == "left_ankle" && kp.X < 1020 && kp.Y > 900 && kp.Y < 1020) &&
|
|
||||||
// h.Keypoints.Any(kp => kp.Name == "right_ankle" && kp.X > 750 && kp.Y > 900 && kp.Y < 1020)
|
|
||||||
// )
|
|
||||||
// .FirstOrDefault();
|
|
||||||
|
|
||||||
var human = humans.FirstOrDefault();
|
|
||||||
|
|
||||||
if (human == null) return;
|
|
||||||
|
|
||||||
//检测挥手动作
|
|
||||||
var wavingaction = _sportOperate.VerifyWavingAction(human);
|
|
||||||
|
|
||||||
// 把低 8 位作为动作类型,高 8 位作为进度
|
|
||||||
//int actionType = wavingaction & 0xFF;
|
|
||||||
//int progress = (wavingaction >> 8) & 0xFF;
|
|
||||||
|
|
||||||
switch (wavingaction)
|
|
||||||
{
|
|
||||||
case (int)WavingAction.LeftWave: // 左手挥动
|
|
||||||
Application.Current.Dispatcher.BeginInvoke(() => coverFlow.SlideRight());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case (int)WavingAction.RightWave: // 右手挥动
|
|
||||||
Application.Current.Dispatcher.BeginInvoke(() => coverFlow.SlideLeft());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case (int)WavingAction.FirstHand: // 举手开始
|
|
||||||
Application.Current.Dispatcher.BeginInvoke(() => coverFlow.StartSelectedProgress());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case (int)WavingAction.Raising: // 举手中,实时更新进度
|
|
||||||
break;
|
|
||||||
|
|
||||||
case (int)WavingAction.RaiseHand: // 举手完成
|
|
||||||
_cts.Cancel();
|
|
||||||
coverFlow.ProgressCompleted += CoverFlow_ProgressCompleted;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default: // 没有动作 → 取消进度
|
|
||||||
Application.Current.Dispatcher.BeginInvoke(() => coverFlow.CancelSelectedProgress());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine("OnFrameExtracted error: " + ex.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_cts.Cancel();
|
|
||||||
_webcamClient?.StopExtract();
|
|
||||||
_webcamClient = null;
|
|
||||||
_humanPredictor.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
//测试点击
|
//测试点击
|
||||||
private void GoNew(object sender, MouseButtonEventArgs e)
|
private void GoNew(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -11,6 +11,17 @@
|
|||||||
</Grid.Background>
|
</Grid.Background>
|
||||||
<Grid>
|
<Grid>
|
||||||
<Canvas x:Name="overlayCanvas" IsHitTestVisible="False" Height="1080" Width="1920"/>
|
<Canvas x:Name="overlayCanvas" IsHitTestVisible="False" Height="1080" Width="1920"/>
|
||||||
|
|
||||||
|
<TextBlock x:Name="countdownText"
|
||||||
|
Text="3"
|
||||||
|
FontSize="120"
|
||||||
|
FontWeight="Bold"
|
||||||
|
Foreground="Red"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Margin="0,50,0,0"
|
||||||
|
TextAlignment="Center"
|
||||||
|
Visibility="Collapsed"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@ -38,122 +38,276 @@ namespace Wpf_AiSportsMicrospace.Views
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class GroupJumpRope : UserControl
|
public partial class GroupJumpRope : UserControl
|
||||||
{
|
{
|
||||||
private IHumanPredictor _humanPredictor;
|
|
||||||
private IObjectDetector _objectDetector;
|
|
||||||
private HumanGraphicsRenderer _humanGraphicsRenderer;
|
|
||||||
private WebcamClient _webcamClient;
|
|
||||||
private ConcurrentQueue<VideoFrame> _frameQueue = new();
|
|
||||||
private CancellationTokenSource _cts = new();
|
|
||||||
private SportOperate _sportOperate;
|
|
||||||
|
|
||||||
private List<SportBase> sports = new();
|
private List<SportBase> sports = new();
|
||||||
private List<TextBlock> circleTexts = new();
|
private List<TextBlock> circleTexts = new();
|
||||||
private double[] circlePositionsX = { 0.07, 0.21, 0.36, 0.50, 0.64, 0.78, 0.92 };
|
private double[] circlePositionsX = { 0.07, 0.21, 0.36, 0.50, 0.64, 0.78, 0.92 };
|
||||||
private Main _mainWin => Application.Current.MainWindow as Main;
|
private Main _mainWin => Application.Current.MainWindow as Main;
|
||||||
ConfigService configService = new ConfigService();
|
|
||||||
|
private DispatcherTimer _countdownTimer;
|
||||||
|
private int _countdownValue;
|
||||||
|
private bool _isCountingDown = false;
|
||||||
|
private SportOperate sportOperate;
|
||||||
|
private List<(double XNorm, double YNorm)> circlePositions = new();
|
||||||
|
|
||||||
|
private bool IsGameStarted = false;
|
||||||
public GroupJumpRope()
|
public GroupJumpRope()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
//_humanPredictor = HumanPredictorFactory.Create(HumanPredictorType.MultiMedium);
|
|
||||||
//_objectDetector = ObjectDetectorFactory.CreateSportGoodsDetector();
|
|
||||||
//_humanGraphicsRenderer = new HumanGraphicsRenderer();
|
|
||||||
//_humanGraphicsRenderer.DrawLabel = false;
|
|
||||||
|
|
||||||
|
|
||||||
Loaded += UserControl_Loaded;
|
Loaded += UserControl_Loaded;
|
||||||
Unloaded += UserControl_Unloaded;
|
Unloaded += UserControl_Unloaded;
|
||||||
|
|
||||||
|
_countdownTimer = new DispatcherTimer();
|
||||||
|
_countdownTimer.Interval = TimeSpan.FromSeconds(1);
|
||||||
|
_countdownTimer.Tick += CountdownTimer_Tick;
|
||||||
|
|
||||||
|
sportOperate = new SportOperate();
|
||||||
}
|
}
|
||||||
private void UserControl_Loaded(object sender, RoutedEventArgs e)
|
private void UserControl_Loaded(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
//DrawJumpRope3DPointsWithGlow();
|
|
||||||
|
|
||||||
DrawCirclesWithText();
|
DrawCirclesWithText();
|
||||||
|
|
||||||
_mainWin.HumanFrameUpdated += OnHumanFrameUpdated;
|
_mainWin.HumanFrameUpdated += OnHumanFrameUpdated;
|
||||||
|
|
||||||
//_sportOperate = new SportOperate();
|
|
||||||
//_webcamClient = _sportOperate.CreateRTSP();
|
|
||||||
|
|
||||||
//_webcamClient.OnExtractFrame += frame =>
|
|
||||||
//{
|
|
||||||
// if (frame != null)
|
|
||||||
// _frameQueue.Enqueue(frame);
|
|
||||||
//};
|
|
||||||
//_webcamClient.StartExtract();
|
|
||||||
|
|
||||||
//StartFrameProcessing();
|
|
||||||
}
|
}
|
||||||
private void UserControl_Unloaded(object sender, RoutedEventArgs e)
|
private void UserControl_Unloaded(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
_mainWin.HumanFrameUpdated -= OnHumanFrameUpdated;
|
_mainWin.HumanFrameUpdated -= OnHumanFrameUpdated;
|
||||||
}
|
}
|
||||||
|
private void CountdownTimer_Tick(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (_countdownValue > 0)
|
||||||
|
{
|
||||||
|
countdownText.Text = _countdownValue.ToString();
|
||||||
|
_countdownValue--;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_countdownTimer.Stop();
|
||||||
|
countdownText.Visibility = Visibility.Collapsed;
|
||||||
|
_isCountingDown = false;
|
||||||
|
IsGameStarted = true; // 倒计时结束,游戏正式开始
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartCountdown()
|
||||||
|
{
|
||||||
|
if (_isCountingDown) return; // 避免重复启动
|
||||||
|
|
||||||
|
_isCountingDown = true;
|
||||||
|
_countdownValue = 3; // 从3开始倒计时
|
||||||
|
countdownText.Visibility = Visibility.Visible;
|
||||||
|
_countdownTimer.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private DateTime? _waitStartTime = null;
|
||||||
private void OnHumanFrameUpdated(object sender, List<Human> humans)
|
private void OnHumanFrameUpdated(object sender, List<Human> humans)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
UpdateCircleCounts(humans);
|
if (!IsGameStarted)
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine("OnFrameExtracted error: " + ex.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void StartFrameProcessing()
|
|
||||||
{
|
|
||||||
Task.Run(() =>
|
|
||||||
{
|
|
||||||
while (!_cts.Token.IsCancellationRequested)
|
|
||||||
{
|
{
|
||||||
if (_frameQueue.TryDequeue(out var frame))
|
var human = humans.LastOrDefault();
|
||||||
|
if (human == null) return;
|
||||||
|
|
||||||
|
//检测挥手动作
|
||||||
|
var wavingaction = DetectRightHandRaise(human);
|
||||||
|
|
||||||
|
switch (wavingaction)
|
||||||
{
|
{
|
||||||
ProcessFrame(frame);
|
case (int)WavingAction.FirstHand:
|
||||||
}
|
// 第一次举手,初始化倒计时
|
||||||
else
|
StartCountdown(3);
|
||||||
{
|
break;
|
||||||
Thread.Sleep(5);
|
|
||||||
|
case (int)WavingAction.Raising:
|
||||||
|
// 持续倒计时中
|
||||||
|
UpdateCountdown();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case (int)WavingAction.RaiseHand:
|
||||||
|
// 举手完成,倒计时结束
|
||||||
|
FinishCountdown();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// 没检测到动作,重置倒计时显示
|
||||||
|
countdownText.Text = "3";
|
||||||
|
countdownText.Visibility = Visibility.Collapsed;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, _cts.Token);
|
else
|
||||||
}
|
{
|
||||||
|
UpdateCircleCounts(humans);
|
||||||
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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine("OnFrameExtracted error: " + ex.Message);
|
Console.WriteLine("OnFrameExtracted error: " + ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
public Human LocateHuman(List<Human> humans, double begin, double end, double frameWidth)
|
private int _currentCountdown = 3;
|
||||||
|
private DateTime _lastUpdateTime = DateTime.Now;
|
||||||
|
private void StartCountdown(int start = 3)
|
||||||
|
{
|
||||||
|
_currentCountdown = start;
|
||||||
|
countdownText.Text = _currentCountdown.ToString();
|
||||||
|
countdownText.Visibility = Visibility.Visible;
|
||||||
|
_lastUpdateTime = DateTime.Now;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateCountdown()
|
||||||
|
{
|
||||||
|
if ((DateTime.Now - _lastUpdateTime).TotalSeconds >= 1)
|
||||||
|
{
|
||||||
|
_lastUpdateTime = DateTime.Now;
|
||||||
|
_currentCountdown--;
|
||||||
|
|
||||||
|
if (_currentCountdown > 0)
|
||||||
|
{
|
||||||
|
countdownText.Text = _currentCountdown.ToString();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FinishCountdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FinishCountdown()
|
||||||
|
{
|
||||||
|
countdownText.Text = "✔";
|
||||||
|
countdownText.Visibility = Visibility.Collapsed;
|
||||||
|
|
||||||
|
IsGameStarted = true;
|
||||||
|
|
||||||
|
// 你也可以在这里触发其他动作,例如:
|
||||||
|
// 播放音效、触发事件、执行下一步逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private DateTime? _raiseStartTime;
|
||||||
|
private DateTime? _wristStartTime;
|
||||||
|
private bool _firstHandTriggered;
|
||||||
|
private int _lastCountdownSecond = 3;
|
||||||
|
private DateTime? _countdownStartTime;
|
||||||
|
public int DetectRightHandRaise(Human human)
|
||||||
|
{
|
||||||
|
var rightWrist = human.Keypoints.FirstOrDefault(k => k.Name == "right_wrist");
|
||||||
|
var rightElbow = human.Keypoints.FirstOrDefault(k => k.Name == "right_elbow");
|
||||||
|
|
||||||
|
if (rightWrist == null || rightElbow == null)
|
||||||
|
return (int)WavingAction.None;
|
||||||
|
|
||||||
|
double verticalRise = rightElbow.Y - rightWrist.Y; // 手腕高于肘部 → 正值
|
||||||
|
const double raiseThreshold = 60; // 举手阈值,可调整
|
||||||
|
|
||||||
|
if (verticalRise >= raiseThreshold)
|
||||||
|
{
|
||||||
|
// 初始化
|
||||||
|
_raiseStartTime ??= DateTime.Now;
|
||||||
|
_wristStartTime ??= DateTime.Now;
|
||||||
|
_countdownStartTime ??= DateTime.Now;
|
||||||
|
|
||||||
|
var wristDuration = DateTime.Now - _wristStartTime.Value;
|
||||||
|
|
||||||
|
// 第一次举手触发
|
||||||
|
if (!_firstHandTriggered && wristDuration.TotalSeconds >= 1)
|
||||||
|
{
|
||||||
|
_firstHandTriggered = true;
|
||||||
|
_lastCountdownSecond = 3;
|
||||||
|
_countdownStartTime = DateTime.Now;
|
||||||
|
return (int)WavingAction.FirstHand;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 倒计时逻辑
|
||||||
|
if (_firstHandTriggered)
|
||||||
|
{
|
||||||
|
var elapsed = DateTime.Now - _countdownStartTime.Value;
|
||||||
|
int currentSecond = 3 - (int)Math.Floor(elapsed.TotalSeconds);
|
||||||
|
|
||||||
|
if (currentSecond != _lastCountdownSecond && currentSecond > 0)
|
||||||
|
{
|
||||||
|
_lastCountdownSecond = currentSecond;
|
||||||
|
// 你可以在这里触发倒计时提示(比如语音播报或 UI 更新)
|
||||||
|
Console.WriteLine($"倒计时:{currentSecond}");
|
||||||
|
return (int)WavingAction.Raising; // 可选:倒计时状态
|
||||||
|
}
|
||||||
|
|
||||||
|
// 举手完成
|
||||||
|
if (elapsed.TotalSeconds >= 3)
|
||||||
|
{
|
||||||
|
ResetRaiseState();
|
||||||
|
return (int)WavingAction.RaiseHand; // 举手完成
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int)WavingAction.Raising; // 举手中
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int)WavingAction.Raising;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 手放下重置状态
|
||||||
|
ResetRaiseState();
|
||||||
|
return (int)WavingAction.None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResetRaiseState()
|
||||||
|
{
|
||||||
|
_raiseStartTime = null;
|
||||||
|
_wristStartTime = null;
|
||||||
|
_countdownStartTime = null;
|
||||||
|
_firstHandTriggered = false;
|
||||||
|
_lastCountdownSecond = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Human LocateHuman(List<Human> humans, double begin, double end, double frameWidth, double frameHeight, int circleIndex)
|
||||||
{
|
{
|
||||||
if (humans == null || humans.Count == 0)
|
if (humans == null || humans.Count == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
double circleX = circlePositions[circleIndex].XNorm;
|
||||||
|
double circleY = circlePositions[circleIndex].YNorm;
|
||||||
|
double radiusNormX = 0.07; // X方向半径
|
||||||
|
double radiusNormY = 0.1; // Y方向半径
|
||||||
|
|
||||||
foreach (var hu in humans)
|
foreach (var hu in humans)
|
||||||
{
|
{
|
||||||
var nose = hu.Keypoints.FirstOrDefault(k => k.Name == "right_ankle");
|
var rightFoot = hu.Keypoints.FirstOrDefault(k => k.Name == "right_ankle");
|
||||||
if (nose == null)
|
var leftFoot = hu.Keypoints.FirstOrDefault(k => k.Name == "left_ankle");
|
||||||
|
|
||||||
|
if (rightFoot == null || leftFoot == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// 使用 Canvas 宽度归一化
|
double xRightNorm = rightFoot.X / frameWidth;
|
||||||
double xNorm = nose.X / frameWidth;
|
double xLeftNorm = leftFoot.X / frameWidth;
|
||||||
|
|
||||||
if (xNorm >= begin && xNorm <= end)
|
// 前后出圈检测(Y方向)
|
||||||
return hu;
|
double yRightNorm = rightFoot.Y / frameHeight;
|
||||||
|
double yLeftNorm = leftFoot.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);
|
||||||
|
|
||||||
|
if (outOfCircle)
|
||||||
|
{
|
||||||
|
Application.Current.Dispatcher.BeginInvoke(() =>
|
||||||
|
{
|
||||||
|
circleTexts[circleIndex].Text = "您已出圈!";
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// X 范围匹配 → 未出圈的人
|
||||||
|
if (xRightNorm >= (circleX - radiusNormX) && xRightNorm <= (circleX + radiusNormX))
|
||||||
|
{
|
||||||
|
IsGameStarted = true;
|
||||||
|
return hu; // 返回给计数逻辑
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@ -170,7 +324,7 @@ namespace Wpf_AiSportsMicrospace.Views
|
|||||||
double radius = 100;
|
double radius = 100;
|
||||||
|
|
||||||
// 每个圆的位置:X 和 Y 都归一化 0~1
|
// 每个圆的位置:X 和 Y 都归一化 0~1
|
||||||
var circlePositions = new List<(double XNorm, double YNorm)>
|
circlePositions = new List<(double XNorm, double YNorm)>
|
||||||
{
|
{
|
||||||
(0.07, 0.58),
|
(0.07, 0.58),
|
||||||
(0.21, 0.88 ),
|
(0.21, 0.88 ),
|
||||||
@ -187,8 +341,11 @@ namespace Wpf_AiSportsMicrospace.Views
|
|||||||
double y = pos.YNorm * imgHeight;
|
double y = pos.YNorm * imgHeight;
|
||||||
|
|
||||||
// 绘制发光圆
|
// 绘制发光圆
|
||||||
|
//AddGlowEllipse(x, y, overlayCanvas);
|
||||||
|
|
||||||
AddGlowEllipse(x, y, overlayCanvas);
|
AddGlowEllipse(x, y, overlayCanvas);
|
||||||
|
|
||||||
|
|
||||||
// 创建文本控件
|
// 创建文本控件
|
||||||
var text = new TextBlock
|
var text = new TextBlock
|
||||||
{
|
{
|
||||||
@ -199,6 +356,7 @@ namespace Wpf_AiSportsMicrospace.Views
|
|||||||
TextAlignment = TextAlignment.Center,
|
TextAlignment = TextAlignment.Center,
|
||||||
Width = radius * 2
|
Width = radius * 2
|
||||||
};
|
};
|
||||||
|
|
||||||
Canvas.SetLeft(text, x - radius);
|
Canvas.SetLeft(text, x - radius);
|
||||||
Canvas.SetTop(text, y - radius - 25);
|
Canvas.SetTop(text, y - radius - 25);
|
||||||
overlayCanvas.Children.Add(text);
|
overlayCanvas.Children.Add(text);
|
||||||
@ -209,7 +367,7 @@ namespace Wpf_AiSportsMicrospace.Views
|
|||||||
int index = circleTexts.Count - 1;
|
int index = circleTexts.Count - 1;
|
||||||
sport.OnTicked += (count, times) =>
|
sport.OnTicked += (count, times) =>
|
||||||
{
|
{
|
||||||
Application.Current.Dispatcher.Invoke(() =>
|
Application.Current.Dispatcher.BeginInvoke(() =>
|
||||||
{
|
{
|
||||||
circleTexts[index].Text = count.ToString();
|
circleTexts[index].Text = count.ToString();
|
||||||
});
|
});
|
||||||
@ -227,131 +385,25 @@ namespace Wpf_AiSportsMicrospace.Views
|
|||||||
double begin = center - range;
|
double begin = center - range;
|
||||||
double end = center + range;
|
double end = center + range;
|
||||||
|
|
||||||
var human = LocateHuman(humans, begin, end, overlayCanvas.ActualWidth);
|
var human = LocateHuman(humans, begin, end, overlayCanvas.ActualWidth, overlayCanvas.ActualHeight, i);
|
||||||
if (human != null)
|
if (human != null)
|
||||||
{
|
{
|
||||||
sports[i].Pushing(human);
|
sports[i].Pushing(human);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 添加带渐变光的圆圈(中心红色,边缘蓝色)
|
/// 添加带渐变光的圆圈(中心红色,边缘蓝色)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void AddGlowEllipse(double centerX, double centerY, Canvas canvas)
|
private Ellipse AddGlowEllipse(double centerX, double centerY, Canvas canvas)
|
||||||
{
|
{
|
||||||
double radius = 70; // 统一半径
|
double radius = 70; // 统一半径
|
||||||
double flattenFactor = 0.5; // 统一扁平化比例
|
double flattenFactor = 0.5; // 扁平化比例
|
||||||
double opacity = 0.8; // 统一透明度
|
double opacity = 0.8; // 透明度
|
||||||
|
|
||||||
|
// 默认颜色
|
||||||
|
Color cColor = Color.FromArgb(220, 255, 255, 0); // 黄
|
||||||
|
Color eColor = Color.FromArgb(180, 0, 0, 255); // 蓝
|
||||||
|
|
||||||
var ellipse = new Ellipse
|
var ellipse = new Ellipse
|
||||||
{
|
{
|
||||||
@ -366,10 +418,10 @@ namespace Wpf_AiSportsMicrospace.Views
|
|||||||
RadiusY = 0.5,
|
RadiusY = 0.5,
|
||||||
GradientStops = new GradientStopCollection
|
GradientStops = new GradientStopCollection
|
||||||
{
|
{
|
||||||
new GradientStop(Color.FromArgb(220, 255, 255, 0), 0.0), // 中心黄
|
new GradientStop(cColor, 0.0), // 中心颜色
|
||||||
new GradientStop(Color.FromArgb(180, 255, 255, 0), 0.4), // 中间黄
|
new GradientStop(cColor, 0.4), // 内部颜色
|
||||||
new GradientStop(Color.FromArgb(180, 0, 0, 255), 0.7), // 边缘蓝
|
new GradientStop(eColor, 0.7), // 边缘颜色
|
||||||
new GradientStop(Color.FromArgb(0, 0, 0, 255), 1.0) // 外部透明
|
new GradientStop(Color.FromArgb(0, eColor.R, eColor.G, eColor.B), 1.0) // 外部透明
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -377,8 +429,17 @@ namespace Wpf_AiSportsMicrospace.Views
|
|||||||
// 定位到中心
|
// 定位到中心
|
||||||
Canvas.SetLeft(ellipse, centerX - radius);
|
Canvas.SetLeft(ellipse, centerX - radius);
|
||||||
Canvas.SetTop(ellipse, centerY - (radius * flattenFactor) / 2);
|
Canvas.SetTop(ellipse, centerY - (radius * flattenFactor) / 2);
|
||||||
|
|
||||||
canvas.Children.Add(ellipse);
|
canvas.Children.Add(ellipse);
|
||||||
|
return ellipse; // 返回 Ellipse 对象方便后续修改或移除
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public bool IsInsideCircle(double px, double py, double cx, double cy, double r)
|
||||||
|
{
|
||||||
|
double dx = px - cx;
|
||||||
|
double dy = py - cy;
|
||||||
|
return dx * dx + dy * dy <= r * r;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,25 +42,28 @@ namespace Wpf_AiSportsMicrospace.Views
|
|||||||
};
|
};
|
||||||
Yztob.AiSports.Common.SportAppSettingService.Set("inferences", options);
|
Yztob.AiSports.Common.SportAppSettingService.Set("inferences", options);
|
||||||
|
|
||||||
_humanPredictor = HumanPredictorFactory.Create(HumanPredictorType.MultiMedium);
|
_humanPredictor = HumanPredictorFactory.Create(HumanPredictorType.MultiLow);
|
||||||
_humanGraphicsRenderer = new HumanGraphicsRenderer();
|
_humanGraphicsRenderer = new HumanGraphicsRenderer();
|
||||||
_humanGraphicsRenderer.DrawLabel = false;
|
_humanGraphicsRenderer.DrawLabel = false;
|
||||||
|
|
||||||
SportOperate = new SportOperate();
|
|
||||||
WebcamClient = SportOperate.CreateRTSP();
|
|
||||||
|
|
||||||
// 开始抽帧线程
|
// 开始抽帧线程
|
||||||
StartFrameProcessing();
|
StartFrameProcessing();
|
||||||
|
|
||||||
|
//WebcamClient.OnExtractFrame += this.ProcessFrame;
|
||||||
|
//WebcamClient.StartExtract();//开始抽帧
|
||||||
|
|
||||||
// 默认显示首页
|
// 默认显示首页
|
||||||
MainContent.Content = new Home();
|
MainContent.Content = new Home();
|
||||||
}
|
}
|
||||||
private void StartFrameProcessing()
|
private void StartFrameProcessing()
|
||||||
{
|
{
|
||||||
|
SportOperate = new SportOperate();
|
||||||
|
WebcamClient = SportOperate.CreateRTSP();
|
||||||
|
|
||||||
WebcamClient.OnExtractFrame += frame =>
|
WebcamClient.OnExtractFrame += frame =>
|
||||||
{
|
{
|
||||||
if (frame != null)
|
//if (frame != null)
|
||||||
_frameQueue.Enqueue(frame);
|
_frameQueue.Enqueue(frame);
|
||||||
};
|
};
|
||||||
|
|
||||||
WebcamClient.StartExtract();
|
WebcamClient.StartExtract();
|
||||||
@ -76,7 +79,17 @@ namespace Wpf_AiSportsMicrospace.Views
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 避免 CPU 占满
|
// 避免 CPU 占满
|
||||||
Thread.Sleep(5);
|
//Thread.Sleep(5);
|
||||||
|
//if (frame == null)
|
||||||
|
//{
|
||||||
|
// WebcamClient.OnExtractFrame += frame =>
|
||||||
|
// {
|
||||||
|
// //if (frame != null)
|
||||||
|
// _frameQueue.Enqueue(frame);
|
||||||
|
// };
|
||||||
|
//}
|
||||||
|
//else
|
||||||
|
Thread.Sleep(5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, _cts.Token);
|
}, _cts.Token);
|
||||||
@ -94,10 +107,11 @@ namespace Wpf_AiSportsMicrospace.Views
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// 触发全局事件
|
// 触发全局事件
|
||||||
Application.Current.Dispatcher.Invoke(() =>
|
Application.Current.Dispatcher.BeginInvoke(() =>
|
||||||
{
|
{
|
||||||
HumanFrameUpdated?.Invoke(this, humans);
|
HumanFrameUpdated?.Invoke(this, humans);
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user