在OpenGL中利用shader進行實時瘦臉大眼等臉型微調

在現在這個靠臉吃飯的時代,如果你沒有一張瓜子臉一雙大眼睛,那還怎麼去吃飯呢,而現在一些直播視頻App相機應用基本都會有瘦臉大眼效果.本文是在OpenGl環境下,在shader中通過對像素位置進行偏移來實現放大縮小的,實現起來快速簡單,也是各大主流應用裁劉德基本方式.

舉個栗子

首先在這裏給大家看一個效果圖fff288efc485790eddeeb81cf52b5010cd3.jpg

臉型微調之後

c8972a81ab241ed26f85bfe7e6878599de8.jpg

細心的朋友應該可以看出來兩張圖片的區別,圖二明顯臉更尖了,更趨近於瓜子臉,眼睛也稍微大了點.這裏其實就是用到了最常見的拉伸和縮放處理.

原理解析

其實整個過程可以分成一下三種類型的處理

  • 圓內放大
  • 圓內縮小
  • 向某一點拉伸

其實拉伸縮放理解並不困難,大家都對Bitmap有進行拉伸或者縮放過,但是這裏的拉伸和縮放同Bitmap的拉伸縮放最大的區別就是要考慮到處理範圍的周邊像素,要使整個圖片看起來過度正常,如果只是使用Bitmap的拉伸縮放必然會在邊界處像素差別很大,能看到一條很明顯的像素分割線,而且這樣還會丟失一些像素,我們來看一個簡單的放大操作效果.

原圖

11a671965e3c0483b5d41525597cc4d2453.jpg

放大後

9349c965dde07c7d3332ba8458e433973a1.jpg

這是普通的放大操作,存在很明顯的一條圓形邊界,而且放大前的好多像素直接被放大後的像素給替代(覆蓋)了,像素丟失了,眼睛的其他部位都沒有了,這顯然不是我們想要的結果.這種簡單的放大操作實現起來也很簡單,但是他的適用也很侷限

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函數,也就是平方函數了,這個平方函數是怎麼實現放大的呢.

12bcd34792ad263be66495639b5e26b09df.jpg

其實從圖像中大家就能明白原理了,座標經過平方處理之後,本來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,對其原理進行分析.

6d7a72b26d6e9fa920a792313ebd34012d4.jpg

上圖是我對拉伸原理的一個簡單描述,A爲拉伸中心座標,B爲拉伸目標座標,經過拉伸後,圓1上的像素會被平移到圓1’,圓2上的像素會被平移到圓2’.以A爲圓心的同心圓經過拉伸之後都會被平移,離A越近平移的距離越遠,A點直接平移到B點, 而R處則不會平移.看到這裏應該已經知道怎樣將一個圓臉變成瓜子臉了吧.當然這都是微調,如果調整過大會產生不自然的效果.curve 值越大,拉伸到指定點越圓潤,越小越尖.

上面只是我對人臉變形時的原理進行分析,要想使用變形,首先要確定人臉特徵點,有了這些特徵點,你才知道縮放中心半徑等等,而使用時往往不是一步就能達到理想效果,比如說我們大眼 一般是首先對一個比較大的包含眼睛的區域進行放大,然後再對眼睛中心瞳孔位置進行進一步放大.實際使用時爲了達到某種效果一般都是對這幾種操作進行組合使用,而且是多次操作.

附一篇优秀博客:https://www.jianshu.com/p/3334a3af331f

转载于:https://my.oschina.net/HeroOneHY/blog/3094905

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值