在如今在线会议、网络教学盛行的时代,员工和学生被要求打开摄像头,将自己、居住环境、隐私暴露在公众视野中。背景虚化、虚拟背景应用恰恰可以解决这一问题,而人像分割技术正是背后支撑这些应用的关键技术。
有读者可能疑惑,Native环境下的背景虚化、虚拟背景技术已经存在多时了,把它直接迁移到Web端能有多难呢,我们今天就从这个问题出发,展开聊聊。
与Native相比 Web端进行实时人像分割有何不同
相比于Native端的AI推理任务实现,目前Web端实现时有如下难点:
- 模型轻量:Native端可以在软件包中预置推理模型,而Web端则需要重复加载(根据选择框架和runtime不同,一次成功的推理可能需要加载模型、runtime解释器、框架代码三部分),在网络速率的限制下,模型需要十分轻量。
- 推理性能低下:Web端进行AI推理的最大痛点无非是缺乏高性能的推理运行时。什么是推理运行时(inference runtimes)呢?如果我们将一些常用的数据处理方式抽象出来,称为算子,那么模型的推理过程可以理解为算子的并行调用与数据同步,推理运行时则是算子的具体实现。纵观Tensorflow.js、ONNX.js框架可知,目前Web端性能最高的runtime基于WebGL2.0实现,然而WebGL2.0所基于的GLES3.0标准过于古老,当时并没有考虑使用GPU进行通用计算的场景,为了适配这个古老的图形接口,runtime需要做一些诡异的"hacking":首先将待处理数据转换为图像的像素数据,作为纹理上传到GPU,其次将算子编写为对应的着色器,进一步将纹理与着色器同步渲染,来完成实际计算,最终调用getPixelsData同步读取渲染结果,再将像素数据转换为处理结果。反观新一代图形API(Vulkan、Metal和D3D12),它们提供了诸如计算着色器(compute shaders)和通用存储缓冲器(generic storage buffers)的支持,二者是利用GPU进行高性能通用计算的关键组件,这些组件直到GLES3.1标准和WebGL2.0 Extension中才得以支持,时至今日也没有被浏览器广泛支持,它们的缺失也是Web版本运行时在性能上落后于Native端运行时的根本原因。这种困境有望在新一代Web图形标准WebGPU中得以解决,我将在展望部分加以介绍。
- 数据IO缺乏最佳实践:在RTC场景下,如何优雅地从MediaStream中采集数据、进行前后置处理并送入推理框架,最终输出MediaStream,是一件很微妙的事情,稍不留神,这些简单的操作就可能对于模型吞吐与时延有极大影响。
针对上述难点,笔者将从模型选择、框架选择、算法调优、数据IO优化几方面介绍TRTC的Web端人像分割技术实践。
Web端实时人像分割技术实现
一个基本的实现思路是:首先利用WebRTC视频采集能力收集MediaStream,视频流式处理过程通过编写Insertable Stream变换函数来刻画:变换函数中利用canvas获得逐帧数据,进一步调用人像分割模型,系统还可以根据当前运行环境选择tensorflow.js的WASM或是WebGL作为runtime,模型输出为一个与原视频帧相同大小的mask,该mask将作为掩膜指导WebGL进行背景模糊、虚拟背景等后置处理完成应用需求。下面对上述方案中几个关键点详细阐述:
- 模型选择:实践中采用了一个轻量的TFLite模型,模型架构选择了类MobileNetV3设计,改进了decoder模块以达到更好的运行速度,同时对模型进行了量化压缩优化,进一步减小模型大小与推理时延。
- 框架选择:实践中没有选择直接使用Tensorflow.js、ONNX.js框架,而是采用了一种抽象层次更高的控制框架。框架提供了一套对于复杂机器学习应用的DAG式描述和流式计算引擎,同时允许用户添加帧率控制逻辑,能显著减少数据拷⻉和处理时间浪费。以人像分割任务为例,由于模型仅支持固定大小的图像输入,因此在调用模型前需要对采集到的媒体数据进行伸缩操作,同理模型的输出mask也为固定大小,因此也需要对模型输出进行伸缩操作,整个流程可以在框架中被抽象成三个算子:图像前置伸缩算子、TFLite模型调用算子、图像后置伸缩算子。这三个算子的数据依赖关系由一张用户定义的DAG图维护,控制框架将待处理的视频帧数据逐帧送入DAG图的起点算子,并逐帧从终点算子处取出处理结果。这使得控制框架可以自动为用户处理算子间的并发、算子间的高效数据传输,同时可以根据关键路径时间调控各个算子的流量,避免耗时低的算子的处理结果由于超时而被丢弃。至于底层Tensorflow.js的运行时该如何挑选呢?引官方博客分享[1][3],在任何情形下,我们都只需考虑WASM或是WebGL作为runtime,而不是原生js,对于轻量模型(约20-60M次乘加)推荐WASM运行时,同时应尽可能开启SIMD指令优化以及threads优化,而中型以上模型(约100-500M次乘加)选择WebGL运行时: 较小的模型(BlazeFace)
较大的模型(MobileNet v2)
上表显示,针对这些模型,WASM 比普通的 JS CPU 后端快 10-30 倍;并且针对 BlazeFace(https://github.com/tensorflow/tfjs-models/tree/master/blazeface)(轻量化 (400KB) 但运算数量尚可 (~20M))之类的较小模型,则可与 WebGL 抗衡。考虑到 WebGL 程序每执行一次运算的固定开销成本,这就解释为何较小模型在 WASM 运行时上速度更快。 笔者又对本实践中使用的模型[1][2]进行了测试,测试结果和上表中FaceMesh性能结果相近。
- 算法调优:实践初期,我们发现无论如何调节模型参数,人像在视频中的分割边缘都会出现剧烈抖动,而且抖动会随着帧率增加进一步恶化。这缘于模型对视频帧进行独立预测,未考虑帧间信息,导致生成的掩模帧间产生较大抖动,因此实践中对掩模进行了帧间平滑。至于帧内边缘参差的问题,实践中添加了联合双边滤波器。
- 数据IO优化:数据IO方面,控制框架已经解决了前后置处理中涉及的数据传输问题,只需解决在RTC场景下,如何优雅而高效地获取逐帧数据并送入推理框架,最终逐帧组装输出MediaStream。一种常⻅方法是将处理结果使用WebGL绘制于Canvas对象上,再进一步调用Canvas对象的captureStream方法获取生成流,然而这种方式效率较低。笔者尝试了较新的Insertable Stream API,它使得开发者可以直接访问流,进而对其进行操作,下面是笔者实践中用到的设有启停功能的一种写法,抛砖引玉:
// 源MediaTrack const {readable: cameraReadable} = new MediaStreamTrackProcessor({track: inputTrack});
// 目标MediaTrack outputTrack = new MediaStreamTrackGenerator({kind: 'video'});// 流式算子:将源MediaTrack中的cameraFrame处理为composedFrame transformer = new TransformStream({ async transform(cameraFrame, controller) { const composedFrame = new VideoFrame(......); cameraFrame.close(); controller.enqueue(composedFrame); }})
// 启动流式处理,并注册终止信号 let abortController = new AbortController(); cameraReadable .pipeThrough(transformer, {signal : abortController.signal}) .pipeTo(outputTrack.writable) .catch((e)=>{});
// 发出终止信号,终止流式处理 abortController.abort();
最终实现效果
总结展望
首先是一点方法论的分享,笔者在具体实践过程中走了很多弯路,回顾下来,如果你也想动手创建一个Web端AI推理的应用,你可以从选择什么模型、选择什么推理框架&runtime、如何进行前后置处理与数据io、是否需要算法优化五方面去思考梳理您的方案。
接下来展望一下潜在高性能的推理运行时:基于WebGPU标准的运行时,WebGPU是即将推出的下一代Web 图形标准,它不再继承或者完全仿制某一个已经实现的本地图形标准,而是参考了新一代图形API(Vulkan、Metal 和 D3D12)的设计理念,对标这些图形框架研发了一个全新的跨平台的高性能图形接口,同时提供一流的通用计算接口,诸如计算着色器与通用存储缓冲器的支持,这也是它和WebGL最大的区别,下图展示了二者进行通用计算时的过程[4]:
WebGL:
WebGPU:
可见WebGPU有五个优势:
- 数据通过缓冲区上传到GPU,无须将其转换为像素,减少性能损失。
- 计算操作天然就是异步的,不会阻塞js主线程。
- 无须输出到画布元素Canvas,数据大小不受画布大小限制。
- 无须昂贵的getPixelData操作。
- 无须将像素值转换为数据。
为探索 WebGPU 进行机器学习部署的潜力,主流机器学习框架(Tensorflow.js、ONNX.js、TVM)都开始实验性地支持WebGPU运行时,这些runtime同时利用了WASM和WebGPU技术:其中WASM用于构建计算启动参数和调用设备启动的主机代码,WebGPU则用于构建设备的实际执行代码。
最后回到人像分割这一任务,本文使用的模型是逐帧独立预测,没有考虑帧间信息,最近开源的如RVM模型[2]基于循环神经网络构建,加入了对于帧间信息的考察,同时团队也给出了一个经过INT8量化的轻量模型。从性能角度来看,目前Web端runtime尚未针对循环神经网络(RNN)提供良好支持,模型的循环状态无法驻留于设备中,需要开发者手工往返拷贝,这带来了一定的性能损失。
参考文献:
[1] When should I use WASM?https://www.tensorflow.org/js/guide/platform_environment#when_should_i_use_wasm
[2] Robust Video Matting (RVM)
https://github.com/PeterL1n/RobustVideoMatting
[3] Webassembly backend for Tensorflow.jshttps://blog.tensorflow.org/2020/03/introducing-webassembly-backend-for-tensorflow-js.html
[4] WebGPU computations performance in comparision to WebGLhttp://pixelscommander.com/javascript/webgpu-computations-performance-in-comparison-to-webgl/#more
腾讯云音视频在音视频领域已有超过21年的技术积累,持续支持国内90%的音视频客户实现云上创新,独家具备 RT-ONE™ 全球网络,在此基础上,构建了业界最完整的 PaaS 产品家族,并通过腾讯云视立方 RT-Cube™ 提供All in One 的终端SDK,助力客户一键获取众多腾讯云音视频能力。腾讯云音视频为全真互联时代,提供坚实的数字化助力。