在OpenGL中利用shader进行实时瘦脸大眼等脸型微调

在OpenGL中利用shader进行实时瘦脸大眼等脸型微调

在现在这个靠脸吃饭的时代,如果你没有一张瓜子脸一双大眼睛,那还怎么去吃饭呢,而现在一些直播视频App相机应用基本都会有瘦脸大眼效果.本文是在OpenGl环境下,在shader中通过对像素位置进行偏移来实现放大缩小的,实现起来快速简单,也是各大主流应用裁刘德基本方式.

举个栗子

首先在这里给大家看一个效果图

原图

脸型微调之后

微调后

细心的朋友应该可以看出来两张图片的区别,图二明显脸更尖了,更趋近于瓜子脸,眼睛也稍微大了点.这里其实就是用到了最常见的拉伸和缩放处理.

原理解析

其实整个过程可以分成一下三种类型的处理

  • 圆内放大
  • 圆内缩小
  • 向某一点拉伸

其实拉伸缩放理解并不困难,大家都对Bitmap有进行拉伸或者缩放过,但是这里的拉伸和缩放同Bitmap的拉伸缩放最大的区别就是要考虑到处理范围的周边像素,要使整个图片看起来过度正常,如果只是使用Bitmap的拉伸缩放必然会在边界处像素差别很大,能看到一条很明显的像素分割线,而且这样还会丢失一些像素,我们来看一个简单的放大操作效果.

原图

原图

放大后

放大后

这是普通的放大操作,存在很明显的一条圆形边界,而且放大前的好多像素直接被放大后的像素给替代(覆盖)了,像素丢失了,眼睛的其他部位都没有了,这显然不是我们想要的结果.这种简单的放大操作实现起来也很简单,但是他的适用也很局限

float dis=distance(vec2(gPosition.x,gPosition.y/uXY),vec2(centerPoint.x,centerPoint.y));
if(dis< RADIUS){
    gl_FragColor=texture2D(vTexture,vec2(aCoordinate.x/2.0+0.25,aCoordinate.y/2.0+0.25));
}
gl_FragColor=orignalColor;

而我们这里用到的是局部拉伸和缩放,显然用这种算法是不合理的.

而局部微调中用到的算法原理更多是把局部区域进行微小的挤压,将一些像素进行缩放拉伸的同时会对另一些像素进行挤压,基本不会产生像素的丢失和明显的分界线.本质是对像素的坐标值进行偏移(或者说是对每个位置上的像素进行偏移).

一个形象的比如:我们可以将一幅图像的所有像素点映射到一张面片上,面片的厚度表示单位面积上原始像素的多少,开始时面片厚度是一致的,我们可以设为1,越厚则便是这里堆积的原始像素的密度越大,局部放大就是将面片局部进行擀薄,且厚薄区域的过度圆滑,该区域的原始像素值变少并且向外扩张,这样原本离中心距离为1的像素跑到距离为3的位置,距离为2的像素跑到距离为5的位置,以此类推,就会产生放大的效果;但是这种放大并不会有明显的过度.假设我们的放大半径为100,这样离中心35的像素移动到离中心71的位置,而后面的29个距离上却堆积了原来从36到100的像素,并且产生一种平稳的过度.

下面看一下具体的实现.

圆内放大

//圆内放大
vec2 enlargeFun(vec2 curCoord,vec2 circleCenter,float radius,float intensity,float curve)
{
    float currentDistance = distance(curCoord,circleCenter);

    {
        float weight = currentDistance/radius;

        weight = 1.0-intensity*(1.0-pow(weight,curve));//默认curve 为2 ,当 curve 越大时, 会放大得越大的,
        weight = clamp(weight,0.0,1.0);
        curCoord = circleCenter+(curCoord-circleCenter)*weight;
    }
    return curCoord;
}

这是圆内放大算法,
输入:坐标,放大中心坐标,放大半径,放大比例系数,放大算法参数.
返回:放大之后应该取的像素的位置.

为了分析这个算法到底是怎么实现的,我们先可以简单的另 intensity = 1.0,curve = 2.0;此时再看这个算法其实就是pow函数,也就是平方函数了,这个平方函数是怎么实现放大的呢.

其实从图像中大家就能明白原理了,坐标经过平方处理之后,本来A点应该取A1像素,结果取的是A2像素,也就是离中心0.25位置的像素B1被放在了离中心0.5的位置A上,这显然就是放大的操作.由于横纵坐标一一对应,所以该算法并不会造成像素的丢失,只是会在放大区域的边缘内有一部分像素比较密集的区域.

上面的分析是建立在特殊值下的,我们再回到这个函数本身,
curve 是我们的pow函数的次方值,由于我们考虑的都是[0,1]区间的,所以该值越大,离中心点向外扩散也越厉害,放大效果越大.
intensity 的取值是[0,1]当它取1时就是我们上面分析的情况,会最大化的利用pow次方产生的坐标偏移来取像素,若它为0,则不会产生任何缩放效果,intensity 是一个影响因子,一个对 pow函数产生的坐标偏移的采用度,intensity越大则会更大化利用 pow 函数产生坐标便宜作为最后的坐标偏移.

圆内缩小

vec2 narrowFun(vec2 curCoord,vec2 circleCenter,float radius,float intensity,float curve)
{
    float currentDistance = distance(curCoord,circleCenter);

    {
        float weight = currentDistance/radius;
        weight = 1.0-intensity*(1.0-pow(weight,curve));//默认curve 为2 ,当curve 越大时, 会缩小得越小的,
        weight = clamp(weight,0.0001,1.0);
        curCoord = circleCenter+(curCoord-circleCenter)/weight;
    }
    return curCoord;
}

上面分析了圆内放大,看一下缩小的代码,其实也不难理解,也是利用pow函数进行像素坐标的偏移.这里就不多家分析了.

向某一点拉伸

// 拉伸
vec2 stretchFun(vec2 textureCoord, vec2 originPosition, vec2 targetPosition, float radius,float curve)
{
    vec2 offset = vec2(0.0);
    vec2 result = vec2(0.0);

    vec2 direction = targetPosition - originPosition;


    float infect = distance(textureCoord, originPosition)/radius;

    infect = pow(infect,curve);// 默认 curve 为1,这个值越大,拉伸到指定点越圆润,越小越尖
    infect = 1.0-infect;
    infect = clamp(infect,0.0,1.0);
    offset = direction * infect;
    result = textureCoord - offset;

    return result;
}

输入:坐标,拉伸中心坐标,拉伸目标坐标,拉伸半径,拉伸算法参数.
返回:放大之后应该取的像素的位置.

拉伸和缩放原理其实是一样的,只是理解起来有些差距.我们还是设 curve = 2,对其原理进行分析.

上图是我对拉伸原理的一个简单描述,A为拉伸中心坐标,B为拉伸目标坐标,经过拉伸后,圆1上的像素会被平移到圆1’,圆2上的像素会被平移到圆2’.以A为圆心的同心圆经过拉伸之后都会被平移,离A越近平移的距离越远,A点直接平移到B点, 而R处则不会平移.看到这里应该已经知道怎样将一个圆脸变成瓜子脸了吧.当然这都是微调,如果调整过大会产生不自然的效果.curve 值越大,拉伸到指定点越圆润,越小越尖.

上面只是我对人脸变形时的原理进行分析,要想使用变形,首先要确定人脸特征点,有了这些特征点,你才知道缩放中心半径等等,而使用时往往不是一步就能达到理想效果,比如说我们大眼 一般是首先对一个比较大的包含眼睛的区域进行放大,然后再对眼睛中心瞳孔位置进行进一步放大.实际使用时为了达到某种效果一般都是对这几种操作进行组合使用,而且是多次操作.

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页