简体   繁体   English

Python VTK“标准化”点云

[英]Python VTK “normalize” a point cloud

I have done a lot of searching but have yet to find an answer. 我已经做了很多搜索,但是还没有找到答案。 I am currently working on some data of a crop field. 我目前正在处理一些农作物的数据。 I have PLY files for multiple fields which I have successfully read into, filtered, and visualised using Python and VTK. 我有用于多个字段的PLY文件,这些文件已使用Python和VTK成功读取,过滤和可视化。 My main goal is to eventually segment and run analysis on individual crop plots. 我的主要目标是最终对各个农作物地段进行细分并进行分析。

However to make that task easier I first want to "Normalize" my point cloud so that all plots are essentially "on the same level". 但是,为了简化该任务,我首先要对点云进行“归一化”,以便所有图基本上都处于“同一水平”上。 From the image I have attached you can see that the point clod slopes from one corner to its opposite. 从我附带的图像中,您可以看到点块从一个角向相反的角度倾斜。 So what I want to flatten out the image so the ground points are all on the same plane/ level. 因此,我想弄平图像,使接地点都在同一平面/水平上。 And the reset of the points adjusted accordingly. 并相应调整了积分的重置。

Point Cloud 点云

I've also included my code to show how I got to this point. 我还包含了我的代码,以说明如何达到这一点。 If anyone has any advice on how I can achieve the normalising to one plane I would be very appreciative. 如果有人对我如何实现对一架飞机的标准化有任何建议,我将非常感激。 Sadly I cannot include my data as it is work related. 遗憾的是,我无法包含我的数据,因为它与工作相关。

Thanks. 谢谢。 Josh 乔希

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()

I once solved a similar problem. 我曾经解决过类似的问题。 Find below some code that I used back then. 在下面找到我当时使用的一些代码。 It uses two functions fitPlane and findTransformFromVectors that you could replace with your own implementations. 它使用两个函数fitPlanefindTransformFromVectors ,您可以用自己的实现替换它们。

Note that there are many ways to fit a plane through a set of points. 请注意,有多种方法可以通过一组点拟合平面。 This SO post discusses compares scipy.optimize.minimize with scipy.linalg.lstsq . 该SO帖子讨论将scipy.optimize.minimizescipy.linalg.lstsq进行比较。 In another SO post , the use of PCA or RANSAC and other methods are suggested. 另一篇SO文章中 ,建议使用PCA或RANSAC以及其他方法。 You probably want to use methods provided by sklearn , numpy or other modules. 您可能要使用sklearnnumpy或其他模块提供的方法。 My solution simply (and non-robustly) computes ordinary least squares regression. 我的解决方案简单地(并且非稳健地)计算普通最小二乘回归。

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...

Here my implementations of fitPlane and findTransformFromVectors : 这是我的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

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

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