feat: 算法升级、整车配准集成与性能分析文档

算法更新: delivery v1.2.1、平整度v1.5、测距v2.2、整车配准v1.3
适配器: vehicle-registration 新增、各算法 manifest 和 adapter 更新
后端: taskCadFiles/source 字段迁移、derivedPointClouds 注册、取消任务联动
前端: ElapsedTime 时长显示、两步算法流程、canRun 逻辑修复
文档: 新增5份算法性能分析报告
This commit is contained in:
2026-06-25 08:22:20 +00:00
parent cec0766e67
commit 29f1d45d66
266 changed files with 1311475 additions and 2846 deletions

View File

@@ -0,0 +1,362 @@
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")