我有4个点,非常接近于一个平面 - 它是1,4-二氢吡啶循环。
我需要计算从C3和N1到平面的距离,该平面由C1-C2-C4-C5组成。 计算距离是可以的,但拟合平面对我来说很难。
1,4-DHP周期http://i.stack.imgur.com/dhNDo.png
1,4-DHP周期,另一种观点http://i.stack.imgur.com/6Xs0z.png
from array import *
from numpy import *
from scipy import *
# coordinates (XYZ) of C1, C2, C4 and C5
x = [0.274791784, -1.001679346, -1.851320839, 0.365840754]
y = [-1.155674199, -1.215133985, 0.053119249, 1.162878076]
z = [1.216239624, 0.764265677, 0.956099579, 1.198231236]
# plane equation Ax + By + Cz = D
# non-fitted plane
abcd = [0.506645455682, -0.185724560275, -1.43998120646, 1.37626378129]
# creating distance variable
distance = zeros(4, float)
# calculating distance from point to plane
for i in range(4):
distance[i] = (x[i]*abcd[0]+y[i]*abcd[1]+z[i]*abcd[2]+abcd[3])/sqrt(abcd[0]**2 + abcd[1]**2 + abcd[2]**2)
print distance
# calculating squares
squares = distance**2
print squares
如何使和(方块)最小化?我尝试过最小的方块,但它对我来说太过分了。
答案 0 :(得分:10)
你适合飞机的事实在这里只是略微相关。您要做的是从猜测开始最小化特定的函数。为此使用scipy.optimize
。请注意,无法保证这是全局最优解决方案,只能本地优化。不同的初始条件可能会收敛到不同的结果,如果您开始接近您正在寻找的局部最小值,这种方法效果很好。
我冒昧地利用numpy的广播来清理你的代码:
import numpy as np
# coordinates (XYZ) of C1, C2, C4 and C5
XYZ = np.array([
[0.274791784, -1.001679346, -1.851320839, 0.365840754],
[-1.155674199, -1.215133985, 0.053119249, 1.162878076],
[1.216239624, 0.764265677, 0.956099579, 1.198231236]])
# Inital guess of the plane
p0 = [0.506645455682, -0.185724560275, -1.43998120646, 1.37626378129]
def f_min(X,p):
plane_xyz = p[0:3]
distance = (plane_xyz*X.T).sum(axis=1) + p[3]
return distance / np.linalg.norm(plane_xyz)
def residuals(params, signal, X):
return f_min(X, params)
from scipy.optimize import leastsq
sol = leastsq(residuals, p0, args=(None, XYZ))[0]
print("Solution: ", sol)
print("Old Error: ", (f_min(XYZ, p0)**2).sum())
print("New Error: ", (f_min(XYZ, sol)**2).sum())
这给出了:
Solution: [ 14.74286241 5.84070802 -101.4155017 114.6745077 ]
Old Error: 0.441513295404
New Error: 0.0453564286112
答案 1 :(得分:10)
这听起来不对,但你应该用SVD取代非线性优化。下面创建惯性矩张量M,然后SVD得到平面的法线。这应该是最小二乘拟合的近似值,并且更快且更可预测。它返回点云中心和法线。
def planeFit(points):
"""
p, n = planeFit(points)
Given an array, points, of shape (d,...)
representing points in d-dimensional space,
fit an d-dimensional plane to the points.
Return a point, p, on the plane (the point-cloud centroid),
and the normal, n.
"""
import numpy as np
from numpy.linalg import svd
points = np.reshape(points, (np.shape(points)[0], -1)) # Collapse trialing dimensions
assert points.shape[0] <= points.shape[1], "There are only {} points in {} dimensions.".format(points.shape[1], points.shape[0])
ctr = points.mean(axis=1)
x = points - ctr[:,np.newaxis]
M = np.dot(x, x.T) # Could also use np.cov(x) here.
return ctr, svd(M)[0][:,-1]
例如:在(10,100)构建一个2D云,它在x方向上很薄,在y方向上大100倍:
>>> pts = np.diag((.1, 10)).dot(randn(2,1000)) + np.reshape((10, 100),(2,-1))
拟合平面非常接近(10,100),法线几乎沿x轴。
>>> planeFit(pts)
(array([ 10.00382471, 99.48404676]),
array([ 9.99999881e-01, 4.88824145e-04]))
答案 2 :(得分:7)
最小方块应该很容易适合飞机。平面的等式是:ax + by + c = z。因此,使用您的所有数据设置这样的矩阵:
a
x = b
c
和
z_0
B = z_1
...
z_n
和
a
b = (A^T A)^-1 A^T B
c
换句话说:Ax = B.现在求解x是你的系数。但由于你有超过3个点,系统是过度确定的,所以你需要使用左伪逆。所以答案是:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
N_POINTS = 10
TARGET_X_SLOPE = 2
TARGET_y_SLOPE = 3
TARGET_OFFSET = 5
EXTENTS = 5
NOISE = 5
# create random data
xs = [np.random.uniform(2*EXTENTS)-EXTENTS for i in range(N_POINTS)]
ys = [np.random.uniform(2*EXTENTS)-EXTENTS for i in range(N_POINTS)]
zs = []
for i in range(N_POINTS):
zs.append(xs[i]*TARGET_X_SLOPE + \
ys[i]*TARGET_y_SLOPE + \
TARGET_OFFSET + np.random.normal(scale=NOISE))
# plot raw data
plt.figure()
ax = plt.subplot(111, projection='3d')
ax.scatter(xs, ys, zs, color='b')
# do fit
tmp_A = []
tmp_b = []
for i in range(len(xs)):
tmp_A.append([xs[i], ys[i], 1])
tmp_b.append(zs[i])
b = np.matrix(tmp_b).T
A = np.matrix(tmp_A)
fit = (A.T * A).I * A.T * b
errors = b - A * fit
residual = np.linalg.norm(errors)
print "solution:"
print "%f x + %f y + %f = z" % (fit[0], fit[1], fit[2])
print "errors:"
print errors
print "residual:"
print residual
# plot plane
xlim = ax.get_xlim()
ylim = ax.get_ylim()
X,Y = np.meshgrid(np.arange(xlim[0], xlim[1]),
np.arange(ylim[0], ylim[1]))
Z = np.zeros(X.shape)
for r in range(X.shape[0]):
for c in range(X.shape[1]):
Z[r,c] = fit[0] * X[r,c] + fit[1] * Y[r,c] + fit[2]
ax.plot_wireframe(X,Y,Z, color='k')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
plt.show()
以下是一些带有示例的简单Python代码:
0.143509 x + 0.057196 y + 1.129595 = z
您的观点的解决方案:
(defun assert-valid-alist (alist)
(assert (and (not (null alist)) ; not the empty list
(listp alist) ; it is a list
(every #'consp alist) ; every item is a cons
(every (lambda (item)
(symbolp (car item))) ; every key should be a symbol
alist))
(alist)
"Not a valid assoc list: ~a" alist))
(defun get-age (person)
(assert-valid-alist person)
(cdr (assoc 'age person)))
答案 3 :(得分:2)
这会返回 3D 平面系数以及拟合的 RMSE。
平面以齐次坐标表示形式提供,这意味着它与一个点的齐次坐标的点积产生了两者之间的距离。
def fit_plane(points):
assert points.shape[1] == 3
centroid = points.mean(axis=0)
x = points - centroid[None, :]
U, S, Vt = np.linalg.svd(x.T @ x)
normal = U[:, -1]
origin_distance = normal @ centroid
rmse = np.sqrt(S[-1] / len(points))
return np.hstack([normal, -origin_distance]), rmse
小注:SVD 也可以直接应用于点而不是外积矩阵,但我发现使用 NumPy 的 SVD 实现它会更慢。
U, S, Vt = np.linalg.svd(x.T, full_matrices=False)
rmse = S[-1] / np.sqrt(len(points))
答案 4 :(得分:1)
这是一种方式。如果你的点是P [1] .. P [n]则计算这些点的平均值M并从每个点中减去它,得到点p [1] .. p [n]。然后计算C = Sum {p [i] * p [i]'}(点的“协方差”矩阵)。接下来的对角线C,即找到正交U和对角线E,使得C = U * E * U'。如果你的点确实在一个平面上,则其中一个特征值(即E的对角线条目)将非常小(完美算术将为0)。在任何情况下,如果这些中的第j个是最小的,那么让U的第j列为(A,B,C)并计算D = -M'* N.这些参数定义了“最佳”平面,即从P []到平面的距离的平方和最小。
答案 5 :(得分:1)
除了svd在处理异常值时(当你有大量数据集时)快速达到解决方案的另一种方法是ransac:
def fit_plane(voxels, iterations=50, inlier_thresh=10): # voxels : x,y,z
inliers, planes = [], []
xy1 = np.concatenate([voxels[:, :-1], np.ones((voxels.shape[0], 1))], axis=1)
z = voxels[:, -1].reshape(-1, 1)
for _ in range(iterations):
random_pts = voxels[np.random.choice(voxels.shape[0], voxels.shape[1] * 10, replace=False), :]
plane_transformation, residual = fit_pts_to_plane(random_pts)
inliers.append(((z - np.matmul(xy1, plane_transformation)) <= inlier_thresh).sum())
planes.append(plane_transformation)
return planes[np.array(inliers).argmax()]
def fit_pts_to_plane(voxels): # x y z (m x 3)
# https: // math.stackexchange.com / questions / 99299 / best - fitting - plane - given - a - set - of - points
xy1 = np.concatenate([voxels[:, :-1], np.ones((voxels.shape[0], 1))], axis=1)
z = voxels[:, -1].reshape(-1, 1)
fit = np.matmul(np.matmul(np.linalg.inv(np.matmul(xy1.T, xy1)), xy1.T), z)
errors = z - np.matmul(xy1, fit)
residual = np.linalg.norm(errors)
return fit, residual