【问题标题】:Python VTK "normalize" a point cloudPython VTK“标准化”点云
【发布时间】:2019-03-13 23:04:02
【问题描述】:

我已经做了很多搜索,但还没有找到答案。我目前正在研究农田的一些数据。我有多个字段的 PLY 文件,我已使用 Python 和 VTK 成功读取、过滤和可视化这些字段。我的主要目标是最终对单个作物地块进行分割和分析。

然而,为了使这项任务更容易,我首先想“标准化”我的点云,以便所有图基本上“在同一水平上”。从我附上的图片中,您可以看到点土块从一个角向其对面倾斜。所以我想要展平图像,使地面点都在同一平面/水平面上。并相应调整点的重置。

Point Cloud

我还包含了我的代码来展示我是如何做到这一点的。如果有人对我如何实现对一架飞机的正常化有任何建议,我将不胜感激。遗憾的是,我无法包含我的数据,因为它与工作相关。

谢谢。 乔什

import vtk
from vtk.util import numpy_support
import numpy as np

filename = 'File.ply'

# Reader
r = vtk.vtkPLYReader()
r.SetFileName(filename)

# Filters
vgf = vtk.vtkVertexGlyphFilter()
vgf.SetInputConnection(r.GetOutputPort())

# Elevation
pc = r.GetOutput()
bounds = pc.GetBounds()
#print(bounds)
minz = bounds[4]
maxz = bounds[5]
#print(bounds[4], bounds[5])
evgf = vtk.vtkElevationFilter()
evgf.SetInputConnection(vgf.GetOutputPort())
evgf.SetLowPoint(0, 0, minz)
evgf.SetHighPoint(0, 0, maxz)
#pc.GetNumberOfPoints()

# Look up table
lut = vtk.vtkLookupTable()
lut.SetHueRange(0.667, 0)
lut.SetSaturationRange(1, 1)
lut.SetValueRange(1, 1)
lut.Build

# Renderer
mapper = vtk.vtkPolyDataMapper()
mapper.SetInputConnection(evgf.GetOutputPort())
mapper.SetLookupTable(lut)

actor = vtk.vtkActor()
actor.SetMapper(mapper)

renderer = vtk.vtkRenderer()
renWin = vtk.vtkRenderWindow()
renWin.AddRenderer(renderer)
iren = vtk.vtkRenderWindowInteractor()
iren.SetRenderWindow(renWin)

renderer.AddActor(actor)
renderer.SetBackground(0, 0, 0) 

renWin.Render()
iren.Start()

【问题讨论】:

  • 如何计算点云上的线性回归,然后将结果平面转换为 XY 平面(使用一些假设)?
  • 感谢您的建议,我会尝试一下。我对使用 3D 点云和 VTK 还很陌生(现在大约 2 周)。实现您的建议的最佳方法是将数据转换为 numpy 数组,然后计算线性回归?
  • 有几种方法可以通过一组点计算参考平面。这些点是位于(完美)平面上,还是这些点来自(非平面)表面的噪声测量?
  • 非平面,主云下方有一些嘈杂的测量结果。

标签: python vtk point-clouds


【解决方案1】:

我曾经解决过一个类似的问题。在下面找到我当时使用的一些代码。它使用两个函数fitPlanefindTransformFromVectors,您可以用自己的实现替换它们。

请注意,有很多方法可以通过一组点来拟合平面。 This SO post 讨论将 scipy.optimize.minimizescipy.linalg.lstsq 进行比较。在another SO post中,建议使用PCA或RANSAC等方法。您可能想使用sklearnnumpy 或其他模块提供的方法。我的解决方案简单(且非稳健)计算普通最小二乘回归。

import vtk
import numpy as np
# Convert vtk to numpy arrays
from vtk.util.numpy_support import vtk_to_numpy as vtk2np

# Create a random point cloud.
center = [3.0, 2.0, 1.0]
source = vtk.vtkPointSource()
source.SetCenter(center)
source.SetNumberOfPoints(50)
source.SetRadius(1.)
source.Update()
source = source.GetOutput()
# Extract the points from the point cloud.
points = vtk2np(source.GetPoints().GetData())
points = points.transpose()

# Fit a plane. nRegression contains the normal vector of the
# regression surface.
nRegression = fitPlane(points)
# Compute a transform that maps the source center to the origin and
# plane normal to the z-axis.
trafo = findTransformFromVectors(originFrom=center,
                                 axisFrom=nRegression.transpose(),
                                 originTo=(0,0,0),
                                 axisTo=(0.,0.,1.))

# Apply transform to source.
sourceTransformed = vtk.vtkTransformFilter()
sourceTransformed.SetInputData(source)
sourceTransformed.SetTransform(trafo)
sourceTransformed.Update()

# Visualize output...

这是我对fitPlanefindTransformFromVectors 的实现:

# The following code has been written by normanius under the CC BY-SA 4.0 
# license.
# License:    https://creativecommons.org/licenses/by-sa/4.0/
# Author:     normanius: https://stackoverflow.com/users/3388962/normanius
# Date:       October 2018
# Reference:  https://stackoverflow.com/questions/52716438

def fitPlane(X, tolerance=1e-10):
    '''
    Estimate the plane normal by means of ordinary least dsquares.
    Requirement: points X span the full column rank. If the points lie in a
    perfect plane, the regression problem is ill-conditioned!

    Formulas:
        a = (XX^T)^(-1)*X*z
    Surface normal:
        n = [a[0], a[1], -1]
        n = n/norm(n)
    Plane intercept:
        c = a[2]/norm(n)

    NOTE: The condition number for the pseudo-inverse improves if the
          formulation is changed to homogenous notation.
    Formulas (homogenous):
        a = (XX^T)^(-1)*[1,1,1]^T
        n = a[:-1]
        n = n/norm(n)
        c = a[-1]/norm(n)

    Arguments:
        X:          A matrix with n rows and 3 columns
        tolerance:  Minimal condition number accepted. If the condition
                    number is lower, the algorithm returns None.

    Returns:
        If the computation was successful, a numpy array of length three is
        returned that represents the estimated plane normal. On failure,
        None is returned.
    '''
    X = np.asarray(X)
    d,N = X.shape
    X = np.vstack([X,np.ones([1,N])])
    z = np.ones([d+1,1])
    XXT = np.dot(X, np.transpose(X)) # XXT=X*X^T
    if np.linalg.det(XXT) < 1e-10:
        # The test covers the case where n<3
        return None
    n = np.dot(np.linalg.inv(XXT), z)
    intercept = n[-1]
    n = n[:-1]
    scale = np.linalg.norm(n)
    n /= scale
    intercept /= scale
    return n

def findTransformFromVectors(originFrom=None, axisFrom=None,
                             originTo=None, axisTo=None,
                             origin=None,
                             scale=1):
    '''
    Compute a transformation that maps originFrom and axisFrom to originTo
    and axisTo respectively. If scale is set to 'auto', the scale will be
    determined such that the axes will also match in length:
        scale = norm(axisTo)/norm(axisFrom)

    Arguments:  originFrom:     sequences with 3 elements, or None
                axisFrom:       sequences with 3 elements, or None
                originTo:       sequences with 3 elements, or None
                axisTo:         sequences with 3 elements, or None
                origin:         sequences with 3 elements, or None,
                                overrides originFrom and originTo if set
                scale:          - scalar (isotropic scaling)
                                - sequence with 3 elements (anisotropic scaling),
                                - 'auto' (sets scale such that input axes match
                                  in length after transforming axisFrom)
                                - None (no scaling)

    Align two axes alone, assuming that we sit on (0,0,0)
        findTransformFromVectors(axisFrom=a0, axisTo=a1)
    Align two axes in one point (all calls are equivalent):
        findTransformFromVectors(origin=o, axisFrom=a0, axisTo=a1)
        findTransformFromVectors(originFrom=o, axisFrom=a0, axisTo=a1)
        findTransformFromVectors(axisFrom=a0, originTo=o, axisTo=a1)
    Move between two points:
        findTransformFromVectors(orgin=o0, originTo=o1)
    Move from one position to the other and align axes:
        findTransformFromVectors(orgin=o0, axisFrom=a0, originTo=o1, axisTo=a1)
    '''
    # Prelude with trickle-down logic.
    # Infer the origins if an information is not set.
    if origin is not None:
        # Check for ambiguous input.
        assert(originFrom is None and originTo is None)
        originFrom = origin
        originTo = origin
    if originFrom is None:
        originFrom = originTo
    if originTo is None:
        originTo = originFrom
    if originTo is None:
        # We arrive here only if no origin information was set.
        originTo = [0.,0.,0.]
        originFrom = [0.,0.,0.]
    originFrom = np.asarray(originFrom)
    originTo = np.asarray(originTo)
    # Check if any rotation will be involved.
    axisFrom = np.asarray(axisFrom)
    axisTo = np.asarray(axisTo)
    axisFromL2 = np.linalg.norm(axisFrom)
    axisToL2 = np.linalg.norm(axisTo)
    if axisFrom is None or axisTo is None or axisFromL2==0 or axisToL2==0:
        rotate = False
    else:
        rotate = not np.array_equal(axisFrom, axisTo)
    # Scale.
    if scale is None:
        scale = 1.
    if scale == 'auto':
        scale = axisToL2/axisFromL2 if axisFromL2!=0. else 1.
    if np.isscalar(scale):
        scale = scale*np.ones(3)
    if rotate:
        rAxis = np.cross(axisFrom.ravel(), axisTo.ravel())  # rotation axis
        angle = np.dot(axisFrom, axisTo) / axisFromL2 / axisToL2
        angle = np.arccos(angle)

    # Here we finally compute the transform.
    trafo = vtk.vtkTransform()
    trafo.Translate(originTo)
    if rotate:
        trafo.RotateWXYZ(angle / np.pi * 180, rAxis[0], rAxis[1], rAxis[2])
    trafo.Scale(scale[0],scale[1],scale[2])
    trafo.Translate(-originFrom)
    return trafo

【讨论】:

  • @downvoters:我很高兴知道如何改进我的答案。
  • 抱歉,过了一段时间才回复您。你的方法对我很有效,谢谢。生成的云中有一些小的波动,但它是对我原来的云的巨大改进。
猜你喜欢
  • 1970-01-01
  • 2022-11-29
  • 1970-01-01
  • 2022-07-29
  • 2019-03-09
  • 2018-05-19
  • 1970-01-01
  • 2017-09-04
  • 2014-12-10
相关资源
最近更新 更多