美颜相机---AI 发型管家效果的算法解析

####前言

本文为去年写的Gitchat文章,由于Gitchat有时间版权限制,一年时间,所以今天才能发布到CSDN博客上来。

本文为大家介绍美颜相机中 AI 发型管家效果的算法解析,当然,本人并没有美颜相机的算法代码,只是从自己的角度根据美颜相机的效果呈现,来分析猜测算法流程,最后得到近似的效果。
    
首先,我们看一下美颜相机的发型管家介绍: 
                                                      

这个界面明确划分了男生和女生,我们发现效果呈现流程是这样的:

###具体流程

1.上传一张标准正脸测试图,效果流程如下:

2.上传一张侧脸的测试图,发现界面提示 “ 使用正脸照片效果更加呦 ~ ”;

3.上传一张男生测试图,会得到一个男生发型效果,女生测试图会得到女生发型效果;

4.发型效果中发型的颜色可以更改选择;

5.默认推荐发型如果用户不喜欢,可以自行更改;

有了上面的 5 个特点总结,我们就可以分析算法流程了,这里我分析的结果如下:

####分析算法流程

1.用户选择一张照片或者拍照之后,首先进行人脸旋转角度检测,如果角度非正脸角度,提示用户最好重新上传照片;

2.承接 1 之后,用户人像照片会进行性别识别等分析,也就是界面 1 中所示的过程,这个过程会做如下处理:

① 性别识别;    
② 根据性别到男女对应的发型模版库中进行脸型和发型匹配,得到最接近或者最优的发型模版匹配;    
③ 根据最佳模版的一些参数,对用户照片进行美颜、美妆处理;

3.根据 2 中得到的最佳模版,将用户美颜美妆处理之后的照片进行换脸,得到界面 2 中对应的推荐发型;

4.中得到的效果图进行头发分割,得到对应的头发区域;

5.根据 4 中的头发区域,对 3 中得到的效果图进行头发换色,得到最终的效果图;

6.用户可以手动选择不同的发型,然后按照 2 - 5 的步骤得到对应的效果;

好了,到此,我们已经仔细分析了美颜相机发型管家的算法实现原理,当然是猜测的结果,下面我们来验证一下。

####实现以及过程调整

这里我将分模块来实现上述的过程,并将过程做了简单的调整:

#####**1.人脸检测 + 关键点识别 + 人脸角度检测**

这一步通常使用第三方人脸 SDK,比如商汤,旷世或者腾讯开源的调用 API 等,当然你也可以自己训练制作人脸 SDK;

① 判断有无人脸,有人脸则进行关键点检测和角度检测;    
② 根据角度判断,正脸范围则继续;

#####**2.性别识别**

这里本人基于最简单的 CNN 网络来构架性别识别,具体连接:[性别识别算法博客链接](https://blog.csdn.net/trent1985/article/details/80253642)

网络结构如下:

该网络中输入图片为大小为 92 X 112 的人脸单通道灰度图像,类别标签(男标签 [1,0],女标签 [0,1]),所有参数均在网络结构图中标注。

代码分为 `GenderUtils.py/GenderTrain.py/GenderTest.py` 三部分
网络结构部分代码如下:

    # AGE
import matplotlib.image as img
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
from tensorflow.python.framework import ops
import math
import os
import csvdef create_placeholders(n_H0, n_W0, n_C0, n_y):"""Creates the placeholders for the tensorflow session.Arguments:n_H0 -- scalar, height of an input imagen_W0 -- scalar, width of an input imagen_C0 -- scalar, number of channels of the inputn_y -- scalar, number of classesReturns:X -- placeholder for the data input, of shape [None, n_H0, n_W0, n_C0] and dtype "float"Y -- placeholder for the input labels, of shape [None, n_y] and dtype "float""""X = tf.placeholder(name='X', shape=(None, n_H0, n_W0, n_C0), dtype=tf.float32)Y = tf.placeholder(name='Y', shape=(None, n_y), dtype=tf.float32)    return X, Ydef random_mini_batches(X, Y, mini_batch_size = 64, seed = 0):"""Creates a list of random minibatches from (X, Y)Arguments:X -- input data, of shape (input size, number of examples) (m, Hi, Wi, Ci)Y -- true "label" vector (containing 0 if cat, 1 if non-cat), of shape (1, number of examples) (m, n_y)mini_batch_size - size of the mini-batches, integerseed -- this is only for the purpose of grading, so that you're "random minibatches are the same as ours.Returns:mini_batches -- list of synchronous (mini_batch_X, mini_batch_Y)"""m = X.shape[0]                  # number of training examplesmini_batches = []np.random.seed(seed)# Step 1: Shuffle (X, Y)permutation = list(np.random.permutation(m))shuffled_X = X[permutation,:,:,:]shuffled_Y = Y[permutation,:]# Step 2: Partition (shuffled_X, shuffled_Y). Minus the end case.num_complete_minibatches = int(math.floor(m / mini_batch_size)) # number of mini batches of size mini_batch_size in your partitionningfor k in range(0, int(num_complete_minibatches)):mini_batch_X = shuffled_X[k * mini_batch_size : k * mini_batch_size + mini_batch_size,:,:,:]mini_batch_Y = shuffled_Y[k * mini_batch_size : k * mini_batch_size + mini_batch_size,:]mini_batch = (mini_batch_X, mini_batch_Y)mini_batches.append(mini_batch)# Handling the end case (last mini-batch < mini_batch_size)if m % mini_batch_size != 0:mini_batch_X = shuffled_X[num_complete_minibatches * mini_batch_size : m,:,:,:]mini_batch_Y = shuffled_Y[num_complete_minibatches * mini_batch_size : m,:]mini_batch = (mini_batch_X, mini_batch_Y)mini_batches.append(mini_batch)return mini_batchesdef row_csv2dict(csv_file):dict_club={}with open(csv_file)as f:reader=csv.reader(f,delimiter=',')for row in reader:dict_club[row[0]]=row[1]return dict_clubdef input_data():path = "data/train/"train_num = sum([len(x) for _, _, x in os.walk(os.path.dirname(path))])image_train = np.zeros((train_num,112,92))label_train = np.ones((train_num,2))train_label_dict = row_csv2dict("data/train.csv")count = 0for key in train_label_dict:if int(train_label_dict[key]) == 0:label_train[count, 0] = 1label_train[count, 1] = 0else:label_train[count, 1] = 1label_train[count, 0] = 0filename = path + str(key)image_train[count] = img.imread(filename)count = count + 1path = "data/test/" test_num = sum([len(x) for _, _, x in os.walk(os.path.dirname(path))])image_test = np.zeros((test_num, 112,92))label_test = np.ones((test_num,2))test_label_dict = row_csv2dict("data/test.csv")count = 0for key in test_label_dict:if int(test_label_dict[key]) == 0:label_test[count, 0] = 1label_test[count, 1] = 0else:label_test[count, 1] = 1label_test[count, 0] = 0filename = path + str(key)image_test[count] = img.imread(filename)count = count + 1return image_train, label_train,image_test, label_testdef weight_variable(shape,name):return tf.Variable(tf.truncated_normal(shape, stddev = 0.1),name=name)def bias_variable(shape,name):return tf.Variable(tf.constant(0.1, shape = shape),name=name)def conv2d(x,w,padding="SAME"):if padding=="SAME" :return tf.nn.conv2d(x, w, strides = [1,1,1,1], padding = "SAME")else:return tf.nn.conv2d(x, w, strides = [1,1,1,1], padding = "VALID")def max_pool(x, kSize, Strides):return tf.nn.max_pool(x, ksize = [1,kSize,kSize,1],strides = [1,Strides,Strides,1], padding = "SAME")    def compute_cost(Z3, Y):"""Computes the costArguments:Z3 -- output of forward propagation (output of the last LINEAR unit), of shape (6, number of examples)Y -- "true" labels vector placeholder, same shape as Z3Returns:cost - Tensor of the cost function"""cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=Z3, labels=Y))    return costdef initialize_parameters():tf.set_random_seed(1)W1 = tf.cast(weight_variable([5,5,1,32],"W1"), dtype = tf.float32)b1 = tf.cast(bias_variable([32],"b1"), dtype = tf.float32)W2 = tf.cast(weight_variable([5,5,32,64],"W2"), dtype = tf.float32)b2 = tf.cast(bias_variable([64],"b2"), dtype = tf.float32)W3 = tf.cast(weight_variable([5,5,64,128],"W3"), dtype = tf.float32)b3 = tf.cast(bias_variable([128],"b3"), dtype = tf.float32)W4 = tf.cast(weight_variable([14*12*128,500],"W4"), dtype = tf.float32)b4 = tf.cast(bias_variable([500],"b4"), dtype = tf.float32)W5 = tf.cast(weight_variable([500,500],"W5"), dtype = tf.float32)b5 = tf.cast(bias_variable([500],"b5"), dtype = tf.float32)W6 = tf.cast(weight_variable([500,2],"W6"), dtype = tf.float32)b6 = tf.cast(bias_variable([2],"b6"), dtype = tf.float32)parameters = {"W1":W1,"b1":b1,"W2":W2,"b2":b2,"W3":W3,"b3":b3,"W4":W4,"b4":b4,"W5":W5,"b5":b5,"W6":W6,"b6":b6}return parametersdef cnn_net(x, parameters, keep_prob = 1.0):#frist convolution layerw_conv1 = parameters["W1"]b_conv1 = parameters["b1"]h_conv1 = tf.nn.relu(conv2d(x,w_conv1) + b_conv1)  #output size 112x92x32h_pool1 = max_pool(h_conv1,2,2)    #output size 56x46x32#second convolution layerw_conv2 = parameters["W2"]b_conv2 = parameters["b2"]h_conv2 = tf.nn.relu(conv2d(h_pool1, w_conv2) + b_conv2) #output size 56x46x64h_pool2 = max_pool(h_conv2,2,2) #output size 28x23x64#third convolution layerw_conv3 = parameters["W3"]b_conv3 = parameters["b3"]h_conv3 = tf.nn.relu(conv2d(h_pool2,w_conv3) + b_conv3) #output size 28x23x128h_pool3 = max_pool(h_conv3,2,2) #output size 14x12x128#full convolution layer w_fc1 = parameters["W4"]b_fc1 = parameters["b4"]h_fc11 = tf.reshape(h_pool3,[-1,14*12*128])h_fc1 = tf.nn.relu(tf.matmul(h_fc11,w_fc1) + b_fc1)w_fc2 = parameters["W5"]b_fc2 = parameters["b5"]h_fc2 = tf.nn.relu(tf.matmul(h_fc1,w_fc2)+b_fc2)h_fc2_drop = tf.nn.dropout(h_fc2,keep_prob)w_fc3 = parameters["W6"]b_fc3 = parameters["b6"]y_conv = tf.matmul(h_fc2_drop, w_fc3) + b_fc3#y_conv = tf.nn.softmax(tf.matmul(h_fc2_drop, w_fc3) + b_fc3)#rmse = tf.sqrt(tf.reduce_mean(tf.square(y_ - y_conv)))#cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels = y, logits = y_conv))#train_step = tf.train.GradientDescentOptimizer(0.001).minimize(cross_entropy)#correct_prediction  = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y,1))#accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))return y_convdef save_model(saver,sess,save_path):path = saver.save(sess, save_path)print 'model save in :{0}'.format(path)

完整代码工程下载连接:

百度网盘地址(密码5wst):[百度网盘下载地址]  
Github地址:[Github下载地址]

#####**3.脸型与发型匹配**

在该模块中,本人使用了最简单的欧式距离,来进行用户照片与模版脸型的匹配计算:

假设用户脸型点位(人脸关键点)为A,模版关键点为B,遍历模版库中所有模版,计算距离 Dis:

>Dis =sqrt( (Ax - Bx) * (Ax - Bx) + (Ay - By) * (Ay - By))

选取 Dis 最小的模版作为最优模版;

上述过程是脸型的匹配,根据美颜相机的提示,只显示了脸型分析,这里本人猜测还有对于发型的匹配,比如,短发型的用户照片,会匹配到短发型的模版效果,不过本人暂时没有找到合适的算法,这里暂时忽略;

#####**4. 根据 3 中得到的最优模版,设定该模版对应的美颜和美妆参数,对用户照片进行美颜美妆处理;**

这个过程中,美颜包括磨皮美白、大眼、瘦脸等等,看大家具体需求而定,我这里只进行了磨皮美白美颜算法,这里相关连接如下:
>[磨皮算法博客 A]
[磨皮算法博客 B]

磨皮美白算法相关的资料很多,大家也可以自行百度,本人这里主要讲 AI 发型管家的算法流程问题。

#####**为什么这里不是统一固定的美颜美妆参数呢?**

原因是这样的:不同的发型效果图也是不一样的,考虑到不同发型,不同颜色场景,实际上从审美角度看,是需要搭配不同的服饰,不同的妆容的,所以这里是不同模版对应不同的参数。

美颜之后是化妆,这里本人使用的是妆容迁移技术,直接将妆容模版的妆容效果迁移到用户照片中去,相关的算法、效果连接如下:

>[妆容迁移算法连接 A] 
[妆容迁移算法连接 B]

这一步对应的效果图举例:

这里本人直接使用美颜相机发型管家处理的效果图当作本人的模版图(模版的设计非常考究,需要设计高手+有版权的模特,本人这里测试图仅供测试,切勿做商务用途,以免侵权,若有侵权敬请告知,本人立刻删除),如下所示:

根据上面的换脸模版图,我们进行妆容迁移 + 换脸,如下图所示:

#####**6.头发换色**

这个模块主要实现最后效果图的头发颜色更改,用户可以有多种颜色选择,来满足自己的审美需求;

换发色的算法基本上是基于颜色空间的颜色替换,具体流程如下:

本人做过详细的算法讲解,连接如下:[染发算法博客链接]

这一步的算法效果举例如下:

上面 1 - 6 个步骤,就是本人实现的关于美颜相机发型管家效果的算法流程,由于这个流程中相关的算法太复杂,模块组合太多,所以基本上本人以算法流程解析为主,相关的算法具体实现都给了对应的算法与 demo 的连接,大家可以仔细研究,这里不要吐槽算法实现的繁琐,实际上任何一个好的效果,它的背后大多数情况下都是多个算法的组合;

最后给出本人实现的完整的效果流程图:

对比美颜相机发型管家的效果如下:

最后本人给出一个 DEMO 看效果:[美颜相机发型管家 PC DEMO ]

>注意:算法是核心,掌握了算法流程,才是真正的掌握,不要过分追求代码,没意义。(不少读者吐槽我不分享代码,一方面本人理论上只讲思路,另一方面,研究可能涉及商业机密,不方便给出详细代码,再者,一味追求代码复制粘贴的图像算法工程师绝对不是一个合格的图像算法工程师!)

深度学习AI美颜系列---AI 发型管家(美颜相机发型管家算法解析)相关推荐

  1. 深度学习AI美颜系列---AI美颜磨皮算法一

    深度学习AI美颜系列---AI美颜磨皮算法一 转自:https://blog.csdn.net/trent1985/article/details/80661230 首先说明一点,为什么本结内容是&q ...

  2. TorchFusion 是一个深度学习框架,主要用于 AI 系统加速研究和开发

    TorchFusion 是一个深度学习框架,主要用于 AI 系统加速研究和开发. TorchFusion 基于 PyTorch 并且完全兼容纯 PyTorch 和其他 PyTorch 软件包,它供了一 ...

  3. 【百家稷学】深度学习与嵌入式平台AI实践(北京交通大学实训)

    继续咱们百家稷学专题,本次是有三AI在北京交通大学进行的暑期课程教学.百家稷学专题的目标,是走进100所高校和企业进行学习与分享. 分享主题 本次分享是在北京交通大学计算机与信息技术学院进行,主题是& ...

  4. Yoshua Bengio首次中国演讲:深度学习通往人类水平AI的挑战

    11 月 7 日,Yoshua Bengio 受邀来到北京参加第二十届「二十一世纪的计算」国际学术研讨会.会上以及随后受邀前往清华时,他给出了题为「深度学习通往人类水平 AI 的挑战」(Challen ...

  5. Amazon 首席科学家李沐亲授「深度学习」,2019 AI ProCon震撼来袭!(日程出炉)...

    2019年9月5-7日,面向AI技术人的年度盛会-- 2019 AI开发者大会 AI ProCon,火热来袭!  继2018 年由CSDN成功举办AI 开发者大会一年之后,全球AI市场正发生着巨大的变 ...

  6. 我用深度学习做个视觉AI微型处理器!

    Datawhale干货 作者:张强,Datawhale成员 讲多了算法,如何真正将算法应用到产品领域?本文将带你从0用深度学习打造一个视觉AI的微型处理器.文章含完整代码,知识点相对独立,欢迎点赞收藏 ...

  7. 2.7mnist手写数字识别之训练调试与优化精讲(百度架构师手把手带你零基础实践深度学习原版笔记系列)

    2.7mnist手写数字识别之训练调试与优化精讲(百度架构师手把手带你零基础实践深度学习原版笔记系列) 目录 2.7mnist手写数字识别之训练调试与优化精讲(百度架构师手把手带你零基础实践深度学习原 ...

  8. 深度学习与计算机视觉系列(9)_串一串神经网络之动手实现小例子

    深度学习与计算机视觉系列(9)_串一串神经网络之动手实现小例子 作者:寒小阳  时间:2016年1月.  出处:http://blog.csdn.net/han_xiaoyang/article/de ...

  9. 深度学习与计算机视觉系列(8)_神经网络训练与注意点

    深度学习与计算机视觉系列(8)_神经网络训练与注意点 作者:寒小阳  时间:2016年1月.  出处:http://blog.csdn.net/han_xiaoyang/article/details ...

  10. 深度学习与计算机视觉系列(4)_最优化与随机梯度下降\数据预处理,正则化与损失函数

    1. 引言 上一节深度学习与计算机视觉系列(3)_线性SVM与SoftMax分类器中提到两个对图像识别至关重要的概念: 用于把原始像素信息映射到不同类别得分的得分函数/score function 用 ...

最新文章

  1. 地面标识检测与识别算法
  2. 想学python有什么用-python学来有什么用
  3. 疫情期间在公共场所要全程佩戴口罩,不要抱有侥幸心理
  4. 爱上MVC~为非法进行Action的用户提供HttpStatusCodeResult
  5. Oracle编程入门经典 第9章 掌握SQL*Plus
  6. stm32 invalid rom table(转载)
  7. distance在函数 int_Arduino智能小车——超声波避障
  8. linux VNC简单配置
  9. 【报错笔记】MAVEN pom.xml 报错解决方法
  10. ik分词和jieba分词哪个好_Python 中文 文本分析 实战:jieba分词+自定义词典补充+停用词词库补充+词频统计...
  11. win10安装misql8_Windows10安装MySQL 8.0.11
  12. 作为一个测试人,软件测试流程包括哪些内容,你都知道吗?
  13. 【吐槽】Android 第一步,雀氏纸尿裤
  14. 《生物信息学:导论与方法》--本体论、分子通路鉴定--听课笔记(十八)
  15. 【kafka】二、kafka安装
  16. 【VUE项目实战】32、权限管理-实现角色列表
  17. BCK生态链第一条价值共识侧链CK正式上线
  18. 王爽汇编语言 实验15
  19. 如何查看小方侦测云存储_小方智能摄像机和手机怎样连接?
  20. Scipy sparse中关于CSC矩阵的自我理解

热门文章

  1. 共享储存服务——NFS
  2. 多线程计算多分批计算_如何在Excel 2013中更改自动计算和多线程功能
  3. 122个常见问题收集整理(FLASH初学者参见)
  4. 易语言 在电脑上操作手机网页填表 无USB线 不限网络
  5. FDS中稳定性条件和分析
  6. android: EditTextView不自动获取焦点
  7. 001:获取unity源码、美术素材
  8. APS高级排产助力传统工艺品行业搭乘数字化快车,实现转型升级
  9. linux 打版本包,mysql官网下载linux版本安装包
  10. Android软件脱壳分析,使用FRIDA为Android应用进行脱壳的操作指南