简体   繁体   English

如何将曲线拟合到 3d 点云?

[英]How can I fit a curve to a 3d point cloud?

My goal is to fit a line through a point cloud.我的目标是通过点云拟合一条线。 The point cloud is approximately cylindrical but can be curved, which is why the fitted line should not be straight.点云近似圆柱形但可以弯曲,这就是为什么拟合线不应该是直的。 I've tried several things, but this is my current approach:我已经尝试了几件事,但这是我目前的方法:

I use PyTorch to optimise on a surface equation and compute the loss for each point.我使用 PyTorch 优化曲面方程并计算每个点的损失。 However, this does not seem to lead to “good” results, since the plane/surface does not cut the point cloud vertically, which I would expect to lead to the least error.然而,这似乎不会导致“好的”结果,因为平面/表面不会垂直切割点云,我希望这会导致最少的错误。 Since I'm new to PyTorch I don't know whether it's a mistake in my code or whether there's a mathematical problem with the idea.由于我是 PyTorch 的新手,我不知道这是我的代码中的错误还是这个想法是否存在数学问题。 This approach is in the code below.这种方法在下面的代码中。

Additionally, once I've fitted this surface, I would like to obtain a line on it, but I'm unsure on the best way to do this.此外,一旦我安装了这个表面,我想在上面画一条线,但我不确定最好的方法。 My questions are: Why is the fit of the plane/surface not more centred?我的问题是:为什么平面/曲面的拟合不更居中? How could I then obtain a curved line of best fit on the resulting surface?那么我怎样才能获得最适合结果表面的曲线呢?

Other approaches I've tried:我尝试过的其他方法:

  • SVD, but like I said, im looking for a line that is not straight SVD,但就像我说的,我正在寻找一条不直的线
  • Computing the mean of each z-slice (ie each horizontal slice) in the y and x direction and using the resulting points to fit a spline (using python's splrep).计算 y 和 x 方向上每个 z 切片(即每个水平切片)的平均值,并使用结果点来拟合样条(使用 python 的 splrep)。 The problem with this approach is that a couple of top or bottom slices can consist of very few points that are not necessarily in the “center” of the point cloud (eg are on the left extreme of the top slice), which forces the spline in that direction.这种方法的问题在于,几个顶部或底部切片可能由很少的点组成,这些点不一定位于点云的“中心”(例如,位于顶部切片的最左侧),这会强制样条在那个方向。 It's really important for the project that the line is “central”.这条线是“中心”对于项目来说真的很重要。

The data in this code example is just toy data, the real data would have a more complex shape.此代码示例中的数据只是玩具数据,真实数据的形状会更复杂。 To check the idea I'm fitting a plane, but in the end I'd like a more complex surface.为了验证我正在装配平面的想法,但最后我想要一个更复杂的曲面。 The code results in this figure .代码生成此图

import numpy as np
import torch
import matplotlib.pyplot as plt
from torch import nn
from torch.functional import F


ix = np.random.uniform(0,2, 1000)
iy = np.random.uniform(0,2, 1000)
iz = np.random.uniform(0,100, 1000)


x = torch.tensor(np.array([  ix, iy ]).T).float()
y = torch.tensor(iz).float()
degree =1


class Model(nn.Module):
    def __init__(self):
        super().__init__()
        if degree == 1 : 
            weights = torch.distributions.Uniform(-1, 1).sample((4,))
        if degree == 3 : 
            weights = torch.distributions.Uniform(-1, 1).sample((8,))

        self.weights = nn.Parameter(weights)        
        
    def forward(self, X):
        if degree ==1 : 
            a_1, a_2, a_3, a_4 = self.weights
            return (a_1 *X[:,0] + a_2*X[:,1] +a_3) 
        if degree == 3 : 
            a_1, a_2, a_3, a_4, a_5, a_6, a_7, a_8 = self.weights
            return (a_1 * X[:,0]**3 + a_2*X[:,1]**3  + a_3*X[:,0]**2 + a_4 *X[:,1] **2  + a_5 *X[:,0] + a_6 *X[:,1] + a_7)


def training_loop(model, optimizer):
    losses = []
    loss = 10000
    it = 0
    if degree == 3 : 
        lim = 0.1
    if degree == 1 : 
        lim = 0.1
    while loss > lim:
        it +=1
        if it > 5000:
            break
        preds1= model(x).float()
        l1 = torch.nn.L1Loss()
        loss = l1(preds1, y)
        loss.backward()
        optimizer.step()
        optimizer.zero_grad()
        losses.append(loss.detach().numpy())
        print(loss)  
    return losses


m = Model()
if degree == 1 :
    opt = torch.optim.Adam(m.parameters(), lr=0.01)
    losses = np.array(training_loop(m, opt))
if degree == 3 : 
    opt= torch.optim.Adam(m.parameters(), lr=0.001)
    losses = np.array(training_loop(m, opt))

params=list(m.parameters())[0].detach().numpy()

X = np.arange(0, 2, 0.1)
Y = np.arange(0, 2, 0.1)
X, Y = np.meshgrid(X, Y)

if degree == 1 : 
    Z = (params[0] * X + params[1]*Y + params[2])
if degree == 3: 
    Z = (params[0] * X**3 + params[1]*Y**3 + params[2]*X**2 + params[3]*Y**2 + params[4]*X + params[5]*Y + params[6])

fig = plt.figure()
ax = plt.axes(projection='3d')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
surf = ax.plot_surface(X, Y, Z, color='tab:orange', alpha = 0.5,linewidth=0, antialiased=False)
ax.scatter3D(ix,iy,iz, alpha = 0.3, s=2)
plt.show()

Edit: I tried the approach in this post: Fit Curve-Spline to 3D Point Cloud However this forces me to specify a source and target for the shortest path.编辑:我尝试了这篇文章中的方法: Fit Curve-Spline to 3D Point Cloud然而,这迫使我为最短路径指定源和目标。 Simply choosing the centers of the lowest and highest slice results in this:简单地选择最低和最高切片的中心会导致:

在此处输入图像描述

You can use Delaunay/Voronoi methods to get an approximation of the medial axis of the point cloud and pass a spline curve through it.您可以使用Delaunay/Voronoi方法来获得点云中轴的近似值,并通过它传递一条样条曲线。 See my previous answer here , which does exactly that for points sampled on a cylindrical surface.在此处查看我之前的回答,它对在圆柱面上采样的点执行的操作完全相同。 The figure below was taken from that answer.下图取自该答案。 If your point cloud also has points from inside the bounding surface (and not just samples from the outer surface), you can compute the 3D alpha-shape using the code from this answer and then just take the points on the outer surface and approximate the medial axis as I describe in the answer (or use a different method to extract the medial curve from the triangulated boundary surface).如果您的点云也有来自边界表面内部的点(而不仅仅是来自外表面的样本),您可以使用此答案中的代码计算 3D alpha 形状,然后只需取外表面上的点并近似我在答案中描述的中轴(或使用不同的方法从三角边界表面提取中曲线)。

在此处输入图像描述

If you get an accurate equation defining the cylinder, that should allow you to perform a cartesian (x,y,z) to cylindrical(r,theta,z) space transform.如果你得到一个精确的定义圆柱体的方程,那应该允许你执行笛卡尔 (x,y,z) 到圆柱 (r,theta,z) 空间变换。 The curved surface of the cylinder should resolve as a flat plane in the cylindrical space.圆柱体的曲面应分解为圆柱空间中的平面。 Whatever features and or critical locations from the cylinder can similarly be transformed into this space and be used to draw a line or spline or any other shape that you fancy.圆柱体的任何特征和/或关键位置都可以类似地转换到这个空间中,并用于绘制直线或样条曲线或您喜欢的任何其他形状。 Then simply transform the resulting line/spline/shape backwards (cylindrical to cartesian) and you have your curved thing that tracks the surface of the cylinder.然后简单地将生成的直线/样条曲线/形状向后转换(圆柱形到笛卡尔),你就有了跟踪圆柱体表面的弯曲的东西。

Keep in mind that to perform the cartesian to cylindrical transform, you only really need the center of mass and the primary axis of variation from the cylinder in question (obtained by pca or equivalent).请记住,要执行笛卡尔到圆柱的变换,您只需要质心和相关圆柱的主轴变化(通过 pca 或等效方法获得)。

I also think a devoted 3d processing library such as pcl or open3d is generally better for this type of thing.我还认为专门的 3d 处理库(例如pclopen3d )通常更适合此类事情。 (where you could run an optimized cylindrical ransac) (您可以在其中运行优化的圆柱形 ransac)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM