Python / Scipy 3D双线性曲面映射到单位正方形

时间:2011-03-05 01:28:33

标签: python mapping coordinates scipy transformation

我在3D中有一个双线性曲面(由4个顶点之间的线条定义的曲面)。我想将此曲面映射到单位平方。我知道如何进行这种映射,我知道如何在给定单位平方的参数坐标(比如r,s)的情况下计算点的x,y,z坐标。但是,我需要走另一条路。我有x,y,z点(我知道它位于表面上),我想确定这一点的参数坐标。

我认为我可以使用griddata来做这个,因为它只是坐标的线性映射。在某些情况下,这很有效,我得到了正在寻找的正确的r,s值。但在其他情况下,我从Qhull返回一个错误。我想知道是否有另一种方法来获得给定x,y,z的r,s坐标。

这是一个工作示例(按照我的意愿工作):

from numpy import array
from scipy.interpolate import griddata

P1 = array([[-1.0, -1.0,  1.0],
            [ 1.0, -1.0,  0.0],
            [ 1.0,  1.0,  0.0],
            [-1.0,  1.0,  0.0]])

Mapped_Corners = array([array([0.0, 0.0, 0.0]),
                        array([1.0, 0.0, 0.0]),
                        array([1.0, 1.0, 0.0]),
                        array([0.0, 1.0, 0.0])])

Point_On_Surface = array([[0.5, 0.5, 0.25]])
print griddata(P1, Mapped_Corners, Point_On_Surface, method='linear')

现在,将P1和Point_On_Surface更改为以下内容,我从Qhull得到一个很长的错误:

P1 = array([[-1.0, -1.0,  0.0],
            [ 1.0, -1.0,  0.0],
            [ 1.0,  1.0,  0.0],
            [-1.0,  1.0,  0.0]])
Point_On_Surface = array([[0.5, 0.5, 0.0]])

还有其他方法可以计算双线性曲面上某点的参数坐标吗?我意识到在失败的情况下,所有'z'值都为零。我认为这是它失败的原因的一部分,但我想要一个通用算法,它将任何表面贴片(包括所有z值为零的平面一个或一个)映射到单位平方(z值为零)。在我的程序中,那些z值可以是任何值,包括全部为零......

就像一个FYI ...如果我从P1输出最后一列(z = 0)以及从Point_On_Surface获取z坐标,我确实注意到它正常工作。但我想知道在没有特殊情况下这样做我忽略了一些专栏等等。

一般来说,将双线性曲面的参数坐标转换为相应的x,y,z坐标(P1-P3的正确方向)是一个简单的函数:

xyz = P0*(1-r)*(1-s) + P1*(r)*(1-s) + P2*(r)*(s) + P3*(1-r)*(s)

其中P0,P1,P2和P3是P1中的点。

修改

在接受Paul和pv的建议后,我创建了以下代码,这似乎是要走的路。很抱歉很长。具有最小二乘法的解决方案比使用fmin更快,更快。此外,我通过自己指定雅各比亚而不是让minimalsq估计它来获得显着的时间改进。

当按原样运行时,最小化的结果应等于任何输入的输入'p'(因为指定的SurfPts等于单位平方本身)。然后您可以取消注释SurfPts的第二个定义以及为'p'列出的任何其他值以查看其他结果(投射到曲面上的[-20,15,4]点是下一个点,-19 .. ... - >因此r,s输出应该相同,但偏移量z应该不同)。

输出[r,s,z]将是r,s系统中的坐标,z是垂直于曲面的点的偏移量。这非常方便,因为它的输出提供了大量有用的信息,因为函数基本上“外推”表面(因为它在每个方向上是线性的)。因此,如果r和s超出范围[0,1],那么该点将被投射到双线性表面之外(因此,如果出于我的目的,我会忽略)。然后你还得到垂直于曲面的点的偏移量(z值)。我会忽略z,因为我只关心点投影的位置,但这对某些应用程序可能很有用。我已经用几个表面和点(偏移而不是偏移以及表面外部和内部)测试了这个代码,它似乎适用于我迄今为止尝试过的所有内容。

CODE:

from numpy          import array, cross, set_printoptions
from numpy.linalg   import norm
from scipy.optimize import fmin, leastsq
set_printoptions(precision=6, suppress=True)


#-------------------------------------------------------------------------------
#---[ Shape Functions ]---------------------------------------------------------
#-------------------------------------------------------------------------------
# These are the 4 shape functions for an isoparametric unit square with vertices
# at (0,0), (1,0), (1,1), (0,1)
def N1(r, s): return (1-r)*(1-s)
def N2(r, s): return (  r)*(1-s)
def N3(r, s): return (  r)*(  s)
def N4(r, s): return (1-r)*(  s)


#-------------------------------------------------------------------------------
#---[ Surface Tangent (Derivative) along r and s Parametric Directions ]--------
#-------------------------------------------------------------------------------
# This is the dervative of the unit square with respect to r and s
# The vertices are at (0,0), (1,0), (1,1), (0,1)
def dnT_dr(r, s, Pt):
   return (-Pt[0]*(1-s) + Pt[1]*(1-s) + Pt[2]*(s) - Pt[3]*(s))
def dnT_ds(r, s, Pt):
   return (-Pt[0]*(1-r) - Pt[1]*(r) + Pt[2]*(r) + Pt[3]*(1-r))


#-------------------------------------------------------------------------------
#---[ Jacobian ]----------------------------------------------------------------
#-------------------------------------------------------------------------------
# The Jacobian matrix.  The last row is 1 since the parametric coordinates have
# just r and s, no third dimension for the parametric flat surface
def J(arg, Pt, SurfPoints):
   return array([dnT_dr(arg[0], arg[1], SurfPoints),
                 dnT_ds(arg[0], arg[1], SurfPoints),
                 array([1.,     1.,     1.])])


#-------------------------------------------------------------------------------
#---[ Surface Normal ]----------------------------------------------------------
#-------------------------------------------------------------------------------
# This is the normal vector in x,y,z at any location of r,s
def Norm(r, s, Pt):
   cross_prod = cross(dnT_dr(r, s, Pt), dnT_ds(r, s, Pt))
   return cross_prod / norm(cross_prod)


#-------------------------------------------------------------------------------
#---[ Bilinear Surface Function ]-----------------------------------------------
#-------------------------------------------------------------------------------
# This function converts coordinates in (r, s) to (x, y, z).  When 'normOffset'
# is non-zero, then the point is projected in the direction of the local surface
# normal by that many units (in the x,y,z system)
def nTz(r, s, normOffset, Pt):
   return Pt[0]*N1(r,s) + Pt[1]*N2(r,s) + Pt[2]*N3(r,s) + Pt[3]*N4(r,s) + \
          normOffset*Norm(r, s, Pt)


#-------------------------------------------------------------------------------
#---[ Cost Functions (to minimize) ]--------------------------------------------
#-------------------------------------------------------------------------------
def minFunc(arg, Pt, SurfPoints):
   return norm(nTz(arg[0], arg[1], arg[2], SurfPoints) - Pt)
def lstSqFunc(arg, Pt, SurfPoints):
   return (nTz(arg[0], arg[1], arg[2], SurfPoints) - Pt)


#-------------------------------------------------------------------------------
# ---[ The python script starts here! ]-----------------------------------------
#-------------------------------------------------------------------------------
if __name__ == '__main__':
   # The points defining the surface
   SurfPts = array([array([0.0, 0.0, 0.0]),
                    array([1.0, 0.0, 0.0]),
                    array([1.0, 1.0, 0.0]),
                    array([0.0, 1.0, 0.0])])
   #SurfPts = array([array([-48.62664,        68.346764,  0.3870956   ]),
   #                 array([-56.986549,      -27.516319, -0.70402116  ]),
   #                 array([  0.0080659632,  -32.913471,  0.0068369969]),
   #                 array([ -0.00028359704,  66.750908,  1.6197989   ])])

   # The input point (in x,y,z) where we want the (r,s,z) coordinates of
   p = array([0.1, 1.0, 0.05])
   #p = array([-20., 15.,  4. ])
   #p = array([-19.931894, 15.049718,  0.40244904 ])
   #p = array([20., 15.,  4. ])

   # Make the first guess be the center of the parametric element
   FirstGuess = [0.5, 0.5, 0.0]

   import timeit
   repeats = 100
   def t0():
      return leastsq(lstSqFunc, FirstGuess, args=(p,SurfPts), Dfun=None)[0]
   def t1():
      return leastsq(lstSqFunc, FirstGuess, args=(p,SurfPts), Dfun=J,
                     col_deriv=True)[0]
   def t2():
      return fmin(minFunc, FirstGuess, args=(p,SurfPts), xtol=1.e-6, ftol=1.e-6,
                  disp=False)

   print 'Order:'
   print 'Least Squares, No Jacobian Specified'
   print 'Least Squares, Jacobian Specified'
   print 'Fmin'

   print '\nResults:'
   print t0()
   print t1()
   print t2()

   print '\nTiming for %d runs:' % repeats
   t = timeit.Timer('t0()', 'from __main__ import t0')
   print round(t.timeit(repeats), 6)
   t = timeit.Timer('t1()', 'from __main__ import t1')
   print round(t.timeit(repeats), 6)
   t = timeit.Timer('t2()', 'from __main__ import t2')
   print round(t.timeit(repeats), 6)

另外,如果你不关心'z'偏移,你可以将雅可比矩阵的最后一行设置为全零,而不是现在的那些。这将进一步加快计算速度,r和s的值仍然正确,你只是没有得到z偏移值。

2 个答案:

答案 0 :(得分:1)

可以回答您的问题。我在评论中描述了代码中的问题。这描述了一种获取坐标的方法,该方法试图回答问题的文本,忽略代码。

这只是在高度z_target的任意曲面上找到点的一种方法。例如,如果你知道表面是分段平面,或者单调增加或者其他什么,那么有更有效的方法可以做到这一点。对于你有意生成表面的情况,这种方法有点过分

当然,表面上有无数个点符合z_target(想想轮廓线)。找到一个任意点可能没什么价值。

无论如何,这将始终寻找符合目标表面高度的x,y对坐标。将有多个符合目标的坐标,但如果存在,则会找到一个。

from numpy import array
from scipy.interpolate import LinearNDInterpolator
from scipy.optimize import fmin

# X,Y points (unstructured)
points = array([
    [-1.0, -1.0],
    [ 1.0, -1.0],
    [ 1.0,  1.0],
    [-1.0,  1.0]])

# Z - height of surface at points given above
Z = array([5.,13.,23.,4.0])

# interpolant (function that interpoates)
# this function will return the interpolated z value for any given point
# this is the interpolant being used for griddata(method='linear')
# check numpy.source(griddata) to see for yourself.
interp = LinearNDInterpolator(points, Z)

# this is the desired z value for which we want to know the coordinates
# there is no guarantee that there is only one pair of points that
# returns this value.
z_target = 7.0

# so we select an initial guess for the coordinates.
# we will make a root finding function to find a point near this one
point0 = array((0.5, 0.5))

# this is the cost function.  It tells us how far we are from our target
# ideally we want this function to return zero
def f(arr):
    return (interp(*arr) - z_target)**2

# run the downhill simplex algorithm to root-find the zero of our cost function.
# the result should be the coords of a point who's height is z_target
# there are other solvers besides fmin
print fmin(f, x0)

答案 1 :(得分:1)

1)Griddata不能这样做,因为它只进行体积插值。 Surface的体积为零,因此该用例超出了griddata的范围,因此出现了错误。此外,由于浮点只有有限的精度,通常没有点恰好位于曲面上。

griddata的初始尝试在任何情况下都无法正常运行:griddata基于三角形,无法为双线性曲面生成正确的地图。

你真的需要表面是双线性的吗?如果是,通常无法避免解决非线性最小化问题以找到rs坐标,因为坐标图本身是非线性的(您将r乘以{{ 1}}在其中)。这些问题可以解决,例如与s

scipy.optimize.leastsq

2)如果您对三角形表面感到满意,每个三角形都是平坦的,那么您可以通过线性投影到三角形来解决问题。

因此,我们假设您的曲面由平面三角形组成。问题分为两个子问题:(A)找到一个3D点到一个给定三角形上的投影,以及(B)找到该点投射到的三角形。

给定三角形的角A,B,C(在3D中),三角形内的任何点都可以以# untested code, but gives the idea def func(c): r, s = c xyz = P0*(1-r)*(1-s) + P1*(r)*(1-s) + P2*(r)*(s) + P3*(1-r)*(s) return xyz - Point_on_Surface res = scipy.optimize.leastsq(func, [0, 0]) r, s = res[0] 的形式写出。 P = c_1 A + c_2 B + (1 - c_1 - c_2) C指定三角形上的重心坐标系。给定3D中的点c_k,然后我们可以通过P找到三角形上与其最接近的点的重心坐标。因此,使用c = np.linalg.lstsq([A-C, B-C], P-C)

V[0], V[1], V[2] = A, B, C

如果您需要重复执行此操作,请注意def project_onto_triangle(P, V): c, distance, rank, s = np.linalg.lstsq(np.c_[V[0] - V[2], V[1] - V[2]], P - V[2]) return c, distance Point_on_Surface = 0.25 * P1[0] + 0.25 * P1[1] + 0.5 * P1[2] c, dist = project_onto_triangle(Point_on_Surface, P1[0:3]) # >>> c # array([ 0.25, 0.25]) # barycentric coordinates # >>> dist # array([ 4.07457566e-33]) # the distance of the point from the triangle plane # second triangle c2, dist2 = project_onto_triangle(Point_on_Surface, [P1[0], P1[2], P1[3]]) # >>> c2 # array([ 0.45, 0.75]) # >>> dist2 # array([ 0.05]) np.linalg.lstsq(V, P - C) == Q.dot(P - C) ---,以便预先计算投影机矩阵Q = np.linalg.pinv(V)。然后获得的Q坐标会告诉您单位平方的坐标。

您将需要遍历所有三角形以找到该点所在的三角形。如果满足以下两个条件,则三角形上的点为:

  • c,其中dist < epsilon是一个小的公差(靠近三角形平面的点)

  • epsilon0 <= c_1 <= 10 <= c_2 <= 1(三角形内的点投影)

在上面的示例中,我们发现该点位于第一个三角形中。对于第二个三角形,距离很小(0.05),但重心坐标表示投影位于三角形之外。

如果需要一次投射多个点,可以在上面的代码中进行一些优化等。

如果这仍然太慢,问题就会变得更难。 0 <= 1 - c_1 - c_2 <= 1使用特殊技巧来查找正确的单纯形,但这些只适用于体网格。在这种情况下,我不知道快速算法,尽管在计算几何文献中可能有适合的东西。