其他
iOS移动端人脸贴纸实践
出于业务的需求,我们实现了一套直播可用的轻量型人脸贴纸。下面简单介绍一下实现的过程和难点。
01
技术分析
首先保证可用和效果良好; 由于使用场景是在直播,本身功能的各方面性能消耗就高,所以要尽可能的优化性能; 控制包增量; 侵入性低; 使用方便。
视屏处理:视频方面采用GPUImage来进行处理。开源,完成度高,而且流式结构以及对滤镜,视频的抽象都很好,使用方便。唯一美中不足的是采用单例来保存BufferCache以及Context,对于只是实现额外贴纸功能的我们(APP的主功能并非视频处理)带来的内存增长利大于弊【注释1】。 人脸识别模块:人脸识别模块我们做了很多调研。其中包括付费的商汤,Face++,开源的智云(HyperLandmark),OpenCV,原生的Vision,ARKit等。多方对比过:商汤,Face++这些在人脸准确性,稳定性,使用便捷上都非常出色,但是本身带有不小的包增量,对于我们这个小功能来说负担很大。开源方面,智云只有安卓版本,而iOS版本(HyperLandmark-iOS)内部使用的引擎是opencv,稳定性和准确性不高,另外包增量很大(OpenCV依赖,内部很难分离其中的人脸识别模块,cv::mat的数据流通也需要转换)。原生方面ARKit的性能由于实时分析,结构光等原因,很难在低端机使用(本身版本限制,硬件限制很多)。只有Vision是相对较好的选择,无包增量,识别效果较好,稳定性虽然较差,但是这个可以通过算法优化。版本限制也不高(iOS12即可使用),考虑到性能问题本身就有限制,这个的影响其实可以接受。当然,Vision作为一个原生库,很多地方都不够完善,例如欧拉角的识别,识别点防抖的处理等。 人脸贴图:人脸贴图与视频的合成,视频处理的内容,使用OpenGL。贴纸格式参考市面上SDK的部分进行设计。
02
流程介绍
接收到视频数据后,对视频方向和分辨率进行处理(视频方向对人脸识别精度影响很大),生成人脸特征点,留待备用。 接收到人脸贴纸后,对放大比例,坐标,特征点,触发进行处理,留待备用 拿到视频数据,人脸特征点,贴纸图片和信息,进行绘制
03
实现细节及难点
定位相关的数据在下面
"position": {
"flag": 49,
"x": 185.59,
"y": 299.39
},
"rotateCenter": [
{
"flag": 49,
"x": 239.92,
"y": 357.87
}
],
"scale": {
"pointA":
{
"flag": 6,
"x": 185.59,
"y": 299.39
},
"pointB":
{
"flag": 13,
"x": 293.67,
"y": 298.72
}
},
"begin":“sticker1.complete”
先从坐标系说起。这个项目涉及的坐标系共有五个:
Vision坐标系 模型坐标系 世界坐标系 摄像机坐标系 齐次坐标系
模型坐标系,代表模型本身的坐标系,原点在模型的中心点 世界坐标系,代表整个固定空间的坐标系,这个坐标系下,模型的平移才有了意义(模型坐标系下平移并不会引起坐标变化) 摄像机坐标系,代表有了观测点的坐标系,此时我们同时拥有了摄像机和模型的世界坐标,模型此时的坐标全部转化为了摄像机为原点的坐标。 ndc坐标系,此时视椎体【注释4】形成,位于视椎体之外的部分将被剪裁,模型进行了投影变换,产生了近大远小的投影效果。
三维变换在引入了齐次坐标【注释5】后,都可以在数学上划归为4*4的矩阵相乘。
_modelViewMatrix = GLKMatrix4Identity;
_viewMatrix = GLKMatrix4MakeLookAt(0, 0, 6, 0, 0, 0, 0, 1, 0);
float ratio = outputFramebuffer.size.width/outputFramebuffer.size.height;
_projectionMatrix = GLKMatrix4MakeFrustum(-ratio, ratio, -1, 1, 3, 9);
x = x * imageWidth / imageHeight * 2 * ProjectionScale ;
y = y * imageHeight / imageHeight * 2 * ProjectionScale;
//x,y范围[-1, 1],ProjectionScale代表这贴纸跟近平面的坐标比例为2
由于Vision封装好的人脸识别模块,我们想要通过AI解决抖动的问题代价太大,基本不可能。但是观察目前的市面上的付费SDK,发现有些是在识别特征点生成后处理平滑问题的。那么证明我们对这个问题也有入手之处。先寻找一下有没有成熟的解决方案。目前开源的人脸算法和解决方案都不多,找过一遍也只找到智云的算法,这个项目里并没有解决防抖的问题。但人脸识别点抖动实际可以抽象成识别的误差抖动,在人脸领域之外,也有很多的相关资料,我去查询了传感器的平滑算法,传感器的预估算法等。总结一下找到的算法如下:卡尔曼滤波,一阶低通滤波,Savitzky-Golay算法等。卡尔曼滤波假设对于一个实际值,观测值会产生无规律的噪音偏差,而这些偏差,在分布上符合正态分布。由此基础推出了一个迭代公式。我找到了一个开源算法接入尝试了一下,实际效果并不算好,而且由于知识储备不足,这个算法的调优异常困难,所以放弃了这个方案。Savitzky-Golay算法更倾向于数据组已有后的平滑,需要前置窗口和后置窗口,对于实时预测的观测点平滑效果也较差,尝试后选择了放弃。
最后采用的是一种动态修改滤波系数的一阶低通滤波形式。一阶滤波公式为:Y(n)=αX(n)+(1−α)Y(n−1)
式中:α 为滤波系数,X ( n ) 为本次采样值,Y ( n − 1 )为上次滤波输出值,Y ( n )为本次滤波输出值。
当数据快速变化时,滤波结果能及时跟进,并且数据的变化越快,灵敏度应该越高(灵敏度优先原则) 当数据趋于稳定,并在一个范围内振荡时,滤波结果能趋于平稳(平稳度优先原则) 当数据稳定后,滤波结果能逼近并最终等于采样数据(消除因计算中小数带来的误差)
数据变化方向是否为同一个方向(如当连续两次的采样值都比其上次滤波结果大时,视为变化方向一致,否则视为不一致) 数据变化是否较快(主要是判断采样值和上一次滤波结果之间的差值)
当两次数据变化不一致时,说明有抖动,将滤波系数清零,忽略本次新采样值 当数据持续向一个方向变化时,逐渐提高滤波系数,提供本次采样值得权; 当数据变化较快(差值>消抖计数加速反应阈值)时,要加速提高滤波系数
4.欧拉角的探索对于人脸欧拉角的问题,本身vision是提供了相关的参数的。但是目前只包含了roll和yaw,pitch直到iOS15才会给出。并且Vision在训练上可能有些问题,这些欧拉角本身都不够平滑。
欧拉角的估计实际上不属于人脸识别,而属于一个单独的hand pose预估,这个问题也有解决方案。PNP算法是通用的处理头部估计的算法关于PNP问题就是指通过世界中的N个特征点与图像成像中的N个像点,计算出其投影关系,从而获得相机或物体的位姿的问题。
opencv提供的solvepnp函数就是用来解决pnp问题。利用该函数可以实现测算相机/物体的空间姿态,也可以用来空间定位。也是照旧的学习,参照开源,接入流程。这里直接给出测试结果,想要详细讨论的伙伴可以联系我一起研究。PNP算法对于头部估计的效果不错(手动去除噪声),但是有个很大的缺点,在头偏转超过45度后,预估错误的几率会很大。可能这块还是前置处理(AI部分)的效果最好。
04
项目优势
体积小巧,iOS基本无增加。由于采用了iOS原生的Vision库,在人脸模块上无需另外增加引擎,也不用新引入model训练集来支持AI识别。 性能优良,CPU增量单核5%-20%,内存增量10-20M,高端机使用无压力,低端设备在本身场景性能消耗不高的情况下使用也完全可以。 使用方便,支持流式输入输出,支持直播场景。且我们在内部做了贴纸json的转换,使得此项目支持了现有的贴纸生成工具,无缝对接原有业务。 支持拆分,在实现上特意将有可能替换的Json转换,人脸引擎分离实现。替换人脸识别库,替换贴纸格式的改动都很小。只需要实现一个方法就可以使用内部的视频处理,流式输入输出,人脸稳定算法等模块。 可扩展性,由于采用GPUImage架构,所以完美支持滤镜链的添加删除,扩展工作和自定义功能都十分方便。
05
注释
用一句话说,欧拉角就是物体绕坐标系三个坐标轴(x,y,z轴)的旋转角度,放两张图。
3.人脸特征点
人脸识别其实给出的统一格式是特征点,Vision可以参考这个
指的就是观测者看的视界形成的平截头体,上面左侧的面叫近平面,右侧是远平面。5.齐次坐标
指在坐标加一维w,转回笛卡尔坐标系只需要前几个维度除以w即可,这个w大多数情况为1,所以前几维度都保持不变。
引入齐次坐标可以使得原来笛卡尔坐标系的矩阵加法也可以用齐次坐标的矩阵乘法表示。使得任何坐标的转换可以表示为乘以一个矩阵。6.MVP转换矩阵
经典图示
一个模型坐标系的坐标乘以这三个矩阵,即可得到一个齐次坐标系的坐标。7.单元矩阵
即矩阵中左上至右下对角线为1,其余都为0的矩阵,这个矩阵乘以任何矩阵都不变,相当于自然数的1。8.Projection矩阵
透视矩阵,即mvp矩阵中的p,经过这个矩阵后,物体的形状会发生变化,上面视椎体的物体会变形如下
正交情况下仍然会显示出近大远小的投影效果。
我知道你“在看”哟~