整体替换 Google MediaPipe (C++)¶
MediaPipe4U
提供了可插拔的算法连接器,你可以使用自己的算法,或者自己的 MediaPipe 程序(例如: python 服务器端运行的 mediapipe 程序)来代替内置的 google mediapipe 算解程序。
前提条件¶
虽然 MediaPipe4U
提供了替换 mediapipe 算法的能力, 但是 MediaPipe4U
使用的是 mediapipe 的 Blazor Pose 骨骼结构,其他方案例如 SMPL、SMPLX 在 MediaPipe4U
中将无法正确运行。
因此,如果你决定替换 google mediapipe 算法,必须符合如下要求:
- 算法输出的骨骼结构符合 Blazor Pose 结构。
- 算法输出的地标(Landmarks)坐标系和 google mediapipe 算法保持一致。
- 如果算法包含表情 (BlendShape) 输出,必须符合 Apple ARKit 标准。
Tip
除了骨骼结构,坐标系,表情规范,地标使用的规格也应该和 google mediapipe 保持一致:
Google MediaPipe 中地标有两种地标,具体含义如下:
NormalizedLandmark
x
,y
和 z
: 以米为单位的真实世界3D坐标,原点位于臀部之间的中心。
Landmark
x
和 x
: 分别由图像宽度和高度归一化为 [0.0,1.0] 的地标坐标, 原点 [0.0, 0.0] 是图片的左上角;
z
: 表示以 hips 中点处的深度为原点的地标深度,该值越小,地标越靠近相机。z
的大小使用与 x
大致相同的比例。
MediaPipe4U
中PoseWorldLandmark
使用的是 NormalizedLandmark,其余均为 Landmark。
实现步骤¶
要完整的替换 MediaPipe4U
中的 google mediapipe 算法,可以采取如下步骤:
- 实现一个继承自
IMediaPipeHolisticConnector
自定义连接器 (Connector). - 提供一个蓝图函数库 (
UBlueprintFunctionLibrary
)来调用UMediaPipeHolisticComponent
的StartCustomConnector
或StartCustomConnectorAsync
函数。 - 在 Unreal Engine 蓝图编辑器中使用步骤 2 提供的蓝图函数来代码
StartCamer
/StartImageSource
等函数。
下面详细介绍这些技术细节。
IMediaPipeHolisticConnector¶
MediaPipe4U
提供了一个 IMediaPipeHolisticConnector
用于定义 Unreal Engine 和 google mediapipe api 之间的连接。
通过在 C++ 中实现一个继承自 IMediaPipeHolisticConnector
的类可以替换 MediaPipe4U
内置的连接器 (Connector)。
下面是 IMediaPipeHolisticConnector
的 C++ 定义。
class IMediaPipeHolisticConnector
{
public:
static IMediaPipeHolisticConnector& Get();
static IMediaPipeHolisticFeatureRegistry& GetFeatureRegistry();
virtual bool ConfigureGraph(const MediaPipeGraphCnf& InConfig) const = 0;
virtual void EnableFrameCallback(bool Enabled) = 0;
virtual bool IsConnected(UObject* Owner) const = 0;
virtual bool Connect(UObject* Owner) = 0;
virtual bool Disconnect(const UObject* Owner) = 0;
virtual bool StartPipeline(long long SessionId, IImageSource* ImageSource, const FMediaPipeHolisticOptions& Options) = 0;
virtual void StopPipeline() = 0;
virtual bool PushFrameToPipeline(TSharedRef<IMediaPipeTexture> InTexture, int RotationDegrees) = 0;
virtual FLandmarksOutputEvent& OnPoseLandmarksTrigger() = 0;
virtual FLandmarksOutputEvent& OnPoseWorldLandmarksTrigger() = 0;
virtual FLandmarksOutputEvent& OnLeftHandLandmarksTrigger() = 0;
virtual FLandmarksOutputEvent& OnRightHandLandmarksTrigger() = 0;
virtual FFaceBlendShapesOutputEvent& OnFaceBlendShapesTrigger() = 0;
virtual FFaceGeometryOutputEvent& OnFaceGeometryTrigger() = 0;
virtual FLandmarksOutputEvent& OnFaceLandmarksTrigger() = 0;
virtual FImageSizeDetectedEvent& OnImageSizeDetectedTrigger() = 0;
virtual FMediaPipeFailedEvent& OnMediaPipeFailedTrigger() = 0;
virtual FMediaPipeFrameEvent& OnMediaPipeFrameTrigger() = 0;
virtual bool AddListener(const TSharedPtr<FMediaPipeHolisticListener>& InListener) = 0;
virtual bool RemoveListener(const TSharedPtr<FMediaPipeHolisticListener>& InListener) = 0;
protected:
virtual ~IMediaPipeHolisticConnector() = default;
};
生命周期函数¶
Tip
在 MediaPipe4U
中 IMediaPipeHolisticConnector
调用顺序(生命周期)如下:
graph LR
A[Connect] --> B{StartPipeline};
B --> C{StopPipeline}
C --> D{Disconnect}
Connect
: 内置的 Connector 在 MediaPipeHolisticComponent 初始化时调用, 自定义 Connector 在 UMediaPipeHolisticComponent 的 StartXXX 函数调用时调用。StartPipeline
: UMediaPipeHolisticComponent 的 StartXX 函数调用时调用。StopPipeline
: UMediaPipeHolisticComponent 的Stop
/StopAsync
函数调用时调用。Disconnect
: 内置的 Connector 在 MediaPipeHolisticComponent 卸载 ( Uninitialize ) 时调用, 自定义 Connector 在 UMediaPipeHolisticComponent 的Stop
/StopAsync
函数调用时调用该函数。
实现自定义 Connector 时,下面的函数实现是可以选的:
ConfigureGraph
: 配置 mediapipe 图形,自定义 Connector 时直接返回 true 即可。EnableFrameCallback
: 允许 frame 回调, 自定义 Connector 可以什么都不做。Connect
: 如果你在StartPipeline
中处理逻辑,直接返回 true 即可。Disconnect
: 如果你在StopPipeline
中处理逻辑,直接返回 true 即可。
实现自定义 Connector 时,下面的函数无需实现:
PushFrameToPipeline
: 当 ImageSource 的模式是 Push 时,这个函数接受主动推送帧, 由于自定义 Connector 目前不使用 ImageSource , 因此你无需实现它。
事件函数¶
以 On 开头,并且以 Trigger 结束的函数表示当自定义算法求解出数据时需要触发的事件,你实现的事件越多,功能就越完整。
函数 | 数据类型 | 受影响功能 |
---|---|---|
OnPoseLandmarksTrigger | 主要数据 | MediaPipeAnimInstance 用它计算人物位置( MediaPipe Location Solver 动画蓝图节点 )。 |
OnPoseWorldLandmarksTrigger | NormalizedLandmark | MediaPipeAnimInstance 用它计算人物姿势( MediaPipe Pose Solver 动画蓝图节点 )。 |
OnLeftHandLandmarksTrigger | Landmark | MediaPipeAnimInstance 用它计算人物左手的姿势和手腕翻转( MediaPipe Hand Solver 动画蓝图节点 )。 |
OnRightHandLandmarksTrigger | Landmark | MediaPipeAnimInstance 用它计算人物右手的姿势和手腕翻转( MediaPipe Hand Solver 动画蓝图节点 )。 |
OnFaceBlendShapesTrigger | Blend Shape Map | MediaPipeLiveLinkActor 用它计算人物表情。 |
OnFaceGeometryTrigger | Landmark | 无 |
OnFaceLandmarksTrigger | Landmark | MediaPipeAnimInstance 用它计算人物头部转动( MediaPipe Head Solver 动画蓝图节点 )。 |
OnImageSizeDetectedTrigger | Landmark | MediaPipeAnimInstance 用根据它配合 PoseLandmarks 计算人物位置( MediaPipe Location Solver 动画蓝图节点 )。 |
OnMediaPipeFailedTrigger | int64 | MediaPipeHolisticComponent 错误处理。参数: session id : 来自 StartPipeline 的第一个参数。 |
OnMediaPipeFrameTrigger | IMediaPipeOutFrame | 输出图像, MediaPipeHolisticComponent 用它来回显图像。 |
IMediaPipeOutFrame
IMediaPipeOutFrame
是表示回显的图像的接口,在内置 MediaPipeConnector 中通常是带有装饰线条的图片帧。
这个接口必须实现引用计数功能(类似智能指针)。
特别说明几个函数:
IncreaseReferenceCount
: 引用计数 + 1,当你输出时,引用计数为 1。
Release
: 引用计数 - 1,当引用为 0 时,必须释放内存。
如果你觉得回显图像不是必要的,你的连接器可以没有 OnMediaPipeFrame 事件(永远不触发
OnMediaPipeFrameTrigger
)。
监听支持函数¶
AddListener
和 RemoveListener
为外部提供数据监听器支持,MediaPipe4U
中通过这些函数完成其他功能。
使用 FMediaPipeConnectorBase 基类¶
为了简化实现过程, MediaPipe4U
提供了简化版的基类 FMediaPipeConnectorBase
, 它实现起来比 IMediaPipeHolisticConnector
要容易得多。
建议你使用 FMediaPipeConnectorBase
作为基类。
使用 FMediaPipeConnectorBase
, 你只需要实现如下函数, 其他函数已经由 FMediaPipeConnectorBase
实现:
OnConnect
OnStartPipeline
OnStopPipeline
OnDisconnect
例子¶
你可以在 MediaPipe4U Demo 工程 源码中找到一个替换 Google MediaPipe 的具体例子,该例子中我们通过读取一个数据文件,来输出 mediapipe 数据,而不是从图像源捕获图像进行计算。
在这个例子中,重点关注如下文件:
MediaPipeFileConnector.h
: Connector 头文件 , 查看源码MediaPipeFileConnector
: Connector 的具体实现,查看源码DemoBlueprintLibrary.cpp
: 蓝图函数库头文件, 查看源码DemoBlueprintLibrary.cpp
: 提供蓝图函数库实现, 查看源码
最终我们在蓝图编辑器中调用 StartMediaPipeDataFile
函数来读取本地磁盘上的一个 MediaPipe 数据文件。
这个数据文件也可以在 MediaPipe4U Demo 工程的根目录下找到 (查看数据文件)。