Mediapipe

  • tensorflow lite推导时,个人建议优化先使用xnnpack,即在“InferenceCalculator”计算器写上xnnpack代理。编译tensorflow时,对windows,不要预定义TFLITE_WITH_RUY,即如果某个模型不使用xnnpack,则使用eigin。在android,则预定义TFLITE_WITH_RUY,即不使用xnnpack的,会用ruy。

主要组件

  1. 计算器/处理单元(Calculator):核心处理单元。
  2. 图表(Graph):Mediapipe 的核心是其图表结构,图表定义了数据流和处理模块的连接方式。每个图表由一系列节点(nodes)和边(edges)组成,节点表示具体的处理模块,边表示数据在节点之间的流动。
  3. 节点(Nodes):节点是图表的基本单元,表示具体的处理操作。Mediapipe 提供了许多内置的节点,如数据输入输出节点、图像处理节点、机器学习推理节点等。
  4. 数据包(Packets):数据包是图表中传输的数据单元,节点之间通过发送和接收数据包来通信。数据包可以包含各种类型的数据,如图像帧、音频信号、检测结果等。
  5. 流/数据流(Stream):数据包的传输通道。
  6. 计算机视觉解决方案:Mediapipe 提供了许多预构建的计算机视觉解决方案,这些解决方案已经高度优化,能够在实时应用中使用。常见的解决方案包括人脸检测、手部追踪、姿态估计、对象检测等。

 

常见使用场景

  1. 姿态估计(Pose Estimation):Mediapipe 可以实时检测和追踪人体的关键点(如肩膀、肘部、膝盖等),并估计人体的姿态。这对于体育训练、动作捕捉、增强现实等应用非常有用。
  2. 手部追踪(Hand Tracking):Mediapipe 能够检测和追踪手部的关键点,提供手势识别和手部动作分析的能力。这在手势控制、虚拟现实、手写输入等应用中有广泛的应用。
  3. 人脸检测(Face Detection):Mediapipe 提供了高效的人脸检测和关键点追踪功能,可以用于面部识别、表情分析、虚拟化妆等场景。
  4. 对象检测(Object Detection):Mediapipe 还提供了实时的对象检测解决方案,可以用于监控、无人驾驶、智能家居等领域。

 

一、编译

1.1 宏:MEDIAPIPE_DISABLE_GPU = 1

编译mediapipe时,要定义MEDIAPIPE_DISABLE_GPU宏。目的是避免在使用opengles上,和SDL发生冲突。

SDL_SetRenderTarget(ERR) texture: 83b64fe0(404x72), renderer->frame: f978fe58, renderer->SetRenderTarget(renderer, texture) < 0

要是不定义,SDL中的SDL_SetRendereTarget会报类似上面错误。发生这问题原因是,SDL渲染要使用opengles,要是不定义MEDIAPIPE_DISABLE_GPU,mediapipe也会使用opengles。 这时一旦使用过mediapipe检测,会让后面SDL的渲染功能出现问题。要解决这冲突,须将来修改相关代码。

mediapipe有个MEDIAPIPE_DISABLE_GL_COMPUTE宏,是不是可以不定义MEDIAPIPE_DISABLE_GPU、而改定义它解决问题?——不能。定义MEDIAPIPE_DISABLE_GL_COMPUTE,会强制关闭mediapipe中基于OpenGL Compute Shader的GPU计算路径,其它opengles相关操作是不禁止的,像eglCreateContext。在定义了MEDIAPIPE_DISABLE_GL_COMPUTE环境下,ImageToTensorCalculator就会用到eglCreateContext。

1.2 宏:ROSE_USE_GLOG

ROSE_USE_GLOG是自个新加宏,不要定义。一旦定义ROSE_USE_GLOG,会使用glog开源项目去输出日志、以及CHECK诊断。较高版本absl已支持输出日志和CHECK诊断,mediapipe已经在使用它,但还有部分代码、以及要用到的第三方库在使用glog,像com_google_audio_tools。

#include "absl/log/absl_log.h"
VLOG改为ABSL_VLOG

#include "absl/log/absl_check.h"
CHECK改为ABSL_CHECK

因为不再使用glog,用了glog的都要用absl去代替。如何修改参考上面,一般就是前面加上“ABSL_”。

 

二、iOS 平台禁用 MEDIAPIPE_TFLITE_METAL_INFERENCE

1. 背景与冲突描述

在当前项目中,iOS 端采用了 SDL 渲染 独占 GPU 资源(通过 OpenGL ES / Metal 驱动),而 MediaPipe 用于执行 Pose Detection 和 Landmark 推理。由于系统资源(尤其是 GPU 上下文)的唯一性,MediaPipe 的 TFLite GPU 推理能力必须被禁用,以让位给 SDL 渲染线程。

然而,MediaPipe 官方源码中,对于 iOS 平台存在硬编码的宏定义:

#ifdef MEDIAPIPE_IOS
#define MEDIAPIPE_TFLITE_METAL_INFERENCE 1
#endif

经过深度调试与性能分析,必须强制将其置为 0,原因如下:

2. 禁用 MEDIAPIPE_TFLITE_METAL_INFERENCE 的核心原因

2.1 编译期的头文件与符号依赖冲突(前期报错的根本原因)

  • 原理:当 MEDIAPIPE_TFLITE_METAL_INFERENCE=1 生效时,MediaPipe 底层的 tflite_inference_calculator 和 gpu_context 会强制引入 Objective-C 的 <Metal/Metal.h>、<Foundation/Foundation.h> 以及 GpuResources 等 GPU 相关的头文件。
  • 冲突后果:由于我们在编译配置中定义了全局宏 MEDIAPIPE_DISABLE_GPU=1,这会导致编译器屏蔽了底层的 GPU 结构体(如 GlTextureInfo、GlVersion 等)。结果就是 Xcode 在编译 C++ 上下文时会报大量 Unknown type name 和 Expected expression 错误。只有将其设为 0,才能让 MediaPipe 正确编译纯 CPU 版的前处理和后处理模块。

 

2.2 运行期 GPU 上下文(EAGLContext / MTLDevice)的资源争夺

  • 原理:SDL 渲染需要独占 GPU 的上下文。如果在推理时开启 MEDIAPIPE_TFLITE_METAL_INFERENCE=1,MediaPipe 会在子线程尝试抢占 GPU 资源,创建一个独立的 id<MTLCommandBuffer> 或 EAGLContext 去执行神经网络推理。
  • 冲突后果:
    性能降级:不同线程跨上下文去操作共享 GPU 资源,会触发 iOS 底层驱动强制进行频繁的上下文切换和 GPU 等待同步,导致渲染掉帧和推理耗时激增。
    潜在崩溃:在 A 系列芯片上,多线程同时抢占 Metal 和 OpenGL 资源极易触发 EXC_BAD_ACCESS 或 GPU 超时崩溃。

 

2.3 架构隔离原则(SDL 布局与渲染层)

  • 原理:本项目的架构设计基于“渲染与计算分离”原则。
  • 考量:SDL 负责渲染负责画面合成,MediaPipe 仅负责纯 CPU 算力计算(利用 A14 芯片的 4 个小核和 XNNPACK 指令集)。强行开启 Metal 推理违背了架构初衷,且无法直接利用 SDL 现有的渲染管线,徒增不必要的跨平台代码复杂度。

 

3. 技术解决方案:如何强制覆盖该宏

由于 config.h 源码中的 #ifdef MEDIAPIPE_IOS 硬编码优先级较高,无法通过单纯在 Xcode Build Settings 中修改 Preprocessor Macros 来覆盖。最终采用的方案是:

修改 MediaPipe 源码 config.h,加入条件编译防护:

将原有代码:

#ifdef MEDIAPIPE_IOS
#define MEDIAPIPE_TFLITE_METAL_INFERENCE 1
#else
#define MEDIAPIPE_TFLITE_METAL_INFERENCE 0
#endif

修改为:

#ifndef MEDIAPIPE_TFLITE_METAL_INFERENCE
    #ifdef MEDIAPIPE_IOS
        #define MEDIAPIPE_TFLITE_METAL_INFERENCE 1
    #else
        #define MEDIAPIPE_TFLITE_METAL_INFERENCE 0
    #endif
#endif

通过此手段,允许 Xcode 在 Build Settings 中通过追加 MEDIAPIPE_TFLITE_METAL_INFERENCE=0 来覆写编译期逻辑,确保安全地将推理卸载到纯 CPU(XNNPACK)上执行。

 

三、选择哪种优化

tensorflow lite有三种优化方法:ruy、Eigen多线程加速和xnnpack,优化效率会因平台、任务类型和模型特性而不同。具体到mediapipe姿势检测,测试下来最好的是xnnpack。

 xnnpackruyeigen
启用方法在calculator写上xnnpack代理预定义宏TFLITE_WITH_RUYTFLITE_WITH_MULTITHREADED_EIGEN
平台windows、android[windows]、androidwindows、[android]
一次pose_tracking_cpu(4G)rk3576: 250ms/rk3588s: 120msrk3576: 350ms/rk3588s: 150ms 

只要在*.pbtxt中的“InferenceCalculator”计算器写上xnnpack代理,就会使用xnnpack优化,不论是否有预定义宏TFLITE_WITH_RUY。

编译时不要主动定义TFLITE_WITH_MULTITHREADED_EIGEN。tensorflow在编译<lite>/kernels/conv.cc时,一旦发现没定义TFLITE_WITH_RUY,就会去定义TFLITE_WITH_MULTITHREADED_EIGEN。

在会使用的优先级上,有着以下次序。

  1. xnnpack。只要calculator写上xnnpack代理
  2. ruy。编译时预定义宏TFLITE_WITH_RUY
  3. eigen。不出现上面两种情况

在使用某次编译出的tensroflow.so时,可让模型A使用xnnpack,模型B使用ruy或eigin。但做不到模型A使用ruy,模型B使用eigin。

ruy也能用在windows。但同时须要定义cpu指令集相关宏,否则推理速度会很慢。像用eigen加速只要200毫秒的,会变成3秒。

eigen也能用在android。但用eigen,我在pose_landmark_full.tflite进行本地推导时,android上会出现阻塞、或崩溃。查下来,阻塞发生在multithreaded_conv.h中的struct MatMulConvFunctor,更具体是在in0.contract(in1, dim_pair)。

<mediapipe>/modules/pose_detection/pose_detection_cpu.pbtxt
------
node {
  calculator: "InferenceCalculator"
  input_stream: "TENSORS:input_tensors"
  output_stream: "TENSORS:detection_tensors"
  options: {
    [mediapipe.InferenceCalculatorOptions.ext] {
      model_path: "mediapipe/modules/pose_detection/pose_detection.tflite"
      delegate {
        xnnpack {}
      }
    }
  }
}

对mediapipe,目前个人建议优化先使用xnnpack,即在“InferenceCalculator”计算器写上xnnpack代理。编译tensorflow时,对windows,不要预定义TFLITE_WITH_RUY,即如果某个模型不使用xnnpack,则使用eigin。在android,则预定义TFLITE_WITH_RUY,即不使用xnnpack的,会用ruy。

 

全部评论: 0

    写评论: