Files
lipan 29f1d45d66 feat: 算法升级、整车配准集成与性能分析文档
算法更新: delivery v1.2.1、平整度v1.5、测距v2.2、整车配准v1.3
适配器: vehicle-registration 新增、各算法 manifest 和 adapter 更新
后端: taskCadFiles/source 字段迁移、derivedPointClouds 注册、取消任务联动
前端: ElapsedTime 时长显示、两步算法流程、canRun 逻辑修复
文档: 新增5份算法性能分析报告
2026-06-25 08:22:20 +00:00

362 lines
10 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import numpy as np
from scipy.spatial import KDTree
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
def point_to_line_distance(point, line_start, line_end):
"""
计算点到三维线段的最短距离
Args:
point: 待计算点 (x, y, z)
line_start: 线段起点 (x, y, z)
line_end: 线段终点 (x, y, z)
Returns:
点到线段的垂直距离
"""
p = np.array(point)
a = np.array(line_start)
b = np.array(line_end)
# 计算向量
ab = b - a
ap = p - a
# 投影参数 t
t = np.dot(ap, ab) / np.dot(ab, ab)
# 找到线段上的最近点
if t <= 0:
closest = a
elif t >= 1:
closest = b
else:
closest = a + t * ab
# 计算距离
distance = np.linalg.norm(p - closest)
return distance
def point_to_plane_distance(point, plane_point1, plane_point2, plane_point3):
"""
计算点到三维平面的距离(三点确定一个平面)
Args:
point: 待计算点 (x, y, z)
plane_point1, plane_point2, plane_point3: 平面上的三个点
Returns:
点到平面的垂直距离
"""
p = np.array(point)
p1 = np.array(plane_point1)
p2 = np.array(plane_point2)
p3 = np.array(plane_point3)
# 计算平面法向量
v1 = p2 - p1
v2 = p3 - p1
normal = np.cross(v1, v2)
normal = normal / np.linalg.norm(normal)
# 计算点到平面的距离
distance = abs(np.dot(normal, p - p1))
return distance
def douglas_peucker_3d(points, epsilon, use_plane=False):
"""
3D Douglas-Peucker 算法
Args:
points: 三维点列表或numpy数组,形状 (N, 3)
epsilon: 容差阈值
use_plane: 是否使用平面距离(True)还是线段距离(False)
Returns:
简化后的点云列表
"""
if len(points) <= 2:
return points.tolist() if isinstance(points, np.ndarray) else points
# 转换为列表格式
if isinstance(points, np.ndarray):
points = points.tolist()
# 首尾点
start = points[0]
end = points[-1]
# 找到距离最大的点
max_dist = 0
max_index = 0
for i in range(1, len(points) - 1):
if use_plane and len(points) > 2:
# 使用平面距离(需要至少3个点确定平面)
if i > 0 and i < len(points) - 1:
# 使用当前点、前一个点和后一个点确定平面
plane_p1 = points[i - 1]
plane_p2 = points[i]
plane_p3 = points[i + 1]
dist = point_to_plane_distance(points[i], plane_p1, plane_p2, plane_p3)
else:
dist = point_to_line_distance(points[i], start, end)
else:
# 使用线段距离
dist = point_to_line_distance(points[i], start, end)
if dist > max_dist:
max_dist = dist
max_index = i
# 递归简化
if max_dist > epsilon:
# 保留该点,递归处理左右两部分
left = douglas_peucker_3d(points[:max_index + 1], epsilon, use_plane)
right = douglas_peucker_3d(points[max_index:], epsilon, use_plane)
# 合并结果(避免重复端点)
return left[:-1] + right
else:
# 只保留首尾点
return [start, end]
class DouglasPeucker3D:
"""
3D Douglas-Peucker 算法的面向对象封装
支持点云简化、轨迹压缩、轮廓提取等
"""
def __init__(self, epsilon=0.01, use_plane=False):
"""
Args:
epsilon: 容差阈值
use_plane: 是否使用平面距离(更适合曲面点云)
"""
self.epsilon = epsilon
self.use_plane = use_plane
def simplify(self, points):
"""
简化点云
Args:
points: (N, 3) 的 numpy 数组或列表
Returns:
简化后的点云 (N', 3) numpy 数组
"""
if isinstance(points, np.ndarray):
points = points.tolist()
simplified = douglas_peucker_3d(points, self.epsilon, self.use_plane)
return np.array(simplified)
def simplify_trajectory(self, trajectory, time_weight=False):
"""
专门用于轨迹简化(考虑时间维度)
Args:
trajectory: (N, 4) 的数组 [x, y, z, t] 或 [x, y, z]
time_weight: 是否加入时间权重
Returns:
简化后的轨迹
"""
if time_weight and trajectory.shape[1] == 4:
# 时间加权:将时间作为第四维参与距离计算
# 需要自定义距离函数
pass
return self.simplify(trajectory[:, :3])
def extract_contour(self, points, return_indices=False):
"""
提取点云轮廓(结合法线估计)
Args:
points: (N, 3) 点云
return_indices: 是否返回原始点索引
Returns:
轮廓点集
"""
# 先进行粗略简化
simplified = self.simplify(points)
# 这里可以结合法线估计进一步提取边界点
# 简化实现:返回简化后的点作为轮廓候选
if return_indices:
# 查找简化点在原始点云中的最近邻索引
from scipy.spatial import KDTree
tree = KDTree(points)
indices = []
for p in simplified:
_, idx = tree.query(p)
indices.append(idx)
return simplified, np.array(indices)
return simplified
# ========== 示例和使用 ==========
def generate_test_curve(n_points=100, noise=0.02):
"""
生成测试用的三维曲线(螺旋线 + 噪声)
"""
t = np.linspace(0, 4 * np.pi, n_points)
x = np.sin(t)
y = np.cos(t)
z = t / (2 * np.pi)
points = np.column_stack([x, y, z])
# 添加噪声
if noise > 0:
points += np.random.randn(n_points, 3) * noise
return points
def visualize_simplification(original, simplified, title="3D Douglas-Peucker Simplification"):
"""
可视化原始点云和简化后的点云
"""
fig = plt.figure(figsize=(12, 6))
# 原始点云
ax1 = fig.add_subplot(121, projection='3d')
ax1.scatter(original[:, 0], original[:, 1], original[:, 2],
c='blue', s=10, alpha=0.6, label='Original')
ax1.plot(original[:, 0], original[:, 1], original[:, 2],
'b-', alpha=0.3)
ax1.set_title(f'Original: {len(original)} points')
ax1.legend()
# 简化后点云
ax2 = fig.add_subplot(122, projection='3d')
ax2.scatter(simplified[:, 0], simplified[:, 1], simplified[:, 2],
c='red', s=30, alpha=0.8, label='Simplified')
ax2.plot(simplified[:, 0], simplified[:, 1], simplified[:, 2],
'r-', linewidth=2)
ax2.set_title(f'Simplified: {len(simplified)} points (ε={epsilon})')
ax2.legend()
plt.suptitle(title)
plt.tight_layout()
plt.show()
def compare_epsilon(points, epsilons):
"""
比较不同 epsilon 值的简化效果
"""
results = {}
for eps in epsilons:
dp = DouglasPeucker3D(epsilon=eps)
simplified = dp.simplify(points)
results[eps] = {
'points': simplified,
'count': len(simplified),
'compression_ratio': len(simplified) / len(points)
}
print(f"ε={eps:.4f}: {len(simplified)} 点 (压缩率: {results[eps]['compression_ratio']:.2%})")
return results
def demo_point_cloud():
"""
点云简化演示(使用螺旋线模拟)
"""
print("=" * 50)
print("3D Douglas-Peucker 算法演示")
print("=" * 50)
# 生成测试数据
points = generate_test_curve(200, noise=0.01)
print(f"\n原始点云: {len(points)} 个点")
# 测试不同 epsilon
epsilons = [0.01, 0.03, 0.05, 0.1, 0.2]
print("\n不同容差阈值的简化效果:")
results = compare_epsilon(points, epsilons)
# 可视化
dp = DouglasPeucker3D(epsilon=0.05)
simplified = dp.simplify(points)
visualize_simplification(points, simplified,
f"3D DP Simplification (ε=0.05, {len(simplified)}/{len(points)} points)")
def demo_trajectory():
"""
轨迹简化演示(模拟移动轨迹)
"""
print("\n" + "=" * 50)
print("3D 轨迹简化演示")
print("=" * 50)
# 生成测试轨迹(随机路径)
np.random.seed(42)
n_points = 100
t = np.linspace(0, 10, n_points)
x = np.sin(t) + np.random.randn(n_points) * 0.02
y = np.cos(t) + np.random.randn(n_points) * 0.02
z = t / 5 + np.random.randn(n_points) * 0.01
trajectory = np.column_stack([x, y, z])
print(f"原始轨迹: {len(trajectory)} 个点")
# 简化轨迹
dp = DouglasPeucker3D(epsilon=0.08)
simplified = dp.simplify_trajectory(trajectory)
print(f"简化后轨迹: {len(simplified)} 个点 (压缩率: {len(simplified) / len(trajectory):.2%})")
# 可视化
visualize_simplification(trajectory, simplified, "3D Trajectory Simplification")
def demo_point_cloud_from_file(pcd_file=None):
"""
从文件加载点云并简化
"""
try:
import open3d as o3d
if pcd_file is None:
# 使用示例:生成随机点云
print("\n生成随机点云进行演示...")
points = np.random.randn(500, 3) * 0.5
else:
pcd = o3d.io.read_point_cloud(pcd_file)
points = np.asarray(pcd.points)
print(f"原始点云: {len(points)} 个点")
# 简化
dp = DouglasPeucker3D(epsilon=0.05)
# 注意:传统 DP 算法需要有序点集,对于无序点云需要先排序或使用其他方法
# 这里演示对有序轨迹/轮廓的应用
print("\n注意: DP算法适用于有序点集(如轮廓线、轨迹),")
print("对于无序点云,建议先进行排序或使用其他简化方法(如体素滤波)")
except ImportError:
print("需要安装 open3d 来读取点云文件: pip install open3d")
if __name__ == "__main__":
# 运行演示
demo_point_cloud()
demo_trajectory()
# 如果需要处理实际点云文件
# demo_point_cloud_from_file("your_pointcloud.pcd")