第七章 采用AAM和POSIT的3D头部姿态估计.docx
- 文档编号:3508800
- 上传时间:2022-11-23
- 格式:DOCX
- 页数:22
- 大小:1.48MB
第七章 采用AAM和POSIT的3D头部姿态估计.docx
《第七章 采用AAM和POSIT的3D头部姿态估计.docx》由会员分享,可在线阅读,更多相关《第七章 采用AAM和POSIT的3D头部姿态估计.docx(22页珍藏版)》请在冰豆网上搜索。
第七章采用AAM和POSIT的3D头部姿态估计
一个好的计算机视觉算法如果没有伟大健壮的功能以及广泛的普遍化和一个坚实的数学基础是不完整的。
所有的这些优点伴随着主要由TimCootes开发的主动表观模型(ActiveAppearanceModels)。
这一章将教给你怎样使用OpenCV创建一个你自己的主动表观模型以及怎样在一个给出的图像帧中使用它搜索模型所在的最邻近位置。
而且你将学习如何使用POSIT算法和如何在你的”posed”图像中拟合你的3D模形。
使用这些工具,你将能够实时地在一个视频中跟踪一个3D模型。
不是很棒吗?
尽管例子的焦点在于头部姿态,事实上,任何可变模型可以采用同样的方法。
当你读这些部分,你将碰到下列主题:
1、主动表观模型概述
2、主动形状模型概述
3、模型实例化——运行主动表观模型
4、AAM搜索和匹配(或拟合,fitting)
5、POIST
下面的列表阐述了本章中你将要碰到的术语:
1、主动表观模型(AAM):
一个对象的模型,包含着对象形状和纹理的统计信息。
它是捕获对象形状和纹理变化的一个强大的方法。
2、主动形状模型(ASM):
对象形状的统计模型。
它对于学习形状变化非常有用。
3、主成分分析(PCA):
一个正交线性变换。
它将数据转换到一个新的坐标系统,满足:
数据任何投影产生的最大方差将位于第一个坐标(称为第一个主成分),第二个最大的方差位于第二个坐标上,等等。
这个过程通常用在降维。
当最初的问题减少了维数,我们可以使用一个更快的匹配(faster-fitting)算法。
4、三角剖分(DT):
对于平面上的一组点P,它是一个三角剖分,以使得在三角剖分中P中的任何点都不会在任何三角形的外接圆内。
它倾向于避免紧瘦的三角形。
三角剖分用来纹理的映射。
5、仿射变换:
任何转换都可以用一个矩阵相乘跟着一个矢量的加法的形式来表示。
仿射变换可以用来纹理的映射。
6、PosefromOrthographyandScalingwithIterations(POSIT):
一个执行3D姿态估计的计算机视觉算法。
主动表观模型概述——ActiveAppearnceModelsoverview
简单地说,主动表观模型是一个组合纹理和形状耦合到一个有效的搜索算法的参数化模型,它可以准确的告诉我们一个模型在一个图像帧中的位置以及如何定位于这个位置。
为了做到这一点,我们将以主动形状模型部分开始并且将看到它们与标记的位置紧密关联。
主成分分析和一些实践经验将在下面的部分更好的描述。
那时,我们将能够从OpenCV的Delaunay函数的获得一些帮助并且学习一些三角剖分。
从那起,在三角纹理变换部分,我们将发展到应用分段的仿射变换并且我们可以从一个对象的纹理中获得信息。
当我们获得足够的背景来建立一个好的模型,我们可以在模型的实例部分应用这些技术。
然后,我们将能够通过AAM搜索和匹配来解决反问题(theinverseproblem)。
对于2D或许甚至3D图像匹配,这些算法本身已经是非常有用的算法。
但是当我们能够运用这些算法时,为什么不把它联系到POSIT——3D模型拟合的另一个坚如磐石的算法呢?
投入到POSIT部分(DivingintothePOSITsection)我们将获得足够的背景来和它(算法)在OpenCV中一起工作,然后,在下面的部分,我们将学习如何耦合它的一个头部模型。
这样,我们可以使用一个3D模型来拟合已匹配的2D图像帧。
并且如果一个强烈的读者想知道这将把我们带到哪里,这正是用一个帧到帧的方式组合AAM和Posit,通过可变模型的检测来得到一个实时的3D跟踪。
这些细节将在来至网络相机或者视频文件的跟踪部分涉及到。
据说一个照片顶一千个词。
想象一下如果我们获得了N个照片。
这样,我们先前提到的内容可以在下面截图中简单的跟踪。
本章算法的概述:
给出一个图像(先前截图的左上图像),我们可以使用主动表观搜索算法来找到人类头部的2D姿态。
截图中右上边的图像展示了先前训练好的用在搜索算法中的一个主动表观模型。
找到一个姿态之后,POSIT可以用来推广这个结果到3D姿态。
如果这个过程应用到了一个视频序列,将通过检测获得3D跟踪。
主动形状模型——ActiveShapeModels
像先前提到的,AAM需要一个形状模型,这个角色通过ASM扮演(ActiveShapeModels)。
在接下来的部分中,我们将创建一个ASM,它是形状变化的统计模型。
这个形状模型通过
形状变化的组合产生。
需要一个标记过的图像训练集,就像文章形状模型——他们的训练和应用(ActiveShapeModels-TheirTrainingandApplication)中描述的那样。
为了建立一个人脸形状模型,一些标记了人脸关键位置点的图像用来概述主要的特征。
下面的截图展示了一个这样的例子:
一个人脸上有76个标记,这来至于MUCT数据库。
这些标记通常是手工标记的并且概述了一些人脸特征,例如嘴的轮廓,鼻子,眼睛,眉毛和脸的形状,因为这些特征易于跟踪。
注释:
Procrustes分析:
统计形状分析的一个形式,常用于分析一组形状的分布。
Procrustes叠加是通过最优地平移,旋转和一致地缩放对象来执行的。
如果我们有先前提到的图像集,我们可以产生一个形状变化的统计模型。
因为标记点在对象上描述了这个对象的形状,如果需要的话,首先我们使用Procrustes分析将这些点集对齐到一个坐标框架,并且通过一个矢量x表示每一个形状。
然后,我们对数据应用主成分分析。
那么我们可以使用下面的公式估算任何实例:
x=x+Psbs
在前面的公式中,x是均值形状,Ps是一组变化的正交模式,bs是一组形状参数。
好的,为了更好的理解,我们将在本节的剩余部分创建一个简单的应用,这将为我们展示如何处理PCA和形状模型。
使用PCA究竟为什么?
因为当减少我们模型参数的数量时,PCA能真正的帮助我们。
在本章的后面,我也将看到当在一个给定的图像中搜索它时,它给我们的帮助是多大。
下面的引用来至于一个网页:
http:
//en.wikipedia.org/wiki/Principal_component_analysis
当人们从目标的丰富信息视点观察时,PCA可以为用户提供一个低维图像——对象的一个“影子”。
通过仅使用前几个少量主成分就可以完成,这使得转换后的数据维数减少。
当我们看一个截图时,理解更加清晰,如下:
先前的截图展示了一个中心在(2,3)的多元高斯分布的PCA。
所示的向量是协方差矩阵的特征向量。
移动向量,这样它们的尾巴在均值处。
如果我们想用一个单一的参数表示我们的模型,那么将这些点的特征矢量的方向转移到截图的右上部分将是一个好的方法。
而且,通过轻微的改变参数,我们可以推断数据并且获得类似于我们将要寻找的值。
浅尝PCA——GettingthefeelofPCA
为了获得PCA是怎么样帮助我们人脸模型的一个感觉,我们将开始一个主动形状模型并且测试一些参数。
因为人脸检测和跟踪已经学习了一段时间,对于研究的目标,几个人脸数据库可以在线访问到。
我们将使用来至于IMM数据集的一对样本。
首先,让我们理解一些OpenCV中PCA类是怎么工作的。
我们可以从文献中得到结论:
PCA类用来计算一组矢量的特殊基,它包含通过一组输入矢量计算得到协方差矩阵的特征矢量。
这个类同样可以使用project和backproject方法将矢量投影到新的坐标空间或者从新的坐标空间反投影到原空间,仅通过获取它的前几个少量成分就可以精确的估计这个新的坐标系统。
这意味着我们可以使用一个非常短的子空间中的投影矢量坐标组成的矢量来表示来至高维空间的原始矢量。
因为在少量尺度值方面,我们将要一个参数化,我们将要使用的主要方法来至于类的backproject方法。
它带有投影矢量的主成份坐标并且重构这些原始坐标。
如果我们保留所有的成分,我们可以重新获得原始矢量,但是如果我们仅使用两个成分,差别将非常小。
这是使用PCA的一个理由。
既然我们想得到围绕原矢量的变换,我们参数化尺度将能够推断原始数据。
而且,PCA类可以转换矢量到和从新的坐标空间,通过基本的定义。
从数学上讲,
它意味着我们计算矢量的投影到一个子空间,该子空间是通过与协方差矩阵的主导特征值相对应的一些少量的特征矢量形成,就像我们从文献中看到的那样。
我们将用landmark标记一下我们的人脸图像来为点分布模型(pointdistributionmodelPDM)产生一个训练集。
如果我们有k个二维的对齐标记,我们的形状可以描述为:
X={x1,y1,x2,y2,…,xk,yk}
我们需要所有图像上的一致性的标记,这很重要。
因此,例如,如果第一个图像上嘴的左边部分的是标记3,这将需要所有的其他图像上的也是标记3.
现在这些标记序列将形成形状的轮廓,并且一个给出的训练形状可以定位为一个矢量。
一般地我们假定这些分布是在这个空间上的高斯分布,并且我们使用PCA来计算所有训练形状形成的协方差矩阵的标准化特征矢量和特征值。
使用中上的特征矢量,我们创建一个2k*m的矩阵,我们称它为P。
这样,每个特征矢量描述为这个集合变化的一个主要模式。
现在我们可以通过下面的等式定义一个新的形状:
X'=X'+Pb
这里,X’是所有训练图像的均值形状——我们刚刚计算的标记的每一个平均——以及b是一个尺度化每一个主成份的矢量。
这引导我们通过修改b的值创建一个新的形状。
通常将b设置在3个标准差范围内,这样产生的形状可以落入训练集。
下面的截图展示了用点注释的嘴标记的3个不同图像:
就像我们在先前截图看到的那样,形状通过它们的标记序列来描述。
我们可以使用一个像GIMP或者ImageJ的程序,同样地在OpenCV中建立一个简单的应用,为了标记这些训练图像,我们将假定用户已经完成了这个过程并且为所有的训练图像将这些点保存为x和y标记位置的序列,存储到一个文本文件中,这在我们的PCA分析中将有用。
然而,我们将添加两个参数到这个文件的第一个行,即训练图像的数目和读取列的数目。
因此,对于K个2D点,这个数字是2*k。
在下面的数据中,我们有一个这个文件的一个例子,我们通过对IMMdatabase中的三个图像的标记获得。
这里k是5:
310
265311303321337310302298265311
255315305337346316305309255315
262316303342332315298299262316
既然我们已经标记了图像,让我们将这些数据带入我们的形状模型。
首先,导入这些数据到一个矩阵。
这个过程通过函数loadPCA来完成。
下面的代码片段展示了loadPCA函数的使用。
PCAloadPCA(char*fileName,int&rows,int&cols,Mat&pcaset){
FILE*in=fopen(fileName,"r");
inta;
fscanf(in,"%d%d",&rows,&cols);
pcaset=Mat:
:
eye(rows,cols,CV_64F);
inti,j;
for(i=0;i for(j=0;j fscanf(in,"%d",&a); pcaset.at } } PCApca(pcaset,//passthedata//传入数据 Mat(),//wedonothaveapre-computedmeanvector,//我们没有预计算均值矢量 //soletthePCAenginecomputeit//因此让PCA引擎计算它 CV_PCA_DATA_AS_ROW,//indicatethatthevectors//表示这些矢量作为矩阵的行存储 //arestoredasmatrixrows //(useCV_PCA_DATA_AS_COLifthevectorsare //thematrixcolumns) pcaset.cols//specify,howmanyprincipalcomponentstoretain//指定,保留多少主成分 ); returnpca; } 注意我们的矩阵在pcaset=Mat: : eye(rows,cols,CV_64F)中创建并且有足够的容量来存储2*k个值。 两个for循环导入数据到这个矩阵后,调用带有一个数据和一个空矩阵的PCA构造器,如果我们希望仅使用它一次,那么这个矩阵是空矩阵,当然也可以是我们预先计算的均值矢量。 我们同样指出我们的矢量将作为矩阵的行存储并且我们希望保持给定行(这里应该是列)的数目和成分数目相同,尽管我们可能仅使用少量的成分。 既然我们用我们的训练集填充了我们的PCA对象,根据给定的参数,它拥有所需的所有事情来反投影我们的形状。 我们通过调用PCA.backproject来这样做,传递参数作为一个行矢量并且在第二个参数中获得反投影矢量。 先前的两个截图展示了根据滑动滚动条选择的两个不同形状的配置参数。 黄色和绿色形状表明训练数据集,红色形状反映了选择参数产生的形状。 一个简单的应用程序可以用来做主动形状模型实验,对于模型,它允许用户尝试不同的参数。 我们能够注意到通过滑动滑块(分别对应于第一个和第二个变化模式),仅前两个尺度值发生变化,我们可以获得一个形状,这个形状可以非常接近于训练的形状。 当我们在AAM中搜索一个模型时,这样的可变性将帮助我们。 因为它提供了插值形状。 我们将在接下来的部分讨论三角剖分,纹理,AAM和AAM-search。 三角剖分——Trangulaiton 因为我们正在寻找的形状可能被歪曲,例如张开嘴的一个实例,我们需要将我们的纹理映射到一个均值形状,然后对这个标准化的纹理应用PCA。 为了这样做,我们将使用三角剖分。 它的概念非常简单: 我们创建三角形包含我们标记的点,然而从一个三角形映射到另外一个三角形。 OpenCV带有一个标记的函数,叫做cvCreateSubdivDelaunay2D,它创建一个空的Delaunay三角剖分。 你可以仅认为这是一个好的三角剖分,它可以避免瘦长三角形。 注释: 在数学和计算几何上,平面内一个点集P的Delaunay三角剖分是一个这样的一个三角剖分: P中的每一个点都不会落在任何三角形的外接圆内。 在三角剖分中,Delaunay三角剖分最大化三角形中所有角的最小角。 从1934年之后,三角剖分以BorisDelaunay在这个方面的工作而命名。 一个Delaunay划分初始化之后,我们将使用cvSubdivDelaunay2DInsert函数将这些点填充到三角划分中。 下面的代码行将说明一个三角剖分的直接使用将是什么样的: CvMemStorage*storage; CvSubdiv2D*subdiv; CvRectrect={0,0,640,480}; storage=cvCreateMemStorage(0); subdiv=cvCreateSubdivDelaunay2D(rect,storage); std: : vector //initializepointssomehow ... //iteratethroughpointsinsertingtheminthesubdivision//迭代遍历所有点,将它们插入到细分中 for(inti=0;i floatx=points.at(i).x; floaty=points.at(i).y; CvPoint2D32ffloatingPoint=cvPoint2D32f(x,y); cvSubdivDelaunay2DInsert(subdiv,floatingPoint); } 注意我们的点都是在一个矩形框架内,这个矩形将作为一个参数传递给cvCreateSubdivDelaunay2D。 为了创建一个划分,我们也需要创建和初始化一个内存存储器。 这可以在先前的代码的前5行看到。 然而,为了创建三角剖分,我们需要使用cvSubdivDelaunay2DInset函数插入这些点。 这发生在先前代码的for循环中。 请注意点应当已经被初始化,因为它们是我们正在使用作为输入的点。 下面的截图展示了三角剖分可能是什么样子: 这个截图是针对一组点先前的代码的输出,是使用Delaunay算法产生的三角剖分。 尽管划分的创建是OpenCV的一个非常便利的函数,迭代遍历所有的三角剖分可能不是非常简单。 下面的代码展示了如何迭代遍历一个子划分的边。 voiditerate(CvSubdiv2D*subdiv,CvNextEdgeTypetriangleDirection){ CvSeqReaderreader;//序列阅读器 CvPointbuf[3];//存储三角形的顶点 inti,j,total=subdiv->edges->total;//边的总数 intelem_size=subdiv->edges->elem_size;//边的大小,用来步进 cvStartReadSeq((CvSeq*)(subdiv->edges),&reader,0);//初始化阅读器 for(i=0;i CvQuadEdge2D*edge=(CvQuadEdge2D*)(reader.ptr);//将阅读器的指针初始化为四方边缘指针 if(CV_IS_SET_ELEM(edge)){ CvSubdiv2DEdget=(CvSubdiv2DEdge)edge;//获取四方边缘的一条边 for(j=0;j<3;j++){ CvSubdiv2DPoint*pt=cvSubdiv2DEdgeOrg(t);//获取边的原点 if(! pt)break; buf[j]=cvPoint(cvRound(pt->pt.x),cvRound(pt->pt.y));//将点存储起来 t=cvSubdiv2DGetEdge(t,triangleDirection);//获取下一条边 } } CV_NEXT_SEQ_ELEM(elem_size,reader);//使阅读器指向下一个元素 } } 给定一个剖分,我们初始化它的边读器(edgereader)称为cvStartReadSeq函数。 从OpenCV的文献中,我们引用如下的定义: 函数初始化reader的状态,然后,所有序列元素,从第一个到最后一个,在前向阅读的情况下,可以通过调用子序列的宏CV_READ_SEQ_ELEM,在反向阅读的情况下,调用宏CV_REV_READ_SEQ_ELEM.。 这两个宏将序列元素放入到read_elem并且移动reading指针指向下一个元素。 获得接下来的元素的一个可替代的方式是使用宏CV_NEXT_SEQ_ELEM(elem_size,reader),如何序列元素很多,它是首选的。 在这种情况下,我们使用CvQuadEdge2D*edge=(CvQuadEge2D*)(reader.ptr)来访问边缘,这仅是一个从一个reader指针到CvQuadEdge2D指针的一个强制转换。 宏CV_IS_SET_ELEM仅检查指定的边是否被占用。 给定一个边,我们需要调用cvSubdiv2DEdgeOrg函数来获得该边的源点。 为了遍历一个三角形,我们重复的调用cvSubdiv2DEdge并且传递三角形方向,例如它可以是AROUND_LEFT或者CV_NEXT_AROUND_RIGHT. 三角纹理变形(映射)——Triangletexturewarping 既然我们能够迭代的访问一个划分的三角形,我们能够将一个原始标记的图像变换到一个产生的扭曲的图像。 这对于将原始形状的纹理映射到扭曲的形状很有用。 下面的代码将引导这个过程: voidwarpTextureFromTriangle(Point2fsrcTri[3],MatoriginalImage, Point2fdstTri[3],Matwarp_final){ Matwarp_mat(2,3,CV_32FC1);//仿射变换矩阵 Matwarp_dst,warp_mask; CvPointtrianglePoints[3]; trianglePoints[0]=dstTri[0]; trianglePoints[1]=dstTri[1]; trianglePoints[2]=dstTri[2]; warp_dst=Mat: : zeros(originalImage.rows,originalImage.cols,//存储仿射变换后图像 originalImage.type()); warp_mask=Mat: : zeros(originalImage.rows,originalImage.cols, originalImage.type()); ///GettheAffineTransform//获取仿射变换 warp_mat=getAffineTransform(srcTri,dstTri); ///ApplytheAffineTransformtothesrcimage//对图像应用仿射变化 warpAffine(originalImage,warp_dst,warp_mat,warp_dst.size()); cvFillConvexPoly(newIplImage(warp_mask),trianglePoints,3,CV_ RGB(255,255,255),CV_AA,0);//newIplImage是一个指针指向warp_mask warp_dst.copyTo(warp_final,warp_mask);//效果参考下图(除三角剖分映射的地方,其他均为白色)。 } 先前的代码假定我们有了三角形顶点,并且存储在srcTri数组中以及目的点存储在dstTri数组中。 2×3的warp_mat矩阵用来获取从源三角形到目的三角形的仿射变换。 更多的信息可以OpenCV的cvGetAffineTransform文献中引用。 函数cvGetAffineTransform计算一个仿射变换的矩阵,如下: 在前面的等式中,目的(i)等于(x’i,y’i),源(i)等于(xi,yi),i等于0,1,2. 获得仿射矩阵之后,我们可以应用仿射变换到源图像。 这通过warpAffine函数实现。 因为我们不像在整个图像上进行——我们想关注我们的三角形——掩码可以用来完成这个任务。 这样,最后一行代码仅拷贝带有刚才创建的掩码的源图像上的三角形,掩码通过函数cvFillConvexPoly调用填充。 下面的截图展示了应用这个过程到注释图像中的每一个三角形的结果。 注意这些三角形映射到匹配的框架,他们的脸朝向视者。 这个过程用于创建AAM的统计纹理。 前面的截图展示了将左图像中所有的三角形映射到了一个均值参考框架。 模型实例化——使用主动表观模型——ModelInstantiation-playingwitht
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 第七章 采用AAM和POSIT的3D头部姿态估计 第七 采用 AAM POSIT 头部 姿态 估计