基于OpenCV和dlib的人脸交换实现探究

计算机视觉对我来说是一个全新的知识领域,希望能够逐步入门,从图像识别、人脸检测等问题的研究探讨逐步过渡到三维人脸重建技术的研究。在知识空白的情况下,我首先了阅读一些相关的论文,然后选择了一个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] }

landmarks 68个关键点位置图


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

人脸识别应用之“变脸”

dlib实现视频中人脸68特征点提取

变脸

浅析人脸检测之Haar分类器方法

曹晨.基于单目视频相机的实时人脸跟踪与动画方法研究[D].浙江大学,2016


未来展望:

之前没怎么接触过计算机视觉领域的具体指示,这次reseach对我来说是一个不小的挑战,发现其中涉及大量的数学知识,线代,统计学,数学分析等等,虽然感到困难重重,但我感觉到巨大的兴趣,在看着paper中对三维人脸重建的讲解,我眼前展开的是一幅美妙的画面,大牛们神乎其技各显神通,复杂的数学公式背后蕴含着深刻又淳朴的哲理和思想。

作为一个刚接触的这方面的本科生,很多理论基础都不扎实,在这个项目中的每个环节需要了解的更深,要想透彻的理解算法,一是要看透算法原作者的论文, 二是要读懂相关的优秀源码实现,日后我还需要进一步的夯实基础,向这个方向努力。

另外关于三维人脸重建(3D face reconstruction)的技术,读了一些论文和当前的成果,感觉超级有意思,对这个领域充满了好奇和兴趣,之后希望能够在老师和师兄的指导下逐步深入的学习和研究。I'll  keep trying and do my best!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容