举手 手势

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.Threading.Tasks;
using System.Windows;
using Wpf_AiSportsMicrospace.Enum;
using Yztob.AiSports.Inferences.Abstractions;
using Yztob.AiSports.Inferences.Things;
using Yztob.AiSports.Postures;
@ -21,16 +22,17 @@ namespace Wpf_AiSportsMicrospace.Common
/// </summary>
public class SportOperate
{
private DateTime _lastSlideTime = DateTime.MinValue;
IPointTracker _leftTracker;
IPointTracker _rightTracker;
IPointTracker _leftElbow;
IPointTracker _rightElbow;
WebcamClient _webcamClient;
private Point? _lastWrist = null; // 上一帧的手腕位置
private DateTime _lastActionTime = DateTime.MinValue; // 上次动作时间
private readonly TimeSpan _cooldown = TimeSpan.FromMilliseconds(800); // 动作冷却,避免误触发
private Point? _lastLeftWrist = null;
private Point? _lastRightWrist = null;
private DateTime _lastActionTime = DateTime.MinValue;
// 记录举手开始时间
private DateTime? _raiseStartTime = null;
public SportOperate()
@ -52,88 +54,120 @@ namespace Wpf_AiSportsMicrospace.Common
return _webcamClient;
}
/// <summary>
/// 验证挥手动作[0无效12右]
/// </summary>
/// <param name="human"></param>
public int VerifyWavingAction(Human human)
{
var leftResult = _leftTracker.Tracking(human);
var rightResult = _rightTracker.Tracking(human);
var leftElbowResult = _leftElbow.Tracking(human);
var rightElbowResult = _rightElbow.Tracking(human);
var leftWrist = human.Keypoints.FirstOrDefault(x => x.Name == "left_wrist");
var leftElbow = human.Keypoints.FirstOrDefault(x => x.Name == "left_elbow");
var rightWrist = human.Keypoints.FirstOrDefault(x => x.Name == "right_wrist");
var rightElbow = human.Keypoints.FirstOrDefault(x => x.Name == "right_elbow");
// 节流:每 300ms 最多更新一次 UI
//if ((DateTime.Now - _lastSlideTime).TotalMilliseconds < 500) return 0;
//_lastSlideTime = DateTime.Now;
// 根据手势结果选择滑动方向
if (leftResult != 0 && leftElbowResult != 0) return 1;
if (rightResult != 0 & rightElbowResult != 0)
// 左手逻辑
if (leftWrist != null && leftElbow != null)
{
var wrist = human.Keypoints.Where(x => x.Name == "right_wrist").FirstOrDefault();
var elbow = human.Keypoints.Where(x => x.Name == "right_elbow").FirstOrDefault();
if (wrist == null || elbow == null) return 0;
return RecognizeRightHandGesture(new Point(wrist.X, wrist.Y), new Point(elbow.X, elbow.Y));
}
return 0;
var result = RecognizeLeftHandGesture(
new Point(leftWrist.X, leftWrist.Y),
new Point(leftElbow.X, leftElbow.Y));
if (result != 0) return result;
}
public int VerifyLiftHandAction(Human human)
// 右手逻辑
if (rightWrist != null && rightElbow != null)
{
var rightResult = _rightTracker.Tracking(human);
var rightElbowResult = _rightElbow.Tracking(human);
// 节流:每 300ms 最多更新一次 UI
if ((DateTime.Now - _lastSlideTime).TotalMilliseconds < 500) return 0;
_lastSlideTime = DateTime.Now;
// 根据手势结果选择滑动方向
if (rightResult != 0 & rightElbowResult != 0) return 1;
var result = RecognizeRightHandGesture(
new Point(rightWrist.X, rightWrist.Y),
new Point(rightElbow.X, rightElbow.Y));
if (result != 0) return result;
}
return 0;
}
public int RecognizeRightHandGesture(Point wrist, Point elbow)
/// <summary>
/// 识别左手动作(左→右为 1
/// </summary>
public int RecognizeLeftHandGesture(Point wrist, Point elbow)
{
// 判断举手:手腕在肘部之上,且垂直距离超过一定阈值
if (wrist.Y + 40 < elbow.Y) // 手腕比肘高 40 像素以上
if (_lastLeftWrist != null)
{
if (CheckCooldown())
return 3; // 举手
}
double dx = wrist.X - _lastLeftWrist.Value.X;
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 (CheckCooldown())
{
if (dx > 0)
return 2; // 右挥手
else
return 1; // 左挥手
return 1; // 左手往右挥
}
}
}
_lastWrist = wrist;
_lastLeftWrist = wrist;
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)
{
if (_raiseStartTime == null)
{
_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; // 举手中
}
}
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;
}
return false;
}
}
}

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.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
@ -14,6 +15,7 @@ using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using Wpf_AiSportsMicrospace.Common;
using Wpf_AiSportsMicrospace.Enum;
using Wpf_AiSportsMicrospace.MyUserControl;
using Wpf_AiSportsMicrospace.Views;
using Yunzhi.Database;
@ -33,11 +35,6 @@ namespace Wpf_AiSportsMicrospace
public partial class Home : Window
{
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 ConcurrentQueue<VideoFrame> _frameQueue = new();
private CancellationTokenSource _cts = new();
@ -47,12 +44,6 @@ namespace Wpf_AiSportsMicrospace
{
InitializeComponent();
_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 albumPath = Path.Combine(projectRoot, "Resources", "Img", "Album");
@ -73,7 +64,7 @@ namespace Wpf_AiSportsMicrospace
coverFlow.SelectedIndex = 2;
// 监听进度条完成事件
coverFlow.ProgressCompleted += CoverFlow_ProgressCompleted;
//coverFlow.ProgressCompleted += CoverFlow_ProgressCompleted;
}
private void Window_Loaded(object sender, RoutedEventArgs e)
@ -93,36 +84,35 @@ namespace Wpf_AiSportsMicrospace
private void CoverFlow_ProgressCompleted(CoverFlowItem item)
{
// 根据不同图片跳转不同页面
if (item.ImageUri.ToString().EndsWith("1.jpg"))
// 停止抽帧线程/释放资源
try
{
// 跳转到页面1
Dispatcher.BeginInvoke(new Action(() =>
{
GroupJumpRope groupJumpRope = new GroupJumpRope();
groupJumpRope.Owner = Application.Current.MainWindow;
groupJumpRope.Show();
}));
_webcamClient?.StopExtract(); // 停止抽帧
_webcamClient = null; // 释放引用
}
else if (item.ImageUri.ToString().EndsWith("2.jpg"))
catch (Exception ex)
{
// 跳转到页面2
Dispatcher.BeginInvoke(new Action(() =>
{
GroupJumpRope groupJumpRope = new GroupJumpRope();
groupJumpRope.Owner = Application.Current.MainWindow;
groupJumpRope.Show();
}));
Debug.WriteLine($"停止抽帧异常: {ex.Message}");
}
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(new Action(() =>
Dispatcher.BeginInvoke(() =>
{
GroupJumpRope groupJumpRope = new GroupJumpRope();
groupJumpRope.Owner = Application.Current.MainWindow;
groupJumpRope.Show();
}));
newWindow.Show(); // 先显示新窗口
this.Close(); // 再关闭当前窗口
});
}
}
@ -169,31 +159,27 @@ namespace Wpf_AiSportsMicrospace
//检测挥手动作
var wavingaction = _sportOperate.VerifyWavingAction(human);
switch (wavingaction)
{
case 1:
case (int)WavingAction.LeftWave: // 1
Dispatcher.BeginInvoke(() => coverFlow.SlideLeft());
break;
case 2:
case (int)WavingAction.RightWave: // 2
Dispatcher.BeginInvoke(() => coverFlow.SlideRight());
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());
break;
default:
Dispatcher.BeginInvoke(() => coverFlow.CancelSelectedProgress());
break;
}
////检测举手动作
//var liftHandAction = _sportOperate.VerifyLiftHandAction(human);
//// 根据手势结果
//if (liftHandAction == 1)
//{
// // 启动进度条动画;
// Dispatcher.BeginInvoke(() => coverFlow.StartSelectedProgress());
//}
}
catch (Exception ex)
{

View File

@ -82,10 +82,23 @@ namespace Wpf_AiSportsMicrospace.MyUserControl
if (SelectedIndex >= 0 && SelectedIndex < Images.Count)
{
var current = Images[SelectedIndex];
if (current.Progress >= 1) return; // 已完成,直接返回,不再启动
StartProgress(current);
}
}
public void CancelSelectedProgress()
{
if (SelectedIndex >= 0 && SelectedIndex < Images.Count)
{
var current = Images[SelectedIndex];
// 停止动画/计时
StopProgress();
// 清零当前的进度值
current.Progress = 0;
}
}
public void StartProgress(CoverFlowItem item)
{
StopProgress();
@ -257,7 +270,15 @@ namespace Wpf_AiSportsMicrospace.MyUserControl
return null;
}
public void SlideLeft() => SelectedIndex++;
public void SlideRight() => SelectedIndex--;
public void SlideLeft()
{
StopProgress();
SelectedIndex++;
}
public void SlideRight()
{
StopProgress();
SelectedIndex--;
}
}
}