如何用深度学习搞定 AI 人脸自动美型算法?| 技术头条

百家 作者:CSDN 2019-04-10 06:43:56


作者 | Trent
责编 | 郭? 芮

人脸智能美型技术主要用于智能美颜,对用户的照片进行自动智能调整,而不需要用户手工调整,该技术在美颜相机、天天P图等App中都已应用。本文在这里对人脸智能美型做个详解。

人脸智能美型包含如下两个部分:

  • 人脸轮廓自动调整:对人脸大小,胖瘦进行自动调整,目前App中常用的瘦脸只是其中一个特例而已;

  • 五官自动修正:包含眼睛大小自动调整,鼻子形状位置修正,眉毛位置修正以及嘴巴形状、大小和位置自动修正等等。App中常用的大眼和立体修鼻功能,也属于其中一个特例。

人脸智能美型和磨皮美白结合使用,就是所谓的智能美颜。

人脸智能美型算法逻辑如下。


构建平均脸


针对男女分别构建正脸平均脸,如下图所示:



性别识别


这一步需要将用户的人像照片进行性别识别,根据识别结果分别选择男/女平均脸数据。

核心代码如下:

def?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?cost

????Arguments:
????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?Z3

????Returns:
????cost?-?Tensor?of?the?cost?function
????"""

????cost?=?tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=Z3,?labels=Y))????
????return?cost

def?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?parameters

def?cnn_net(x,?parameters,?keep_prob?=?1.0):
????#frist?convolution?layer
????w_conv1?=?parameters["W1"]
????b_conv1?=?parameters["b1"]
????h_conv1?=?tf.nn.relu(conv2d(x,w_conv1)?+?b_conv1)??#output?size?112x92x32
????h_pool1?=?max_pool(h_conv1,2,2)????#output?size?56x46x32

????#second?convolution?layer
????w_conv2?=?parameters["W2"]
????b_conv2?=?parameters["b2"]
????h_conv2?=?tf.nn.relu(conv2d(h_pool1,?w_conv2)?+?b_conv2)?#output?size?56x46x64
????h_pool2?=?max_pool(h_conv2,2,2)?#output?size?28x23x64

????#third?convolution?layer
????w_conv3?=?parameters["W3"]
????b_conv3?=?parameters["b3"]
????h_conv3?=?tf.nn.relu(conv2d(h_pool2,w_conv3)?+?b_conv3)?#output?size?28x23x128
????h_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))


将用户照片人脸映射到平均脸


这一步主要根据用户照片人脸关键点和平均脸的人脸关键点,加上一定的映射算法将用户照片对齐到平均脸中,可以参考仿射变换等。

关键点可以使用商汤科技或者FACE++等人脸SDK。

效果如下图所示:



计算用户人脸和平均脸的距离D


此处计算规则可以使用欧氏距离等,距离D表示用户人脸到完美人脸的差。

距离的计算还需要参考人脸的旋转角度信息,根据人脸旋转角度对距离进行加权处理,以此来适应各种角度的用户人脸照片。


对人脸进行不同程度的变形,得到智能美型结果


此处变形可以使用MLS、三角网格变形等等。

MLS核心代码如下:

static?void?setSrcPoints(const?vector?&qsrc,?vector?&newDotL,?int*?nPoint)?{
????*nPoint?=?qsrc.size();
????newDotL.clear();
????newDotL.reserve(*nPoint);
????for?(size_t?i?=?0;?i?< ?qsrc.size();?i++)?
????????newDotL.push_back(qsrc[i]);
}

static?void?setDstPoints(const?vector?&qdst,vector?&oldDotL,?int*?nPoint)?{
????*nPoint?=?qdst.size();
????oldDotL.clear();
????oldDotL.reserve(*nPoint);

????for?(size_t?i?=?0;?i?< ?qdst.size();?i++)?oldDotL.push_back(qdst[i]);
}
static?double?bilinear_interp(double?x,?double?y,?double?v11,?double?v12,
??????????????????????????????double?v21,?double?v22)
?
{
????return?(v11?*?(1?-?y)?+?v12?*?y)?*?(1?-?x)?+?(v21?*?(1?-?y)?+?v22?*?y)?*?x;
}

static?double?calcArea(const?vector?&V)?{
????PointD?lt,?rb;
????lt.x?=?lt.y?=?1e10;
????rb.x?=?rb.y?=?-1e10;
????for?(vector::const_iterator?i?=?V.begin();?i?!=?V.end();
?????????i++)?{
????????if?(i->x?< ?lt.x)?lt.x?=?i->x;
????????if?(i->x?>?rb.x)?rb.x?=?i->x;
????????if?(i->y?< ?lt.y)?lt.y?=?i->y;
????????if?(i->y?>?rb.y)?rb.y?=?i->y;
????}
????return?(rb.x?-?lt.x)?*?(rb.y?-?lt.y);
}
static?void?calcDelta_rigid(int?srcW,?int?srcH,?int?tarW,?int?tarH,?double?alpha,?int?gridSize,?int?nPoint,?int?preScale,?double?*rDx,?double?*rDy,?vector?&oldDotL,?vector?&newDotL)
{
????int?i,?j,?k;
????PointD?swq,?qstar,?newP,?tmpP;
????double?sw;

????double?ratio;

????if?(preScale)?{
????????ratio?=?sqrt(calcArea(newDotL)?/?calcArea(oldDotL));
????????for?(i?=?0;?i?< ?nPoint;?i++)?{
????????????newDotL[i].x?*=?1?/?ratio;
????????????newDotL[i].y?*=?1?/?ratio;
????????}
????}
????double?*w?=?new?double[nPoint];

????if?(nPoint?< ?2)?{
????????//rDx.setTo(0);
????????//rDy.setTo(0);
????????return;
????}
????PointD?swp,?pstar,?curV,?curVJ,?Pi,?PiJ,?Qi;
????double?miu_r;

????for?(i?=?0;;?i?+=?gridSize)?{
????????if?(i?>=?tarW?&&?i?< ?tarW?+?gridSize?-?1)
????????????i?=?tarW?-?1;
????????else?if?(i?>=?tarW)
????????????break;
????????for?(j?=?0;;?j?+=?gridSize)?{
????????????if?(j?>=?tarH?&&?j?< ?tarH?+?gridSize?-?1)
????????????????j?=?tarH?-?1;
????????????else?if?(j?>=?tarH)
????????????????break;
????????????sw?=?0;
????????????swp.x?=?swp.y?=?0;
????????????swq.x?=?swq.y?=?0;
????????????newP.x?=?newP.y?=?0;
????????????curV.x?=?i;
????????????curV.y?=?j;
????????????for?(k?=?0;?k?< ?nPoint;?k++)?{
????????????????if?((i?==?oldDotL[k].x)?&&?j?==?oldDotL[k].y)?break;
????????????????if?(alpha?==?1)
????????????????????w[k]?=?1?/?((i?-?oldDotL[k].x)?*?(i?-?oldDotL[k].x)?+
????????????????????????????????(j?-?oldDotL[k].y)?*?(j?-?oldDotL[k].y));
????????????????else
????????????????????w[k]?=?pow((i?-?oldDotL[k].x)?*?(i?-?oldDotL[k].x)?+
???????????????????????????????????(j?-?oldDotL[k].y)?*?(j?-?oldDotL[k].y),
???????????????????????????????-alpha);
????????????????sw?=?sw?+?w[k];
????????????????swp.x?=?swp.x?+?w[k]?*?oldDotL[k].x;
????????????????swp.y?=?swp.y?+?w[k]?*?oldDotL[k].y;
????????????????swq.x?=?swq.x?+?w[k]?*?newDotL[k].x;
????????????????swq.y?=?swq.y?+?w[k]?*?newDotL[k].y;
????????????}
????????????if?(k?==?nPoint)?{
????????????????pstar.x?=?(1?/?sw)?*?swp.x;
????????????????pstar.y?=?(1?/?sw)?*?swp.y;
????????????????qstar.x?=?1?/?sw?*?swq.x;
????????????????qstar.y?=?1?/?sw?*?swq.y;
????????????????//?Calc?miu_r
????????????????double?s1?=?0,?s2?=?0;
????????????????for?(k?=?0;?k?< ?nPoint;?k++)?{
????????????????????if?(i?==?oldDotL[k].x?&&?j?==?oldDotL[k].y)?continue;
????????????????????Pi.x?=?oldDotL[k].x?-?pstar.x;
????????????????????Pi.y?=?oldDotL[k].y?-?pstar.y;
????????????????????PiJ.x?=?-Pi.y,?PiJ.y?=?Pi.x;
????????????????????Qi.x?=?newDotL[k].x?-?qstar.x;
????????????????????Qi.y?=?newDotL[k].y?-?qstar.y;
????????????????????s1?+=?w[k]?*?(Qi.x*Pi.x+Qi.y*Pi.y);
????????????????????s2?+=?w[k]?*?(Qi.x*PiJ.x+Qi.y*PiJ.y);
????????????????}
????????????????miu_r?=?sqrt(s1?*?s1?+?s2?*?s2);
????????????????curV.x?-=?pstar.x;
????????????????curV.y?-=?pstar.y;

????????????????curVJ.x?=?-curV.y,?curVJ.y?=?curV.x;

????????????????for?(k?=?0;?k?< ?nPoint;?k++)?{
????????????????????if?(i?==?oldDotL[k].x?&&?j?==?oldDotL[k].y)?continue;
?????????????????????Pi.x?=?oldDotL[k].x?-?pstar.x;
??????????????????????Pi.y?=?oldDotL[k].y?-?pstar.y;
????????????????????PiJ.x?=?-Pi.y,?PiJ.y?=?Pi.x;
?????????????????????tmpP.x?=?(Pi.x*curV.x+Pi.y*curV.y)*?newDotL[k].x?-
?????????????????????????????(PiJ.x*curV.x+PiJ.y*curV.y)*?newDotL[k].y;
????????????????????tmpP.y?=?-(Pi.x*curVJ.x+Pi.y*curVJ.y)?*?newDotL[k].x?+
?????????????????????????????(PiJ.x*curVJ.x+PiJ.y*curVJ.y)?*?newDotL[k].y;
????????????????????tmpP.x?*=?w[k]?/?miu_r;
????????????????????tmpP.y?*=?w[k]?/?miu_r;
????????????????????newP.x?+=?tmpP.x;
????????????????????newP.y?+=?tmpP.y;
????????????????}
????????????????newP.x?+=?qstar.x;
????????????????newP.y?+=?qstar.y;
????????????}?else?{
????????????????newP?=?newDotL[k];
????????????}

????????????if?(preScale)?{
????????????????rDx[j?*?tarW?+?i]?=?newP.x?*?ratio?-?i;
????????????????rDy[j?*?tarW?+?i]?=?newP.y?*?ratio?-?j;
????????????}?else?{
????????????????rDx[j?*?tarW?+?i]?=?newP.x?-?i;
????????????????rDy[j?*?tarW?+?i]?=?newP.y?-?j;
????????????}
????????}
????}
????delete[]?w;

????if?(preScale!=0)?{
????????for?(i?=?0;?i?< ?nPoint;?i++){
????????????newDotL[i].x?*=?ratio;
????????????newDotL[i].y?*=?ratio;
????????}
????}
}

static?int?GetNewImg(unsigned?char*?oriImg,?int?width,?int?height,?int?stride,?unsigned?char*?tarImg,?int?tarW,?int?tarH,?int?tarStride,?int?gridSize,?double*?rDx,?double*?rDy,?double?transRatio)
{
????int?i,?j;
????double?di,?dj;
????double?nx,?ny;
????int?nxi,?nyi,?nxi1,?nyi1;
????double?deltaX,?deltaY;
????double?w,?h;
????int?ni,?nj;
????int?pos,?posa,?posb,?posc,?posd;
????for?(i?=?0;?i?< ?tarH;?i?+=?gridSize)
????????for?(j?=?0;?j?< ?tarW;?j?+=?gridSize)?{
????????????ni?=?i?+?gridSize,?nj?=?j?+?gridSize;
????????????w?=?h?=?gridSize;
????????????if?(ni?>=?tarH)?ni?=?tarH?-?1,?h?=?ni?-?i?+?1;
????????????if?(nj?>=?tarW)?nj?=?tarW?-?1,?w?=?nj?-?j?+?1;
????????????for?(di?=?0;?di?< ?h;?di++)
????????????????for?(dj?=?0;?dj?< ?w;?dj++)?{
????????????????????deltaX?=
????????????????????????bilinear_interp(di?/?h,?dj?/?w,?rDx[i?*?tarW?+?j],?rDx[i?*?tarW?+?nj],
????????????????????????????????????????rDx[ni?*?tarW?+?j],?rDx[ni?*?tarW?+?nj]);
????????????????????deltaY?=
????????????????????????bilinear_interp(di?/?h,?dj?/?w,?rDy[i?*?tarW?+?j],?rDy[i?*?tarW?+?nj],
????????????????????????????????????????rDy[ni?*?tarW?+?j],?rDy[ni?*?tarW?+?nj]);
????????????????????nx?=?j?+?dj?+?deltaX?*?transRatio;
????????????????????ny?=?i?+?di?+?deltaY?*?transRatio;
????????????????????if?(nx?>?width?-?1)?nx?=?width?-?1;
????????????????????if?(ny?>?height?-?1)?ny?=?height?-?1;
????????????????????if?(nx?< ?0)?nx?=?0;
????????????????????if?(ny?< ?0)?ny?=?0;
????????????????????nxi?=?int(nx);
????????????????????nyi?=?int(ny);
????????????????????nxi1?=?ceil(nx);
????????????????????nyi1?=?ceil(ny);
????????????????????pos?=?(int)(i?+?di)?*?tarStride?+?((int)(j?+?dj)?< 2);
????????????????????posa?=?nyi?*?stride?+?(nxi?< 2);
????????????????????posb?=?nyi?*?stride?+?(nxi1?< 2);
????????????????????posc?=?nyi1?*?stride?+?(nxi?< 2);
????????????????????posd?=?nyi1?*?stride?+?(nxi1?< 2);
????????????????????tarImg[pos]?????=?(unsigned?char)bilinear_interp(ny?-?nyi,?nx?-?nxi,?oriImg[posa],????oriImg[posb],???oriImg[posc],???oriImg[posd]);
????????????????????tarImg[pos?+?1]?=?(unsigned?char)bilinear_interp(ny?-?nyi,?nx?-?nxi,?oriImg[posa?+?1],oriImg[posb?+?1],?oriImg[posc?+?1],?oriImg[posd?+?1]);
????????????????????tarImg[pos?+?2]?=?(unsigned?char)bilinear_interp(ny?-?nyi,?nx?-?nxi,?oriImg[posa?+?2],oriImg[posb?+?2],?oriImg[posc?+?2],?oriImg[posd?+?2]);
????????????????????tarImg[pos?+?3]?=?(unsigned?char)bilinear_interp(ny?-?nyi,?nx?-?nxi,?oriImg[posa?+?3],oriImg[posb?+?3],?oriImg[posc?+?3],?oriImg[posd?+?3]);
????????????????}
????????}
????????return?0;
};

上述过程就是人脸自动美型的算法逻辑,对于不同的人像照片,会自动判断大眼矫正或者小眼矫正,瘦脸或者胖脸等等,综合达到一个相对完美的结果。

自动美型效果如下图所示:

作者:Trent1985,CSDN博客专家,本文来源于作者CSDN博客,原文链接https://blog.csdn.net/trent1985/article/details/80295532,CSDN 公众号经授权发布。欢迎更多开发者朋友通过下方联系投稿。



【End】

?热 文?推 荐?

?救救中国 996 程序员!GitHub 近 230,000 Star、Python 之父伸张正义!

?软件开发团队中,凭什么新手当道?| 畅言

?崩溃!还未修复的 Bug,凌晨三点遭到黑客 DDoS 攻击 | 技术头条

?“入职 6 年,新人工资高我 2 千”:老板不加钱,不是嫌你老

?V神最新亲笔:“你是如何被欺骗的?”

?特斯拉Q1销量大跌,马斯克吹出的“交付100万”如何破?| 极客头条

?2019年技术盘点微服务篇(二):青云直上云霄 | 程序员硬核评测

?刺激!我31岁敲代码10年,明天退休!

System.out.println("点个在看吧!");
console.log("点个在看吧!");
print("点个在看吧!");
printf("点个在看吧!n");
cout?< "点个在看吧!"?< Console.WriteLine("点个在看吧!");
Response.Write("点个在看吧!");
alert("点个在看吧!")
echo "点个在看吧!"

点击阅读原文,输入关键词,即可搜索您想要的 CSDN 文章。

你点的每个“在看”,我都认真当成了喜欢

关注公众号:拾黑(shiheibook)了解更多

[广告]赞助链接:

四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

公众号 关注网络尖刀微信公众号
随时掌握互联网精彩
赞助链接