You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

平面法向量计算及两平面夹角计算异常问题排查

平面法向量计算及两平面夹角计算异常问题排查

我现在遇到了一个问题:在求两个平面的法向量并计算两平面夹角时,用RANSAC方法拟合平面得到的法向量看起来并不垂直于平面,而且计算出的两平面夹角也和预期不符。

上下文

我生成了两个完美的矩阵来代表两个垂直的平面,矩阵如下:

left_points_measured = np.array([
    [0.0, 0.0, 292.89321881],
    [0.0, 1.0, 352.06658487],
    [0.0, 2.0, 411.23995092],
    [1.0, 0.0, 292.89321881],
    [1.0, 1.0, 352.06658487],
    [1.0, 2.0, 411.23995092],
    [2.0, 0.0, 292.89321881],
    [2.0, 1.0, 352.06658487],
    [2.0, 2.0, 411.23995092],
    [3.0, 0.0, 292.89321881],
    [3.0, 1.0, 352.06658487],
    [3.0, 2.0, 411.23995092],
    [4.0, 0.0, 292.89321881],
    [4.0, 1.0, 352.06658487],
    [4.0, 2.0, 411.23995092],
    [5.0, 0.0, 292.89321881],
    [5.0, 1.0, 352.06658487],
    [5.0, 2.0, 411.23995092],
    [6.0, 0.0, 292.89321881],
    [6.0, 1.0, 352.06658487],
    [6.0, 2.0, 411.23995092],
    [7.0, 0.0, 292.89321881],
    [7.0, 1.0, 352.06658487],
    [7.0, 2.0, 411.23995092]
])

right_points_measured = np.array([
    [0.0, 4.0, 470.41331697],
    [0.0, 5.0, 411.23995092],
    [0.0, 6.0, 352.06658487],
    [0.0, 7.0, 292.89321881],
    [1.0, 4.0, 470.41331697],
    [1.0, 5.0, 411.23995092],
    [1.0, 6.0, 352.06658487],
    [1.0, 7.0, 292.89321881],
    [2.0, 4.0, 470.41331697],
    [2.0, 5.0, 411.23995092],
    [2.0, 6.0, 352.06658487],
    [2.0, 7.0, 292.89321881],
    [3.0, 4.0, 470.41331697],
    [3.0, 5.0, 411.23995092],
    [3.0, 6.0, 352.06658487],
    [3.0, 7.0, 292.89321881],
    [4.0, 4.0, 470.41331697],
    [4.0, 5.0, 411.23995092],
    [4.0, 6.0, 352.06658487],
    [4.0, 7.0, 292.89321881],
    [5.0, 4.0, 470.41331697],
    [5.0, 5.0, 411.23995092],
    [5.0, 6.0, 352.06658487],
    [5.0, 7.0, 292.89321881],
    [6.0, 4.0, 470.41331697],
    [6.0, 5.0, 411.23995092],
    [6.0, 6.0, 352.06658487],
    [6.0, 7.0, 292.89321881],
    [7.0, 4.0, 470.41331697],
    [7.0, 5.0, 411.23995092],
    [7.0, 6.0, 352.06658487],
    [7.0, 7.0, 292.89321881]
])

使用方法

我用RANSAC方法拟合平面并计算法向量,代码如下:

import numpy as np
import plotly.graph_objs as go
import webbrowser
from sklearn.linear_model import RANSACRegressor
from sklearn.linear_model import LinearRegression

def fit_plane_and_plot(points, name):
    # Extract x, y, z coordinates
    X = points[:, :2]  # (x, y) coordinates
    z = points[:, 2]   # z coordinate

    # Use RANSAC to fit a plane
    ransac = RANSACRegressor(LinearRegression())
    ransac.fit(X, z)

    # Coefficients of the plane: z = ax + by + c
    a, b = ransac.estimator_.coef_
    c = ransac.estimator_.intercept_

    # Normal vector to the plane
    normal_vector = np.array([a, b, -1])

    # Choose a point on the plane (e.g., x = 3.5, y = 3.5)
    x, y = 3.5, 3.5
    z = a * x + b * y + c
    point_on_plane = np.array([x, y, z])

    # Normal line to the plane: r(t) = p0 + t * normal_vector
    def normal_line(t):
        return point_on_plane + t * normal_vector

    # Test for some t values
    t_values = np.linspace(0, 1, 50)
    line_points = np.array([normal_line(t) for t in t_values])

    # Create grids for plane visualization
    x_range = np.linspace(0, 7, 10)  # Range for x
    y_range = np.linspace(0, 7, 10)  # Range for y
    x_grid, y_grid = np.meshgrid(x_range, y_range)

    # Plane
    z_plane = a * x_grid + b * y_grid + c

    return x_grid, y_grid, z_plane, line_points, points, name, normal_vector

def plot_planes_and_normals(left_points, right_points):
    # Use plotly for an interactive plot
    fig = go.Figure()

    # Add planes and normal vectors for left and right points
    x_grid_left, y_grid_left, z_plane_left, line_points_left, points_left, name_left, normal_vector_left = fit_plane_and_plot(left_points, "Left Plane")
    x_grid_right, y_grid_right, z_plane_right, line_points_right, points_right, name_right, normal_vector_right = fit_plane_and_plot(right_points, "Right Plane")

    # Plot the planes
    fig.add_trace(go.Surface(
        x=x_grid_left,
        y=y_grid_left,
        z=z_plane_left,
        colorscale='Blues',
        opacity=0.5,
        name=name_left
    ))

    fig.add_trace(go.Surface(
        x=x_grid_right,
        y=y_grid_right,
        z=z_plane_right,
        colorscale='Reds',
        opacity=0.5,
        name=name_right
    ))

    # Plot the normal lines
    fig.add_trace(go.Scatter3d(
        x=line_points_left[:, 0],
        y=line_points_left[:, 1],
        z=line_points_left[:, 2],
        mode='lines',
        line=dict(color='blue', width=2),
        name='Left Normal Vector'
    ))

    fig.add_trace(go.Scatter3d(
        x=line_points_right[:, 0],
        y=line_points_right[:, 1],
        z=line_points_right[:, 2],
        mode='lines',
        line=dict(color='red', width=2),
        name='Right Normal Vector'
    ))

    # Plot the points
    fig.add_trace(go.Scatter3d(
        x=points_left[:, 0],
        y=points_left[:, 1],
        z=points_left[:, 2],
        mode='markers',
        marker=dict(color='green', size=4),
        name='Left Points'
    ))

    fig.add_trace(go.Scatter3d(
        x=points_right[:, 0],
        y=points_right[:, 1],
        z=points_right[:, 2],
        mode='markers',
        marker=dict(color='purple', size=4),
        name='Right Points'
    ))

    # Add labels and legend
    fig.update_layout(scene=dict(
        xaxis=dict(title='X'),
        yaxis=dict(title='Y', range=[0, 7]),  # Set Y-axis limits
        zaxis=dict(title='Z')
    ), title='Left and Right Planes and Normal Vectors')

    # Save the figure to an HTML file and open it in a browser
    file_path = 'plot_superposition.html'
    fig.write_html(file_path)
    webbrowser.open(file_path)

    # Calculate the orientation angles of the planes
    def calculate_angles(normal_vector):
        norm = np.linalg.norm(normal_vector)
        alpha = np.arccos(normal_vector[0] / norm)
        beta = np.arccos(normal_vector[1] / norm)
        gamma = np.arccos(normal_vector[2] / norm)
        return np.degrees(alpha), np.degrees(beta), np.degrees(gamma)

    alpha_left, beta_left, gamma_left = calculate_angles(normal_vector_left)
    alpha_right, beta_right, gamma_right = calculate_angles(normal_vector_right)

    print(f"Angles for the left plane:")
    print(f"Angle with x-axis (alpha): {alpha_left:.2f} degrees")
    print(f"Angle with y-axis (beta): {beta_left:.2f} degrees")
    print(f"Angle with z-axis (gamma): {gamma_left:.2f} degrees")

    print(f"Angles for the right plane:")
    print(f"Angle with x-axis (alpha): {alpha_right:.2f} degrees")
    print(f"Angle with y-axis (beta): {beta_right:.2f} degrees")
    print(f"Angle with z-axis (gamma): {gamma_right:.2f} degrees")

    # Calculate the angle between the two planes
    dot_product = np.dot(normal_vector_left, normal_vector_right)
    norm_left = np.linalg.norm(normal_vector_left)
    norm_right = np.linalg.norm(normal_vector_right)
    cos_angle = dot_product / (norm_left * norm_right)
    angle = np.arccos(np.clip(cos_angle, -1.0, 1.0))
    angle_deg = np.degrees(angle)

    print(f"Angle between the two planes: {angle_deg:.2f} degrees")

# Call the function to plot the planes and normal vectors
plot_planes_and_normals(left_points_measured, right_points_measured)

问题

当我绘制平面和法向量时,预期法向量应该垂直于平面,但实际绘制出来的法向量并不垂直。另外,计算出的两平面夹角大约是170度,而我预期应该是90度,因为这两个平面本来是垂直的。

疑问

你能帮我理解为什么法向量不垂直于平面,以及为什么计算出的夹角不符合预期吗?是我计算法向量的方法有错误,还是计算两平面夹角的步骤出了问题?

我还尝试过反转矩阵,但也没有解决问题。


备注:内容来源于stack exchange,提问作者JPannetier

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 15:13:01