簡體   English   中英

將曲線樣條擬合到 3D 點雲

[英]Fit Curve-Spline to 3D Point Cloud

客觀的

我有一個 3D facet 模型(例如 .off 文件),例如它看起來像一個管道(見示例圖片)。 目標是使用 python 推導出表示該管的 3D 骨架的近似樣條(直線和樣條的最佳組合)。

最先進的

同一領域的 Stackoverflow 帖子:

一般的:

我的方法(到目前為止)

從示例 facet 模型(圖 1)開始,我使用 python 包將 3d 模型轉換為點雲(圖 2)。 該點雲可用於體素化表示(圖 3)。 因此,這三種類型的數據是我的起點。

基本上,這個問題對我來說似乎並不復雜,但是我缺少一個起始邏輯。 大多數研究論文對於各種更深入的任務來說都過於復雜了。 一種想法是進行 PCA 以導出組件的主軸,並沿這些軸進行掃描。 然而,這似乎並沒有以一種高性能的方式產生好的結果。 另一個想法是使用體素化網格並檢測由於體素鄰接而產生的路徑。 另一個想法是使用 KD-Tree 來評估最近的點,以檢測正確的平面,通過它們的平面法線定義樣條方向。

我嘗試的一種方法是從點雲中選擇 N 個隨機點並搜索半徑內的所有鄰居 (cKDTree.query_ball_point)。 我計算了所有相鄰點的中心。 這導致了圖像 4 中的結果。結果似乎與第一種方法一樣好,但它或多或少是半徑參數的調整。

圖 1:

初始點

圖 2: 點雲

圖 3: 體素網格

圖 4: 在此處輸入圖片說明

Delaunay/Voronoi 方法可用於此問題,因為中軸是 Voronoi 圖的子圖(例如,參見 Attali、Boissonnat 和 Edelsbrunner 的這篇論文)。

在下文中,我將演示從小半徑 10 和大半徑 100 的四分之一圓環表面采樣的點的示例(中間路徑/骨架從點 (100, 0, 0) 開始,到 (0, 100) 結束) , 0))。

Voronoi 圖是 3D Delaunay 四面體化的對偶(從現在開始我將使用術語三角剖分)。 可以使用 scipy 的scipy.spatial.Delaunay包計算 Delaunay 三角剖分。

下圖是樣本點(在本例中為 200)及其完整的 Delaunay 三角剖分(三角剖分是使用此處的函數繪制的)。 全三角剖分

對應於 Delaunay 四面體的 Voronoi 頂點是四面體外接球面的中心。 以下是計算這些 Delaunay 中心的函數,它是我之前在此處的答案中對 2D 函數的擴展。

def compute_delaunay_tetra_circumcenters(dt):
"""
Compute the centers of the circumscribing circle of each tetrahedron in the Delaunay triangulation.
:param dt: the Delaunay triangulation
:return: array of xyz points
"""
simp_pts = dt.points[dt.simplices]
# (n, 4, 3) array of tetrahedra points where simp_pts[i, j, :] holds the j'th 3D point (of four) of the i'th tetrahedron
assert simp_pts.shape[1] == 4 and simp_pts.shape[2] == 3

# finding the circumcenter (x, y, z) of a simplex defined by four points:
# (x-x0)**2 + (y-y0)**2 + (z-z0)**2 = (x-x1)**2 + (y-y1)**2 + (z-z1)**2
# (x-x0)**2 + (y-y0)**2 + (z-z0)**2 = (x-x2)**2 + (y-y2)**2 + (z-z2)**2
# (x-x0)**2 + (y-y0)**2 + (z-z0)**2 = (x-x3)**2 + (y-y3)**2 + (z-z3)**2
# becomes three linear equations (squares are canceled):
# 2(x1-x0)*x + 2(y1-y0)*y + 2(z1-z0)*y = (x1**2 + y1**2 + z1**2) - (x0**2 + y0**2 + z0**2)
# 2(x2-x0)*x + 2(y2-y0)*y + 2(z2-z0)*y = (x2**2 + y2**2 + z2**2) - (x0**2 + y0**2 + z0**2)
# 2(x3-x0)*x + 2(y3-y0)*y + 2(z3-z0)*y = (x3**2 + y3**2 + z3**2) - (x0**2 + y0**2 + z0**2)

# building the 3x3 matrix of the linear equations
a = 2 * (simp_pts[:, 1, 0] - simp_pts[:, 0, 0])
b = 2 * (simp_pts[:, 1, 1] - simp_pts[:, 0, 1])
c = 2 * (simp_pts[:, 1, 2] - simp_pts[:, 0, 2])
d = 2 * (simp_pts[:, 2, 0] - simp_pts[:, 0, 0])
e = 2 * (simp_pts[:, 2, 1] - simp_pts[:, 0, 1])
f = 2 * (simp_pts[:, 2, 2] - simp_pts[:, 0, 2])
g = 2 * (simp_pts[:, 3, 0] - simp_pts[:, 0, 0])
h = 2 * (simp_pts[:, 3, 1] - simp_pts[:, 0, 1])
i = 2 * (simp_pts[:, 3, 2] - simp_pts[:, 0, 2])

v1 = (simp_pts[:, 1, 0] ** 2 + simp_pts[:, 1, 1] ** 2 + simp_pts[:, 1, 2] ** 2) - (simp_pts[:, 0, 0] ** 2 + simp_pts[:, 0, 1] ** 2 + simp_pts[:, 0, 2] ** 2)
v2 = (simp_pts[:, 2, 0] ** 2 + simp_pts[:, 2, 1] ** 2 + simp_pts[:, 2, 2] ** 2) - (simp_pts[:, 0, 0] ** 2 + simp_pts[:, 0, 1] ** 2 + simp_pts[:, 0, 2] ** 2)
v3 = (simp_pts[:, 3, 0] ** 2 + simp_pts[:, 3, 1] ** 2 + simp_pts[:, 3, 2] ** 2) - (simp_pts[:, 0, 0] ** 2 + simp_pts[:, 0, 1] ** 2 + simp_pts[:, 0, 2] ** 2)

# solve a 3x3 system by inversion (see https://en.wikipedia.org/wiki/Invertible_matrix#Inversion_of_3_%C3%97_3_matrices)
A = e*i - f*h
B = -(d*i - f*g)
C = d*h - e*g
D = -(b*i - c*h)
E = a*i - c*g
F = -(a*h - b*g)
G = b*f - c*e
H = -(a*f - c*d)
I = a*e - b*d

det = a*A + b*B + c*C

# multiplying inv*[v1, v2, v3] to get solution point (x, y, z)
x = (A*v1 + D*v2 + G*v3) / det
y = (B*v1 + E*v2 + H*v3) / det
z = (C*v1 + F*v2 + I*v3) / det

return (np.vstack((x, y, z))).T

我們想過濾掉原始表面之外的四面體(例如參見上圖中的長四面體)。 這可以通過在原始表面上測試四面體來完成。 然而,一種非常適合輸入管/管道表面的更簡單的方法是過濾掉具有大外接半徑的四面體。 這就是alpha-shape算法所做的。 這在我們的上下文中很容易完成,因為半徑只是中心和任何四面體點之間的距離。

下圖顯示了過濾掉半徑大於 20 的四面體后的 Delaunay 三角剖分。

3d 阿爾法形狀

我們現在可以使用這些構建塊來構建通過半徑條件的四面體的 Voronoi 子圖。 下面的函數使用 Delaunay 三角剖分中的連通性信息來構建 Voronoi 子圖,表示為邊列表。

def compute_voronoi_vertices_and_edges(points, r_thresh=np.inf):
"""
Compute (finite) Voronoi edges and vertices of a set of points.
:param points: input points.
:param r_thresh: radius value for filtering out vertices corresponding to
Delaunay tetrahedrons with large radii of circumscribing sphere (alpha-shape condition).
:return: array of xyz Voronoi vertex points and an edge list.
"""
dt = Delaunay(points)
xyz_centers = compute_delaunay_tetra_circumcenters(dt)

# filtering tetrahedrons that have radius > thresh
simp_pts_0 = dt.points[dt.simplices[:, 0]]
radii = np.linalg.norm(xyz_centers - simp_pts_0, axis=1)
is_in = radii < r_thresh

# build an edge list from (filtered) tetrahedrons neighbor relations
edge_lst = []
for i in range(len(dt.neighbors)):
    if not is_in[i]:
        continue  # i is an outside tetra
    for j in dt.neighbors[i]:
        if j != -1 and is_in[j]:
            edge_lst.append((i, j))

return xyz_centers, edge_lst

結果仍然不夠充分,如下圖所示,其中子圖邊緣是黑色線段。 原因是 3D Delaunay 三角剖分因具有細四面體(Shewchuk 在本文中稱為條子、針)而臭名昭著,這會導致結果中出現外部“尖峰”。

Voronoi 子圖

雖然有去除這些不需要的尖峰的通用方法(例如,參見Amenta 和 Bern ),但在管表面的情況下有一個簡單的解決方案。 我們正在尋找的路徑可以計算為圖中最短的歐幾里得路徑,從最接近管起點的點開始,到最接近終點的點結束。 以下代碼使用權重設置為邊長度的networkx圖執行此操作。

# get closest vertex to start and end points
xyz_centers, edge_lst = compute_voronoi_vertices_and_edges(pts, r_thresh=20.)
kdt = cKDTree(xyz_centers)
dist0, idx0 = kdt.query(np.array([100., 0, 0]))
dist1, idx1 = kdt.query(np.array([0, 100., 0]))

# compute shortest weighted path
edge_lengths = [np.linalg.norm(xyz_centers[e[0], :] - xyz_centers[e[1], :]) for e in edge_lst]
g = nx.Graph((i, j, {'weight': dist}) for (i, j), dist in zip(edge_lst, edge_lengths))
path_s = nx.shortest_path(g,source=idx0,target=idx1, weight='weight')

下圖顯示了原始 200 點的結果。

200分的最終路徑

這是 1000 個點的更密集樣本的結果。

1000 點的最終路徑

現在您可以通過路徑點傳遞近似樣條 - 插值或最小二乘擬合。 您可以按照鏈接中的建議使用scipy.interpolate.UnivariateSplinescipy.interpolate.splrep如此或任何其他標准樣條實現。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM