K-means的缺點
昨天記錄了使用K-means對網格模型進行分割的步驟和一些簡單的結果,從昨天的實驗結果來看,使用頂點坐標和頂點法向作為K-means聚類的特征得到的分割效果總體上還是不錯的。分割結束后,每個頂點會被賦予一個分割的標號。
但是,只使用頂點距離作為聚類得到的結果並沒有語義,因此可能會得到一些不太好的聚類結果。
如上圖所示,“馬”模型的右后腿和兩條左腿被分割到了同一個類,這使得分割的結果不具有連通性,因此在后續的學習中,需要對該算法的分割結果進行調整,或者需找更加合適的分割方法。
分割邊界的獲取
邊界獲取的思想挺簡單,首先遍歷網格頂點,篩選出某一特定區域的頂點,對於這一區域中某一頂點,判斷其一鄰域中的頂點的標號與其自身標號是否相同,如果存在不同標號與自身標號不同的頂點,那么這樣的頂點就是邊界頂點。找出邊界頂點后,為其設置邊界標記。同時,記錄區域中非邊界頂點的個數,以便於計算區域局部的拉普拉斯矩陣。
尋找邊界代碼如下
void find_Boundary(PolygonMesh::Mesh * _mesh, int area) { /*_mesh: 輸入的網格 area: 區域編號 */ num_vtx = _mesh->n_vertices(); is_boundray.clear(); for ( int i = 0; i < num_vtx; i++ ) { //默認所有頂點都不是邊界當某個頂點的1領域中有一個頂點標號和它不同時,這個頂點為邊界頂點 is_boundray.push_back(0); } int v_idx = 0; cnt_non_bdy = 0;//非邊界頂點個數 for (auto v_it = _mesh->vertices_begin();v_it != _mesh->vertices_end();++v_it,v_idx++) { if (all_lables[v_idx] == area) { for ( auto vv_it = _mesh->vv_begin(v_it); vv_it != _mesh->vv_end(v_it); ++vv_it) { OpenMesh::VertexHandle vertex2 = vv_it.handle(); int v2_idx = vertex2.idx(); //當某個頂點的1領域中有一個頂點標號和它不同時,這個頂點為邊界頂點 if ( all_lables[v2_idx] !=area ) { is_boundray[v_idx]=1; break; } } } //區域area中的非邊界頂點 if (all_lables[v_idx] == area && is_boundray[v_idx] !=area ) { cnt_non_bdy++; } } }
需要注意的是,在計算區域的拉普拉斯矩陣時,如果使用L = D - A這種形式的拉普拉斯矩陣,在計算頂點度矩陣和頂點鄰接矩陣A時,都要將其鄰域中的邊界迪昂點排除在外
計算度矩陣和鄰接矩陣代碼
int v_idx = 0; int n_vidx = 0;//對同一區域中的頂點重新編號 VectorX byd_neibor;//記錄每個頂點的邊界鄰域 byd_neibor.setZero(cnt_non_bdy); for(auto v_it = _mesh->vertices_begin();v_it != _mesh->vertices_end();++v_it,++v_idx) { if (all_lables[v_idx] == 1 && is_boundray[v_idx] !=1 ) { //計算其邊界點鄰居個數 for ( auto vv_it = _mesh->vv_begin(v_it); vv_it != _mesh->vv_end(v_it); ++vv_it) { OpenMesh::VertexHandle vertex2 = vv_it.handle(); int v2_idx = vertex2.idx(); //當某個頂點的1領域中有一個頂點標號和它不同時,這個頂點為邊界頂點 if ( all_lables[v2_idx] !=1 ) { byd_neibor[n_vidx]++; } } //獲得頂點i的度,然后減去邊界點個數 int val = _mesh->valence(v_it.handle()); D_matrix(n_vidx ,n_vidx ) = val - byd_neibor[n_vidx]; //獲得頂點的1鄰域鄰接矩陣 OpenMesh::VertexHandle vertex1 = v_it.handle(); int v1_idx = vertex1.idx(); for ( auto vv_it = _mesh->vv_begin(v_it); vv_it != _mesh->vv_end(v_it); ++vv_it) { OpenMesh::VertexHandle vertex2 = vv_it.handle(); int v2_idx = vertex2.idx(); if (is_boundray[v2_idx] != 1)//篩選非邊界的鄰居頂點 { A_matrix(n_vidx,n_vidx) = 1; } } n_vidx ++;//對同一區域中的頂點進行新的編號 } }