zoukankan      html  css  js  c++  java
  • MediaPipe加速流程和原理

    1. MediaPipe加速流程

    1.1 OpenGL ES准备

    (1) OpenGL上下文(Context)
    在调用任何OpenGL指令之前,需要创建一个OpenGL上下文,该上下文是一个非常庞大的状态机,保存了OpenGL的各种状态。由于OpenGL上下文是一个巨大的状态机,因此切换上下文需要较大的开销,但是不同的绘制模块需要完全独立的状态管理。因此可以通过在应用程序中分别创建多个不同上下文,在不同线程中使用不同上下文,同时上下文之间共享纹理,缓冲区等资源,避免反复切换上下文,造成较大开销。(OpenGL的一个上下文不能被多个线程同时访问。此外,在同一线程上切换上下文可能会很慢。因此更有效的做法是为每个上下文设置一个专用线程,每个线程发出GL命令,在其上下文建立一个串行命令队列,然后由GPU异步执行)

    (2) 帧缓冲区(FrameBuffer)
    OpenGL可以理解成图形API,因此所有运算和结果输出都需要通过图像进行输出。绘图需要有一块画板,那么帧缓冲区就是这块画板。

    (3) 附着(Attachment)
    附着可以理解成画板上的夹子,夹住哪块画布,就往对应的画布上输出数据。

    (4) 纹理(Texture)和渲染缓冲区(RenderBuffer)
    帧缓冲区并不是实际存储数据的地方,实际存储图像数据数据的对象就是纹理和渲染缓冲区。(注意,一般来说渲染缓冲区和纹理不能同时挂载在同一帧缓冲区上)

    (5) 顶点数组(VertexArray)和顶点缓冲区(VertexBuffer)
    有了画板,夹子,画布就可以开始绘画了,画图先画好图像骨架,然后往骨架里面添加颜色,顶点数据就是要画的图像的骨架,OpenGL图像都是由图元组成的(点,线,三角形),顶点数据预先传入显存当中,这部分显存称为顶点缓冲区。

    (6) 索引数组(ElementArray)和索引缓冲区(ElementBuffer)
    索引数据的目的是为了实现顶点复用,在绘制图像时,总是会有一些顶点被多个图元共享,避免反复对这个顶点进行计算。索引数据存储在显存中,这部分显存称为索引缓冲区。

    (7) 着色器程序(Shader)

    • 顶点着色器(VertexShader)
      顶点着色器是OpenGL中用于计算顶点属性的程序。顶点着色器是逐顶点运算的程序,每个顶点数据都会执行一次顶点着色器,每个顶点执行时并行的,并且顶点着色器运算过程中无法访问其他顶点数据。

    • 片段着色器(FragmentShader)
      片段着色器是OpenGL中用于计算像素颜色的程序。每个像素都会执行一次片段着色器,每个像素运行时同样也是并行的。

    (8) 逐片段操作(Per-Fragment Operation)

    • 测试(Test)
      着色器程序完成后,我们就得到了像素数据,这些数据必须通过测试才能最终绘制到画布,也就是帧缓冲上的颜色附着上。

    • 混合(Blending)

    • 抖动(Dithering)
      抖动是针对可用颜色较少的系统,可以牺牲分辨率为代价,通过颜色值的抖动来增加可用颜色数量的技术。机器分辨率足够高的情况下,激活抖动操作没有太大意义。

    (9) 渲染到纹理
    OpenGL程序并不希望渲染出来的图像立即显示在屏幕上,而是需要多次渲染。其中一次的渲染结果作为下一次渲染的输入。如果帧缓冲区的颜色附着设置为一张纹理,那么渲染完成之后,可以重新构造新的帧缓冲区,并将上次渲染出来的纹理作为输入,重复上述流程。

    (10) 渲染上屏/交换缓冲区(SwapBuffer)

    1.2 CameraX 准备

    在 Android 应用中要实现 Camera 功能还是比较困难的,为了保证在各品牌手机设备上的兼容性、响应速度等体验细节,Camera 应用的开发者往往需要花很大的时间和精力进行测试,甚至需要手动在数百种不同设备上进行测试。CameraX 正是为解决这个痛点而诞生的。
    优势:

    • CameraX和生命周期结合在一起,方便开发者管理生命周期,相比camera2减少了大量的样板代码的使用
    • CameraX是基于Camera2 API实现的,兼容Andorid L(API 21),保证兼容市面上的绝大多数手机
    • 开发者可以通过扩展形式使用和原生摄像头应用相同的功能(人像,夜间模式,HDR,滤镜,美颜)
    • Google对CameraX进行了深度测试,确保能够给覆盖到更加广泛的设备中。

    (1) 添加CameraX依赖

    (2) 显示相机预览

    (3) 拍照和存储图片

    (4) 实时分析图像帧

    1.3 Android平台上加速流程

    (1) 设置

    • 系统中安装MediaPipe
    • 安装Android Development SDK和Android NDK
    • Android设备开发模式
    • 设置Bazel编译部署Android应用

    (2) 特定功能的图结构

    (3) 初始最小应用程序设置

    (4) 调用CameraX相机驱动

    • 相机权限
    • 相机访问

    (5) 设置外部纹理转换器
    表面纹理(SurfaceTexture)从流中捕获图像帧作为OpenGL ES纹理。要使用MediaPipe图形,从摄像机捕获的帧应该存储在一个常规的Open GL纹理对象中。MediaPipe提供了一个名为ExternalTextureConverter的类,用于将存储在SurfaceTexture对象中的图像转换为常规OpenGL纹理对象。要使用ExternalTextureConverter,我们需要EglManager对象创建和管理EGLContext。向构建(BUILD)文件添加依赖项以使用EglManager

    1. 计算摄像头的帧在设备屏幕上的适当的显示尺寸
    2. 传入previewFrameTexturedisplaySizeconvert现在摄像头获取到的图像帧已传输到MediaPipe graph中了。

    (6) 在Android中调用MediaPipe图结构

    • 添加相关依赖
    • 主活动MainActivity中使用图

    2. MediaPipe加速原理

    2.1 MediaPipe源码结构

    MediaPipe整个技术栈如图所示

    MediaPipe中核心源码的结构如下,BUILD为Bazel编译文件、calculators为图结构的计算单元、docs为开发文档、examples为mediapipe的应用示例、framework为框架包含计算单元属性,上下文环境,数据流管理,调度队列,线程池,时间戳等、gpu为OpenGL的依赖文件、graphs为mediapipe各项示例的图结构(边缘检测,人脸检测,姿态追踪等等)、java为安卓应用开发的依赖项、MediaPipe.tulsiproj为相关配置文件、models为各个应用的tflite模型,modules为示例组件、objc为 objective-c语言相关文件、util为工具代码。

    2.2 框架加速组件和原理

    框架的加速部分主要在framework中,源码中包括计算单元基类,计算单元数据类型定义、计算单元的状态控制、计算单元的上下文环境管理、图结构中输入流和输出流、调度器队列、线程池、时间戳同步等。下面主要分析调度器队列(scheduler_queue),线程池(thread_pool),时间戳(timestamp)怎样通过调度数据流实现时数据流时间戳同步,再GPU计算渲染,从而达到mediapipe管线的最大数据吞吐量。

    • 调度器机制
      优先级队列、线程池的原理可以看这个链接:https://www.cnblogs.com/zhongzhaoxie/p/13630795.html

      MediaPipe图是由计算单元构成的,整个调度机制决定何时运行每个计算单元。每个图至少有一个调度队列,每个调度队列只有一个执行器。默认情况下,执行器是一个线程池,根据系统的能力决定线程数量。每个计算单元作为一个节点都有一个调度状态(未就绪,就绪或者正在运行)。
      对于源节点,没有数据流输入的节点成为源节点,源节点总是处于准备运行的状态,一直到整个图结构没有数据输出,源节点才会关闭。对于非源节点有要处理的输入时,根据节点的输入策略(下面将讨论),形成一个有效的输出集,此时非源节点保持准备状态。当一个节点准备就绪时,意味着一个任务添加到优先调度程序队列中,优先级函数目前时固定的,考虑到节点的静态属性及其图中的拓扑排序。靠近图输出端的节点具有更高的优先级,而源节点具有最低的优先级。

    • 时间戳同步
      MediaPipe图结构执行时去中心化的:没有全局锁,不同的节点能够在同一时间处理不同时间戳的数据。这允许管道有更高的吞吐量。然而时间信息对于许多感知工作流非常重要。同时接收多个输入流的节点需要以某种方式取协调它们。例如,一个目标检测器可能产生一系列候选框,然后再将这个信息输送到渲染节点,该节点应该与原始帧一起处理。
      因此MediaPipe主要功能之一就是让节点输入同步。就框架而言,时间戳的主要作用是充当同步键。此外,MediaPipe被设计为支持确定性操作,这在许多场景(测试、模拟、批处理等)中非常重要,同时允许图设计者在需要满足实时约束的地方放松确定性。
      同步和决定论这两个目标是几种设计选择的基础。值得注意的是,推入给定流的数据包必须有单调递增的时间戳:这不仅是对许多节点有用的假设,而且同步逻辑也依赖于此。每个数据流有时间戳限制,这是数据流上新包允许的最低时间戳。当一个时间戳为T的数据包到达时,边界自动推进到T+1,反映了单调要求。这允许框架确定没有时间戳小于T的数据包会到达。

    • 输入策略
      DefaultInputStreamHandler定义的默认输入策略提供了确定性的输入同步,可以保证多个输入流上具有相同时间戳的数据包,输入数据流严格按照时间戳升序处理。基于计算单元的方法使图可以控制在哪里丢弃数据包,并允许根据资源约束灵活的适应和定制图行为。

    • GPU计算和渲染
      MediaPipe支持用于GPU计算和渲染的计算单元节点,并允许合并多个GPU节点,以及将它们与基于CPU的计算单元节点混合。MediaPipe中GPU设计原则是保证GPU计算单元可以出现在图的任何地方,帧数据在GPU计算单元到另一个计算单元应该不需要复制操作,CPU和GPU之间的数据传输应该高效。
      MediaPipe允许图在多个GL上下文中运行OpenGL。举例来说,这可能是在图结构中非常有用,结合较慢的GPU推理路径(例如,在10帧/秒)和更快的GPU渲染路径(如30 FPS):因为一个GL上下文对应于一个连续的命令队列,所以这两个任务使用相同的上下文将会减少渲染的帧速率。
      MediaPipe使用多个上下文解决的一个挑战是跨它们进行通信的能力。比如这样一个示例场景,同时发送输入视频到显示和推理路径,显示需要先访问推理的结果。
      一个OpenGL上下文不能被多个线程同时访问。此外,在某些Android设备上,在同一线程上切换活动GL上下文可能会很慢。因此,我们的方法是为每个上下文设置一个专用线程。每个线程发出GL命令,在其上下文上建立一个串行命令队列,然后由GPU异步执行。

    2.3 人手姿态估计示例

    参考文献:
    [1] https://zhuanlan.zhihu.com/p/56693625
    [2] OPENGL ES 3.0编程指南
    [3] https://codelabs.developers.google.com/codelabs/camerax-getting-started/#0
    [4] https://zhuanlan.zhihu.com/p/110411044

  • 相关阅读:
    Java实现大批量数据导入导出(100W以上) -(三)超过25列Excel导出
    华为狼性文化与新员工引导
    盈利模式!商业保理 VS银行保理
    供应链金融保理业务最全解析-四大模式
    Spring Boot下Bean定义方式及调用方式
    Java非侵入式API接口即文档工具apigcc
    Java一个简单的重试工具包
    多层级汇总报表生成
    Spring事物隔离级别及事物传播行为@Transactional实现
    四种事物隔离级别详解
  • 原文地址:https://www.cnblogs.com/zhongzhaoxie/p/13539623.html
Copyright © 2011-2022 走看看