举手 手势
This commit is contained in:
parent
ffa3ea7489
commit
1ca82f99a6
@ -5,6 +5,7 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
using Wpf_AiSportsMicrospace.Enum;
|
||||||
using Yztob.AiSports.Inferences.Abstractions;
|
using Yztob.AiSports.Inferences.Abstractions;
|
||||||
using Yztob.AiSports.Inferences.Things;
|
using Yztob.AiSports.Inferences.Things;
|
||||||
using Yztob.AiSports.Postures;
|
using Yztob.AiSports.Postures;
|
||||||
@ -21,16 +22,17 @@ namespace Wpf_AiSportsMicrospace.Common
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class SportOperate
|
public class SportOperate
|
||||||
{
|
{
|
||||||
private DateTime _lastSlideTime = DateTime.MinValue;
|
|
||||||
|
|
||||||
IPointTracker _leftTracker;
|
IPointTracker _leftTracker;
|
||||||
IPointTracker _rightTracker;
|
IPointTracker _rightTracker;
|
||||||
IPointTracker _leftElbow;
|
IPointTracker _leftElbow;
|
||||||
IPointTracker _rightElbow;
|
IPointTracker _rightElbow;
|
||||||
WebcamClient _webcamClient;
|
WebcamClient _webcamClient;
|
||||||
private Point? _lastWrist = null; // 上一帧的手腕位置
|
|
||||||
private DateTime _lastActionTime = DateTime.MinValue; // 上次动作时间
|
private Point? _lastLeftWrist = null;
|
||||||
private readonly TimeSpan _cooldown = TimeSpan.FromMilliseconds(800); // 动作冷却,避免误触发
|
private Point? _lastRightWrist = null;
|
||||||
|
private DateTime _lastActionTime = DateTime.MinValue;
|
||||||
|
// 记录举手开始时间
|
||||||
|
private DateTime? _raiseStartTime = null;
|
||||||
|
|
||||||
|
|
||||||
public SportOperate()
|
public SportOperate()
|
||||||
@ -52,88 +54,120 @@ namespace Wpf_AiSportsMicrospace.Common
|
|||||||
return _webcamClient;
|
return _webcamClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 验证挥手动作[0:无效,1:左,2:右]
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="human"></param>
|
|
||||||
public int VerifyWavingAction(Human human)
|
public int VerifyWavingAction(Human human)
|
||||||
{
|
{
|
||||||
var leftResult = _leftTracker.Tracking(human);
|
var leftWrist = human.Keypoints.FirstOrDefault(x => x.Name == "left_wrist");
|
||||||
var rightResult = _rightTracker.Tracking(human);
|
var leftElbow = human.Keypoints.FirstOrDefault(x => x.Name == "left_elbow");
|
||||||
var leftElbowResult = _leftElbow.Tracking(human);
|
var rightWrist = human.Keypoints.FirstOrDefault(x => x.Name == "right_wrist");
|
||||||
var rightElbowResult = _rightElbow.Tracking(human);
|
var rightElbow = human.Keypoints.FirstOrDefault(x => x.Name == "right_elbow");
|
||||||
|
|
||||||
// 节流:每 300ms 最多更新一次 UI
|
// 左手逻辑
|
||||||
//if ((DateTime.Now - _lastSlideTime).TotalMilliseconds < 500) return 0;
|
if (leftWrist != null && leftElbow != null)
|
||||||
//_lastSlideTime = DateTime.Now;
|
|
||||||
|
|
||||||
// 根据手势结果选择滑动方向
|
|
||||||
if (leftResult != 0 && leftElbowResult != 0) return 1;
|
|
||||||
if (rightResult != 0 & rightElbowResult != 0)
|
|
||||||
{
|
{
|
||||||
var wrist = human.Keypoints.Where(x => x.Name == "right_wrist").FirstOrDefault();
|
var result = RecognizeLeftHandGesture(
|
||||||
var elbow = human.Keypoints.Where(x => x.Name == "right_elbow").FirstOrDefault();
|
new Point(leftWrist.X, leftWrist.Y),
|
||||||
if (wrist == null || elbow == null) return 0;
|
new Point(leftElbow.X, leftElbow.Y));
|
||||||
return RecognizeRightHandGesture(new Point(wrist.X, wrist.Y), new Point(elbow.X, elbow.Y));
|
if (result != 0) return result;
|
||||||
}
|
}
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 右手逻辑
|
||||||
public int VerifyLiftHandAction(Human human)
|
if (rightWrist != null && rightElbow != null)
|
||||||
{
|
{
|
||||||
var rightResult = _rightTracker.Tracking(human);
|
var result = RecognizeRightHandGesture(
|
||||||
var rightElbowResult = _rightElbow.Tracking(human);
|
new Point(rightWrist.X, rightWrist.Y),
|
||||||
|
new Point(rightElbow.X, rightElbow.Y));
|
||||||
// 节流:每 300ms 最多更新一次 UI
|
if (result != 0) return result;
|
||||||
if ((DateTime.Now - _lastSlideTime).TotalMilliseconds < 500) return 0;
|
}
|
||||||
_lastSlideTime = DateTime.Now;
|
|
||||||
|
|
||||||
// 根据手势结果选择滑动方向
|
|
||||||
if (rightResult != 0 & rightElbowResult != 0) return 1;
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int RecognizeRightHandGesture(Point wrist, Point elbow)
|
/// <summary>
|
||||||
|
/// 识别左手动作(左→右为 1)
|
||||||
|
/// </summary>
|
||||||
|
public int RecognizeLeftHandGesture(Point wrist, Point elbow)
|
||||||
{
|
{
|
||||||
// 判断举手:手腕在肘部之上,且垂直距离超过一定阈值
|
if (_lastLeftWrist != null)
|
||||||
if (wrist.Y + 40 < elbow.Y) // 手腕比肘高 40 像素以上
|
|
||||||
{
|
{
|
||||||
if (CheckCooldown())
|
double dx = wrist.X - _lastLeftWrist.Value.X;
|
||||||
return 3; // 举手
|
double dy = Math.Abs(wrist.Y - _lastLeftWrist.Value.Y);
|
||||||
}
|
|
||||||
|
|
||||||
// 需要至少两帧来判断水平移动方向
|
|
||||||
if (_lastWrist != null)
|
|
||||||
{
|
|
||||||
double dx = wrist.X - _lastWrist.Value.X; // 水平位移
|
|
||||||
double dy = Math.Abs(wrist.Y - _lastWrist.Value.Y);
|
|
||||||
|
|
||||||
// 要求水平位移明显大于垂直位移,且超过一定阈值
|
|
||||||
if (Math.Abs(dx) > 30 && dy < 40)
|
if (Math.Abs(dx) > 30 && dy < 40)
|
||||||
{
|
{
|
||||||
if (CheckCooldown())
|
if (CheckCooldown())
|
||||||
{
|
{
|
||||||
if (dx > 0)
|
if (dx > 0)
|
||||||
return 2; // 右挥手
|
return 1; // 左手往右挥
|
||||||
else
|
|
||||||
return 1; // 左挥手
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_lastWrist = wrist;
|
_lastLeftWrist = wrist;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
private bool CheckCooldown()
|
|
||||||
|
/// <summary>
|
||||||
|
/// 识别右手动作(右→左为 2,举手为 3)
|
||||||
|
/// </summary>
|
||||||
|
|
||||||
|
public int RecognizeRightHandGesture(Point wrist, Point elbow)
|
||||||
{
|
{
|
||||||
if (DateTime.Now - _lastActionTime > _cooldown)
|
// 判断手腕是否在肘部上方,并且达到高度阈值
|
||||||
|
//if (wrist.Y + 40 < elbow.Y && wrist.Y < minRaiseHeight)
|
||||||
|
if (wrist.Y + 40 < elbow.Y)
|
||||||
{
|
{
|
||||||
_lastActionTime = DateTime.Now;
|
if (_raiseStartTime == null)
|
||||||
return true;
|
{
|
||||||
|
_raiseStartTime = DateTime.Now;
|
||||||
|
return (int)WavingAction.FirstHand;
|
||||||
|
}
|
||||||
|
|
||||||
|
var duration = DateTime.Now - _raiseStartTime.Value;
|
||||||
|
if (duration.TotalSeconds >= 3)
|
||||||
|
{
|
||||||
|
_raiseStartTime = null;
|
||||||
|
return (int)WavingAction.RaiseHand; // 举手完成
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return (int)WavingAction.Raising; // 举手中
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
else
|
||||||
|
{
|
||||||
|
_raiseStartTime = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 挥手逻辑
|
||||||
|
if (_lastRightWrist != null)
|
||||||
|
{
|
||||||
|
double dx = wrist.X - _lastRightWrist.Value.X;
|
||||||
|
double dy = Math.Abs(wrist.Y - _lastRightWrist.Value.Y);
|
||||||
|
|
||||||
|
if (Math.Abs(dx) > 30 && dy < 40)
|
||||||
|
{
|
||||||
|
if (CheckCooldown())
|
||||||
|
{
|
||||||
|
if (dx < 0)
|
||||||
|
return 2; // 右手往左挥
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastRightWrist = wrist;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 冷却防抖(避免重复触发)
|
||||||
|
/// </summary>
|
||||||
|
private bool CheckCooldown(int cooldownMs = 500)
|
||||||
|
{
|
||||||
|
if ((DateTime.Now - _lastActionTime).TotalMilliseconds < cooldownMs)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_lastActionTime = DateTime.Now;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
19
Wpf_AiSportsMicrospace/Enum/WavingAction.cs
Normal file
19
Wpf_AiSportsMicrospace/Enum/WavingAction.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Wpf_AiSportsMicrospace.Enum
|
||||||
|
{
|
||||||
|
public enum WavingAction
|
||||||
|
{
|
||||||
|
None = 0, // 没动作
|
||||||
|
LeftWave = 1, // 左挥手
|
||||||
|
RightWave = 2, // 右挥手
|
||||||
|
RaiseHand = 3, // 举手完成
|
||||||
|
Raising = 4, // 举手中,未完成
|
||||||
|
FirstHand = 5,//首次举手
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -5,6 +5,7 @@ using System.Collections.Concurrent;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@ -14,6 +15,7 @@ using System.Windows.Controls;
|
|||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.Animation;
|
using System.Windows.Media.Animation;
|
||||||
using Wpf_AiSportsMicrospace.Common;
|
using Wpf_AiSportsMicrospace.Common;
|
||||||
|
using Wpf_AiSportsMicrospace.Enum;
|
||||||
using Wpf_AiSportsMicrospace.MyUserControl;
|
using Wpf_AiSportsMicrospace.MyUserControl;
|
||||||
using Wpf_AiSportsMicrospace.Views;
|
using Wpf_AiSportsMicrospace.Views;
|
||||||
using Yunzhi.Database;
|
using Yunzhi.Database;
|
||||||
@ -33,11 +35,6 @@ namespace Wpf_AiSportsMicrospace
|
|||||||
public partial class Home : Window
|
public partial class Home : Window
|
||||||
{
|
{
|
||||||
private IHumanPredictor _humanPredictor;
|
private IHumanPredictor _humanPredictor;
|
||||||
private IObjectDetector _objectDetector;
|
|
||||||
private HumanGraphicsRenderer _humanGraphicsRenderer;
|
|
||||||
private readonly List<SportDescriptor> _sports;
|
|
||||||
private SportBase _sport;
|
|
||||||
private readonly SportDetectionQueue _detectQueue;
|
|
||||||
private WebcamClient _webcamClient;
|
private WebcamClient _webcamClient;
|
||||||
private ConcurrentQueue<VideoFrame> _frameQueue = new();
|
private ConcurrentQueue<VideoFrame> _frameQueue = new();
|
||||||
private CancellationTokenSource _cts = new();
|
private CancellationTokenSource _cts = new();
|
||||||
@ -47,12 +44,6 @@ namespace Wpf_AiSportsMicrospace
|
|||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_humanPredictor = HumanPredictorFactory.Create(HumanPredictorType.SingleHigh);
|
_humanPredictor = HumanPredictorFactory.Create(HumanPredictorType.SingleHigh);
|
||||||
_objectDetector = ObjectDetectorFactory.CreateSportGoodsDetector();
|
|
||||||
_humanGraphicsRenderer = new HumanGraphicsRenderer();
|
|
||||||
_humanGraphicsRenderer.DrawLabel = false;
|
|
||||||
|
|
||||||
//_sports = SportBase.GetSports();
|
|
||||||
//_detectQueue = new SportDetectionQueue();
|
|
||||||
|
|
||||||
string projectRoot = Path.Combine(AppContext.BaseDirectory, @"..\..\..");
|
string projectRoot = Path.Combine(AppContext.BaseDirectory, @"..\..\..");
|
||||||
string albumPath = Path.Combine(projectRoot, "Resources", "Img", "Album");
|
string albumPath = Path.Combine(projectRoot, "Resources", "Img", "Album");
|
||||||
@ -73,7 +64,7 @@ namespace Wpf_AiSportsMicrospace
|
|||||||
coverFlow.SelectedIndex = 2;
|
coverFlow.SelectedIndex = 2;
|
||||||
|
|
||||||
// 监听进度条完成事件
|
// 监听进度条完成事件
|
||||||
coverFlow.ProgressCompleted += CoverFlow_ProgressCompleted;
|
//coverFlow.ProgressCompleted += CoverFlow_ProgressCompleted;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Window_Loaded(object sender, RoutedEventArgs e)
|
private void Window_Loaded(object sender, RoutedEventArgs e)
|
||||||
@ -93,36 +84,35 @@ namespace Wpf_AiSportsMicrospace
|
|||||||
|
|
||||||
private void CoverFlow_ProgressCompleted(CoverFlowItem item)
|
private void CoverFlow_ProgressCompleted(CoverFlowItem item)
|
||||||
{
|
{
|
||||||
// 根据不同图片跳转不同页面
|
// 停止抽帧线程/释放资源
|
||||||
if (item.ImageUri.ToString().EndsWith("1.jpg"))
|
try
|
||||||
{
|
{
|
||||||
// 跳转到页面1
|
_webcamClient?.StopExtract(); // 停止抽帧
|
||||||
Dispatcher.BeginInvoke(new Action(() =>
|
_webcamClient = null; // 释放引用
|
||||||
{
|
|
||||||
GroupJumpRope groupJumpRope = new GroupJumpRope();
|
|
||||||
groupJumpRope.Owner = Application.Current.MainWindow;
|
|
||||||
groupJumpRope.Show();
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
else if (item.ImageUri.ToString().EndsWith("2.jpg"))
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// 跳转到页面2
|
Debug.WriteLine($"停止抽帧异常: {ex.Message}");
|
||||||
Dispatcher.BeginInvoke(new Action(() =>
|
|
||||||
{
|
|
||||||
GroupJumpRope groupJumpRope = new GroupJumpRope();
|
|
||||||
groupJumpRope.Owner = Application.Current.MainWindow;
|
|
||||||
groupJumpRope.Show();
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
else if (item.ImageUri.ToString().EndsWith("3.jpg"))
|
|
||||||
|
// 根据图片跳转新窗口
|
||||||
|
string uri = item.ImageUri.ToString();
|
||||||
|
Window newWindow = null;
|
||||||
|
|
||||||
|
if (uri.EndsWith("1.jpg"))
|
||||||
|
newWindow = new GroupJumpRope();
|
||||||
|
else if (uri.EndsWith("2.jpg"))
|
||||||
|
newWindow = new GroupJumpRope();
|
||||||
|
else if (uri.EndsWith("3.jpg"))
|
||||||
|
newWindow = new GroupJumpRope();
|
||||||
|
|
||||||
|
if (newWindow != null)
|
||||||
{
|
{
|
||||||
// 跳转到页面3
|
Dispatcher.BeginInvoke(() =>
|
||||||
Dispatcher.BeginInvoke(new Action(() =>
|
|
||||||
{
|
{
|
||||||
GroupJumpRope groupJumpRope = new GroupJumpRope();
|
newWindow.Show(); // 先显示新窗口
|
||||||
groupJumpRope.Owner = Application.Current.MainWindow;
|
this.Close(); // 再关闭当前窗口
|
||||||
groupJumpRope.Show();
|
});
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,31 +159,27 @@ namespace Wpf_AiSportsMicrospace
|
|||||||
|
|
||||||
//检测挥手动作
|
//检测挥手动作
|
||||||
var wavingaction = _sportOperate.VerifyWavingAction(human);
|
var wavingaction = _sportOperate.VerifyWavingAction(human);
|
||||||
|
|
||||||
switch (wavingaction)
|
switch (wavingaction)
|
||||||
{
|
{
|
||||||
case 1:
|
case (int)WavingAction.LeftWave: // 1
|
||||||
Dispatcher.BeginInvoke(() => coverFlow.SlideLeft());
|
Dispatcher.BeginInvoke(() => coverFlow.SlideLeft());
|
||||||
break;
|
break;
|
||||||
case 2:
|
case (int)WavingAction.RightWave: // 2
|
||||||
Dispatcher.BeginInvoke(() => coverFlow.SlideRight());
|
Dispatcher.BeginInvoke(() => coverFlow.SlideRight());
|
||||||
break;
|
break;
|
||||||
case 3:
|
case (int)WavingAction.RaiseHand: // 3 举手完成
|
||||||
|
coverFlow.ProgressCompleted += CoverFlow_ProgressCompleted;
|
||||||
|
break;
|
||||||
|
case (int)WavingAction.Raising: // 4 举手中
|
||||||
|
break;
|
||||||
|
case 5: // 如果还有其他动作
|
||||||
Dispatcher.BeginInvoke(() => coverFlow.StartSelectedProgress());
|
Dispatcher.BeginInvoke(() => coverFlow.StartSelectedProgress());
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
Dispatcher.BeginInvoke(() => coverFlow.CancelSelectedProgress());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
////检测举手动作
|
|
||||||
//var liftHandAction = _sportOperate.VerifyLiftHandAction(human);
|
|
||||||
|
|
||||||
//// 根据手势结果
|
|
||||||
//if (liftHandAction == 1)
|
|
||||||
//{
|
|
||||||
// // 启动进度条动画;
|
|
||||||
// Dispatcher.BeginInvoke(() => coverFlow.StartSelectedProgress());
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -82,10 +82,23 @@ namespace Wpf_AiSportsMicrospace.MyUserControl
|
|||||||
if (SelectedIndex >= 0 && SelectedIndex < Images.Count)
|
if (SelectedIndex >= 0 && SelectedIndex < Images.Count)
|
||||||
{
|
{
|
||||||
var current = Images[SelectedIndex];
|
var current = Images[SelectedIndex];
|
||||||
|
if (current.Progress >= 1) return; // 已完成,直接返回,不再启动
|
||||||
StartProgress(current);
|
StartProgress(current);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public void CancelSelectedProgress()
|
||||||
|
{
|
||||||
|
if (SelectedIndex >= 0 && SelectedIndex < Images.Count)
|
||||||
|
{
|
||||||
|
var current = Images[SelectedIndex];
|
||||||
|
|
||||||
|
// 停止动画/计时
|
||||||
|
StopProgress();
|
||||||
|
|
||||||
|
// 清零当前的进度值
|
||||||
|
current.Progress = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
public void StartProgress(CoverFlowItem item)
|
public void StartProgress(CoverFlowItem item)
|
||||||
{
|
{
|
||||||
StopProgress();
|
StopProgress();
|
||||||
@ -257,7 +270,15 @@ namespace Wpf_AiSportsMicrospace.MyUserControl
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SlideLeft() => SelectedIndex++;
|
public void SlideLeft()
|
||||||
public void SlideRight() => SelectedIndex--;
|
{
|
||||||
|
StopProgress();
|
||||||
|
SelectedIndex++;
|
||||||
|
}
|
||||||
|
public void SlideRight()
|
||||||
|
{
|
||||||
|
StopProgress();
|
||||||
|
SelectedIndex--;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user