深度学习AI美颜系列----AI美发算法(美妆相机/天天P图染发特效)

2018 年 8 月 2 日 极市平台

极市平台和图普科技联名推出的AI谜题挑战《The Second Chance》,点击识别文末二维码进入解开AI难度谜题,赢取GPU和极市资源大奖,还有机会获得图普技术offer~小伙伴们快来挑战一下哦~~

为方便大家交流AI谜题相关话题,极市建立了“AI谜题挑战交流群”,有兴趣的小伙伴可添加小助手(cv-mart)申请进群~

来源:OpenCV学堂

给照片或者视频中的人物头发换颜色,这个技术已经在手机app诸如天天P图,美图秀秀等应用中使用,并获得了不少用户的青睐。如何给照片或者视频中的人物头发换发色?换发色算法流程如下图所示:



1.AI头发分割模块

基于深度学习的目标分割算法已经比较成熟,比较常用的有FCN,SegNet,UNet,PspNet,DenseNet等等。这里我们使用Unet网络来进行头发分割,具体可以参考如下链接:点击打开链接Unet头发分割代码如下:

  
  
    
  1. def get_unet_256(input_shape=(256, 256, 3),

  2.                 num_classes=1):

  3.    inputs = Input(shape=input_shape)

  4.    # 256

  5.    down0 = Conv2D(32, (3, 3), padding='same')(inputs)

  6.    down0 = BatchNormalization()(down0)

  7.    down0 = Activation('relu')(down0)

  8.    down0 = Conv2D(32, (3, 3), padding='same')(down0)

  9.    down0 = BatchNormalization()(down0)

  10.    down0 = Activation('relu')(down0)

  11.    down0_pool = MaxPooling2D((2, 2), strides=(2, 2))(down0)

  12.    # 128

  13.    down1 = Conv2D(64, (3, 3), padding='same')(down0_pool)

  14.    down1 = BatchNormalization()(down1)

  15.    down1 = Activation('relu')(down1)

  16.    down1 = Conv2D(64, (3, 3), padding='same')(down1)

  17.    down1 = BatchNormalization()(down1)

  18.    down1 = Activation('relu')(down1)

  19.    down1_pool = MaxPooling2D((2, 2), strides=(2, 2))(down1)

  20.    # 64

  21.    down2 = Conv2D(128, (3, 3), padding='same')(down1_pool)

  22.    down2 = BatchNormalization()(down2)

  23.    down2 = Activation('relu')(down2)

  24.    down2 = Conv2D(128, (3, 3), padding='same')(down2)

  25.    down2 = BatchNormalization()(down2)

  26.    down2 = Activation('relu')(down2)

  27.    down2_pool = MaxPooling2D((2, 2), strides=(2, 2))(down2)

  28.    # 32

  29.    down3 = Conv2D(256, (3, 3), padding='same')(down2_pool)

  30.    down3 = BatchNormalization()(down3)

  31.    down3 = Activation('relu')(down3)

  32.    down3 = Conv2D(256, (3, 3), padding='same')(down3)

  33.    down3 = BatchNormalization()(down3)

  34.    down3 = Activation('relu')(down3)

  35.    down3_pool = MaxPooling2D((2, 2), strides=(2, 2))(down3)

  36.    # 16

  37.    down4 = Conv2D(512, (3, 3), padding='same')(down3_pool)

  38.    down4 = BatchNormalization()(down4)

  39.    down4 = Activation('relu')(down4)

  40.    down4 = Conv2D(512, (3, 3), padding='same')(down4)

  41.    down4 = BatchNormalization()(down4)

  42.    down4 = Activation('relu')(down4)

  43.    down4_pool = MaxPooling2D((2, 2), strides=(2, 2))(down4)

  44.    # 8

  45.    center = Conv2D(1024, (3, 3), padding='same')(down4_pool)

  46.    center = BatchNormalization()(center)

  47.    center = Activation('relu')(center)

  48.    center = Conv2D(1024, (3, 3), padding='same')(center)

  49.    center = BatchNormalization()(center)

  50.    center = Activation('relu')(center)

  51.    # center

  52.    up4 = UpSampling2D((2, 2))(center)

  53.    up4 = concatenate([down4, up4], axis=3)

  54.    up4 = Conv2D(512, (3, 3), padding='same')(up4)

  55.    up4 = BatchNormalization()(up4)

  56.    up4 = Activation('relu')(up4)

  57.    up4 = Conv2D(512, (3, 3), padding='same')(up4)

  58.    up4 = BatchNormalization()(up4)

  59.    up4 = Activation('relu')(up4)

  60.    up4 = Conv2D(512, (3, 3), padding='same')(up4)

  61.    up4 = BatchNormalization()(up4)

  62.    up4 = Activation('relu')(up4)

  63.    # 16

  64.    up3 = UpSampling2D((2, 2))(up4)

  65.    up3 = concatenate([down3, up3], axis=3)

  66.    up3 = Conv2D(256, (3, 3), padding='same')(up3)

  67.    up3 = BatchNormalization()(up3)

  68.    up3 = Activation('relu')(up3)

  69.    up3 = Conv2D(256, (3, 3), padding='same')(up3)

  70.    up3 = BatchNormalization()(up3)

  71.    up3 = Activation('relu')(up3)

  72.    up3 = Conv2D(256, (3, 3), padding='same')(up3)

  73.    up3 = BatchNormalization()(up3)

  74.    up3 = Activation('relu')(up3)

  75.    # 32

  76.    up2 = UpSampling2D((2, 2))(up3)

  77.    up2 = concatenate([down2, up2], axis=3)

  78.    up2 = Conv2D(128, (3, 3), padding='same')(up2)

  79.    up2 = BatchNormalization()(up2)

  80.    up2 = Activation('relu')(up2)

  81.    up2 = Conv2D(128, (3, 3), padding='same')(up2)

  82.    up2 = BatchNormalization()(up2)

  83.    up2 = Activation('relu')(up2)

  84.    up2 = Conv2D(128, (3, 3), padding='same')(up2)

  85.    up2 = BatchNormalization()(up2)

  86.    up2 = Activation('relu')(up2)

  87.    # 64

  88.    up1 = UpSampling2D((2, 2))(up2)

  89.    up1 = concatenate([down1, up1], axis=3)

  90.    up1 = Conv2D(64, (3, 3), padding='same')(up1)

  91.    up1 = BatchNormalization()(up1)

  92.    up1 = Activation('relu')(up1)

  93.    up1 = Conv2D(64, (3, 3), padding='same')(up1)

  94.    up1 = BatchNormalization()(up1)

  95.    up1 = Activation('relu')(up1)

  96.    up1 = Conv2D(64, (3, 3), padding='same')(up1)

  97.    up1 = BatchNormalization()(up1)

  98.    up1 = Activation('relu')(up1)

  99.    # 128

  100.    up0 = UpSampling2D((2, 2))(up1)

  101.    up0 = concatenate([down0, up0], axis=3)

  102.    up0 = Conv2D(32, (3, 3), padding='same')(up0)

  103.    up0 = BatchNormalization()(up0)

  104.    up0 = Activation('relu')(up0)

  105.    up0 = Conv2D(32, (3, 3), padding='same')(up0)

  106.    up0 = BatchNormalization()(up0)

  107.    up0 = Activation('relu')(up0)

  108.    up0 = Conv2D(32, (3, 3), padding='same')(up0)

  109.    up0 = BatchNormalization()(up0)

  110.    up0 = Activation('relu')(up0)

  111.    # 256

  112.    classify = Conv2D(num_classes, (1, 1), activation='sigmoid')(up0)

  113.    model = Model(inputs=inputs, outputs=classify)

  114.    #model.compile(optimizer=RMSprop(lr=0.0001), loss=bce_dice_loss, metrics=[dice_coeff])

  115. return model


分割效果举例如下:



使用的训练和测试数据集合大家自己准备即可。


2.头发换色模块

这个模块看起来比较简单,实际上却并非如此。

这个模块要细分为

①头发颜色增强与修正模块;

②颜色空间染色模块;

③头发细节增强;


头发颜色增强与修正模块

为什么要颜色增强与修正?

先看下面一组图,我们直接使用HSV颜色空间对纯黑色的头发进行染色,目标色是紫色,结果如下:



大家可以看到,针对上面这张原图,头发比较黑,在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]是饱和度和亮度,计算为:



我们假设头发为纯黑色,R=G=B=0,那么按照HSV计算公式可以得到H = S = V = 0;


假设我们要把头发颜色替换为红色(r=255,g=0,b=0);


那么,我们先将红色转换为对应的hsv,然后保留原始黑色头发的V,红色头发的hs,重新组合新的hsV,在转换为RGB颜色空间,即为头发换色之后的效果(hs是颜色属性,v是明度属性,保留原始黑色头发的明度,替换颜色属性以达到换色目的);


HSV转换为RGB的公式如下:



对于黑色,我们计算的结果是H=S=V=0,由于V=0,因此,p=q=t=0,不管目标颜色的hs值是多少,rgb始终都是0,也就是黑色;


这样,虽然我们使用了红色,来替换黑色头发,但是,结果却依旧是黑色,结论也就是hsv/hsl颜色空间,无法对黑色换色。


下面,我们给出天天P图和美妆相机对应紫色的换发色效果:



与之前HSV颜色空间的结果对比,我们明显可以看到,天天P图和美妆相机的效果要更浓,更好看,而且对近乎黑色的头发进行了完美的换色;


由于上述原因,我们这里需要对图像中的头发区域进行一定的增强处理:提亮,轻微改变色调;


这一步通常可以在PS上进行提亮调色,然后使用LUT来处理;


经过提亮之后的上色效果如下图所示:



可以看到,基本与美妆相机和天天P图类似了。


HSV/HSL/YCbCr颜色空间换色

这一步比较简单,保留明度分量不变,将其他颜色、色调分量替换为目标发色就可以了。


这里以HSV颜色空间为例:


假如我们要将头发染发为一半青色,一般粉红色,那么我们构建如下图所示的颜色MAP:



对于头发区域的每一个像素点P,我们将P的RGB转换为HSV颜色空间,得到H/S/V;


根据P在原图头发区域的位置比例关系,我们在颜色MAP中找到对应位置的像素点D,将D的RGB转换为HSV颜色空间,得到目标颜色的h/s/v;


根据目标颜色重组hsV,然后转为RGB即可;

这一模块代码如下:

  
  
    
  1. // h = [0,360], s = [0,1], v = [0,1]

  2. void RGBToHSV(int R, int G, int B, float* h, float* s, float * v)

  3. {

  4.    float min, max;

  5.    float r = R / 255.0f;

  6.    float g = G / 255.0f;

  7.    float b = B / 255.0f;

  8.    min = MIN2(r,MIN2(g,b));

  9.    max = MAX2(r,MAX2(g,b));

  10.    if (max == min)

  11.        *h = 0;

  12.    if (max == r && g >= b)

  13.        *h = 60.0f * (g - b) / (max - min);

  14.    if (max == r && g < b)

  15.        *h = 60.0f * (g - b) / (max - min) + 360.0f;

  16.    if (max == g)

  17.        *h = 60.0f * (b - r) / (max - min) + 120.0f;

  18.    if (max == b)

  19.        *h = 60.0f * (r - g) / (max - min) + 240.0f;

  20.    if (max == 0)

  21.        *s = 0;

  22.    else

  23.        *s = (max - min) / max;

  24.    *v = max;

  25. };

  26. void HSVToRGB(float h, float s, float v, int* R, int *G, int *B)

  27. {

  28.    float q = 0, p = 0, t = 0, r = 0, g = 0, b = 0;

  29.    int hN = 0;

  30.    if (h < 0)

  31.        h = 360 + h;

  32.    hN = (int)(h / 60);

  33.    p = v * (1.0f - s);

  34.    q = v * (1.0f - (h / 60.0f - hN) * s);

  35.    t = v * (1.0f - (1.0f - (h / 60.0f - hN)) * s);

  36.    switch (hN)

  37.    {

  38.    case 0:

  39.        r = v;

  40.        g = t;

  41.        b = p;

  42.        break;

  43.    case 1:

  44.        r = q;

  45.        g = v;

  46.        b = p;

  47.        break;

  48.    case 2:

  49.        r = p;

  50.        g = v;

  51.        b = t;

  52.        break;

  53.    case 3:

  54.        r = p;

  55.        g = q;

  56.        b = v;

  57.        break;

  58.    case 4:

  59.        r = t;

  60.        g = p;

  61.        b = v;

  62.        break;

  63.    case 5:

  64.        r = v;

  65.        g = p;

  66.        b = q;

  67.        break;

  68.    default:

  69.        break;

  70.    }

  71.    *R = (int)CLIP3((r * 255.0f),0,255);

  72.    *G = (int)CLIP3((g * 255.0f),0,255);

  73.    *B = (int)CLIP3((b * 255.0f),0,255);

  74. };


效果图如下:



本文算法对比美妆相机效果如下:



头发区域增强

这一步主要是为了突出头发丝的细节,可以使用锐化算法,如Laplace锐化,USM锐化等等。

上述过程基本是模拟美妆相机染发算法的过程,给大家参考一下,最后给出本文算法的一些效果举例:



本文效果除了实现正常的单色染发,混合色染发之外,还实现了挑染,如最下方一组效果图所示。


对于挑染的算法原理:

计算头发纹理,根据头发纹理选取需要挑染的头发束,然后对这些头发束与其他头发分开染色即可,具体逻辑这里不再累赘,大家自行研究,这里给出解决思路供大家参考。

最后,本文算法理论上实时处理是没有问题的,头发分割已经可以实时处理,所以后面基本没有什么耗时操作,使用opengl实现实时染发是没有问题的。


作者QQ:1358009172






获取更多技术干货资讯,业内大牛学术交流、后续官方谜题直播分享,点击左下角“阅读原文”立刻申请加入“极市&图普AI谜题交流群”群~


ps.已填写过极市开发者调研表的同学请加小助手微信(cv-mart),直接邀请进群~


扫描下方二维码,立解谜题↓↓↓


登录查看更多
2

相关内容

【华侨大学】基于混合深度学习算法的疾病预测模型
专知会员服务
96+阅读 · 2020年1月21日
【模型泛化教程】标签平滑与Keras, TensorFlow,和深度学习
专知会员服务
20+阅读 · 2019年12月31日
【深度学习视频分析/多模态学习资源大列表】
专知会员服务
91+阅读 · 2019年10月16日
开源书:PyTorch深度学习起步
专知会员服务
49+阅读 · 2019年10月11日
[综述]深度学习下的场景文本检测与识别
专知会员服务
77+阅读 · 2019年10月10日
图像分割的U-Net系列方法
极市平台
56+阅读 · 2019年10月21日
面经 | 算法工程师面试题汇总
极市平台
12+阅读 · 2019年10月14日
小于1MB的行人检测网络
极市平台
8+阅读 · 2019年9月23日
CVPR2019 | 文本检测算法综述
极市平台
34+阅读 · 2019年5月30日
图像主题色提取算法
算法与数学之美
4+阅读 · 2018年9月1日
深度学习面试100题(第41-45题)
七月在线实验室
15+阅读 · 2018年7月18日
深度学习之视频图像压缩
论智
13+阅读 · 2018年6月15日
[DLdigest-8] 每日一道算法
深度学习每日摘要
4+阅读 · 2017年11月2日
BAT机器学习面试1000题系列(第76~80题)
七月在线实验室
5+阅读 · 2017年10月13日
Mesh R-CNN
Arxiv
4+阅读 · 2019年6月6日
Arxiv
11+阅读 · 2018年5月13日
Arxiv
6+阅读 · 2018年4月3日
Arxiv
5+阅读 · 2015年9月14日
VIP会员
相关资讯
图像分割的U-Net系列方法
极市平台
56+阅读 · 2019年10月21日
面经 | 算法工程师面试题汇总
极市平台
12+阅读 · 2019年10月14日
小于1MB的行人检测网络
极市平台
8+阅读 · 2019年9月23日
CVPR2019 | 文本检测算法综述
极市平台
34+阅读 · 2019年5月30日
图像主题色提取算法
算法与数学之美
4+阅读 · 2018年9月1日
深度学习面试100题(第41-45题)
七月在线实验室
15+阅读 · 2018年7月18日
深度学习之视频图像压缩
论智
13+阅读 · 2018年6月15日
[DLdigest-8] 每日一道算法
深度学习每日摘要
4+阅读 · 2017年11月2日
BAT机器学习面试1000题系列(第76~80题)
七月在线实验室
5+阅读 · 2017年10月13日
Top
微信扫码咨询专知VIP会员