OpenGLES 大眼瘦脸算法解析

1. 大眼算法

1.1 简介

大眼效果本质上是对图像中某些区域的像素按照我们设定的规则进行移动,而 OpenGL 的片段着色器天然适合处理像素(纹素)层面的操作。而OpenGL实现的大眼效果,可以参照放大镜实现原理,即将纹理上一块区域采样后映射到一块相对较大的区域。而我们实现的算法对大眼进行了简化,是以人脸瞳孔为中心的圆形区域进行放大,距离圆心越远,放大的强大越大。
f s ( r ) = ( 1 − ( r r max ⁡ − 1 ) 2 a ) r f_{s}(r)=\left(1-\left(\frac{r}{r_{\max }}-1\right)^{2} a\right) r fs(r)=(1(rmaxr1)2a)r

这里 r max ⁡ r_{\max} rmax 指代作用半径, r r r 指当前像素点到圆心的距离,a ∈ [0, 1],属于缩放系数。

在这里插入图片描述

如上图所示,圆内部为图像发生形变区域,红色点像素为变形前采样点(原始纹理坐标),绿色点为形变后采样点(纹理坐标发生偏移)。

实际应用的时,我们以人脸关键点检测到的人眼瞳孔坐标作为圆心,选取两眼角的宽度作为直径,然后对圆内区域按上述算法进行形变处理。
在这里插入图片描述

1.2 示例代码(GLSL)

/**
currentCoordinate: 当前采样纹理坐标
centerPos: 形变圆中心坐标
rMax: 形变作用圆半径
scaleRatio: 缩放系数
*/
vec2 calcNewCoord(vec2 currentCoordinate, vec2 centerPos, float rMax, float scaleRatio){
   // 计算当前纹理坐标到眼睛中心的距离,对应 r
   float r = distance(currentCoordinate, centerPos);
   // 计算形变后的采样半径长度
   float fsr = 1.0 - pow(r / rMax - 1.0, 2.0) * scaleRatio;
   // 仅对圆半径内的像素点生效
   if (r < rMax){
       //(新采样点 - 圆中心) / (旧采样点 - 圆中心) = 新距离 / 老距离
       //    (newCoord - centerPos)  / (oldCoord - centerPos)= fsr ;
       // 新的纹理采样点坐标
       currentCoordinate = centerPos + fsr * (currentCoordinate - centerPos);
   }
   return currentCoordinate;
}

/*
大眼睛算法
左眼角:35 39
左眼中心:38
右眼角:89 93
右眼中心:88
*/
vec2 bigEye(vec2 TexCoords){
   vec2 tmp_left_1 = cartesianPoints[35];
   vec2 tmp_left_2 = cartesianPoints[39];
   float dis_left = distance(tmp_left_1,tmp_left_2);
   tmp_left_1 = cartesianPoints[89];
   tmp_left_2 = cartesianPoints[93];
   float dis_right = distance(tmp_left_1,tmp_left_2);
   // 缩放圆作用半径rMax
   float rMax = max(dis_left,dis_right);
   //计算眼睛中心坐标(Idea From CainCamera)
   vec2 left_eye_center = cartesianPoints[38] + (cartesianPoints[88] - cartesianPoints[38]) * 0.05;
   vec2 right_eye_center = cartesianPoints[88] + (cartesianPoints[38] - cartesianPoints[88]) * 0.05;
   // 大眼
   //左眼放大位置的采样点
   TexCoords = calcNewCoord(TexCoords, left_eye_center, rMax, bigEyesIntensity * big_eyes_max_scale);
   //右眼放大位置的采样点
   TexCoords = calcNewCoord(TexCoords, right_eye_center, rMax, bigEyesIntensity * big_eyes_max_scale);
   return TexCoords;
}

1.3 效果

在这里插入图片描述

2. 瘦脸算法

2.1 简介

2.1.1 交互式图像变形算法

瘦脸及放大眼睛的前提是需要检测到人脸,并提取特征点。谈到图像变形,最基础的思路是:由变形前坐标,根据变形映射关系,得到变形后坐标。这其中变形映射关系是最关键的,不同的映射关系,将得到不同的变形效果。平移、缩放、旋转,对应的是不同的映射关系,即不同的变换公式。当然实际在计算过程中,用的是逆变换,即由变形后坐标,根据逆变换公式反推变形前坐标,然后插值得到该坐标 r g b rgb rgb 像素值,将该 r g b rgb rgb 值作为变形后坐标对应的像素值。这样才能保证变形后的图像是连续、完整的。

The source coordinate vector u ⃗ \vec{u} u corresponding to a destination pixel with coordinate vector x ⃗ \vec{x} x is calculated as follows:
u ⃗ = x ⃗ − ( r max ⁡ 2 − ∣ x ⃗ − c ⃗ ∣ 2 ( r max ⁡ 2 − ∣ x ⃗ − c ⃗ ∣ 2 ) + ∣ m ⃗ − c ⃗ ∣ 2 ) 2 ( m ⃗ − c ⃗ ) \vec{u}=\vec{x}-\left(\frac{r_{\max }^{2}-|\vec{x}-\vec{c}|^{2}}{\left(r_{\max }^{2}-|\vec{x}-\vec{c}|^{2}\right)+|\vec{m}-\vec{c}|^{2}}\right)^{2}(\vec{m}-\vec{c}) u =x ((rmax2x c 2)+m c 2rmax2x c 2)2(m c )

在这里插入图片描述

如上图及公式所示, x ⃗ \vec{x} x 是变换后的位置, u ⃗ \vec{u} u 是原坐标位置。整个计算在以 c c c 为圆心, r max ⁡ r_{\max } rmax 为半径的圆内进行。因为是交互式图像局部变形,所以 c c c 也可以看做鼠标点下时的坐标,而 m m m 为鼠标移动一段距离后抬起时的坐标,因此 c c c m m m 决定了变形方向。通过上述方法我们可以实现图像的局部平移及形变。

2.1.2 自动瘦脸算法

通过上述算法我们可以实现手动的人脸区域的瘦脸,而自动瘦脸无非就是在手动瘦脸的基础上确定了瘦脸的中心(原始,目标)以及作用半径。

在这里插入图片描述

如上图所示, B C BC BC 表示偏移方向和偏移程度的向量,将圆内的所有像素按照向量 B C BC BC 的方向进行一定程度的偏移,像素偏移的强度,和像素与圆心的距离相关,越靠近圆心强度越大。

实际应用的时,我们选取脸颊附近的关键点作为圆心,以鼻尖关键点作为偏移变形终点,对脸颊圆形区域进行平移变形(原理同1),实现瘦脸效果。

关键点序号:
12 28 到 73 1/2
15 31 到 86 1/2
3 19 到 86 3/5

在这里插入图片描述

2.2 示例代码


/**
瘦脸曲线形变处理
currentCoordinate: 当前采样纹理坐标
originPosition: 形变作用圆中心坐标
targetPosition: 形变作用圆半径
scaleRatio: 缩放系数
参考:https://blog.csdn.net/Kennethdroid/article/details/104907763
*/
vec2 curveWarp(vec2 currentCoordinate, vec2 originPosition, vec2 targetPosition, float scaleRatio) {
    // 形变需要添加的平移向量
    vec2 direction = targetPosition - originPosition;
    // 计算原始纹理坐标到目标点的距离, 平移作用圆半径rMax(这里使用的是原始圆心和目标圆心的距离作为半径)
    float rMax = distance(targetPosition, originPosition);
    // 计算当前纹理坐标到眼睛中心的距离,对应 r
    float r = distance(currentCoordinate, originPosition);
    // 缩放比例
    float ratio = (1.0 - r / rMax) * scaleRatio * 0.1;
//    float ratio = pow(1.0 - r / rMax, 2.0);
    // 仅对半径作用范围内的像素点生效
    if (r < rMax){
        //    ratio = clamp(ratio, 0.0, 1.0);
        // 变形前点 + 缩放系数 * direction = 变形后的点
        // 新采样点 = 旧采样点 - 缩放系数 * direction
        currentCoordinate = currentCoordinate - direction * ratio;
    }
    return currentCoordinate;
}

/**
瘦脸算法
12 28 到 73 1/2
15 31 到 86 1/2
3 19 到 86 3/5
**/
vec2 thin_face(vec2 currentCoordinate){
    // 作用圆心索引(对于人脸关键点数组)
    int originIndex;
    // 作用圆心坐标
    vec2 originPoint;
    // 目标点索引(对于人脸关键点数组)
    int targetIndex;
    // 目标点坐标
    vec2 targetPoint;
    //12 28 两旁到73
    lowp float intensity_12_28 = 0.5;
    originIndex = 12;targetIndex = 73;
    originPoint = cartesianPoints[originIndex];
    targetPoint = cartesianPoints[targetIndex];
    targetPoint = originPoint + (targetPoint - originPoint) * intensity_12_28;
    currentCoordinate = curveWarp(currentCoordinate, originPoint, targetPoint, liftFaceIntensity);
    originIndex = 28;targetIndex = 73;
    originPoint = cartesianPoints[originIndex];
    targetPoint = cartesianPoints[targetIndex];
    targetPoint = originPoint + (targetPoint - originPoint) * intensity_12_28;
    currentCoordinate = curveWarp(currentCoordinate, originPoint, targetPoint, liftFaceIntensity);

    //15 31 到86
    lowp float intensity_15_31 = 0.5;
    originIndex = 15;targetIndex = 86;
    originPoint = cartesianPoints[originIndex];
    targetPoint = cartesianPoints[targetIndex];
    targetPoint = originPoint + (targetPoint - originPoint) * intensity_15_31;
    currentCoordinate = curveWarp(currentCoordinate, originPoint, targetPoint, liftFaceIntensity);
    originIndex = 31;targetIndex = 86;
    originPoint = cartesianPoints[originIndex];
    targetPoint = cartesianPoints[targetIndex];
    targetPoint = originPoint + (targetPoint - originPoint) * intensity_15_31;
    currentCoordinate = curveWarp(currentCoordinate, originPoint, targetPoint, liftFaceIntensity);

    //3 19 到86
    lowp float intensity_3_19 = 0.6;
    originIndex = 3;targetIndex = 86;
    originPoint = cartesianPoints[originIndex];
    targetPoint = cartesianPoints[targetIndex];
    targetPoint = originPoint + (targetPoint - originPoint) * intensity_3_19;
    currentCoordinate = curveWarp(currentCoordinate, originPoint, targetPoint, liftFaceIntensity);
    originIndex = 19;targetIndex = 86;
    originPoint = cartesianPoints[originIndex];
    targetPoint = cartesianPoints[targetIndex];
    targetPoint = originPoint + (targetPoint - originPoint) * intensity_3_19;
    currentCoordinate = curveWarp(currentCoordinate, originPoint, targetPoint, liftFaceIntensity);

    return currentCoordinate;
}

2.3 效果

在这里插入图片描述

3. 参考

https://juejin.cn/post/6844904094327390215
https://blog.csdn.net/zhouguangfei0717/article/details/103454571
手动瘦脸局部形变:
https://blog.csdn.net/qq_15295565/article/details/87891820
OpenGL局部形变:
https://juejin.cn/post/6844904094327390215
论文链接:
http://www.gson.org/thesis/warping-thesis.pdf

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值