首页/在线代码库/demo9_pseudo_intersection.py

demo9_pseudo_intersection.py

场景: 两条 3D 线段在 2D 投影中交叉(看起来相交), 但实际在空间中一前一后。 用"拓扑一致性"校验: 两条 3D 线的空间最近距离若超过阈值, 判定为视觉交叉并断开。 同时演示: 被遮挡断开的共线线段, 按其空间方向延长并在后端空间补全。

Python 98 行 系列:识别系列 🔒 源码仅供在线学习,不提供文件下载
完整源码 · 在线查看demo9_pseudo_intersection.py
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
"""演示9: 线 — 破除非真实交点(视觉伪交点)与虚拟延伸补全。

场景: 两条 3D 线段在 2D 投影中交叉(看起来相交), 但实际在空间中一前一后。
用"拓扑一致性"校验: 两条 3D 线的空间最近距离若超过阈值, 判定为视觉交叉并断开。
同时演示: 被遮挡断开的共线线段, 按其空间方向延长并在后端空间补全。
"""
import os
import numpy as np
import matplotlib.pyplot as plt
from figure_style import figures_dir, configure

configure()

OUT = figures_dir()
os.makedirs(OUT, exist_ok=True)


def closest_distance_between_lines(p1, d1, p2, d2):
    """两条空间直线(点+方向)之间的最近距离。"""
    w0 = p1 - p2
    a, b = d1 @ d1, d1 @ d2
    c, d = d2 @ d2, d1 @ w0
    e = d2 @ w0
    denom = a * c - b * b
    if abs(denom) < 1e-9:
        return np.linalg.norm(w0 - (e / c) * d2)
    s = (b * e - c * d) / denom
    t = (a * e - b * d) / denom
    return np.linalg.norm((p1 + s * d1) - (p2 + t * d2))


# ---- 场景: 前墙与后墙的两条"相交"线 ----
# 线A: 前墙 z=0 上的水平线 (x 从 -1.2 到 1.2, y=0.3)
pA, dA = np.array([-1.2, 0.3, 0.0]), np.array([2.4, 0.0, 0.0])
# 线B: 后墙 z=2 上的垂直线 (y 从 -1.2 到 1.2, x=0.3) — 2D 投影与 A 交叉
pB, dB = np.array([0.3, -1.2, 2.0]), np.array([0.0, 2.4, 0.0])

# 2D 投影(正投影, 丢 z): 两条线在 (0.3, 0.3) 处"交叉"
proj_A = np.array([pA[:2], pA[:2] + dA[:2]])
proj_B = np.array([pB[:2], pB[:2] + dB[:2]])

dist = closest_distance_between_lines(pA, dA, pB, dB)
threshold = 0.5
is_real = dist < threshold
print(f"两条 3D 线的最近距离: {dist:.3f} (阈值 {threshold})")
print(f"判定: {'真实相交, 保持拓扑连接' if is_real else '视觉伪交点, 强制断开'}")

# ---- 被遮挡断开的线段, 沿空间方向延长补全 ----
# 一条本应连续的空间直线被遮挡物断开成两段
seg1_p, seg1_d = np.array([-1.0, -0.5, 0.5]), np.array([1.0, 0.0, 0.0])
seg2_p, seg2_d = np.array([0.8, -0.5, 0.5]), np.array([1.0, 0.0, 0.0])
# 延长后是否共线: 方向一致 & 一条的延长线经过另一条
same_dir = abs(seg1_d @ seg2_d / (np.linalg.norm(seg1_d) * np.linalg.norm(seg2_d))) > 0.999
gap = np.linalg.norm((seg1_p + seg1_d) - (seg2_p - seg2_d))
stitched = same_dir and gap < 0.8
print(f"遮挡断线段: 方向一致={same_dir}, 断口距离={gap:.3f} -> "
      f"{'共线合并, 虚拟补全为一条' if stitched else '不合并'}")

# ---- 可视化 ----
fig = plt.figure(figsize=(13, 6))

ax1 = fig.add_subplot(1, 2, 1)
ax1.plot(proj_A[:, 0], proj_A[:, 1], "tab:red", lw=2, label="线A (前墙, z=0)")
ax1.plot(proj_B[:, 0], proj_B[:, 1], "tab:blue", lw=2, label="线B (后墙, z=2)")
ax1.plot(*np.array([[0.3], [0.3]]), "o", c="k", ms=8)
ax1.annotate("看起来在此交叉", xy=(0.3, 0.3), xytext=(0.6, 0.9),
             arrowprops=dict(arrowstyle="->"))
ax1.set_title("2D 正投影: 两线交叉(视觉伪交点)")
ax1.set_aspect("equal"); ax1.legend(); ax1.grid(alpha=0.3)

ax2 = fig.add_subplot(1, 2, 2, projection="3d")
s = np.linspace(0, 1, 50)
ax2.plot(*(pA[:, None] + s[None, :] * dA[:, None]), "tab:red", lw=2, label="线A z=0")
ax2.plot(*(pB[:, None] + s[None, :] * dB[:, None]), "tab:blue", lw=2, label="线B z=2")
# 最近距离线段: 用参数公式给出两线最近点
def _closest_pts(p1, d1, p2, d2):
    w0 = p1 - p2
    a, bb = d1 @ d1, d1 @ d2
    cc, dd = d2 @ d2, d1 @ w0
    ee = d2 @ w0
    den = a * cc - bb * bb
    s = (bb * ee - cc * dd) / den
    t = (a * ee - bb * dd) / den
    return p1 + s * d1, p2 + t * d2

pa_c, pb_c = _closest_pts(pA, dA, pB, dB)
ax2.plot([pa_c[0], pb_c[0]], [pa_c[1], pb_c[1]], [pa_c[2], pb_c[2]],
         "g--", lw=1.5, label=f"空间最近距离 {dist:.2f}")
ax2.set_title("3D 视图: 两线一前一后, 并不相交")
ax2.set_xlabel("X"); ax2.set_ylabel("Y"); ax2.set_zlabel("Z")
ax2.legend()

fig.suptitle("三维识别·第二步: 线 — 拓扑一致性校验破除视觉伪交点", fontsize=14)
fig.tight_layout(rect=(0, 0, 1, 0.95))
out_path = os.path.join(OUT, "fig_3d_pseudo_intersection.png")
fig.savefig(out_path, dpi=150)
print(f"-> {out_path}")

输出图

fig_3d_pseudo_intersection.png
输出图 · fig_3d_pseudo_intersection.png