在2019年,也就是去年7月份的时候,俄罗斯下的FaceApp的变老功能被一堆美国政要和明星带火,接着就收到美国的隐私禁令。从技术上看,FaceApp的变老应该是基于机器学习的,今天,笔者给大家分享一种基于传统算法实现变老的效果。
先给大家看下特效,如图1.1所示:
我们探究下这种效果的实现过程,从视觉上去看,其本质是在用户图上的人脸位置贴上皱纹图,具体思路如下所示:
- 将图片A进行人脸检测,得到图片A的人脸关键点集m;
- 根据人脸检测的关键点集m的特征,对皱纹图B进行打点,点集为n;
- 将关键点集n对齐到m,对齐的方法有多样,可以用三角剖分,也可以使用移动最小二乘法中的相似变换;
- 通过对齐得到新的顶点坐标集v,纹理坐标集t,索引集i,对图片A和皱纹B进行混合叠加,得到效果图C;
- 将图片A和效果图C进行透明度混合,实现调整透明度的效果。
前面我们了解了实现这种效果的关键步骤,接下来就深入了解每个环节。
一、关键点检测
人脸检测的技术已经不是什么新鲜事了,各大厂商出了许多人脸检测SDK,有81个关键点的、有106个关键点的,目前在收费上比较出名的人脸关键点SDK有商汤科技、旷视、字节等;免费的人脸关键点SDK有OpenCV、Dlib、以及Google旗下Firebase的MLVision,笔者正是使用它来做此次的人脸关键点检测,MLVision可以检测出133个关键点,包括人脸的额头点,如图2.1所示:
Firebase的MLVision具体使用可以参考Firebase的文档,需要用梯子才能访问,这里笔者简单总结下对Firebase的MLVision的使用感受:
- 优点:
- API设计友好、免费使用、能检测出额头点、五官特征等;
- 关键点稳定,不会过于抖动。
- 不足:
- 只能单人脸检测,且单帧人脸关键点检测相较于收费厂商过慢,对一张1080的图片,用MLVision需要15~20ms,Face++是6ms、字节是3ms,所有理论上MLVision无法用在实时情景下;
- 缺乏更多人脸相关数据,如缺乏部分欧拉角数据等。
- 除了需要将用户图A通过人脸检测获取到关键点集m之外,还需要对皱纹图B打点,其实就是人工计算图B关键点的坐标,坐标数量和特征同点集m保持一致,为点集n,皱纹图如图2.2所示:
二、关键点对齐
在图像变形上,业界有很多种方法,有二维的,也有三维的变形,此次用到的只有二维上的变形,二维上的图像变形有移动最小二乘法,其中包括放射变形、相似变形、刚性变形。其原理简要描述是由用户指定图像中的控制点,并通过拖拽控制点来驱动图像变形。假设p为原图像中控制点的位置,q为拖拽后控制点的位置,我们利用移动最小二乘法来为原图像上的每个像素点v构建相应的仿射变换lv(x),并通过该变换来计算得到图像变形后的位置:
其中权重wi的表达式为wi = 1/|pi - v|2α,仿射变换lv(x)由两部分组成lv(x) = xM + T,其中M为线性转换矩阵,T为平移量。事实上将最小化表达式对变量T求偏导后可以得到T的表达式T = q* - pM,其中p = ∑wipi/∑wi,q* = ∑wiqi/∑wi。于是仿射变换可以化简为lv(x) = (x - p*)M + q*,而最小化表达式可以变化为:
其中
仿射变形是利用经典正规方程对最小化表达式直接求解得到的结果:
有了旋转矩阵M的表达式后,我们得到变形的表达式:
由于用户是通过控制q的位置来实现图像变形,而p的位置是固定不变的,因此上式中大部分内容可以预先计算并保存,从而提高运算速度,重写变形表达式如下:
效果图如图3.1所示:
在这一步,根据点集m和点集n,将皱纹图B对齐到用户图A上,效果如图3.2所示:
三、叠加混合
面具图B一旦变形完后,需要将其同图A进行叠加混合,叠加算法有很多种,有柔光、叠加、强光、变亮、变暗等,具体可以查看Photo Shop,如果没有Photo Shop可以查看一些手机图片编辑工具,如PicsArt美易等工具。这里笔者建议可以使用柔光混合或者叠加混合,这里使用的叠加混合,效果基本上同柔光差不多,但在黑人变老体验上会更好,各种混合公式如图4.1所示:
这里笔者使用的是GPUImage3的叠加混合着色器,是基于Metal的GPU渲染,Metal,苹果推出的今后GPU渲染和计算的御用API,兼顾图形与计算功能、面向底层、低开销的硬件加速、类似将OpenGL和OpenCL的功能集合在同一个API里,iOS8以上,但其实保险得iOS11以上才能用,跟硬件加速有关,除此之外苹果还有一个MetalKit,和Metal共存在iOS里,只不过MetalKit比Metal多了些东西,比如提供更容易的纹理加载API、Metai的高效输入输出、MTKView等,基本上iOS上目前都是使用MetalKit。
使用Metal渲染后,效果图如图4.2所示:
到这里基本上就完成了变老特效的实现,Demo可以在Github上直接下载。