1. 程式人生 > >基於三角網格排序的人體三圍測量算法

基於三角網格排序的人體三圍測量算法

div cte bsp none 距離 分析 半邊數據結構 選擇排序 存在

本文討論的範圍不包含幾何重建、紋理重建,僅包含基於已有的人體三維三角網格模型(下稱人體三角網格)的三維測量算法。圍度測量實際上是對三維模型與特定平面求交,並對交集進一步分析其交集的特定閉合部分(若存在多個閉合部分)的輪廓周長和面積等數據,即求三維網格的切片。國標GB/T 16160-2008《服裝用人體測量的部位及方法》中給出了人體各尺寸特征的與人身高的比例數據(下稱特征比例),求出特定比例處與身高方向構成的點法式平面與三維網格的切片即可進一步得到特定特征的具體數值。

1.1 人體三角網格的坐標系矯正

  假定人體三角網格的坐標系中Z軸方向為人角的中點與頭頂中心構成的方向向量,而手持掃描儀獲取到的人體三角網格的坐標系取決於首次掃描的朝向,因此網格的Z軸方向與正確的坐標系有誤差,需要通過額外的工作進行坐標軸的矯正。


  本文采用的核心方法為PCA(Principal Component Analysis)。對於手臂與身體軀幹的面元粘連問題,可通過掃描目標張開手臂進行規避,而此時掃描目標人站立並張開手臂時的高低肩會影響PCA坐標矯正的效果。影響PCA算法的主要部分為手臂部分三維點,去除手臂部分三維點進行二次PCA算法即可。簡單地可以通過特定的比例關系選取,亦可通過三維點到Z軸的距離選取。

1.2 三角網格切片算法

1.2.1 基於排序的三角網格切片算法

  基於三角網格的切片算法可分為兩類。一是基於半邊數據結構的三角網格切片算法,假定半邊結構半邊對象為:

struct HE_edge{
    HE_vert* from;
    HE_vert* to;
    HE_edge* pair;   // oppositely oriented adjacent half-edge 
    HE_edge* next;   // next half-edge around the face
    ...               // other members
};

並且此時獲取到了平面plane與指定片面相交的起始半邊s, 可知算法的結果為從s出發的一個半邊序列,那麽算法可以簡單描述為HE_Slice(s->pair,*),通過循環去遞歸可進一步優化:

void HE_Slice(HE_edge* curr, std::vector<glm::vec3>& res){
    if(curr->next == s || curr->next->next == s) return ;
    for(auto e: {e->next, e->next->next}) {
        if(is_intersect(e,plane)) {
            ... // calculate and store the intersection
            HE_Slice(e->pair, res);
        }
    }
}

1.2.2 基於排序的三角網格切片算法

  基於半邊數據結構的切片算法是高效的,但半邊數據結構的維持需要較多的內存支持且構建耗時_O(n*log(n))_、只提供了局部拓撲信息不能支持按身高比例高效獲取特定半邊。上述兩個缺點均可通過下面介紹的面元排序解決。考慮到人體三角網格與平面plane的交集可以為多個閉合部分(手與軀幹、雙腳),半邊結構能且只能獲取到給定的初始半邊所在閉合部分,不能獲得指定平面與三角網格的交集的閉合部分的數量(下稱閉合數)。算法框架如下:

1. 對共享頂點結構的三角網格進行排序
2. 通過二分查找確定需要與平面求交的有序面元的最小子序列
3. 遍歷子序列,生成平面與三角形面元的邊的交的集合,集合元素為{A,B,k},其中A,B為邊的頂點索引,k為相交系數。
4. 通過選擇排序對3.生成的集合完成有序化,並得到閉合部分的劃分。

  參與切片算法的最小幾何元素為邊,因此對三角網格排序應當等價於對三角形面元排序。排序算法簡單描述如下:

// 1. 對共享頂點結構的三角網格進行排序
void sortByVector(TriMesh *mesh, glm::vec3 x) {
    std::sort(mesh->f.begin(), mesh->f.end(), [=](glm::ivec3& e1, glm::ivec3& e2){
        return glm::dot(mesh->v[e1[0]] - mesh->v[e2[0]], x) > 0;
    });
}

  易知實際中參與切片算法的面元是排序後的面元序列的一個子序列,當給出切片平面(點法式平面,法向量為Z軸)和面元在Z軸上的最大投影距離_gap_時,子序列提取算法如下:

// 2. 通過二分查找確定需要與平面求交的面元的最小有序子序列
std::array<std::vector<glm::ivec3>::iterator,2> getSliceInterval(TriMesh* mesh,glm::vec3 n, float d, float gap) {
    std::array<std::vector<glm::ivec3>::iterator,2> result = {
        std::lower_bound(mesh->f.begin(),mesh->f.end(), d-gap,
            [=](glm::ivec3& e1,float v){
        return v < glm::dot(mesh->v[e1[0]], n);
    }), std::lower_bound(mesh->f.begin(),mesh->f.end(), d+gap,
            [=](glm::ivec3& e1,float v){
        return v < glm::dot(mesh->v[e1[0]], n);
    })};

    if(result[0] > result[1]) std::swap(result[0],result[1]);
    return result;
}

  確定子序列的邊界,切片算法便只需要極少的面元與特定平面求交。面元與平面的相交判定與交線等幾何計算如下:

bool isFaceInersected(TriMesh* mesh,glm::ivec3 f, glm::vec3 n, float d){
    int flags = ((glm::dot( mesh->v[f[0]], n ) > d)+(glm::dot( mesh->v[f[1]], n ) > d)+(glm::dot( mesh->v[f[2]], n ) > d));
    return flags ==  1 || flags == 2;
}

std::array<glm::vec3,2> getFaceIntersection(TriMesh* mesh,glm::ivec3 f, glm::vec3 n, float d){
    std::array<glm::vec3,2> result;
    int size = 0;
    // dot(N, P1) + dot(N, t*(P1-P2) ) = d  // 0 < t < 1
    for(int i:{0,1,2})  {
        double numerator   =  d - glm::dot( mesh->v[f[i]], n);
        double denominator = glm::dot( mesh->v[f[i==2?0:i+1]] - mesh->v[f[i]], n );
        if(abs(denominator) > 1e-8) {
            numerator /= denominator;
            if(numerator >= 0 && numerator <= 1.0) {
                result[size++] = glm::vec3(f[i],f[i==2?0:i+1],numerator);
            }
        }
    }
    return result;
}

  3.得到的線段是無序的,不能夠滿足後續操作,如閉合數、特定閉合部分的平滑、特定部分的周長面積等幾何特征的計算,故需要對3.的結果有序化。考慮到實際網格規模下的交線的規模往往低於10000,算法設計采用了選擇排序,算法復雜度為_O(n*log(n))_,簡單描述如下:

//4. 通過選擇排序對3.生成的集合完成有序化,並得到閉合部分的劃分。
std::vector<std::array< std::vector<std::array<glm::vec3,2>>::iterator,2>> sortContours(std::vector<std::array<glm::vec3,2>>&& intersections) {
    std::vector<std::array< std::vector<std::array<glm::vec3,2>>::iterator,2>> intervals = {{intersections.begin(),intersections.begin()}};
    for(auto it = intersections.begin(); it != intersections.end(); it++) {
        auto find_it = std::find_if(std::next(it), intersections.end(), [=](std::array<glm::vec3,2>& e){
            return  (e[0][0] == (*it)[1][1] && e[0][1] == (*it)[1][0]) || (e[0][0] == (*it)[0][1] && e[0][1] == (*it)[0][0])
                    ||(e[1][0] == (*it)[0][1] && e[1][1] == (*it)[0][0]) || (e[1][0] == (*it)[1][1] && e[1][1] == (*it)[1][0]);
        });
        if(find_it != intersections.end()) {
            std::swap(*std::next(it),*find_it);
        }else{
            (*std::prev(intervals.end()))[1] = std::next(it);
            if(it != std::prev(intersections.end()))
                intervals.push_back({std::next(it),std::next(it)});
        }
    }
    return intervals;
}   

  到此,

最新機動車行駛證模板PSD可編輯分層文件下載

最新機動車行駛證模板PSD可編輯分層文件下載

最新機動車行駛證模板PSD可編輯分層文件下載

最新機動車行駛證模板PSD可編輯分層文件下載

最新機動車行駛證模板PSD可編輯分層文件下載

最新機動車行駛證模板PSD可編輯分層文件下載

最新機動車行駛證模板PSD可編輯分層文件下載

最新機動車行駛證模板PSD可編輯分層文件下載

最新機動車行駛證模板PSD可編輯分層文件下載

最新機動車行駛證模板PSD可編輯分層文件下載

最新機動車行駛證模板PSD可編輯分層文件下載

基於三角網格排序的人體三圍測量算法