天天看點

【AI超級美發師】深度學習算法打造染發特效(附代碼)

【新智元導讀】如今,在類似天天P圖、美圖秀秀等手機APP中,給指定照片或視訊中的人物更換頭發顔色已經是再正常不過的事情了。那麼本文便介紹了該功能背後如AI頭發分割子產品、頭發換色、顔色增強與修正子產品等技術原理(附代碼)。

首先,為照片或視訊中人物換發色的算法流程如下圖所示:

【AI超級美發師】深度學習算法打造染發特效(附代碼)

AI頭發分割子產品

基于深度學習的目标分割算法已經比較成熟,比較常用的有FCN,SegNet,UNet,PspNet,DenseNet等等。這裡我們使用Unet網絡來進行頭發分割,具體可以參考如下連結:點選打開連結Unet頭發分割代碼如下:

def get_unet_256(input _shape=(256,256,3),
                 num_classes=1):
    inputs=Input(shape=input_shape)
    #256

    down0 = Conv2D(32,(3,3), padding='same')(inputs)
    down0 = BatchNormalization()(down0)
    down0 = Activation('relu')(down0)
    down0 = Conv2D(32,(3,3), padding='same')(down0)
    down0 = BatchNormalization()(down0)
    down0 = Activation('relu')(down0)
    down0_pool = MaxPooling2D((2,2),strides=(2,2))(down0)    
    #128

    down1 = Conv2D(64,(3,3), padding='same')(down0_pool)
    down1 = BatchNormalization()(down1)
    down1 = Activation('relu')(down1)
    down1 = Conv2D(64,(3,3), padding='same')(down1)
    down1 = BatchNormalization()(down1)
    down1 = Activation('relu')(down1)
    down1_pool = MaxPooling2D((2,2),strides=(2,2))(down1)
    #64

    down2 = Conv2D(128,(3,3), padding='same')(down1_pool)
    down2 = BatchNormalization()(down2)
    down2 = Activation('relu')(down2)
    down2 = Conv2D(128,(3,3), padding='same')(down2)
    down2 = BatchNormalization()(down2)
    down2 = Activation('relu')(down2)
    down2_pool = MaxPooling2D((2,2),strides=(2,2))(down2)
    #32

    down3 = Conv2D(256,(3,3), padding='same')(down2_pool)
    down3 = BatchNormalization()(down3)
    down3 = Activation('relu')(down3)
    down3 = Conv2D(256,(3,3), padding='same')(down3)
    down3 = BatchNormalization()(down3)
    down3 = Activation('relu')(down3)
    down3_pool = MaxPooling2D((2,2),strides=(2,2))(down3)
    #16

    down4 = Conv2D(512,(3,3), padding='same')(down3_pool)
    down4 = BatchNormalization()(down4)
    down4 = Activation('relu')(down4)
    down4 = Conv2D(512,(3,3), padding='same')(down4)
    down4 = BatchNormalization()(down4)
    down4 = Activation('relu')(down4)
    down4_pool = MaxPooling2D((2,2),strides=(2,2))(down4)
    #8

    center = Conv2D(1024,(3,3), padding='same')(down4_pool)
    center = BatchNormalization()(center)
    center = Activation('relu')(center)
    center = Conv2D(1024,(3,3), padding='same')(center)
    center = BatchNormalization()(center)
    center = Activation('relu')(center)
    #center

    up4 = UpSamepling2D((2,2))(center)
    up4 = Concatenate([down4,up4],axis=3)
    up4 = Conv2D(512,(3,3),padding='same')(up4)
    up4 = BatchNormalization()(up4)
    up4 = Activation('relu')(up4)
    up4 = Conv2d(512,(3,3),padding='same')(up4)
    up4 = BatchNormalization()(up4)
    up4 = Activation('relu')(up4)
    #16

    up3 = UpSamepling2D((2,2))(up4)
    up3 = Concatenate([down4,up4],axis=3)
    up3 = Conv2D(256,(3,3),padding='same')(up3)
    up3 = BatchNormalization()(up3)
    up3 = Activation('relu')(up3)
    up3 = Conv2d(256,(3,3),padding='same')(up3)
    up3 = BatchNormalization()(up3)
    up3 = Activation('relu')(up3)
    #32

    up2 = UpSamepling2D((2,2))(up3)
    up2 = Concatenate([down4,up4],axis=3)
    up2 = Conv2D(128,(3,3),padding='same')(up2)
    up2 = BatchNormalization()(up2)
    up2 = Activation('relu')(up2)
    up2 = Conv2d(128,(3,3),padding='same')(up2)
    up2 = BatchNormalization()(up2)
    up2 = Activation('relu')(up2)
    #64

    up1 = UpSamepling2D((2,2))(up2)
    up1 = Concatenate([down4,up4],axis=3)
    up1 = Conv2D(64,(3,3),padding='same')(up1)
    up1 = BatchNormalization()(up1)
    up1 = Activation('relu')(up1)
    up1 = Conv2d(64,(3,3),padding='same')(up1)
    up1 = BatchNormalization()(up1)
    up1 = Activation('relu')(up1)
    #128

    up0 = UpSamepling2D((2,2))(up1)
    up0 = Concatenate([down4,up4],axis=3)
    up0 = Conv2D(32,(3,3),padding='same')(up0)
    up0 = BatchNormalization()(up0)
    up0 = Activation('relu')(up0)
    up0 = Conv2d(32,(3,3),padding='same')(up0)
    up0 = BatchNormalization()(up0)
    up0 = Activation('relu')(up0)
    #256

    classify = Con2D(num_classes,(1,1)),activation='sigmoid')(up0)
    model = Model(input=inputs,outputs=classify)
 #model.compile(optimizer=RMSprop(lr=0.0001),loss=bce_dice_loss,metrices=[dice_coeff])

return model
           

分割效果舉例如下:

【AI超級美發師】深度學習算法打造染發特效(附代碼)

使用的訓練和測試資料集合大家自己準備即可。

發色更換子產品

這個子產品看起來比較簡單,實際上卻并非如此。 這個子產品要細分為:

①頭發顔色增強與修正子產品;

②顔色空間染色子產品;

③頭發細節增強;

發色增強與修正子產品

為什麼要對頭發的顔色進行增強與修正? 先看下面一組圖,我們直接使用HSV顔色空間對純黑色的頭發進行染色,目标色是紫色,結果如下:

【AI超級美發師】深度學習算法打造染發特效(附代碼)

大家可以看到,針對上面這張原圖,頭發比較黑,在HSV顔色空間進行頭發換色之後,效果圖中很不明顯,隻有輕微的顔色變化。

為什麼會出現這種情況?原因如下: 我們以RGB和HSV顔色空間為例,首先來看下HSV和RGB之間的轉換公式:

設 (r, g, b)分别是一個顔色的紅、綠和藍坐标,它們的值是在0到1之間的實數。設max等價于r, g和b中的最大者。設min等于這些值中的最小者。要找到在HSL空間中的 (h, s, l)值,這裡的h ∈ [0, 360)度是角度的色相角,而s, l ∈ [0,1]是飽和度和亮度,計算為:

【AI超級美發師】深度學習算法打造染發特效(附代碼)

我們假設頭發為純黑色,R=G=B=0,那麼按照HSV計算公式可以得到H = S = V = 0;

假設我們要把頭發顔色替換為紅色(r=255,g=0,b=0);

那麼,我們先将紅色轉換為對應的hsv,然後保留原始黑色頭發的V,紅色頭發的hs,重新組合新的hsV,在轉換為RGB顔色空間,即為頭發換色之後的效果(hs是顔色屬性,v是明度屬性,保留原始黑色頭發的明度,替換顔色屬性以達到換色目的);

HSV轉換為RGB的公式如下:

【AI超級美發師】深度學習算法打造染發特效(附代碼)

對于黑色,我們計算的結果是H=S=V=0,由于V=0,是以,p=q=t=0,不管目标顔色的hs值是多少,rgb始終都是0,也就是黑色;

這樣,雖然我們使用了紅色,來替換黑色頭發,但是,結果卻依舊是黑色,結論也就是hsv/hsl顔色空間,無法對黑色換色。

下面,我們給出天天P圖和美妝相機對應紫色的換發色效果:

【AI超級美發師】深度學習算法打造染發特效(附代碼)

與之前HSV顔色空間的結果對比,我們明顯可以看到,天天P圖和美妝相機的效果要更濃,更好看,而且對近乎黑色的頭發進行了完美的換色;

由于上述原因,我們這裡需要對圖像中的頭發區域進行一定的增強處理:提亮,輕微改變色調;

這一步通常可以在PS上進行提亮調色,然後使用LUT來處理;

經過提亮之後的上色效果如下圖所示:

【AI超級美發師】深度學習算法打造染發特效(附代碼)

可以看到,基本與美妝相機和天天P圖類似了。

HSV/HSL/YCbCr顔色空間換色

這一步比較簡單,保留明度分量不變,将其他顔色、色調分量替換為目标發色就可以了。

這裡以HSV顔色空間為例:

假如我們要将頭發染發為一半青色,一般粉紅色,那麼我們建構如下圖所示的顔色MAP:

【AI超級美發師】深度學習算法打造染發特效(附代碼)

對于頭發區域的每一個像素點P,我們将P的RGB轉換為HSV顔色空間,得到H/S/V;

根據P在原圖頭發區域的位置比例關系,我們在顔色MAP中找到對應位置的像素點D,将D的RGB轉換為HSV顔色空間,得到目标顔色的h/s/v;

根據目标顔色重組hsV,然後轉為RGB即可;

這一子產品代碼如下:

#h=[0,360],s=[0,1],v=[0,1]
void RGBToHSV(int R, int G, int B, float* h, float* s,float* v)
{
    float min,max;
    float r = R/255.0f;
    float g = G/255.0f;
    float b = B/255.0f;
    min = MIN2(r,MIN2(g,b));
    max = MAX2(r,MAX2(g,b));
    if(max == min)
        *h=0;
    if(max == r && g >= b)
        *h = 60.0f * (g-b) / (max-min);
    if(max == r && g < b)
        *h = 60.0f * (g-b) / (max-min) + 360.0f;
    if(max == g)
        *h = 60.0f * (b-r) / (max-min) + 120.0f;
    if(max == b)
        *h = 60.0f * (r-g) / (max-min) + 240.0f;
    if(max == 0)
        *s = (max-min) / max;
    *v = max;    
};

void HSVToRGB(float h, float s,float v, int* R,int *G,int *B)
{
    float q=0,p=0,t=0,r=0,g=0,b=0;
    int hN=0;
    if(h<0)
        h=260+h;
    hN=(int)(h/60);
    p=v*(1.0f-s);
    q=v*(1.0f-(h/60.0f-hN)*s);
    t=v*(1.0f-(1.0f-(h/60.0f-hN))*s);
    switch(hN)
    {
    case 0:
        r=v;
        q=t;
        b=p;
        break;
    case 1:
        r=q;
        q=v;
        b=p;
        break;
    case 2:
        r=p;
        g=v;
        b=t;
        break;
    case 3:
        r=p;
        g=q;
        b=v;
        break;
    case 4:
        r=t;
        g=p;
        b=v;
        break;
    case 5:
        r=v;
        g=p;
        b=q;
        break;
    default:
        break;
    }
    *R=(int)CLIP3((r*255.0f),0,255);
    *G=(int)CLIP3((g*255.0f),0,255);
    *B=(int)CLIP3((b*255.0f),0,255);
};
           

效果圖如下:

【AI超級美發師】深度學習算法打造染發特效(附代碼)

本文算法對比美妝相機效果如下:

【AI超級美發師】深度學習算法打造染發特效(附代碼)

頭發區域增強

這一步主要是為了突出頭發絲的細節,可以使用銳化算法,如Laplace銳化,USM銳化等等。上述過程基本是模拟美妝相機染發算法的過程,給大家參考一下,最後給出本文算法的一些效果舉例:

【AI超級美發師】深度學習算法打造染發特效(附代碼)

本文效果除了實作正常的單色染發,混合色染發之外,還實作了挑染,如最下方一組效果圖所示。

對于挑染的算法原理:

計算頭發紋理,根據頭發紋理選取需要挑染的頭發束,然後對這些頭發束與其他頭發分開染色即可,具體邏輯這裡不再累贅,大家自行研究,這裡給出解決思路供大家參考。

最後,本文算法理論上實時處理是沒有問題的,頭發分割已經可以實時處理,是以後面基本沒有什麼耗時操作,使用opengl實作實時染發是沒有問題的。

原文釋出時間為:2018-08-02

本文來自雲栖社群合作夥伴新智元,了解相關資訊可以關注“AI_era”。

原文連結:

【AI超級美發師】深度學習算法打造染發特效(附代碼)

繼續閱讀