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

前言

因爲原創圖片加載失敗,添加圖片記錄一下方便學習!原創地址git

目前是準備作一個美顏相機類的項目,後續會更新瘦臉大眼等臉型微調實現部分。 代碼已上傳MagicCameragithub

正文

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

舉個栗子

首先在這裏給你們看一個效果圖 函數

臉型微調以後 學習

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

原理解析

其實整個過程能夠分紅一下三種類型的處理spa

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

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

原圖3d

放大後code

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

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 值越大,拉伸到指定點越圓潤,越小越尖.

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

相關文章
相關標籤/搜索