矩阵形式的 Bezier MxN 曲面(使用 Python 和 numpy)

Bezier MxN Surface in matrix form( with Python and numpy)

我正在尝试用快速方法(矩阵形式“bezier_surface_M”)为贝塞尔曲面重写慢速方法(“bezier_surf_eval”)。

使用这个公式:

[N] 和 [M] 是贝塞尔曲线基础矩阵(4x2 度,因为有 5x3 个控制点), [U]、[W] 是表面 uv 参数,[B] - 控制点

我做错了什么?

import matplotlib.pyplot as plt
import numpy as np
from matplotlib import cm
from matplotlib.figure import Figure
from mpl_toolkits.mplot3d.axes3d import Axes3D
from scipy.special import comb


def bern(i, d, t):
    return comb(d, i) * (t ** (d - i)) * (1 - t) ** i


def bezier_matrix(d):
    return np.array([[(-1) ** (i - j) * comb(j, i) * comb(d, j) for i in range(d + 1)] for j in range(d + 1)], int)


BM = [bezier_matrix(i) for i in range(16)]

poles5x3 = np.array([
    [[1.8, -0.3, 0.], [1.8, 0.13, 0.1], [1.8, 0.5, 0.]],
    [[2., -0.3, 0.06], [2.1, 0.1, 0.1], [2.1, 0.5, 0.1]],
    [[2.3, -0.3, 0.1], [2.3, 0.13, 0.2], [2.3, 0.5, 0.1]],
    [[2.4, -0.3, 0.1], [2.5, 0.1, 0.15], [2.5, 0.5, 0.1]],
    [[2.6, -0.3, 0.], [2.6, 0.1, 0.1], [2.5, 0.5, 0.]]])



def bezier_surf_eval(poles, u, v, deg_u, deg_v):
    count_u, count_v = deg_u + 1, deg_v + 1

    co = [0, 0, 0]
    for i in range(count_u):
        for j in range(count_v):
            bu = bern(i, deg_u, u)
            bv = bern(j, deg_v, v)

            pole_co = poles[i][j]
            m = bu * bv
            co[0] += pole_co[0] * m
            co[1] += pole_co[1] * m
            co[2] += pole_co[2] * m

    return co


def bezier_surface_slow(poles, resol=(16, 16)):
    params_u = np.linspace(0, 1, resol[0])
    params_v = np.linspace(0, 1, resol[1])

    count_u, count_v, _ = poles.shape
    du, dv = count_u - 1, count_v - 1

    cps = poles.tolist()

    coords = np.empty((*resol, 3), float)
    for vi, v in enumerate(params_v):
        for ui, u in enumerate(params_u):
            coords[ui, vi] = bezier_surf_eval(cps, u, v, deg_u=du, deg_v=dv)

    return np.array(coords, np.float32)


def bezier_surface_M(cps: np.ndarray, resol=(16, 16)):
    u = np.linspace(0, 1, resol[0])
    v = np.linspace(0, 1, resol[1])

    count_u, count_v, _ = cps.shape
    deg_u, deg_v = count_u - 1, count_v - 1

    u_vec = np.array([u ** i for i in range(count_u)])
    v_vec = np.array([v ** i for i in range(count_v)])

    BM_u, BM_v = BM[deg_u], BM[deg_v]
    return u_vec.T.dot(BM_u).dot(cps).dot(BM_v.T).dot(v_vec)


fig: Figure = plt.figure(figsize=(7, 7))
ax: Axes3D = fig.add_subplot(111, projection='3d')

# ax.scatter(px, py, pz)

# --------------
resol = 16, 16
co = bezier_surface_slow(poles5x3, resol=resol)
x, y, z = co.T
ax.plot_surface(x, y, z, cmap=cm.gray, linewidth=1, antialiased=False)

# --------------
resol = 16, 16
co = bezier_surface_M(poles5x3, resol=resol)
x, y, z = co.T
ax.plot_surface(x, y, z, cmap=cm.gray, linewidth=1, antialiased=False)

plt.show()

看来我解决了。 问题是点积矩阵的顺序不正确(在方法“bezier_surface_M”中)。与具有正确顺序的 per-axis 矩阵相乘即可​​。

已替换:

u_vec.T.dot(BM_u).dot(cps).dot(BM_v.T).dot(v_vec)

与:

cps_x = cps[:, :, 0]
cps_y = cps[:, :, 1]
cps_z = cps[:, :, 2]

m1 = u_vec.T.dot(BM_u)
m2 = BM_v.T.dot(v_vec)

x = m1.dot(cps_x).dot(m2)
y = m1.dot(cps_y).dot(m2)
z = m1.dot(cps_z).dot(m2)

也许它可以用 np.tensorproduct 或类似的东西优化,但我对 numpy 和矩阵数学不太好。

import matplotlib.pyplot as plt
import numpy as np
from matplotlib import cm
from matplotlib.figure import Figure
from mpl_toolkits.mplot3d.axes3d import Axes3D
from scipy.special import comb


def bezier_matrix(d):
    return np.array([[(-1) ** (i - j) * comb(j, i) * comb(d, j) for i in range(d + 1)] for j in range(d + 1)], int)


BM = [bezier_matrix(i) for i in range(16)]

poles5x3 = np.array([
    [[1.8, -0.3, 0.], [1.8, 0.13, 0.1], [1.8, 0.5, 0.]],
    [[2., -0.3, 0.06], [2.1, 0.1, 0.1], [2.1, 0.5, 0.1]],
    [[2.3, -0.3, 0.1], [2.3, 0.13, 0.2], [2.3, 0.5, 0.1]],
    [[2.4, -0.3, 0.1], [2.5, 0.1, 0.15], [2.5, 0.5, 0.1]],
    [[2.6, -0.3, 0.], [2.6, 0.1, 0.1], [2.5, 0.5, 0.]]])


def bezier_surface_M(cps: np.ndarray, resol=(16, 16)):
    u, v = np.linspace(0, 1, resol[0]), np.linspace(0, 1, resol[1])

    count_u, count_v, _ = cps.shape
    deg_u, deg_v = count_u - 1, count_v - 1

    u_vec = np.array([u ** i for i in range(count_u)])
    v_vec = np.array([v ** i for i in range(count_v)])

    BM_u, BM_v = BM[deg_u], BM[deg_v]

    cps_x = cps[:, :, 0]
    cps_y = cps[:, :, 1]
    cps_z = cps[:, :, 2]

    m1 = u_vec.T.dot(BM_u)
    m2 = BM_v.T.dot(v_vec)

    x = m1.dot(cps_x).dot(m2)
    y = m1.dot(cps_y).dot(m2)
    z = m1.dot(cps_z).dot(m2)

    return x, y, z


fig: Figure = plt.figure(figsize=(7, 7))
ax: Axes3D = fig.add_subplot(111, projection='3d')

resol = 32, 32

# --------------
x, y, z = bezier_surface_M(poles5x3, resol=resol)
ax.plot_surface(x, y, z, cmap=cm.gray, linewidth=1, antialiased=False)
    
plt.show()