目前比较好用的几种手势追踪,很多都是基于AR,需要硬件支持
而Google的MediaPipe则门槛比较低,用电脑 摄像头 也能跑
同时,又有MediaPipeUnityPlugin已经帮我们移植进了Unity
但 MediaPipe 是基于图像识别的,因此我手的往前往后,近大远小 在它看来只是在放大缩小,得到的数据都只发生在一个平面上,缺少硬件的支持所以没有深度信息。
幸运的是,即使只是一个平面,强大的Google至少帮我们做到了骨骼的相对深度
于是,本文要做的是,如最上面视频所示,模拟出手的深度,让我们的手真正在一个三维空间里面活动
如果你尚不了解 MediaPipeUnityPlugin,先看看我上一篇 MediaPipe-UnityPlugin的基本使用
如果你已经认识 MediaPipeUnityPlugin 的 HandTracking,可以直接跳到 [5.获取坐标] 环节
Assets\MediaPipeUnity\Samples\Scenes\Hand Tracking\Hand Tracking.unity
虽说插件已经提供了完整的案例,但因为我们要在这基础上加入额外的功能,所以还是模仿着搭一个自己的Graph和Solution,同时去掉一些用不到的内容,以简化代码
public class MyHandTrackingGraph : GraphRunner
{
public override void StartRun(ImageSource imageSource)
{
// 此处进行输出流的启动 和 CalculatorGraph的StartRun
}
protected override IList<WaitForResult> RequestDependentAssets()
{
// 此处加载用到的数据文件
}
}
输出流我们使用插件提供的 OutputStream<TPacket, TValue>
手部追踪的输出流有6种:
手掌检测、基于手掌检测的矩形区、手节点的坐标、手节点的世界坐标、基于坐标的矩形区、左右手检测
分别如下:
// 手掌检测
OutputStream<DetectionVectorPacket, List<Detection>> _palmDetectionsStream;
const string _PalmDetectionsStreamName = "palm_detections";
// 基于手掌检测的矩形区
OutputStream<NormalizedRectVectorPacket, List<NormalizedRect>> _handRectsFromPalmDetectionsStream;
const string _HandRectsFromPalmDetectionsStreamName = "hand_rects_from_palm_detections";
// 手节点的坐标
OutputStream<NormalizedLandmarkListVectorPacket, List<NormalizedLandmarkList>> _handLandmarksStream;
const string _HandLandmarksStreamName = "hand_landmarks";
// 手节点的世界坐标
OutputStream<LandmarkListVectorPacket, List<LandmarkList>> _handWorldLandmarksStream;
const string _HandWorldLandmarksStreamName = "hand_world_landmarks";
// 基于坐标的矩形区
OutputStream<NormalizedRectVectorPacket, List<NormalizedRect>> _handRectsFromLandmarksStream;
const string _HandRectsFromLandmarksStreamName = "hand_rects_from_landmarks";
// 左右手检测
OutputStream<ClassificationListVectorPacket, List<ClassificationList>> _handednessStream;
const string _HandednessStreamName = "handedness";
还有我们输入流的名称
const string _InputStreamName = "input_video";
我们此次用到的只有手节点的坐标,所以只需要
const string _InputStreamName = "input_video";
OutputStream<NormalizedLandmarkListVectorPacket, List<NormalizedLandmarkList>> _handLandmarksStream;
const string _HandLandmarksStreamName = "hand_landmarks";
protected override Status ConfigureCalculatorGraph(CalculatorGraphConfig config)
{
_handLandmarksStream = new OutputStream<NormalizedLandmarkListVectorPacket, List<NormalizedLandmarkList>>(
calculatorGraph, _HandLandmarksStreamName, config.AddPacketPresenceCalculator(_HandLandmarksStreamName), timeoutMicrosec);
return base.ConfigureCalculatorGraph(config);
}
OutputStream的构造函数如下
/// <summary>
/// 实例化一个 OutputStream class
/// 图形必须具有 PacketPresenceCalculator 节点,用于计算流是否有输出
/// </summary>
/// <remarks>
/// 当您希望同步获取输出,但不希望在等待输出时阻塞线程时,这很有用
/// </remarks>
/// <param name="calculatorGraph"> 流的所有者 </param>
/// <param name="streamName"> 输出流的名称 </param>
/// <param name="presenceStreamName"> 当输出存在时,输出true的流的名称 </param>
/// <param name="timeoutMicrosec"> 如果输出数据包为空,则 OutputStream 实例会丢弃数据包,直到此处指定的时间结束 </param>
public OutputStream(CalculatorGraph calculatorGraph, string streamName, string presenceStreamName, long timeoutMicrosec = 0) : this(calculatorGraph, streamName, false, timeoutMicrosec)
{
this.presenceStreamName = presenceStreamName;
}
public event EventHandler<OutputEventArgs<List<NormalizedLandmarkList>>> OnHandLandmarksOutput
{
add => _handLandmarksStream.AddListener(value);
remove => _handLandmarksStream.RemoveListener(value);
}
public bool TryGetNext(out List<NormalizedLandmarkList> handLandmarks, bool allowBlock = true)
{
var currentTimestampMicrosec = GetCurrentTimestampMicrosec();
return TryGetNext(_handLandmarksStream, out handLandmarks, allowBlock, currentTimestampMicrosec);
}
public override void StartRun(ImageSource imageSource)
{
_handLandmarksStream.StartPolling().AssertOk();
calculatorGraph.StartRun().AssertOk();
}
public override void Stop()
{
_handLandmarksStream?.Close();
_handLandmarksStream = null;
base.Stop();
}
public void AddTextureFrameToInputStream(TextureFrame textureFrame)
{
AddTextureFrameToInputStream(_InputStreamName, textureFrame);
}
public class MyHandTrackingGraph : GraphRunner
{
const string _InputStreamName =
免责声明:本文系网络转载或改编,未找到原创作者,版权归原作者所有。如涉及版权,请联系删