"rotateCenter": [
{
"flag": 49,
"x": 239.92,
"y": 357.87
}
],
rotateCenter描述了贴纸的旋转中心,一般是鼻尖。人的头部偏转时,贴纸围绕这个点进行旋转"scale": {
"pointA":
{
"flag": 6,
"x": 185.59,
"y": 299.39
},
"pointB":
{
"flag": 13,
"x": 293.67,
"y": 298.72
}
},
scale描述了贴纸的缩放比例,这里的具体点逻辑与position相同,但这里会有两个点,这两个点的间距就可以确定实际的距离比例。(scale一般取双眼的点)"begin":“sticker1.complete”
begin描述了贴纸的触发时机,某个动画可能会由另一动画的结束为开始时机,这里的参数决定了贴纸的顺序触发功能。即使有了人脸识别的关键点,人脸贴图的实现也并非易事。因为它并不是简单的二维粘贴,而是包含欧拉角【注释2】的三维变换(人脸的远近旋转都会影响贴纸形态)。
先从坐标系说起。这个项目涉及的坐标系共有五个:我们通过Vision得到的人脸特征点【注释3】属于Vision坐标系,这个坐标系的原点在左下,x,y的范围为[0, imageSize],imageSize为输入图像的像素大小。而下面的四个坐标系则代表了OpenGL三维变换的四个空间(Model,World,Camera,Homogeneou s),这些坐标系都是三维的。- 模型坐标系,代表模型本身的坐标系,原点在模型的中心点
- 世界坐标系,代表整个固定空间的坐标系,这个坐标系下,模型的平移才有了意义(模型坐标系下平移并不会引起坐标变化)
- 摄像机坐标系,代表有了观测点的坐标系,此时我们同时拥有了摄像机和模型的世界坐标,模型此时的坐标全部转化为了摄像机为原点的坐标。
- ndc坐标系,此时视椎体【注释4】形成,位于视椎体之外的部分将被剪裁,模型进行了投影变换,产生了近大远小的投影效果。
三维变换在引入了齐次坐标【注释5】后,都可以在数学上划归为4*4的矩阵相乘。
Vision坐标系与模型坐标系的转化非常简单,只是简单的加减这里暂且不提。剩下的四个空间转换则对应了OpenGL经典的MVP转换矩阵【注释6】,来看一下项目内使用的三矩阵。(项目的模型指的是人脸图像模型,贴纸是在人脸图像模型上进行相对偏移的)_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);
Model矩阵采用了单元矩阵【注释7】,代表世界坐标系与模型坐标系原点相同。View矩阵的参数代表我们的摄像机处在世界坐标系的(0, 0 , 6),望向(0, 0 , 0),且摄影机的正向朝向y轴正方向(这里可能难以理解,但实际想象一下摄像机是个球,有了位置和望向,自己还是能旋转,看到的图形也可能上下倒置,所以需要确定头方向)Projection矩阵【注释8】的参数代表我们的视椎体近平面左右ratio,上下1,近平面距离3,远平面距离9(注意此时的坐标已经不是世界坐标了,转换成世界坐标近平面z是3,而远平面z是-3,模型的z在0)将模型坐标乘上mvp矩阵,我们就可以得到一个ndc坐标系下的人脸坐标锚点,下面可以开始绘制贴纸了。由于贴纸的大小受到远近影响,所以第一步,求出归一化模型坐标系下贴纸的长宽,贴纸的中心点。x = x * imageWidth / imageHeight * 2 * ProjectionScale ;
y = y * imageHeight / imageHeight * 2 * ProjectionScale;
//x,y范围[-1, 1],ProjectionScale代表这贴纸跟近平面的坐标比例为2
除以imageHeight * 2 * ProjectionScale的原因是投影矩阵将近平面上下设为[-1, 1],将左右设置为了宽高比,而我们的图像平面处在摄像机坐标系的-6位置,近平面在-3(摄像机坐标系视角朝向z轴负方向),需要换算。