圖形處理(七)基於熱傳播的測地距離計算-Siggraph 2013_圖形處理

來源:互聯網
上載者:User

這裡要跟大家分享的是2013年Siggraph上面的一篇paper,名為《Geodesics in Heat:A New Approach to Computing Distance Based on Heat Flow》,這篇paper沒有提供原始碼,但是因為演算法的思想相當新穎,如果你之前有研究過其它的測地三角網格曲面上的測地距離演算法,那麼看到這篇paper後,你會非常的激動,覺得這個演算法相當神奇,網格曲面上測地距離的計算方法又有了新的突破。因為看到這篇paper非常激動,以至於我興奮得馬上去把代碼寫了一遍,雖然測地距離的計算方法,對於我來說沒什麼用,但它的想法,它的思想值得我們學習。

《Geodesics in Heat:A New Approach to Computing Distance Based on Heat Flow》演算法主要是通過向量場的方法,通過求解熱流,求解泊松方程。這篇paper我覺得應該把它歸類為向量場類型的文獻,曲面上向量場的應用非常廣泛,可以用於網格最佳化重建、參數化紋理映射、網格變形……等,如今這篇paper又讓我明白向量場也可以求解測地距離。通過熱量傳播的方法,去求解測地距離,真是大牛啊


一、理論知識

在很早之前對於測地距離,大牛們就推匯出了測地距離的求解歸結為求解eikonal equation(程函方程):


且滿足邊界約束條件為:

上面符號Φ就是測地距離。


也就是不管是歐式空間還是曲面空間,其測地距離的梯度模長恒等於1,然後邊界條件:源點的測地距離值為0。

因此很多大牛,都是針對上面的程函方程的求解進行研究,然而所提出的演算法都是非線性方法,因為上面的方程就是非線性方程,在網格曲面上計算量非常大。而這篇paper作者的真正貢獻在於把非線性問題轉換為線性問題,到最後只需要求解一個泊松方程就可以了。開始這個演算法之前,建議先看一下《Mesh Editing with Poisson-Based Gradient Field Manipulation》這篇利用泊松方程進行三角網格模型編輯,其實這篇paper的思想應該是借用了《Poisson Image Editing》,如果你已經很熟悉《Mesh Editing with Poisson-Based Gradient Field Manipulation》那麼看這篇paper將事半功倍。

1、時間離散化,熱傳播方程時間離散化為:


2、空間離散化,在網格曲面上,離散的拉普拉斯座標定義為:



Ai表示三角面片的面積的三份之一,j表示頂點i的鄰接頂點。對於有V個頂點的網格模型,我們可以列出上面的V個方程,寫出矩陣形式為:

其中,Lc為拉普拉矩陣,A為包含每個頂點面積的對角矩陣。

代入公式(3)可得:


在網格曲面上,對於一個給定的三角面片平面上,標量場的梯度計算公式如下:



Af表示三角形的面積,N表示三角面片的法矢,ui就是標量場,我們可以把網格曲面每個頂點的測地距離看成是一個標量場。

頂點i的散度離散形式為:



懶得多說廢話了,總之到最後歸結為求解如下方程:


其中b我們需要先通過求解標量場u,然後根據散度的計算公式,計算出頂點測地距離的散度。

二、演算法實現

演算法總流程:


示意圖:


其中,Φ就是我們要求的測地距離,t是一個無窮小的數,趨近於0

演算法具體實現,先說明一下,我下面自己寫的代碼很亂,懶得整理,因為我只是為了學習:

1、求解u

根據公式:


求解u,因此我們需要先算出A,t,Lc,還有δ。接著我將逐漸講解著四個參數的求解:

a、時間t計算:見文獻3.2.4,時間理論上來說是一個非常小的數,文中作者給出了其合適的值為:網格平均邊長的平方


代碼實現如下:

[cpp]  view plain  copy //計算時間步長,文獻中時間步長為:網格模型的邊長平均,然後平方   void CHeatGeodesics::Set_Time()   {       m_BaseMesh->need_edge();       int en=m_BaseMesh->m_edges.size();       float sumlength=0;       for (int i=0;i<en;i++)       {           sumlength+=m_BaseMesh->m_edges[i].length();       }       sumlength=sumlength/en;//邊長的平均長度       sumlength=sumlength*sumlength;       m_Time_Step=sumlength;   }   b、面積對角矩陣A的計算:

[cpp]  view plain  copy //計算Geodesics in Heat 文獻中的包含頂點Vi的面積矩陣   void CHeatGeodesics::Get_Matrix_A_areas()   {       m_BaseMesh->need_adjacentfaces();       gsi::SparseMatrix &A=m_A_areas;       A.Resize(m_NumberV,m_NumberV);       //計算每個三角面片的面積       int fn=m_BaseMesh->faces.size();       vector<float>&face_area=m_Faces_Area;       face_area.resize(fn);       for (int i=0;i<fn;i++)       {           TriMesh::Face &f=m_BaseMesh->faces[i];           vec vij=m_BaseMesh->vertices[f[1]]-m_BaseMesh->vertices[f[0]];           vec vik=m_BaseMesh->vertices[f[2]]-m_BaseMesh->vertices[f[0]];           float areaf=0.5*len(vij CROSS vik);           face_area[i]=areaf;       }       //計算拉普拉斯運算元中每個頂點所佔據的面積,即鄰接三角面片面積總和的三分之一       for (int i=0;i<m_NumberV;i++)       {           vector<int>&af=m_BaseMesh->adjacentfaces[i];           int n_af=af.size();           float sumarea=0.0;           for (int j=0;j<n_af;j++)           {               sumarea+=face_area[af[j]];           }           //包含頂點的面積為:鄰接三角面片面積總和的三分之一           sumarea=sumarea/3.0;           A(i,i) =sumarea;       }   }   C、拉普拉斯矩陣Lc計算:

[cpp]  view plain  copy //計算Geodesics in Heat 文獻中的Lc矩陣,即拉普拉斯運算元   void CHeatGeodesics::Get_Matrix_Lc()   {       Ls.resize(m_NumberV,m_NumberV);       int m_nEdges=10000 ;       Ls.reserve(m_nEdges+m_NumberV);       for (int i = 0;i<m_NumberV;++i)        {           VProperty & vi = m_vertices[i];           int nNbrs = vi.VNeighbors.size();           for (int k = 0;k<nNbrs;++k)            {               Ls.insert(i, vi.VNeighbors[k]) = vi.VNeiWeight[k];           }           Ls.insert(i, i) = -vi.VSumWeight;       }       Ls.finalize();       gsi::SparseMatrix &A=m_Lc;       A.Resize(m_NumberV,m_NumberV);       for(int k=0;k<m_NumberV;++k)        {           for ( SparseMatrixType::InnerIterator it(Ls,k); it; ++it)           {               A.Set( it.row(), it.col(), it.value() );           }          }      }  

鄰接頂點的餘切cot權重計算:

[cpp]  view plain  copy //鄰接頂點的餘切權重計算   void CHeatGeodesics::CotangentWeights(TriMesh*TMesh,int vIndex,vector<float>&vweight,float &WeightSum,bool bNormalize)//計算一階鄰近點的各自cottan權重   {          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];       }          if ( bNormalize )        {           for (int k=0;k<NeighborNumber;++k)           {               vweight[k]/=WeightSum;           }           WeightSum=1.0;       }   }      void CHeatGeodesics:: UniformWeights(TriMesh*TMesh,int vIndex,vector<float>&vweight,float &WeightSum,bool bNormalize)   {       int NeighborNumber=TMesh->neighbors[vIndex].size();       vweight.resize(NeighborNumber);       WeightSum = 0;       for (int j = 0; j <NeighborNumber; ++j )       {           vweight[j] = 1;           WeightSum += vweight[j];       }          if ( bNormalize )       {           for ( int k = 0; k < NeighborNumber; ++k )               vweight[k] /= WeightSum;           WeightSum=1.0;       }   }   //擷取兩頂點的共同鄰接頂點   void CHeatGeodesics::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=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]);               }           }       }   }  

最後求解方程組,就可以把u求出來了。

2、求解三角網格曲面的熱量場▽u (Heat flow):

這一步直接根據公式:


求解就可以了。然後對▽u進行歸一化,並取熱量場的反方向,即求測地距離的梯度場:

[cpp]  view plain  copy for (int i=0;i<fn;i++)   {       TriMesh::Face &f=m_BaseMesh->faces[i];       for (int j=0;j<3;j++)       {           vec ei=m_BaseMesh->vertices[f[(j+2)%3]]-m_BaseMesh->vertices[f[(j+1)%3]];           vec FNormal=normalize(m_BaseMesh->FaceNormal[i]);           vec gradient=float(m_vertices[f[j]].VU*0.5/m_Faces_Area[i])*(FNormal CROSS ei);           FGradient[i]=FGradient[i]+gradient;       }       normalize(FGradient[i]);//文獻的重要兩步 即梯度單位化和梯度反向       FGradient[i]=float(-1.0)*FGradient[i];       //length0[i]=len(FGradient[i]);   }  
3、對測地距離的梯度場X,求取散度。

根據公式:


求解測地距離標量場梯度場的散度。

[cpp]  view plain  copy //計算頂點的散度   for (int i=0;i<vn;i++)   {          vector<int>&adjacentface=m_BaseMesh->adjacentfaces[i];       for (int j=0;j<adjacentface.size();j++)       {           TriMesh::Face &f=m_BaseMesh->faces[adjacentface[j]];           for (int k=0;k<3;k++)           {               if (f[k]==i)               {                   vec ei=m_BaseMesh->vertices[f[(k+2)%3]]-m_BaseMesh->vertices[f[(k+1)%3]];                   /*vec gradient=float(0.5/m_Faces_Area[adjacentface[j]])*(m_BaseMesh->FaceNormal[adjacentface[j]] CROSS ei);                  //計算散度                  m_vertices[i].VDivergence+=(FGradient[adjacentface[j]] DOT gradient)*m_Faces_Area[adjacentface[j]];*/                      vec e1=m_BaseMesh->vertices[f[(k+1)%3]]-m_BaseMesh->vertices[f[k]];                   vec e2=m_BaseMesh->vertices[f[(k+2)%3]]-m_BaseMesh->vertices[f[k]];                   float cot_angle1=Cot_angle(e2,ei);                   float cot_angle2=Cot_angle(float(-1.0)*e1,ei);               m_vertices[i].VDivergence+=0.5*(cot_angle1*(e1 DOT FGradient[adjacentface[j]])+cot_angle2*(e2 DOT FGradient[adjacentface[j]]));                   break;               }           }       }      }   4、最後求解通過求解泊松方程,求取網格曲面上每個頂點的測地距離。

[cpp]  view plain  copy gsi::SparseLinearSystem *pSystemPos2 = new gsi::SparseLinearSystem();       gsi::Solver_TAUCS * pSolverPos2 = new gsi::Solver_TAUCS(pSystemPos2);       pSystemPos2->Resize(m_NumberV, m_NumberV);       pSystemPos2->ResizeRHS(1);       //由於以下解方程組使用Solver_TAUCS::TAUCS_LLT ,即半正定矩陣模式       m_Lc.Multiply(-1.0);          m_Lc(0,0) =m_Lc(0,0) +10;          pSystemPos2->SetMatrix(m_Lc);       if ( ! pSystemPos2->Matrix().IsSymmetric() )assert(0);       pSolverPos2->OnMatrixChanged();       pSolverPos2->SetStoreFactorization(true);       pSolverPos2->SetSolverMode( gsi::Solver_TAUCS::TAUCS_LLT );       pSolverPos2->SetOrderingMode( gsi::Solver_TAUCS::TAUCS_METIS );       gsi::Vector pRHSPos2 ;       pRHSPos2.Resize(m_NumberV);       // 右邊項約束添加       for (int i=0;i<m_NumberV;i++)       { &

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.