背景
再过不久又是农历新年,近几年在新年期间除了春晚之外,还有一个新的全民活动,那便是支付宝 AR 扫福。支付宝从 2017 年新春开始,五福的玩法变得有趣起来,陆续推出 AR 实景红包:通过将红包藏在特定场景中,打开 AR 相机进行寻找,这种新颖的基于 LBS 的红包方式引起一股热潮。
在 2018 年春节,支付宝将科技元素升级,推出了全民 AR 扫任意福的活动,通过扫福来集福,不管你是商店买的福,还是手写福,支付宝都可以将它们认出来。随了扫福以外,支付宝中还有大量的此类需求,如银行卡识别、身份证识别等,xMedia 多媒体端智能应用框架便由此衍生出来。
「端智能」
AR 扫福即使在网络状况并不好的情况下,识别速度都非常快,而且和一般的上传图片进行识别的流程不同,并不需要用户选择图片并上传的操作。为什么?除了算法与模型优化之外,还有一个很重要的原因就是识别引擎运行在端上,这也是最近端上AI越来越火爆的一个原因,我们称之为“端智能”。为什么要将引擎运行在端上?和一般运行在云端的智能化有什么区别? 引擎之所以运行在端上而不是云端,主要有两大原因:一是由于端上涌现出大量的需求云端无法满足,在很多场景下端上的优势明显;二是由于端自身条件成熟。
- 端上识别引擎的优势:
优势点 | 具体信息 |
成本 | AI 不是一个成本很低的计算,对于扫福这类大型的活动,如果所有计算都部署在云端,那服务器的流量、计算及存储成本将是不可想象的。但移动端本身就包含这些资源,有海量的本地边缘计算能力。 |
体验 | 现在的扫福平均时间是数百毫秒,如果走云端的方式,图片数据在网络传输方面的耗时将极大影响体验,遇到网络不好的情况时更是糟糕。很多人都有过新年抢火车票的经验,在同一时间大量的流量涌入时,等待用户的是无尽的转圈与重试。端上既不存在大数据的网络传输,也不存在大量的并发访问,因此这种场景的体验会好很多。 |
隐私 | 大数据时代人们对个人隐私的意识越来越强,云端可能面临着敏感数据上传的风险,比如前段时间火爆的换脸应用ZAO,它需要上传一张人脸的照片,就是这点让很多人放弃了使用。而如果是纯端上的运算,所有的数据都保存在用户的手机上,并不会像云端一样存在敏感数据上传的风险。 |
- 端上条件成熟:
除了端云对比的优势以外,另外一个更重要的原因是端上条件成熟,足以很好的支撑智能化的计算。主要有下面几个方面的原因:
- 一是终端设备算力的增强,强大的 CPU 和 GPU 可以进行非常复杂的运算,还有很多专门的 AI 芯片集成在手机中,算力进一步加强。
- 第二点是移动端 AI 引擎的发展,如支付宝的 xNN,Google 的 TensorFlow Lite 等,让 AI 运行在移动端成为了可能。同时模型压缩技术的发展也让过去云端动辄数百兆的模型可以被压缩到几兆以内,让模型存储于手机端成为了可能。
- 最后端上有很多类似于五福的实时类应用场景兴起,比如支付宝的 AR 识花/识车等,都是针对视频流进行识别。
AI 为业务带来了非常多的创新,而业务的蓬勃发展也驱动了AI技术的迅速发展,如何让这些场景迅速在移动端落地便成为极其重要的事情,xMedia 端智能应用框架便是为解决这个问题而产生。
端智能应用框架 xMedia
「端智能流程」
既然 AI 运行在端上,那么以扫福为例,整体的开发流程是什么样?来看下面这张流程图:
主要分成两部分,首先是针对扫福场景的模型如何产生。这点和云端的模型制作区别不大,一般由算法工程师负责,包含数据采集、算法设计、模型训练、模型压缩、模型部署等步骤。
待模型完成之后,接下来就到了端上的工作,它由移动端开发工程师负责。首先是数据的采集,如扫福需要打开相机,有些场景还需要其它传感器的数据用于静止判断等。采集到数据之后,不能直接将图像给到引擎,为什么?因为原始的图像分辨率很高,可能是 1080P,引擎不可能直接对这样的图像进行处理,需要先对图像进行裁剪缩放等操作。另外还有检测区域的处理,只有位于屏幕中心位置的区域才会被传递到算法,这是为了优化性能。在运行过程中,也不能够将所有采集的数据直接传递到后面进行处理,需要一定的调度策略,比如如何控制算法处理的帧率等。接下来才到 AI 引擎,引擎计算出来结果之后,也不能够直接返回给业务,而是需要一些处理才能展示出来,比如扫福中的 AR 动画。
整个交互过程看似简单,但其中涉及到非常多的细节。比如:下一帧过来时,上一帧还没有处理完怎么办?系统发热严重怎么办?覆盖率太低怎么办?等等。由此可见,移动端多媒体AI应用能否成功,除了需要一个好的模型,还依赖于资源的调度、数据的同步、部署模型的快捷程度及如何触达到更多的用户,这需要算法工程师与移动开发工程师共同的努力。
除了 AR 扫福为外,支付宝中还有很多其它类似的智能化需求,xMedia 框架便是为了解决各种场景都存在的这些共性问题。
「挑战」
从前面扫福的流程中来看,端上的AI应用场景面临着一些挑战,主要包含以下四个方面: • 工程效率与门槛 • 终端软硬件多样性 • 终端资源与算法 • 端智能的局限性
对于工程效率与门槛,互联网时代最重要的成功条件之一,天下武功,唯快不破,这类挑战是如何以弹性灵活的方式完成各种各样的业务需求,如何在不发版本的情况下部署及升级模型,技术资源较少的垂直场景业务如何快速接入?解决思路主要有两种,一种是能力原子化,可以让业务快速方便的通过某种方式来组合业务。第二种思路是垂直场景的封装,将 OCR 银行卡/身份证等场景封装成可以直接使用的组件,降低这类场景的使用门槛。
面临的第二个挑战是终端软硬件多样性,Android 开发同学深有体会,数以千计的机型,GPU 型号 50+,兼容性与覆盖率问题简单是开发者的恶梦。这类挑战主要表现为如何能够让业务触达到更用的用户,让低端机也能够获得较好的体验。对于这点主要是通过不同维度的配置策略来解决多样性问题,针对各种 GPU 特性的兼容处理。
第三个挑战是终端资源与算法,所面临的问题是如何能够做到效果与电量之间的平衡,比如支付宝 AR 扫福,如果扫福一分钟耗电 30%,那么支付宝将面临着用户无尽的口水。解决这个问题除了算法本身的极致优化以外,还有一些自适应的调度策略,接下来会有详细的描述。
虽说端上智能在很多场景下有非常多的优点,但相对云端仍然存在一定的局限性,识别率与处理能力上无法达到云端的效果,端上面临这点可以通过端云结合的思路来处理。
如前面所说,支付宝中越来越多其它的智能化业务也同样面临着这些问题,如何解决这些共性的问题便成了 xMedia框架所面临的最重要的任务,这些挑战的解决方案也构成了该框架的主要特性。
「xMedia 技术大图」
为了解决前面提到的各种挑战,xMedia 框架逐渐演变成下面的架构,最重要的部分位于框架层与能力层。
先来看看能力层,前面提到,框架的思路是将处理能力原子化,并快速方便的将这些能力组合起来描述整个业务场景。它有些类似于AI引擎中的各种算子,不同的是AI引擎中的算子粒度非常细,而这里算子更加抽象,比如检测/分类算法、OCR 通用算法、图像识别能力等,它面向的是业务的开发者,而不是算法工程师。同时还有一个不同点,这里的算子不仅仅是算法的封装,它还包含渲染能力及数据源的采集能力等,所以为了加以区分,我们称这些原子化的能力为 functor
。业务将这些原子化的 functor
组装起来完成需求,如何组装便是框架层的工作。
框架层类似于AI中的模型,在AI中,模型是将各种各样算子组合成一张有向无环图,用于描述算法的整体处理流程。在xMedia中,我们将前面能力层的各种 functor
通过协议组装成图,来描述整体业务的处理流程。因为这些 functor
不仅包含了算法,还包含渲染,采集等,所以可以构成完整的处理流程。除了将这些表达业务的图构建起来以外,框架层还需要负责算法的调度控制、数据同步、参数动态配置等事情。概括来说,能力层负责functor
,框架层负责将functor
以最高效的方式运行起来。
在接口层,xMedia 提供了多种 API,包含端上的 Android/iOS 平台,为了便于前端使用,H5 与支付宝小程序的 API 也有提供。对于外部用户来说,xMedia 也通过 mPaaS 独立输出(mPaaS 是蚂蚁金服提供的完整 APP 解决方案,在大量的银行及保险公司 APP 中使用)。
因为特性较多,篇幅有限,接下来我们选择其中三个方面来介绍,这三个方面粗略的涉及到业务开发的完整流程:
- 如何灵活应对需求
- 如何让算法更好的运行
- 如何提升覆盖率
1. 如何灵活应对需求
前一节提过,为了应对复杂的业务需求,主要思路为两点:
- 将能力原子化成
functor
,可灵活扩展; - 通过协议将业务流程用上面的
functor
构建成有向无环图。
以下图的处理流程为例:
图中分为上下两个部分,上面部分运行在客户端,有多个业务入口。业务将流程描述的协议传入框架,框架将该协议解析成由functor
组装。针对不同的业务,该协议可以从云端进行配置。
图一般由三部分组成,输入、中间处理和输出。输入可以是麦克风、相机、传感器等数据,这些数据被采集之后输入到后面的处理functor
中,因为这些输入源都是抽象出来的算子,也即functor
,它们可以非常方便的进行扩展,当有其它类型的输入数据时,都可以将扩展后的functor
加入图中。中间部分一般是算法类的functor
,如推理、OCR、图像识别等,它们是输入数据的消费者,每个functor
处理完这帧的数据之后会传到图的下一个节点。最后一部分负责将输出结果展示给用户,比如将手势或者姿态的关键点叠加到相机帧上,或者 AR 场景中 3D 模型合成至相机帧中。
由于functor
可以自由扩展并组装到图中,这便为业务带来了灵活性,与各种深度学习的框架思路一致,通过扩展算子来增加框架的能力,通过模型文件将算子组合来完成不同的算法功能。
手势检测
接下来我们以一个线上例子来说明如何应用。
以蚂蚁庄园运动会的小鸡操为例(可以通过支付宝-蚂蚁庄园-运动会-手保健操体验),这是一个手势识别的场景,通过 AI 引擎检测用户相机中采集到的手势,当用户的手势与 UI 上提示的手势一致时即判定成功。那么,除了手势识别的模型之外,该场景如何用前面所说的思路?
下图是该业务的一个协议描述文件示意图,它描述了整个业务的流程,比如每个步骤具体由哪个functor
来执行,它的参数是什么,functor
输出的数据流向哪个functor
等:
xMedia 框架根据描述文件,运行时生成如下所示的流程图,该图从数据源开始生成数据,并将数据传输至每个functor
处理,最后通过预览的functor
渲染至屏幕上。
通过这种方式,可以非常好的解决业务的灵活性问题。我们要做的就是不断的扩展原子化能力及扩展框架层的控制能力。而业务方除了提供模型以外,用协议就可以较快的将需求描述清楚,动态更新也变会非常方便。
在这个示例中,协议描述了整体业务的流程,但除去业务流程以外,还有一些非常重要的因素决定业务的最终体验,如:数据的同步、运行调度、覆盖率如何提升等,接下来我们来看其中一个非常重要的部分——算法的调度。
2. 自适应调度
注意看前面的小鸡操示例,它将采集到的相机帧传递给算法,可是由于相机帧率一般为 30fps,如果将每一帧都交给算法去处理,由于连续帧之间相差不大,造成算法处理的结果存在大量重复,算法按照 30fps 来处理时大部分的数据无效。同时高帧率会造成非常高的 CPU,导致发热严重,电量以肉眼可见的速度向下掉。
如果帧率不够,又会导致算法不能及时的检测出结果,从而影响用户体验,所以选择一个合适的帧率非常重要。但由于机型的多样性,不同机器对不同的算法处理能力不同,因此不能够为所有的机器选择相同的帧率,那该怎么办?
最直接的办法是针对高中低端机,针对不同算法分别配置一个事先验证好的帧率,这个方法简单粗暴。但这个粗暴的办法会面临新的问题:
- 如果此时手机本身其它业务占用了较高的 CPU,与测试时环境不同怎么办?
- Android 成千上万的机器如何配置,即使降低维度,也是非常繁重的体力活。
- 对于新出来的机器如何配置?
为了解决这些问题,看似需要一种可以动态根据当前机器进行设置帧率的方法。有一种思路是直接根据 CPU、内存等硬件参数来将机器划分成不同级别,为不同级别的机器配置不同的帧率,这样看似可以节省大量的手工配置工作。但另外一个问题,手机后台任务不同,有时后台任务很轻,有时任务很重,甚至会出现 CPU 降频等可能,仅根据机器的配置来进行帧率的控制并不能达到最好的效果,需要一种更加“动态”的方式来选择帧率。
在这里,为了控制能耗,在不影响用户体验的情况下,我们通过始终将 CPU 控制到某个较低的水平来完成帧率的选择,从而达到自适应的算法调度的目的。这个思路很像空调的温度调节,可以将室内控制在一个固定的温度,这在工业控制中很常见,所以通过算法将 CPU 控制在一个固定值来达到自动控制帧率的目的。主要包含下面三个方面:
首先引入 CPU 监控模块,实时获取当前 CPU,用其作为算法的输入。 接着将 CPU 设置在一个期望的值,通过算法来动态的进行控制,算法以当前 CPU 和当前帧率作为输入,计算出下一次算法的帧率。 最后,为了保证用户体验,额外设置最大与最小帧率,以防止出现异常。
我们通过下面这张图来看看该方法调节的效果,图分为上下两个部分,上半部分是 CPU 随时间变化的曲线,下半部分是算法输出的时间间隔随时间的变化曲线。(这里注意一下,为什么不是帧率?其实两个是一样的东西,帧率与间隔互为倒数,而间隔比较直观,所以这里让算法输出下一帧的间隔。)这张图以一个示例来表示算法控制的有效性。
可以看到,曲线可以分成几段:
- 第一段,在 241s 之前,CPU 维持在一个较低的水平,45% 左右,而时间间隔处于 250ms 左右。
- 第二段,在 241s 时突然 CPU 因为其它任务加压 20%,导致整体 CPU 迅速上升到 65%,但随后又回落至 45%。此时下半部分的时间间隔也发生了变化,间隔从 250ms 快速升到 1700ms 左右,这正是导致上半部分中 CPU 迅速回落的原因,处理的间隔增大引起帧率降低,所以 CPU 回落。
- 第三段,840s 左右其它任务对 CPU 的加压消失,所以 CPU 迅速下降至 20% 左右,但随后又升至 45% 左右,回到初始的水平。下半部分的时间间隔也同样下降到 250ms,随后稳定,它也正是导致 CPU 上升的真正原因。
由此可见,该方法可以通过自动控制帧率的方式将 CPU 控制在一个适合的水平,达到体验与手机发热的平衡。通过这种自适应调度的方式,也使得各种不同机器都可以自适应的获得非常好的用户体验,同时也会防止手机发热导致的耗电问题。
3. 覆盖率
第三方面我们来聊下覆盖率。所有的业务都希望将它精心设计的体验能够传达给更多的用户,这里以一个 AR 场景来描述 xMedia 框架中做了哪些事情来提升覆盖率。以 AR 为例是因为它比较典型,能够很好的说明机器的多样性对覆盖率提升的影响。
观察上述视频,该 AR 算法简单的说是用视觉的方式来定位当前手机处于空间中的什么位置,比如这个由花组成的穿越门,利用算法可以将它始终放置在地面的某处。在算法已经优化完的前提下,现在华为 P9 Plus 上 18ms 左右一帧,这里讲述 xMedia 框架层在 Android 上的优化。
先来看看上图描述的整体流程:相机采集数据之后有两步操作,一步操作是将相机帧渲染成纹理,同时将帧数据进行预处理,随后同传感器数据一起传给算法。算法处理完返回当前手机的位置,并将该位置用于 3D 场景渲染。接下来这步是关键,与前面手势识别例子不同的是,这里的数据需要同步。什么是同步?它指的是 3D 渲染的场景与相机渲染的场景是同一帧数据。所以整体一帧的处理时间就取决于相机的渲染与算法+3D 渲染的最大时间,整体帧率也由此决定。
这个流程看起来并不算特别复杂,那问题在哪里呢?有过 Android 相机开发经验的同学可能知道,获取相机的数据有两种方式:
- 一种是直接获取每一帧的 YUV 数据
- 另外一种是获取每一帧的纹理
看起来很美好,YUV 数据直接传给算法,相机纹理用于相机渲染,两条路处理完之后直接合成即可。然而,它们最大的问题在于,这两份数据是不同步的。也就是说获取到的 YUV 数据可能是几帧之前的,无法与获取的相机纹理对应,两者之间根本没有对应关系。所以如果要同步处理,只能够选择其中一种获取相机帧数据的方式。
那么究竟选择哪一种方式?估计你已经猜到,答案是不能够简单的选择,需要多种策略配合。
多策略适配
对于两种不同获取数据的方式,处理链路完全不同,如下图所示:
第一种方式是直接获取每一帧的 YUV 数据,这种方式对于算法来说比较容易处理,直接传递给算法计算出当前相机的位姿,并由 3D 引擎渲染出当前场景。而对于相机渲染来说就没那么方便了,需要将 YUV 数据上传至 GPU 并且渲染出纹理帧,最后与前面的 3D 场景进行合成。这种方式较为直观。
而对于获取每一帧相机纹理的方式,与上面方式完全不同。于相机帧而言,因为本身拿到的就已经是纹理,所以渲染纹理实景这步不用处理,可以直接拿来使用。对算法来说就麻烦的多,由于算法需要的是相机数据,纹理无法直接使用,因此需要从 GPU 中读出 YUV 数据至 CPU 中,读出的方式也较为复杂,接下来会详细描述。
策略选型
前面分别介绍了两种方式的处理方法,那么应该怎么选择?主要思路是根据 GPU 的特点处理,但 Android 平台上仅就 GPU 而言就有 50+ 种,选择变得困难了很多。但好在经过统计,机型占比绝大部分的 GPU 可以划分为两大类:
- 针对 Adreno 类型的大部分 GPU,上传纹理的能力强,直接渲染YUV数据并无太大性能问题,可以将相机采集的 YUV 数据一边渲染,一边传给算法运算后再进行 3D 引擎渲染,待两者完成后合成。所以这种类型的 GPU 选择第一种方式。对于大部分高通平台的机器均是搭载的此类 GPU,占比达到 50% 左右。
- 针对 Mali 类型的 GPU,上传纹理的能力很差,在不进行算法处理的情况下,仅渲染相机采集回调的 YUV 数据,就存在延时的情况,卡顿明显,更不用说加上算法的处理。所以对于此种类型的GPU不能够使用第一种方式。针对该部分机型,渲染时直接使用相机预览回调的
GLES11Ext.GL_TEXTURE_EXTERNAL_OES
纹理作为相机帧(EGLImage,Android 底层使用的 EGL 扩展,将 YUV 数据转化成了该类型纹理,和硬件紧密相关,该使用方式并不开放给应用层)。
接着使用glReadPixels
的方式从 GPU 中读出这一帧纹理,但这里不能够简单的进行读取,有以下几个原因:
- 通常从相机中拿出来的纹理较大,如 1280x720,而算法需要的却很小,比如只需要 300x300,完整的从 GPU 中读出数据非常耗时。
- 算法只需要 YUV 数据中的 Y 通道,因此读出所有的数据存在大量的浪费。
-
glReadPixels
从 GPU 读取数据时,是将纹理当成 RGBA 格式进行输出,但现在需要的是只是 Y 通道,无法与像素一一对应,所以不能够简单的处理读取。
正是基于以上原因,在读取之前需要先进行一次渲染,渲染做了几件事情:
- 将数据进行裁剪、缩放到目标尺寸。
- 去除 UV 通道,只保留 Y 通道。
- 将 Y 通道以特定的方式进行排列,以保证
glReadPixels
读出的方式是连续的。
通过这一次渲染之后,再用glReadPixels
读出数据便是仅有 Y 通道且裁剪之后的数据,可以直接输出给算法进行处理,这两部分耗时一般在 5-10ms。
算法计算出相机位姿后等待引擎渲染完毕,得到一帧场景帧,再与相机预览的纹理进行合成得到最终帧。华为和 MTK 的机器大部分搭载的是 Mali 的 GPU,占比大概 40% 左右。
不同机型根据其搭载的 GPU 来配置合适的策略,这样可以让覆盖率大幅度提升。但这样仍然还不够,还需要配合其它的方式让覆盖率进一步提升。
并行+缓存
耗时较多的部分在两处,一处是 GPU 渲染,一处是算法(运行在 CPU 端),而这两部分并不完全互相依赖,可以通过并行来提升性能。另外,使用多级 FBO 来缓存多帧相机帧,并缓存 YUV 数据也可以提升一部分的性能 。但注意一点,过多的 FBO 缓存也有图形内存开销和二次渲染开销。一般来说最多二级,视配置而定。
通过这两步处理之后,整体的算法可以达到 75% 以上,并且保持 22fps 的帧率。下图是完整的策略:
降级算法
在成千上万的 Android 机器面前,75% 的覆盖率对于这样一种耗时的算法来说已经较高,但仍然有大量的机器无法覆盖。所以为了保证剩下这一部分用户也能触达,我们通过算法降级来达到更高的覆盖率,如这里根据陀螺仪计算出相机的角度,放弃最后的帧数据同步等,最终可以使整体覆盖率达到 99% 以上,当然这是以牺牲部分体验为代价了。
xMedia 应用场景
最后,除了在支付宝新春扫五福以外,我们将应用场景归纳为以下四类:
- 智能交互
- 智能服务
- 智能推理
- 风控安全
智能推理主要用于支付宝中小程序智能预加载,首页腰封推荐等;风控安全包含内容安全检测、视频鉴黄等。目前我们先简单介绍前两类应用场景:
「智能交互」
智能交互是利用算法与用户进行交互,主要用于 AR 中,利用 AR 来替代传统的触摸交互,带来完全不同的体验。比如前面提到的 AR 扫福与小鸡操就是这类场景,小鸡操可以在支付宝-蚂蚁庄园-运动会-手保健操
中体验。
覆盖率中描述的蚂蚁森林的 AR 看树活动,可以通过下面的方式进行体验。
另外一个基于头部的互动是颈保健操,可以从支付宝-小目标-颈保健操
进行体验,适合办公室人群,每天给自己定个目标锻炼颈椎,缓解疲劳。
上面这些交互方式均采用 AR/AI,体验与传统的交互方式有很大的不同。
「智能服务」
第二类是智能服务类,主要是垂直场景的应用,与支付宝业务紧密关联的,比如银行卡 OCR 识别、身份证 OCR、理赔宝等,可以通过下面几个视频来体验,不再一一赘述。端上的识别具有识别准确率高、速度快、模型等特点,很多垂直场景在支付宝小程序中也已经开放。
如何体验支付宝端上智能化能力?
目前,xMedia 在 mPaaS 中已得到相应的落地应用。基于 App 端与小程序场景的联动,实现“数据计算分析+分析决策引擎+mPaaS 场景”的智能化分析及运营能力。
除此之外,借助如智能创意文案、智能分流、智能投放、AB 实验等能力,形成全面自动化的营销流程,真正提升营销自动化水平。
同时欢迎大家使用钉钉搜索群号“23124039”加入 mPaaS 技术交流群,期待与你交流。