我正在尝试使用scipy.odr为某些x,y,z点获得最佳拟合平面。
我将平面方程隐式定义为ax + by + cz + d = 0,并执行最小二乘(使用scipy.linalg.lstsq)为odr提供初始估计。
由odr返回的β矢量的组成部分(其中beta = [a,b,c,d])的幅度在1e167和1e172之间...这样的结果值得信赖吗?我觉得这些数字很荒谬......
请注意,这些点来自于相对平坦的面的3D扫描,该面几乎平行于xz平面(几乎垂直)。
这是odr结果的pprint():
'
Beta: [ 3.14570111e-170 3.21821458e-169 4.49232028e-172 4.49374557e-167]
Beta Std Error: [ 0. 0. 0. 0.]
Beta Covariance: [[ 6.37459471e-10 -8.57690019e-09 -2.18092934e-11 -1.13009384e-06]
[ -8.57690019e-09 5.11732570e-07 1.30123070e-09 6.74263262e-05]
[ -2.18092934e-11 1.30123070e-09 5.22674068e-12 1.70799469e-07]
[ -1.13009384e-06 6.74263262e-05 1.70799469e-07 8.88444676e-03]]
Residual Variance: 0.0
Inverse Condition #: 0.0010484041422201213
Reason(s) for Halting:
Sum of squares convergence
None
'
我正在使用的代码:
import numpy as np
import scipy.linalg
from scipy import odr
import pickle
def planar_fit(points):
# best-fit linear plane
a = np.c_[points[:, 0], points[:, 1], np.ones(points.shape[0])]
c, _, _, _ = scipy.linalg.lstsq(a, points[:, 2]) # coefficients
# The coefficients are returned as an array beta=[a, b, c, d] from the implicit form 'a*x + b*y + c*z + d = 0'.
beta = np.r_[c[0], c[1], -1, c[2]] / c[2]
return beta
def odr_planar_fit(points):
def f_3(beta, xyz):
""" implicit definition of the plane"""
return beta[0] * xyz[0] + beta[1] * xyz[1] + beta[2] * xyz[2] + beta[3]
# # Coordinates of the 2D points
x = points[:, 0]
y = points[:, 1]
z = points[:, 2]
# Use least squares for initial estimate.
beta0 = planar_fit(points)
# Create the data object for the odr. The equation is given in the implicit form 'a*x + b*y + c*z + d = 0' and
# beta=[a, b, c, d] (beta is the vector to be fitted). The positional argument y=1 means that the dimensionality
# of the fitting is 1.
lsc_data = odr.Data(np.row_stack([x, y, z]), y=1)
# Create the odr model
lsc_model = odr.Model(f_3, implicit=True)
# Create the odr object based on the data, the model and the first estimation vector.
lsc_odr = odr.ODR(lsc_data, lsc_model, beta0)
# run the regression.
lsc_out = lsc_odr.run()
return lsc_out, beta0
def main():
#import from pickle.
with open('./points.pkl', 'rb') as f:
points = np.array(pickle.load(f))
# Perform the ODR
odr_out, lstsq = odr_planar_fit(points)
print(lstsq)
print(odr_out.pprint())
main()
答案 0 :(得分:0)
据我所知Traceback (most recent call last):
File "C:/Users/zahid/PycharmProjects/untitled3/test.py", line 1, in <module>
from pywinauto.application import Application
ModuleNotFoundError: No module named 'pywinauto'
它不是为3D数据制作的,但我可能在这里错了。由于这是一个简单的平面拟合,我建议使用简单的odr
。此外,请注意您实际上没有4个免费参数,因为您可以划分leastsq
,例如a * x + b * y + c * z + d = 0
提供d
(如果a' * x + b' * y + c' * z + 1 = 0
不为零)。
如果我们用以下形式编写飞机:所有点d
P
我们已经免费获得了odr函数。可以通过假设平面偏移向量(P - p0) * n = 0
是缩放的法向量来简化。像这样有3个自由参数,比例p0 = s * n
和法线向量s
的方向角。
根据拟合如下
(theta, phi)
提供了
蓝点是噪声数据,紫色是拟合平面,红色是偏移矢量。
很容易看出,import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
from scipy.optimize import leastsq
from random import random
# for rotating test data
def y_mx( theta ):
out = np.array( [ np.cos( theta ),0, np.sin( theta ), 0, 1, 0, -np.sin( theta ),0, np.cos( theta ) ] )
return out.reshape( 3, 3)
# for rotating test data
def z_mx( theta ):
out = np.array( [ np.cos( theta ), np.sin( theta ), 0, -np.sin( theta ), np.cos( theta ), 0, 0, 0, 1 ] )
return out.reshape( 3, 3)
# for test data
def make_plane( theta, phi, px, py, pz, n=100 ):
points=[]
for i in range( n ):
x = 1 - 2 * random( )
y = 1 - 2 * random( )
z = 0.15 * ( 1 - 2 * random() )
points += [ np.array( [ x, y, z] ) ]
points = np.array( points)
points = [ np.array( [px, py, pz ] ) + np.dot( z_mx( phi ), np.dot( y_mx( theta ) , p ) ) for p in points ]
return np.array( points )
# residual function for leastsq
# note the plane equation is (P - p0) n = 0 if P is member of plane
# and n is normal vector of plane directly provides the normal distance function
# moreover p0 can be chosen to be s * n
def residuals( params, points ):
scale, theta, phi = params
nVector = np.array( [ np.sin( theta ) * np.cos( phi ), np.sin( theta ) * np.sin( phi ), np.cos( theta ) ] )
p0 = scale * nVector
diff = [ np.dot( p - p0, nVector ) for p in points]
return diff
# some test data
pnts = make_plane( 1.5, 1.49, .15, .2, .33)
#and the fit
guess=[ 0, 0, 0 ]
bestfit, err = leastsq( residuals, guess, pnts )
#the resulting normal vectot and offset
nVectorFit = np.array( [ np.sin( bestfit[1] ) * np.cos( bestfit[2] ), np.sin( bestfit[1] ) * np.sin( bestfit[2] ), np.cos( bestfit[1] ) ] )
p0Fit = bestfit[0] * nVectorFit
# converting to standard plane equation
a = nVectorFit[0] / nVectorFit[1]
c = nVectorFit[2] / nVectorFit[1]
d = bestfit[0] / nVectorFit[1]
# plane equation data
X = np.linspace( -.6, .6, 20 )
Z = np.linspace( -.6, .6, 20 )
XX, ZZ = np.meshgrid( X, Z )
YY = -a * XX - c * ZZ + d
#plotting
fig = plt.figure()
ax = fig.add_subplot( 1, 1, 1, projection='3d')
# original data
ax.scatter( pnts[:,0], pnts[:,1] , pnts[:,2])
# offset vector
ax.plot( [0, p0Fit[0] ], [0, p0Fit[1] ], [0, p0Fit[2] ], color = 'r')
# fitted plane
ax.plot_wireframe(XX, YY, ZZ , color = '#9900bb')
ax.set_xlim( [-1,1] )
ax.set_ylim( [-1,1] )
ax.set_zlim( [-1,1] )
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("z")
plt.show()
,y = a * x + c * z + d
的情况是从拟合结果中直接计算的。
答案 1 :(得分:0)
ODR完全适合多维数据,您的方向正确。
您只是选择不好,将ODR的隐式版本与f_3
定义一起使用。问题是您有一个函数A*X=0
,在没有任何其他约束的情况下尝试将其最小化。当然,优化器可以做的最好的事情就是将A
的幅度最小化为零-从而最大程度地减少错误!为了使隐式优化起作用,您需要以某种方式引入对A
的大小的约束,例如除以最后一个数字:
def f_3(beta, xyz):
""" implicit definition of the plane"""
return beta[0]/beta[3] * xyz[0] + beta[1]/beta[3] * xyz[1] + beta[2]/beta[3] * xyz[2] + 1.0
这样,优化器除了执行您想要的操作之外别无选择:)
或者,您也可以将模型转换为显式形式y = ax + cz + d
,该形式不会受到幅度问题的困扰(一直以来都是b == 1
)。
当然,通过将点移至原点并将其缩放为距离的单位差异,可以获得更高的精度。
由于我也将要使用ODR,所以我对它的属性感到好奇,因此我四处探索以发现它的精确度和灵敏性,结果如下:https://gist.github.com/peci1/fb1cea77c41fe8ace6c0db8ef82539a3。
我测试了隐式和显式ODR(有无标准化),并通过LSQ或较差的结果进行了初步猜测(以查看其对猜测的敏感程度)。您的数据看起来像这样: 基本上,黄色和灰色平面是未经归一化的隐式拟合,结果非常糟糕,而其他ODR拟合大致相同。您会看到ODR拟合与(淡蓝色)LSQ拟合(预期的)有些不同。