我在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偏移值。
答案 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
基于三角形,无法为双线性曲面生成正确的地图。
你真的需要表面是双线性的吗?如果是,通常无法避免解决非线性最小化问题以找到r
,s
坐标,因为坐标图本身是非线性的(您将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
是一个小的公差(靠近三角形平面的点)
epsilon
,0 <= c_1 <= 1
和0 <= c_2 <= 1
(三角形内的点投影)
在上面的示例中,我们发现该点位于第一个三角形中。对于第二个三角形,距离很小(0.05),但重心坐标表示投影位于三角形之外。
如果需要一次投射多个点,可以在上面的代码中进行一些优化等。
如果这仍然太慢,问题就会变得更难。 0 <= 1 - c_1 - c_2 <= 1
使用特殊技巧来查找正确的单纯形,但这些只适用于体网格。在这种情况下,我不知道快速算法,尽管在计算几何文献中可能有适合的东西。