图形处理(十二)拉普拉斯网格优化、最小二乘网格模型光顺

拉普拉斯网格优化、最小二乘网格模型光顺

原文地址:http://blog.csdn.net/hjimce/article/details/46505863 

作者:hjimce

看这篇博文前,请先参考我的另外一篇博文《图形处理(三)简单拉普拉斯网格变形-Siggraph 2004》学习拉普拉斯坐标的相关理论知识。这里要分享的paper,是通过拉普拉斯的方法实现三角网格模型的优化。如果你已经非常熟悉三角网格曲面的拉普拉斯相关理论,实现这篇paper也就非常容易了。网格曲面的拉普拉斯坐标不但可以用于变形、光顺,还可以用于优化,总之好处多多,你只要学会了这一招,那么就可以学会这些算法了。

图形处理(十二)拉普拉斯网格优化、最小二乘网格模型光顺_第1张图片

一、优化原理

图形处理(十二)拉普拉斯网格优化、最小二乘网格模型光顺_第2张图片

利用Laplacian 坐标重建的方法进行网格光顺,原理很简单,最简单的就是只要把源网格模型Laplacian 坐标δ的模长缩小,方向不变,就可以然后进行Laplacian 网格重建,就可以实现简单的光顺效果。

然而如果要进行网格优化呢?怎么实现?大牛们告诉我们一个比较规则的网格模型一个特点:当网格曲面上任意顶点的局部片中包含的所有三角面片都为等腰三角形时,该顶点的同一Laplacian 坐标和余切Laplacian 坐标相等。当将上述结论由某一个三角面片扩展到整个模型表面时可以发现:如果所有的三角面片都接近于正三角形,所有顶点的同一Laplacian 坐标和余切Laplacian 坐标接近相等。

ok,上面的原理便是paper的思想,只要你懂得了抓住这个思想,那么算法实现起来就容易了。

在三角网格曲面上,顶点vi的拉普拉斯坐标定义为,vi点与其一邻接顶点加权组合的差:


当然权重wij需要满足归一化


权值的选取很多,但比较常用的权值是均匀权、余切权,其计算公式如下:


图形处理(十二)拉普拉斯网格优化、最小二乘网格模型光顺_第3张图片

二、网格优化算法实现

算法原理主要是:

1、通过先求解网格曲面余切权计算得到的Laplacian 坐标δ

2、构建均匀权下的拉普拉斯矩阵A

3、然后求解AX=δ.

void CMeshOptimize::OptimizeMesh(TriMesh*Tmesh)
{
	Tmesh->need_neighbors();
	int vn=Tmesh->vertices.size();
	//拉普拉斯矩阵设置
	int count0=0;
	vector<int>begin_N(vn);
	for (int i=0;i<vn;i++)
	{   
		begin_N[i]=count0;
		count0+=Tmesh->neighbors[i].size()+1;
	}
	typedef Eigen::Triplet<double> T;
	std::vector<T> tripletList(count0+vn);
	for(int i=0;i<vn;i++)
	{
		int nei_n=Tmesh->neighbors[i].size();
		tripletList[begin_N[i]]=T(i,i,-1.0*nei_n);
		for (int k = 0;k<nei_n;++k) 
		{
			tripletList[begin_N[i]+k+1]=T(i,Tmesh->neighbors[i][k],1);
		}
	}
	//约束矩阵设置
	m_boundary_wieght=100*m_contrain_weight;
	for (int i=0;i<vn;i++)
	{
		if(Tmesh->is_bdy(i))tripletList[count0+i]=T(vn+i,i,m_boundary_wieght);
		else tripletList[count0+i]=T(vn+i,i,m_contrain_weight);
	}

	SparseMatrixType Ls(2*vn,vn); 
	Ls.setFromTriplets(tripletList.begin(), tripletList.end());

	//最小二乘解超静定方程组
	SparseMatrixType ls_transpose=Ls.transpose();
	SparseMatrixType LsLs =ls_transpose* Ls;
	vector<Eigen::VectorXd> RHSPos;//超静定方程组右边
	Compute_RHS(RHSPos);
	Eigen::SimplicialCholesky<SparseMatrixType>MatricesCholesky(LsLs);
	#pragma omp parallel for
	for (int i=0;i<3;i++)
	{
		Eigen::VectorXd xyzRHS=ls_transpose*RHSPos[i];
		Eigen::VectorXd xyz=MatricesCholesky.solve(xyzRHS);
		for(int j=0;j<vn;j++)
		{
			Tmesh->vertices[j][i]=xyz[j];
		}
	}
	
}
//设置方程组右边项
void CMeshOptimize::Compute_RHS(vector<Eigen::VectorXd> &RHSPos)
{
	int vn=m_OptimizeMesh->vertices.size();
	m_OptimizeMesh->need_neighbors();
	m_OptimizeMesh->need_adjacentfaces();
	RHSPos.clear();
	RHSPos.resize(3);
	for (int i=0;i<3;i++)
	{
		RHSPos[i].resize(2*vn);
		RHSPos[i].setZero();
	}

	int fn=m_OptimizeMesh->faces.size();
	m_OptimizeMesh->need_adjacentedges();
	#pragma omp parallel for
	for (int i=0;i<vn;i++)
	{   
		vector<float>CotWeight;
		float SumWeight;
		CotangentWeights(m_OptimizeMesh,i,CotWeight,SumWeight);
		int nei_n=m_OptimizeMesh->neighbors[i].size();
		//归一化
		vector<int>&a=m_OptimizeMesh->neighbors[i];
		vec ls;
		for (int j=0;j<nei_n;j++)
		{	
			ls=ls+CotWeight[j]*m_OptimizeMesh->vertices[a[j]];
		}
		ls=ls-SumWeight*m_OptimizeMesh->vertices[i];
		for (int j=0;j<3;j++)
		{
			RHSPos[j][i]=ls[j];
		}
	}
	for (int i=vn;i<2*vn;i++)
	{
		for (int j=0;j<3;j++)
		{   
			if(m_OptimizeMesh->is_bdy(i-vn))RHSPos[j][i]=m_OptimizeMesh->vertices[i-vn][j]*m_boundary_wieght;
			else RHSPos[j][i]=m_OptimizeMesh->vertices[i-vn][j]*m_contrain_weight;
		}
	}


}
//计算一阶邻近点的各自cottan权重
void CMeshOptimize::CotangentWeights(TriMesh*TMesh,int vIndex,vector<float>&vweight,float &WeightSum)
{   
	int NeighborNumber=TMesh->neighbors[vIndex].size();
	vweight.resize(NeighborNumber);
	WeightSum=0;
	vector<int>&NeiV=TMesh->neighbors[vIndex];
	for (int i=0;i<NeighborNumber;i++)
	{
		int j_nei=NeiV[i];
		vector<int>tempnei;
		Co_neighbor(TMesh,vIndex,j_nei,tempnei);
		float cotsum=0.0;
		for (int j=0;j<tempnei.size();j++)
		{
			vec vivo=TMesh->vertices[vIndex]-TMesh->vertices[tempnei[j]];
			vec vjvo=TMesh->vertices[j_nei]-TMesh->vertices[tempnei[j]];
			float dotvector=vivo DOT vjvo;
			dotvector=dotvector/sqrt(len2(vivo)*len2(vjvo)-dotvector*dotvector);
			cotsum+=dotvector;
		}
		vweight[i]=cotsum/2.0;
		WeightSum+=vweight[i];
	}
	for (int k=0;k<NeighborNumber;++k)
	{
		vweight[k]=NeighborNumber*vweight[k]/WeightSum;
	}
	WeightSum=NeighborNumber;

}
void CMeshOptimize::Co_neighbor(TriMesh *Tmesh,int u_id,int v_id,vector<int>&co_neiv)
{
	Tmesh->need_adjacentedges();
	vector<int>&u_id_ae=Tmesh->adjancetedge[u_id]; 
	int en=u_id_ae.size();
	Tedge Co_Edge;
	for (int i=0;i<en;i++)
	{
		Tedge &ae=Tmesh->m_edges[u_id_ae[i]];
		int opsi=ae.opposite_vertex(u_id);
		if (opsi==v_id)
		{
			Co_Edge=ae;
			break;
		}
	}
	for (int i=0;i<Co_Edge.m_adjacent_faces.size();i++)
	{
		TriMesh::Face af=Tmesh->faces[Co_Edge.m_adjacent_faces[i]];
		for (int j=0;j<3;j++)
		{
			if((af[j]!=u_id)&&(af[j]!=v_id))
			{
				co_neiv.push_back(af[j]);
			}
		}
	}
}

至此三角网格的优化可以说算法讲完了。因为算法比较简答,本篇文章篇幅比较小,所以在这篇博文中顺便讲一下最小二乘网格的相关概念。

参考文献:Laplacian Mesh Optimization

三、最小二乘网格相关理论

这边顺便讲一下最小二乘网格,最小二乘网格最初的概念来源于paper《Least-squares Meshes》,我最初看到最小二乘网格这个概念是在paper《基于最小二乘网格的模型变形算法》中看到的,因为之前学习微分域的网格变形算法的时候,基本上对每种变形算法都有看过相关的paper,用最小二乘网格的方法实现网格变形,其实跟基于多分辨率的网格变形算法是一样的,都是对低频空间中的网格模型进行变形操作。

最小二乘网格模型又称为全局的拉普拉斯光顺网格,就是通过把网格模型的拉普拉斯坐标设置为0,然后求解拉普拉斯方程:


即:

图形处理(十二)拉普拉斯网格优化、最小二乘网格模型光顺_第4张图片

其中权重wij为:



这样重建求解的网格即为最小二乘网格,也是一个光顺后的网格模型,因为该模型的拉普拉斯坐标全部为0。

当然求解上面的方程还需要控制顶点,控制顶点的个数对效果的影响还是蛮大的,可以看一下下面这个图:

图形处理(十二)拉普拉斯网格优化、最小二乘网格模型光顺_第5张图片

总之就是控制顶点的个数越少,越是光顺。

在paper《Least-squares Meshes》中还演示了通过给定的拓扑链接关系,进行网格补洞,与原网格模型的区别。

图形处理(十二)拉普拉斯网格优化、最小二乘网格模型光顺_第6张图片

*****************作者:hjimce     联系qq:1393852684   更多资源请关注我的博客:http://blog.csdn.net/hjimce                原创文章,版权所有,转载请保留本行信息。*****************

参考文献:

1、Least-squares Meshes

2、《Laplacian Mesh Optimization》

3、基于最小二乘网格的模型变形算法

你可能感兴趣的:(图形处理(十二)拉普拉斯网格优化、最小二乘网格模型光顺)