OpenCV 脸部跟踪(2)
【原文:http://www.cnblogs.com/mikewolf2002/p/3474188.html】 前面一篇文章中提到,我们在一副脸部图像上选取76个特征点来描述脸部形状特征,本文中我们会把这些特征点映射到一个标准形状模型。 通常,脸部形状特征点能够参数化分解为两个变量,一个是全
【原文:http://www.cnblogs.com/mikewolf2002/p/3474188.html】
前面一篇文章中提到,我们在一副脸部图像上选取76个特征点来描述脸部形状特征,本文中我们会把这些特征点映射到一个标准形状模型。
通常,脸部形状特征点能够参数化分解为两个变量,一个是全局的刚体变化,一个是局部的变形。全局的刚体变化主要是指脸部能够在图像中移动,旋转,缩放,局部的变形则是指脸部的表情变化,不同人脸的特征等等。
形状模型类主要成员如下:
class shape_model
{ //2d linear shape model
public:
Mat p; //parameter vector (kx1) CV_32F,参数向量
Mat V; //shape basis (2nxk) CV_32F, line subspace,线性子空间
Mat e; //parameter variance (kx1) CV_32F 参数方差
Mat C; //connectivity (cx2) CV_32S 连通性
//把一个点集投影到一个可信的脸部形状空间
void calc_params(const vector
const Mat weight = Mat(), //weight of each point (nx1) CV_32F 点集的权重
const float c_factor = 3.0); //clamping factor
//该函数用人脸模型V和e,把向量p转化为点集
vector
...
void train(const vector
const vector
const float frac = 0.95, //fraction of variation to retain
const int kmax = 10); //maximum number of modes to retain
...
}
本文中,我们通过Procrustes analysis来处理特征点,移去全局刚性变化,Procrustes analysis算法可以参考:http://en.wikipedia.org/wiki/Procrustes_analysis
在数学上,Procruster analysis就是寻找一个标准形状,然后把所有其它特征点数据都和标准形状对齐,对齐的时候采用最小平方距离,用迭代的方法不断逼近。下面通过代码来了解如何实现Procrustes analysis。
//Procrustes分析的基本思想是最小化所有形状到平均形状的距离和
Mat shape_model::procrustes(const Mat &X,
const int itol, //最大迭代次数
const float ftol //精度
)
{
X矩阵就是多副样本图像76个特征点组成的矩阵,共152行,列数为图像的个数,每列表示一个样本图像特征点的x,y坐标。
int N = X.cols,n = X.rows/2;
//remove centre of mass
//所有的形状向量(特征)对齐到原点,即每个向量的分量减去其平均值,每列是一个形状向量。
Mat P = X.clone();
for(int i = 0; i
{
Mat p = P.col(i); //第i个向量
float mx = 0,my = 0;
for(int j = 0; j //x,y分别计算得到平均值。
{
mx += p.fl(2*j);
my += p.fl(2*j+1);
}
mx /= n; my /= n;
for(int j = 0; j
{
p.fl(2*j) -= mx;
p.fl(2*j+1) -= my;
}
}
//optimise scale and rotation
Mat C_old;
for(int iter = 0; iter
{
注意下边的一行代码,会把每个图像对齐到原点特征点x,y分别加起来,求平均值,得到一个152*1的矩阵,然后对该矩阵进行归一化处理。
Mat C = P*Mat::ones(N,1,CV_32F)/N; //计算形状变换后的平均值
normalize(C,C); //canonical shape (对x-进行标准化处理)
if(iter > 0) //converged?//收敛?当两个标准形状或者标准形状的误差小于某一值这里是ftol则,停止迭代。
{
norm函数默认是矩阵各元素平方和的根范式
if(norm(C,C_old)
break;
}
C_old = C.clone(); //remember current estimate//记下当前的矩阵,和下一次进行比较
for(int i = 0; i
{
rot_scale_align函数求每副图像的特征点向量和平均向量满足最小乘法时候的旋转矩阵。即求得a,b值组成的旋转矩阵。
该函数的代码:
Mat shape_model::rot_scale_align(<span>const</span> Mat &src, <span>const</span> Mat &dst) { <span>//construct linear system</span> <span>int</span> n = src.rows/2; <span>float</span> a=0,b=0,d=0; <span>for</span>(<span>int</span> i = 0; i //x*x+y*y a += src.fl(2*i) * dst.fl(2*i ) + src.fl(2*i+1) * dst.fl(2*i+1); b += src.fl(2*i) * dst.fl(2*i+1) - src.fl(2*i+1) * dst.fl(2*i ); } a /= d; b /= d; <span>//solved linear system</span> <span>return</span> (Mat_float>(2,2) <br> <br> Mat R = this->rot_scale_align(P.col(i),C); <span>//求两个形状之间的误差满足最小二乘时的旋转矩阵。即相当于两个形状"最靠近"时,需要的旋转的仿射矩阵 <br> </span> for(int j = 0; j { <span>//apply similarity transform//应用相似变换,这对形状中的每一个点,应用仿射矩阵。 变化后,该特征点向量会靠近平均特征向量。之后经过反复迭代,直到平均向量和上次比较变化很小时,退出迭代。 <br> </span> float x = P.fl(2*j,i),y = P.fl(2*j+1,i); <br> P.fl(2*j ,i) = R.fl(0,0)*x + R.fl(0,1)*y; <br> P.fl(2*j+1,i) = R.fl(1,0)*x + R.fl(1,1)*y; <br> } <br> } <br> } <br> return P; <br> } <br> <p> </p> <p> </p> <p> </p> <p> </p> <p> </p> <p> </p> <p> </p> <p> </p> <p> </p> <p> </p> <p> </p> <p> </p> <p> </p> <p> </p> <p> </p> <p> </p> <p> </p> <p> </p> <p> </p> <p> <span> 通过<span>Procrustes analysis对齐的特征向量,我们要用一个统一的矩阵把平移和旋转统一起来表示<span>(成为线性表示),</span>然后把该矩阵追加到局部变形空间,注意对该矩阵表示,我们最后进行了史密斯正交处理。</span></span></p> <p> <img src="/static/imghw/default1.png" data-src="/inc/test.jsp?url=http%3A%2F%2Fimages.cnitblog.com%2Fblog%2F361409%2F201312%2F14113411-9fdaefb021b14f52a72d2e3dae833c22.png&refer=http%3A%2F%2Fblog.csdn.net%2Fzhazhiqiang%2Farticle%2Fdetails%2F20158453" class="lazy" alt="OpenCV 脸部跟踪(2)" ></p> <p> <span>我们通过函数 calc_rigid_basis得到该矩阵表示:</span></p> <p> <span>Mat shape_model::calc_rigid_basis(const Mat &X) <br> { <br> <span>//compute mean shape</span> <br> int N = X.cols,n = X.rows/2; <br> Mat mean = X*Mat::ones(N,1,CV_32F)/N;</span></p> <p> <span> <span>//construct basis for similarity transform <br> </span> Mat R(2*n,4,CV_32F); <br> for(int i = 0; i { <br> R.fl(2*i,0) = mean.fl(2*i ); <br> R.fl(2*i+1,0) = mean.fl(2*i+1); <br> R.fl(2*i,1) = -mean.fl(2*i+1); <br> R.fl(2*i+1,1) = mean.fl(2*i ); <br> R.fl(2*i,2) = 1.0; <br> R.fl(2*i+1,2) = 0.0; <br> R.fl(2*i,3) = 0.0; <br> R.fl(2*i+1,3) = 1.0; <br> } <br> <span>//Gram-Schmidt orthonormalization <br> </span> for(int i = 0; i { <br> Mat r = R.col(i); <br> for(int j = 0; j { <br> Mat b = R.col(j); <br> r -= b*(b.t()*r); <br> } <br> normalize(r,r); <br> } <br> return R; <br> }</span></p> <p> </p> <p> <span>下面我们看看train函数的实现:</span></p> <p> <span>两篇参考的翻译:http://blog.csdn.net/raby_gyl/article/details/13148193</span></p> <p> <span>http://blog.csdn.net/raby_gyl/article/details/13024867</span></p> <p> </p> <p> <span> 该函数的输入为n个样本图像的采样特征点,该点集会被首先转化为行152,列为样本数量的矩阵表示,另外还有连通性点集索引,以及方差的置信区间以及保留模型的最大数量。</span></p> <p> <span>void train(const vector<vector> > &p, <span>//N-example shapes</span> <br> const vector<vec2i> &con = vector<vec2i>(), <span>//point-connectivity <br> </span> const float frac = 0.95, <span>//fraction of variation to retain <br> </span> const int kmax = 10) <span>//maximum number of modes to retain <br> </span> { <br> <span>//vectorize points</span> <br> Mat X = this->pts2mat(points);</vec2i></vec2i></vector></span></p> <p> <span><span>N是样本的数目,n是76,表示76个特征点。 <br> </span> int N = X.cols,n = X.rows/2;</span></p> <p> <span> <span>//align shapes <br> </span> Mat Y = this->procrustes(X);</span></p> <p> <span> <span>//compute rigid transformation</span> <span>计算得到刚体变化矩阵R <br> </span> Mat R = this->calc_rigid_basis(Y);</span></p> <p> <span> 脸部局部变形我们用一个线性模型表示,主要的思想如下图所示:一个有N个面部特征组成面部形状,被建模成一个2N维空间的点。我们要尽量找到一个低维的超平面,在这个平面内,所有的面部形状都在里面,由于这个超平面只是2N维空间的子集,占用刚少的空间,处理起来更快。可以想得到,如果这个子空间来自于一个人,则子空间的点,表现这个人的各种表情变化。前面的教程中,我们知道PCA算法能够找到低维子空间,但PCA算法需要指定子空间的维数,在启发式算法中有时候这个值很难选择。在本程序中,我们通过SVD算法来模拟PCA算法。</span></p> <p> </p> <p> <img src="/static/imghw/default1.png" data-src="/inc/test.jsp?url=http%3A%2F%2Fimages.cnitblog.com%2Fblog%2F361409%2F201312%2F15192025-1229008c4629424d8379873e5a6e517b.png&refer=http%3A%2F%2Fblog.csdn.net%2Fzhazhiqiang%2Farticle%2Fdetails%2F20158453" class="lazy" alt="OpenCV 脸部跟踪(2)" ></p> <p> </p> <p> <span> <span>//compute non-rigid transformation</span> </span></p> <p> <span><span>Y是152*1294的矩阵,它是procrustes分析的结果,R是刚体变化矩阵152*4,它的转置就是4*152 <br> </span> Mat P = R.t()*Y; <span>//原始的位置 <br> </span> <span>Mat dY = Y - R*P; </span><span>//</span><span>dy变量的每一列表示减去均值的Procrustes对齐形状,<span>投影刚体运动</span></span></span></p> <p> <span> 奇异值分解SVD有效的应用到形状数据的<span>协方差矩阵</span>(即,dY.t()*dY),OpenCV的SVD类的w成员存储着数据变化性的主要方向的变量,从最大到最小排序。一个选择子空间维数的普通方法是选择保存数据总能量分数frac的方向最小集(即占总能量的比例为frac),这是通过svd.w记录表示的,因为这些记录是从最大的到最小的排序的,它充分地用来评估子空间,通过用变化性方向的最大值k来评估能量。他们自己的方向存储在SVD类的u成员内。svd.w和svd.u成分一般分别被成为特征值和特征矢量。</span><span><span> <br> </span><span> SVD svd(dY*dY.t()); <br> int m = min(min(kmax,N-1),n-1); <br> float vsum = 0; <br> for(int i = 0; i vsum += svd.w.fl(i); <br> float v = 0; <br> int k = 0;</span></span></p> <p> <span><span><span>达到了95%的主成分量,退出,frac=0.95</span> <br> for(k = 0; k { <br> v += svd.w.fl(k); <br> if(v/vsum >= frac){k++; break;} <br> } <br> if(k > m) k = m;</span></span></p> <p> <span><span><span>取前k个特征向量 <br> </span> <span>Mat D = svd.u(Rect(0,0,k,2*n));</span></span></span></p> <p> <span><span>把全局刚体运动和局部变形运动结合起来,注意V的第一列是缩放,第三、四列分别是x,y偏移。</span></span></p> <p> <span>//combine bases <br> V.create(2*n,4+k,CV_32F); <br> Mat Vr = V(Rect(0,0,4,2*n)); <span>//刚体子空间 <br> </span> R.copyTo(Vr); <span>//非刚体子空间</span> <br> Mat Vd = V(Rect(4,0,k,2*n)); <br> D.copyTo(Vd);</span></p> <p> <span> 最后我们要注意的一点是如何约束子空间坐标,以使得子空间内的面部形状都是有效的。在下面的图中,我们可以看到,对于子空间内的图像,如果在某个方向改变坐标值,当坐标值小时候,它仍是一个脸的形状,但是变化值大时候,就不知道是什么玩意了。防止出现这种情况的最简单方法,就是把变化的值clamp在一个范围内,通常是现在± 3 的范围,这样可以cover到99.7%的脸部变化。clamping的值通过下面的代码计算:</span></p> <p> </p> <p> <img src="/static/imghw/default1.png" data-src="/inc/test.jsp?url=http%3A%2F%2Fimages.cnitblog.com%2Fblog%2F361409%2F201312%2F15192029-199dc6314d9e4c14bbf7fe4b35f098c0.png&refer=http%3A%2F%2Fblog.csdn.net%2Fzhazhiqiang%2Farticle%2Fdetails%2F20158453" class="lazy" alt="OpenCV 脸部跟踪(2)" ></p> <p> <span> <span> //compute variance (normalized wrt scale)</span> </span></p> <p> <span><br> <span>Mat Q = V.t()*X; </span><span>//把数据投影到子空间 <br> </span> for(int i = 0; i //normalize coordinates w.r.t scale <br> </span> { <span>//用第一个坐标缩放,防止太大的缩放值影响脸部识别 <br> </span> float v = Q.fl(0,i); <br> Mat q = Q.col(i); <br> q /= v; <br> } <br> e.create(4+k,1,CV_32F); <br> pow(Q,2,Q); <br> for(int i = 0; i { <br> if(i e.fl(i) = -1; <span>//no clamping for rigid coefficients <br> </span> else <br> e.fl(i) = Q.row(i).dot(Mat::ones(1,N,CV_32F))/(N-1); <br> } <br> <span>//store connectivity <br> </span> if(con.size() > 0) <br> { //default connectivity <br> int m = con.size(); <br> C.create(m,2,CV_32F); <br> for(int i = 0; i { <br> C.at<int>(i,0) = con[i][0]; <br> C.at<int>(i,1) = con[i][1]; <br> } <br> } <br> else <br> { //user-specified connectivity <br> C.create(n,2,CV_32S); <br> for(int i = 0; i { <br> C.at<int>(i,0) = i; C.at<int>(i,1) = i+1; <br> } <br> C.at<int>(n-1,0) = n-1; C.at<int>(n-1,1) = 0; <br> } <br> }</int></int></int></int></int></int></p> <p> </p> <p> <span>工程文件:FirstOpenCV40,</span></p> <p> <span>程序的运行参数为:annotations.yaml shapemodle.yaml</span></p> <p> <span>程序执行后,可以看到我们只保留了14个模型。</span></p> <p> <img src="/static/imghw/default1.png" data-src="/inc/test.jsp?url=http%3A%2F%2Fimages.cnitblog.com%2Fblog%2F361409%2F201312%2F14113419-908c110a67d3464abcd452edcc027668.png&refer=http%3A%2F%2Fblog.csdn.net%2Fzhazhiqiang%2Farticle%2Fdetails%2F20158453" class="lazy" alt="OpenCV 脸部跟踪(2)" ></p> <p> <span>我们也可以使用下面的运行参数:annotations.yaml shapemodle.yaml –mirror</span></p> <p> <span>这时候,每副图像的特征点,会生成一个y轴对称的镜像特征点集,这时训练的采样数目翻倍,为5828。</span></p> <p> <span>在工程文件FirstOpenCV41中,我们可视化了生成的模型,会连续显示14个模型的不同姿态:</span></p> <p> <img src="/static/imghw/default1.png" data-src="/inc/test.jsp?url=http%3A%2F%2Fimages.cnitblog.com%2Fblog%2F361409%2F201312%2F14113424-ac033ab541ca4a349466840ce5b41628.png&refer=http%3A%2F%2Fblog.csdn.net%2Fzhazhiqiang%2Farticle%2Fdetails%2F20158453" class="lazy" alt="OpenCV 脸部跟踪(2)" ></p>

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

Video Face Swap
완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

HTTP 상태 코드 520은 서버가 요청을 처리하는 동안 알 수 없는 오류가 발생하여 더 구체적인 정보를 제공할 수 없음을 의미합니다. 서버가 요청을 처리하는 동안 알 수 없는 오류가 발생했음을 나타내는 데 사용됩니다. 이는 서버 구성 문제, 네트워크 문제 또는 기타 알 수 없는 이유로 인해 발생할 수 있습니다. 이는 일반적으로 서버 구성 문제, 네트워크 문제, 서버 과부하 또는 코딩 오류로 인해 발생합니다. 상태 코드 520 오류가 발생하면 웹사이트 관리자나 기술 지원팀에 문의하여 자세한 정보와 지원을 받는 것이 가장 좋습니다.

경로 추적 또는 광선 추적을 사용하기로 한 결정은 게임 개발자에게 중요한 선택입니다. 둘 다 시각적으로 잘 작동하지만 실제 적용에는 약간의 차이가 있습니다. 따라서 게임 매니아는 두 기술의 장점과 단점을 신중하게 비교하여 자신이 원하는 시각 효과를 달성하는 데 어떤 기술이 더 적합한지 결정해야 합니다. 레이 트레이싱이란 무엇입니까? 광선 추적은 가상 환경에서 빛의 전파 및 상호 작용을 시뮬레이션하는 데 사용되는 복잡한 렌더링 기술입니다. 기존 래스터화 방법과 달리 광선 추적은 빛의 경로를 추적하여 사실적인 조명과 그림자 효과를 생성하여 보다 사실적인 시각적 경험을 제공합니다. 이 기술은 보다 사실적인 이미지를 생성할 뿐만 아니라 보다 복잡한 조명 효과를 시뮬레이션하여 장면을 더욱 사실적이고 생생하게 만듭니다. 주요 개념

특정 코드 예제가 필요한 OpenCV 튜토리얼을 쉽게 설치하려면 pip 명령을 사용하세요. OpenCV(OpenSource 컴퓨터 비전 라이브러리)는 개발자가 이미지를 빠르게 구축하는 데 도움이 되는 수많은 컴퓨터 비전 알고리즘과 기능을 포함하고 있습니다. 및 비디오 처리 관련 애플리케이션. OpenCV를 사용하기 전에 먼저 OpenCV를 설치해야 합니다. 다행스럽게도 Python은 타사 라이브러리를 관리할 수 있는 강력한 도구인 pip를 제공합니다.

OpenCV는 컴퓨터 비전 및 이미지 처리를 위한 오픈 소스 라이브러리로, 기계 학습, 이미지 인식, 비디오 처리 및 기타 분야에서 널리 사용됩니다. OpenCV를 사용하여 개발할 때 프로그램을 더 효과적으로 디버깅하고 실행하기 위해 많은 개발자는 강력한 Python 통합 개발 환경인 PyCharm을 선택합니다. 이 문서에서는 PyCharm 사용자에게 특정 코드 예제와 함께 OpenCV 설치 튜토리얼을 제공합니다. 1단계: Python 설치 먼저 Python이 설치되어 있는지 확인하세요.

HTTP 상태 코드 403은 서버가 클라이언트의 요청을 거부했음을 의미합니다. http 상태 코드 403에 대한 해결 방법은 다음과 같습니다. 1. 서버에 인증이 필요한 경우 올바른 자격 증명이 제공되었는지 확인합니다. 2. 서버가 IP 주소를 제한한 경우 클라이언트의 IP 주소가 제한되어 있거나 블랙리스트에 없습니다. 3. 파일 권한 설정을 확인하십시오. 403 상태 코드가 파일 또는 디렉토리의 권한 설정과 관련되어 있으면 클라이언트가 해당 파일 또는 디렉토리에 액세스할 수 있는 권한이 있는지 확인하십시오. 등.

HTTP 301 상태 코드의 의미 이해: 웹 페이지 리디렉션의 일반적인 응용 시나리오 인터넷의 급속한 발전으로 인해 사람들은 웹 페이지 상호 작용에 대한 요구 사항이 점점 더 높아지고 있습니다. 웹 디자인 분야에서 웹 페이지 리디렉션은 HTTP 301 상태 코드를 통해 구현되는 일반적이고 중요한 기술입니다. 이 기사에서는 HTTP 301 상태 코드의 의미와 웹 페이지 리디렉션의 일반적인 응용 프로그램 시나리오를 살펴봅니다. HTTP301 상태 코드는 영구 리디렉션(PermanentRedirect)을 나타냅니다. 서버가 클라이언트의 정보를 받을 때

HTTP 상태 코드 200: 성공적인 응답의 의미와 목적 탐색 HTTP 상태 코드는 서버 응답 상태를 나타내는 데 사용되는 숫자 코드입니다. 그 중 상태 코드 200은 요청이 서버에 의해 성공적으로 처리되었음을 나타냅니다. 이 기사에서는 HTTP 상태 코드 200의 구체적인 의미와 사용법을 살펴보겠습니다. 먼저 HTTP 상태 코드의 분류를 이해해 보겠습니다. 상태 코드는 1xx, 2xx, 3xx, 4xx 및 5xx의 다섯 가지 범주로 나뉩니다. 그 중 2xx는 성공적인 응답을 나타냅니다. 그리고 200은 2xx에서 가장 일반적인 상태 코드입니다.

NginxProxyManager를 사용하여 HTTP에서 HTTPS로의 자동 점프를 구현하는 방법 인터넷이 발전하면서 점점 더 많은 웹사이트가 HTTPS 프로토콜을 사용하여 데이터 전송을 암호화하여 데이터 보안과 사용자 개인 정보 보호를 향상시키기 시작했습니다. HTTPS 프로토콜에는 SSL 인증서 지원이 필요하므로 HTTPS 프로토콜 배포 시 특정 기술 지원이 필요합니다. Nginx는 강력하고 일반적으로 사용되는 HTTP 서버 및 역방향 프록시 서버이며 NginxProxy
