图形处理(十二)拉普拉斯网格优化、最小二乘网格模型光顺
看这篇博文前,请先参考我的另外一篇博文《图形处理(三)简单拉普拉斯网格变形-Siggraph 2004》学习拉普拉斯坐标的相关理论知识。这里要分享的paper,是通过拉普拉斯的方法实现三角网格模型的优化。如果你已经非常熟悉三角网格曲面的拉普拉斯相关理论,实现这篇paper也就非常容易了。网格曲面的拉普拉斯坐标不但可以用于变形、光顺,还可以用于优化,总之好处多多,你只要学会了这一招,那么就可以学会这些算法了。
一、优化原理
利用Laplacian 坐标重建的方法进行网格光顺,原理很简单,最简单的就是只要把源网格模型Laplacian 坐标δ的模长缩小,方向不变,就可以然后进行Laplacian 网格重建,就可以实现简单的光顺效果。
然而如果要进行网格优化呢?怎么实现?大牛们告诉我们一个比较规则的网格模型一个特点:当网格曲面上任意顶点的局部片中包含的所有三角面片都为等腰三角形时,该顶点的同一Laplacian 坐标和余切Laplacian 坐标相等。当将上述结论由某一个三角面片扩展到整个模型表面时可以发现:如果所有的三角面片都接近于正三角形,所有顶点的同一Laplacian 坐标和余切Laplacian 坐标接近相等。
ok,上面的原理便是paper的思想,只要你懂得了抓住这个思想,那么算法实现起来就容易了。
在三角网格曲面上,顶点vi的拉普拉斯坐标定义为,vi点与其一邻接顶点加权组合的差:
当然权重wij需要满足归一化
权值的选取很多,但比较常用的权值是均匀权、余切权,其计算公式如下:
二、网格优化算法实现
算法原理主要是:
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,然后求解拉普拉斯方程:
即:
其中权重wij为:
这样重建求解的网格即为最小二乘网格,也是一个光顺后的网格模型,因为该模型的拉普拉斯坐标全部为0。
当然求解上面的方程还需要控制顶点,控制顶点的个数对效果的影响还是蛮大的,可以看一下下面这个图:
总之就是控制顶点的个数越少,越是光顺。
在paper《Least-squares Meshes》中还演示了通过给定的拓扑链接关系,进行网格补洞,与原网格模型的区别。
本文地址:http://blog.csdn.net/hjimce/article/details/46505863 作者:hjimce 联系qq:1393852684 更多资源请关注我的博客:http://blog.csdn.net/hjimce 原创文章,版权所有,转载请保留本行信息。
参考文献:
1、《Least-squares Meshes》
2、《Laplacian Mesh Optimization》
3、《基于最小二乘网格的模型变形算法》
图形处理(十二)拉普拉斯网格优化、最小二乘网格模型光顺相关推荐
- OpenGL教程翻译 第二十二课 使用Assimp加载模型
第二十二课 使用Assimp加载模型 原文地址:http://ogldev.atspace.co.uk/(源码请从原文主页下载) 背景 到现在为止我们都在使用手动生成的模型.正如你所想的,指明每个顶点 ...
- Geomagic网格重划分与模型光顺
Geomagic网格重划分与模型光顺 1 网格重划分 2 模型光顺 首先先导入模型,可以看到模型还是相对较为粗糙,面片的法矢方向差别很大,因此需要重新划分下网格. 1 网格重划分 网格重划分的步骤为: ...
- 第十二章 MySQL优化
1. 优化哲学 1.1 为什么优化? 为了获得成就感? 为了证实比系统设计者更懂数据库? 为了从优化成果来证实优化者更有价值? 但通常事实证实的结果往往会和您期待相反! 优化有风险,涉足需谨慎! 1. ...
- 动手学深度学习(二十二)——GoogLeNet:CNN经典模型(五)
文章目录 1. 含并行连结的网络(GoogLeNet) 1.1 Inception块 1.2 GoogLeNet模型 2. 动手实现简化的GoogLeNet模型 2.1 实现Inception块 2. ...
- WPF and Silverlight 学习笔记(十二):WPF Panel内容模型、Decorator内容模型及其他...
由于园子里昨天使用Live Writer上传出现问题,昨天只能使用Web上的文本编辑器上传本文,造成代码.内容等格式的错误,现重发本文. 一.Panel内容模型 Panel内容模型指从System.W ...
- 机器学习笔记(十二)——马尔科夫模型
马尔科夫模型是一种概率图模型,它描述了一类重要的随机过程(随机过程又称为随机函数,是随时间而随机变化的过程).我们常常需要考察一个随机变量序列,这些随机变量序列并不是相互独立的,每个随机变量的值都依赖 ...
- 多传感器融合定位十四-基于图优化的定位方法
多传感器融合定位十四-基于图优化的定位方法 1. 基于图优化的定位简介 1.1 核心思路 1.2 定位流程 2. 边缘化原理及应用 2.1 边缘化原理 2.2 从滤波角度理解边缘化 3. 基于kitt ...
- 【Visual C++】游戏开发笔记四十四 浅墨DirectX教程十二 网格模型和X文件使用面面观
本系列文章由zhmxy555(毛星云)编写,转载请注明出处. 文章链接: http://blog.csdn.net/zhmxy555/article/details/8586540 作者:毛星云(浅墨 ...
- 【Visual C++】游戏开发笔记四十四 浅墨DirectX教程十二 网格模型和X文件使用面面观...
本系列文章由zhmxy555(毛星云)编写,转载请注明出处. 文章链接:http://blog.csdn.net/zhmxy555/article/details/8586540 作者:毛星云(浅墨) ...
最新文章
- 2020年春季信号与系统试卷批改
- linux下防火墙iptables原理及使用
- [转载]C# PropertyGrid控件应用心得
- auto errored after 报错解决_css重点知识和bug解决方法
- ubuntu下面codelite运行代码一闪而过的问题、codelite的代码排版快捷键、去掉xfce4的Ctrl+F5
- 实验 4 操作、输出值和数据表实验报告--软件功能测试与性能测试实验
- java减治法深度优先查找_排序|减治法实现排序
- 宠物火葬行业调研报告 - 市场现状分析与发展前景预测(2021-2027年)
- [重庆邮电大学俱乐部] 成都普创技术总监方锋:从校园人到职业人
- 从实例学Kettle(一):获取股票行情数据
- 大脑是什么样的网络?
- 链塔智库|区块链产业要闻及动态周报(2020年10月第2周)
- 移动App性能测试包含哪些内容?App性能测试工具有哪些?
- HDF文件(.h5)的写入与读取
- python去除Excel重复项
- 【GamePlay】泡泡龙核心算法
- 零基础Unreal Engine 4(UE4)图文笔记之基础篇-基本操作(二)
- 赛维时代IPO过会:第一季净利降71% 出口跨境电商热度降温
- yanderify 让图片动起来 快速安装教程
- 基于JAVA和MySQL的办公自动化系统
热门文章
- python pandas dataframe 行列选择,切片操作 原创 2017年02月15日 21:43:18 标签: python 30760 python pandas dataframe
- 数据挖掘方法论与工程化思考
- 636 起投资事件,吸金 574 亿
- 2015年科技巨头的十个开源产品,不只是.NET、Swift
- Spring Cloud Alibaba - 10 Ribbon 自定义负载均衡策略(权重算法)
- MySQL - 并发事务问题及解决方案
- MyBatis源码-解读Executor的三个实现类之BatchExecutor(批处理执行器)
- Spring Cloud【Finchley】-04使用Ribbon实现客户端负载均衡
- clockdiff-检测两台linux主机的时间差
- 汇编 编程实现从键盘输入三位以内的十进制负数_macOS上的汇编入门(二)——数学基础...