计算机视觉对我来说是一个全新的知识领域,希望能够逐步入门,从图像识别、人脸检测等问题的研究探讨逐步过渡到三维人脸重建技术的研究。在知识空白的情况下,我首先了阅读一些相关的论文,然后选择了一个GitHub上的项目,部署环境并参照运行,在落实到代码层的同时进一步研究实现过程和原理,希望能够理解更多的内容。
DO:
1.阅读《OpenCV入门教程》及dlib官方文档
2.在win10中配置部署OpenCV和dlib
3.在vs2015中运行faceswap项目
4.梳理实现流程,探究算法
项目地址:
Real-time FaceSwap application built with OpenCV and dlib
初探:
工欲善其事,必先利其器。在软件不断的更新迭代的过程中,由于不同版本型号和缺乏经验,开发环境的正确搭建总是一个有点头疼的问题。在OpenCV和dlib的开发环境配置的过程中,还是遇到了不少的问题,试遍StackOverflow以及其他社区的各种可能的解决办法最后终于成功,后来对一些问题的总结记录在我的另一篇简书中。——尝试项目时遇到的问题和解决方案记录
简述:
要实现实时换脸,首先要调用相机,将相机缓存的帧中的人的面部特征标记出来,这里用到了dlib提供的特征点标记方法,定位正脸并返回68个人脸特征点的位置(landmark)。两张人脸对应了两个特征部分,接下来想办法实现这两个特征部分的轮廓对齐(经过平移旋转等变换),即实现人脸对齐。之后我们通过仿射变换实现互相交换覆盖区,再经过颜色矫正和边缘融合就基本实现了人脸交换。
项目实现细节:
1.调用的主要资源文件:
默认的人脸检测器:haarcascade_frontalface_default.xml
Dlib68点特征提取模型:shape_predictor_68_face_landmarks.dat
Dlib与OpenCV其他相关的库函数
2.类:
FaceDetectorAndTracker类:实现相机捕获帧中的人脸检测、跟踪以及得到相应的人脸矩形。
FaceSwapper类:实现了面部特征点的提取,求出仿射变换所需坐标,实现五官区域提取、面部对齐并求出变换矩阵,利用直方图法实现了色彩矫正,最后完成边缘融合完成人脸交换。
3.关键步骤解释:
1).人脸检测
基于OpenCV的级联分类器实现目标检测,利用的是样本的Haar特征,级联分类器的计算特征值的基础类FeatureEvaluator,功能包括读操作read、复制clone、获得特征类getFeatureType,分配图片分配窗口的操作setImage、setWindow,创建分类器特征的结构create函数。
主要实现过程:加载级联分类器->读取视频流->对每一帧使用该分类器->得到脸部兴趣区域的矩形向量。
在检测人脸时调用的一个关键函数detectMultiScale如下
//detect()中调用
CV_WRAP void detectMultiScale( InputArray image, CV_OUT std::vector& objects, double scaleFactor = 1.1, int minNeighbors = 3, int flags = 0, Size minSize = Size(), Size maxSize = Size() );
这个函数的作用是在输入图像中检测不同大小的对象。检测到的对象作为列表返回的矩形。
参数说明:@param image CV_8U类型的矩阵,其中包含检测对象的图像。
@param objects 每个矩形包含检测到的对象的矩形向量,矩形可能部分在原始图像之外。
@param scaleFactor参数指定在每个图像比例下图像大小减少了多少。
@param minNeighbors参数指定每个候选矩形应该有多少个要保留的邻居。
@param flags参数与旧函数中的cvHaarDetectObjects函数具有相同的含义。它不用于新的级联。
@param minSize最小可能的对象大小。小于这个值的对象被忽略。
@param maxSize最大可能的对象大小。大于此的对象将被忽略。如果`maxSize == minSize`模型是单一尺度评估的。
2).关键点定位提取
对摄像头采集到的每一帧图像缓存后进行特征点检测并显示即可。
使用了官方提供的模型构建特征提取器。
predictor = dlib.shape_predictor(predictor_path)
获取特征点坐标:
shapes[shape_index].part(part_index).x()/y()
shape_index是人脸的序号,如shapes[0]代表的是第一个人(可以同时检测到很多个人),part(i)代表的是第i个特征点,x()和y()是访问特征点坐标的途径。
68个点按顺序存放了人脸各部位的坐标信息,程序中选取了8,36,45作为仿射变换的关键点。(还不太明白原理-_-)
{IdxRange jaw; // [0 , 16]
IdxRange rightBrow; // [17, 21]
IdxRange leftBrow; // [22, 26]
IdxRange nose; //[27, 35]
IdxRange rightEye; // [36, 41]
IdxRange leftEye; // [42, 47]
IdxRange mouth;// [48, 59]
IdxRange mouth2; // [60, 67] }
3).仿射变换,人脸对齐
参考了面部对齐部分的讲解。主要用到了OpenCV提供的函数warpAffine实现了图片的变换。
void FaceSwapper::getTransformationMatrices() { trans_ann_to_bob = cv::getAffineTransform(affine_transform_keypoints_ann, affine_transform_keypoints_bob); cv::invertAffineTransform(trans_ann_to_bob, trans_bob_to_ann); }
4).区域提取
getMasks();
getWarppedMasks();
refined_masks = getRefinedMasks();
extractFaces();
首先计算出变换矩阵M,然后提取特征部分的mask并把它变换到要覆盖的位置得到warppedMasks,warppedMasks和它要覆盖的特征部分取并以保证完全覆盖。最后extractFaces实现调整好的mask到对方帧的互相拷贝。
5).色差矫正(color transfer)
色差矫正的目标是使当前人脸与要被替换的人脸色彩相近。项目中采用了直方图调整的方式:先计算当前图像和目标图像的颜色直方图,然后调整当前图像与目标图像的一致,最后将调整后的直方图应用到当前图像。两张图互相经过这样的处理就实现了色差的矫正。
程序中在colorCorrectFaces()函数中调用了specifyHistogram()完成了该功能。
6).边缘融合
a).图像填充/侵蚀cv::erode
CV_EXPORTS_W void erode( InputArray src, OutputArray dst, InputArray kernel, Point anchor = Point(-1,-1), int iterations = 1, int borderType = BORDER_CONSTANT, const Scalar& borderValue = morphologyDefaultBorderValue() );
使用特定的结构元素侵蚀图像。该函数使用指定的结构元素来侵蚀源图像。(译自CV文档)
最小取像素邻域的形状:
\f[\texttt{dst} (x,y) = \max _{(x',y'): \, \texttt{element} (x',y') \ne0 } \texttt{src} (x+x',y+y')\f]
侵蚀可以应用几次(迭代)次。在多通道图像的情况下,每个通道都是独立处理的。
@param src输入图像;通道的数量可以是任意的,但深度应该是CV_8U,CV_16U,CV_16S,CV_32F或CV_64F其中之一。
@参数dst输出与src相同大小和类型的图像。
@param kernel 内核结构化元素用于侵蚀;如果`element = Mat()`,一个`3 x 3`矩形结构元素被使用。内核可以使用getStructuringElement创建。
元素内锚的参数锚位置;默认值(-1,-1)表示锚在元素中心。
@param iterations应用erode的次数。
@param borderType像素外插方法,参阅cv :: BorderTypes
@param borderValue边界值
@sa dilate,morphologyEx,getStructuringElement
b).边缘模糊处理cv::blur
CV_EXPORTS_W void blur( InputArray src, OutputArray dst, Size ksize, Point anchor = Point(-1,-1), int borderType = BORDER_DEFAULT );
使用归一化的盒子过滤器模糊图像。(引用自官方文档)
该函数使用内核平滑图像:\ f {1} {\ texttt {ksize.width * ksize.height}} \ begin {bmatrix} 1&1&1& cdots&1&1 \\ 1&1& 1&1 cdots&1&1 \\ \ hdotsfor {6} \\ 1&1&1& cdots&1&1 \\ \ end {bmatrix} \ f]
调用`blur(src,dst,ksize,anchor,borderType)`相当于`boxFilter(src,dst,src.type(),anchor,true,borderType)`。
@param src输入图像; 它可以有任意数量的独立处理的通道,但是
深度应该是CV_8U,CV_16U,CV_16S,CV_32F或CV_64F。
@参数dst输出与src相同大小和类型的图像。
@参数ksize模糊内核大小。
@参数anchor 默认值Point(-1,-1)表示锚点位于内核处
中央。
@参数 borderType边界模式,用于外推图像外的像素,参阅cv :: BorderTypes
@sa Filter,bilateralFilter,GaussianBlur,medianBlur
4.涉及部分函数说明:
在整个实现过程中调用了OpenCV和dlib库中的一些函数实现关键大部分算法。
如
CV_WRAP void detectMultiScale()在输入图像中检测不同大小的对象。
CV_EXPORTS_W void matchTemplate()比较模板和重叠的图像区域。
CV_EXPORTS_W void normalize()规范化数组的范数或值范围。
CV_EXPORTS_W void minMaxLoc()在数组中查找全局最小值和最大值。
CV_EXPORTS_W void fillConvexPoly()绘制一个填充的凸多边形。
CV_EXPORTS_W void warpAffine()将仿射变换应用于图像。
CV_EXPORTS_W void blur()使用归一化的盒子过滤器模糊图像。
CV_EXPORTS_W void erode()使用特定的结构元素侵蚀图像。
关于这些函数的作用和具体的参数介绍我记录在了我另一篇博客中FaceSwap函数说明
阅读文献及参考链接:
Robust real-time face detection
One millisecond face alignment with an ensemble of regression trees
曹晨.基于单目视频相机的实时人脸跟踪与动画方法研究[D].浙江大学,2016
未来展望:
之前没怎么接触过计算机视觉领域的具体指示,这次reseach对我来说是一个不小的挑战,发现其中涉及大量的数学知识,线代,统计学,数学分析等等,虽然感到困难重重,但我感觉到巨大的兴趣,在看着paper中对三维人脸重建的讲解,我眼前展开的是一幅美妙的画面,大牛们神乎其技各显神通,复杂的数学公式背后蕴含着深刻又淳朴的哲理和思想。
作为一个刚接触的这方面的本科生,很多理论基础都不扎实,在这个项目中的每个环节需要了解的更深,要想透彻的理解算法,一是要看透算法原作者的论文, 二是要读懂相关的优秀源码实现,日后我还需要进一步的夯实基础,向这个方向努力。
另外关于三维人脸重建(3D face reconstruction)的技术,读了一些论文和当前的成果,感觉超级有意思,对这个领域充满了好奇和兴趣,之后希望能够在老师和师兄的指导下逐步深入的学习和研究。I'll keep trying and do my best!