寻找并绘制一组点的回归平面

14

我想对一些数据点拟合一个平面并绘制它。我目前的代码是:

import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt

points = [(1.1,2.1,8.1),
          (3.2,4.2,8.0),
          (5.3,1.3,8.2),
          (3.4,2.4,8.3),
          (1.5,4.5,8.0)]

xs, ys, zs = zip(*points)

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')

ax.scatter(xs, ys, zs)

point  = np.array([0.0, 0.0, 8.1])
normal = np.array([0.0, 0.0, 1.0])
d = -point.dot(normal)
xx, yy = np.meshgrid([-5,10], [-5,10])
z = (-normal[0] * xx - normal[1] * yy - d) * 1. /normal[2]
ax.plot_surface(xx, yy, z, alpha=0.2, color=[0,1,0])

ax.set_xlim(-10,10)
ax.set_ylim(-10,10)
ax.set_zlim(  0,10)

plt.show()

结果如下所示:手动创建的平面

从目前的情况可以看出,我是手动创建这个平面的。如何计算它?我猜可能可以通过scipy.optimize.minimize实现。对于我来说,误差函数的类型并不那么重要。我认为最小二乘法(垂直点-平面距离)应该就可以了。如果有人能向我展示如何实现,那就太棒了。


1
请参考以下链接BEST FIT平面算法为什么有不同结果,其中介绍了几种可能的方法。 - alko
4个回答

17

哦,这个想法突然在我脑海中浮现出来。它相当简单。:-)

import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import scipy.optimize
import functools

def plane(x, y, params):
    a = params[0]
    b = params[1]
    c = params[2]
    z = a*x + b*y + c
    return z

def error(params, points):
    result = 0
    for (x,y,z) in points:
        plane_z = plane(x, y, params)
        diff = abs(plane_z - z)
        result += diff**2
    return result

def cross(a, b):
    return [a[1]*b[2] - a[2]*b[1],
            a[2]*b[0] - a[0]*b[2],
            a[0]*b[1] - a[1]*b[0]]

points = [(1.1,2.1,8.1),
          (3.2,4.2,8.0),
          (5.3,1.3,8.2),
          (3.4,2.4,8.3),
          (1.5,4.5,8.0)]

fun = functools.partial(error, points=points)
params0 = [0, 0, 0]
res = scipy.optimize.minimize(fun, params0)

a = res.x[0]
b = res.x[1]
c = res.x[2]

xs, ys, zs = zip(*points)

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')

ax.scatter(xs, ys, zs)

point  = np.array([0.0, 0.0, c])
normal = np.array(cross([1,0,a], [0,1,b]))
d = -point.dot(normal)
xx, yy = np.meshgrid([-5,10], [-5,10])
z = (-normal[0] * xx - normal[1] * yy - d) * 1. /normal[2]
ax.plot_surface(xx, yy, z, alpha=0.2, color=[0,1,0])

ax.set_xlim(-10,10)
ax.set_ylim(-10,10)
ax.set_zlim(  0,10)

plt.show()

回归平面

抱歉无意义的提问。


8

另一种方法是使用简单的最小二乘解法。平面的方程为:ax + by + c = z。因此,按照以下方式使用所有数据设置矩阵:

    x_0   y_0   1  
A = x_1   y_1   1  
          ... 
    x_n   y_n   1  

并且
    a  
x = b  
    c

并且

    z_0   
B = z_1   
    ...   
    z_n

换句话说:Ax = B。现在解出x,这是您的系数。但是由于(我假设)您拥有超过3个点,所以系统是过度确定的,因此您需要使用左伪逆。因此答案是:
a 
b = (A^T A)^-1 A^T B
c

以下是一个简单的Python代码示例:

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

非常好的答案,感谢分享!由于np.matrix已被弃用,我编辑了代码以便它可以与np数组一起使用。 - Erol444

0

我很惊讶没有人提到lsq_linear。在那里,你可以更或多或少地直接插入数据点并得到平面系数:

import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt

points = np.array([[1.1,2.1,8.1],
                   [3.2,4.2,8.0],
                   [5.3,1.3,8.2],
                   [3.4,2.4,8.3],
                   [1.5,4.5,8.0]])


A = np.hstack((points[:,:2], np.ones((len(xs),1))))
b = points[:,2]

res = scipy.optimize.lsq_linear(A, b)
assert res.success



fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')

ax.scatter(xs, ys, zs)

XnY = np.linspace(-5,10,10)
X, Y = np.meshgrid(XnY, XnY)
Z = res.x[0] * X + res.x[1] * Y + res.x[2]
surf = ax.plot_surface(X, Y, Z, alpha=0.2, color=[0,1,0])

ax.set_xlim(-5,10)
ax.set_ylim(-5,10)
ax.set_zlim(  0,10)

plt.show()

0
感谢 @Ben 分享!由于 np.matrix 已被弃用,我编辑了你的代码以使其与 np 数组一起工作。
import matplotlib.pyplot as plt
import numpy as np
from numpy.linalg import inv

# Pass the function array of points, shape (3, X)
def plane_from_points(points):
    # Create this matrix correctly without transposing it later?
    A = np.array([
        points[0,:],
        points[1,:],
        np.ones(points.shape[1])
    ]).T
    b = np.array([points[2, :]]).T

    # fit = (A.T * A).I * A.T * b
    fit = np.dot(np.dot(inv(np.dot(A.T, A)), A.T), b)
    # errors = b - np.dot(A, fit)
    # residual = np.linalg.norm(errors)
    return fit

N_POINTS = 10
TARGET_X_SLOPE = 2
TARGET_y_SLOPE = 3
TARGET_OFFSET  = 5
EXTENTS = 5
NOISE = 3

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

points = np.array([xs, ys, zs])

# plot raw data
plt.figure()
ax = plt.subplot(111, projection='3d')
ax.scatter(xs, ys, zs, color='b')

fit = plane_from_points(points)
# 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()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接