临近开学,实习也接近尾声了。两个月的时间里,感觉还是干了点微不足道的贡献的。老大看我会点C++和视觉,就让我做一个换脸的项目,场景是演示看吸毒后人脸的变化。从项目研发到上线交付,也算是完成了一个实际的商业项目,对我自己而言意义重大。那么在这里就做个工作总结吧 : )
摄影于华南理工大学
初始化
由于初始化的速度比较慢,所以在构造函数里一开始直接开一个子线程,子线程负责初始化摄像头,构造函数后续负责初始化ui。(这里没有使用线程同步,但实测中暂时没有出现问题)。
人脸检测
项目涉及到人脸检测和人脸特征点检测。一开始版本的人脸检测使用的是OpenCV提供的人脸检测器。OpenCV提供的人脸检测器基于haar特征和adaboost分类器(也提供了LBP特征,但一般用haar),比较传统,效果一般,尤其侧脸的检测比较糟糕,但优点是比较快,每一帧都进行检测也不会感到卡顿。现版本使用的是机器学习库Dlib提供的人脸检测器。Dlib使用的人脸检测器基于HOG(方向梯度直方图,Histogram of Oriented Gradient)特征,并使用线性分类器。Dlib提供的人脸检测器对侧脸的检测效果很好,但速度要慢一些,在我的机器上(i7-7700 & GTX1060)略有卡顿,但几帧进行一次检测还是比较流畅的。
在OpenCV3.3.1版本及以后,提供了DNN模块(Deep Neural Networks),预训练了基于深度学习的人脸检测器。新的人脸检测器使用SSD作为检测网络,ResNet作为分类网络(OpenCV’s deep learning face detector is based on the Single Shot Detector (SSD) framework with a ResNet base network)。Dlib中也有基于深度学习的人脸检测器,原理是使用基于CNN的功能的最大边距对象检测器(MMOD)。当然出于速度考虑,这里并没有使用深度学习的方法。
OpenCV预训练了人脸检测的caffe模型
人脸特征点检测
人脸特征点检测(Facial landmark detection)是人脸检测过程中的一个重要环节。是在人脸检测的基础上进行的,对人脸上的特征点例如嘴角、眼角等进行定位。现在很多美颜APP的一些特效功能,比如说加一副眼镜,P上一个鹿的鼻子之类的操作,都是基于人脸特征点检测算法。对于换脸而言,人脸特征点的检测更是重中之重。由于要展现出吸毒之后人的病态,要进行瘦脸操作。利用特征点进行瘦脸应该会比使用坐标映射变换的方式要更自然一些。
传统方法是使用人脸检测+坐标变换
Dlib中提供了人脸关键点检测的预训练检测器,参考的论文是:One Millisecond Face Alignment with an Ensemble of Regression Trees by Vahid Kazemi and Josephine Sullivan, CVPR 2014。论文里使用的是基于树结构的级联回归器,实现出来的效果良好。Dlib预训练的检测器可以得到68个特征点,结构如下
68个特征点,但不包括额头
对于我刚才提到的一些应用,比如P个鼻子P个眼镜这样的操作,这些点够用了。然而项目中需要换上整张脸,所以还需要额头部位的特征点。一位硅谷的工程师训练了81个点的人脸特征点检测器,包括额头的部位。
硅谷小哥train的81点检测器
这位小哥的初衷是好的,但检测器的效果一般,标记的点会不太准确,估计是训练集的原因(Dlib预训练的.dat文件有100M,这位小哥的.dat文件只有20多M)。没办法,train是不可能自己train的,只好自己跟据68点的位置,根据人脸比例构造出额头的辅助点来进行换脸了。这里我构造了11个额头点,虽然会超出人脸的范围,但可以根据后续的算法来处理。
根据原来的68点瞎构造几个
References:
Dlib face_landmark_detection官方例程
Android:修图技术之瘦脸效果的实现(drawBitmapMesh)
Dlib 81点人脸特征点检测器github
One Millisecond Face Alignment with an Ensemble of Regression Trees by Vahid Kazemi and Josephine Sullivan, CVPR 2014。
肤色分割
前面说到,构造出来的额头点会超出人脸的范围,这样会导致换脸的结果是把头发都弄成皮肤了。另一方面,有可能会有刘海的情况,毕竟不是所有人都光着大脑门的。所以,要使用肤色检测来作为换脸过程中的掩膜,来避免头发也变成皱巴巴的皮肤的情况。
肤色检测的方法很多,我这里使用的是基于椭圆皮肤模型的皮肤检测。经过前人学者大量的皮肤统计信息可以知道,如果将皮肤信息映射到YCrCb空间,则在CrCb二维空间中这些皮肤像素点近似成一个椭圆分布。因此如果我们得到了一个CrCb的椭圆,下次来一个坐标(Cr, Cb)我们只需判断它是否在椭圆内(包括边界),如果是,则可以判断其为皮肤,否则就是非皮肤像素点。
肤色检测+分割
实际项目中,可以把这个椭圆的面积弄大一些,这样分割的条件会宽松一些。也可以弄小一些,看实际需求。
三角剖分
Delaunary三角剖分发明于1943年的一项技术,这种方式使空间中点连接成为三角形组,并且使这些三角形中最小的角取得最大值。这意味着Delaunary三角剖分在将点连接成为三角形的同时试图避免产生狭长的三角形。生成三角剖分的算法有很多,也比较复杂,实际使用中调用接口即可。
Delaunary三角剖分
我们这里需要做的,是把整张脸换过去,而不是单纯地在特征点附近P上一些特效。所以,在检测到人脸特征点以后,需要对特征点进行三角剖分(包括原图和被换脸的图片),然后使用几何变换和像素加权融合的方式,才能真正完成。
透视变换+加权融合
References:
OpenCV——Delaunay三角剖分
Learning OpenCV3 Adrian Kaehler & Gary Bradski 附录A平面划分
瘦脸算法
继续说下前面讲到的瘦脸操作。瘦脸的方式有很多,在本项目中,由于前面已经完成了人脸特征点的检测以及三角区域的剖分,那么利用区域变形(透视变换)的方式进行瘦脸的方式很自然就能够想到,具体的操作就是让下巴上的点往鼻子缩。
这里我并没有刻意去参考一些博客和论文,不过大概看了看跟这篇 Image Deformation Using Moving Least Squares里的思想比较类似。
坐标变换
Reference:
Image deformation using moving least squares. Scott Schaefer, Travis McPhail, Joe D. WarrenPublished in ACM Trans. Graph. 2006
最后
出来被社会毒打了两个月,还是有些收获的 - -!终于要恢复自由身了,真开心。但接下来的秋招还要继续努力啊 :) 。