举手 手势

This commit is contained in:
tanglong 2025-09-26 11:41:34 +08:00
parent ffa3ea7489
commit 1ca82f99a6
4 changed files with 173 additions and 113 deletions

View File

@ -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无效12右]
/// </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;
} }
} }
} }

View 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,//首次举手
}
}

View File

@ -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)
{ {

View File

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