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,141 @@
import open3d as o3d
import numpy as np
from scipy.spatial import KDTree
import copy
def compute_point_to_mesh_distance(point_cloud, mesh):
"""
计算点云到 Mesh 的距离
Args:
point_cloud: Open3D 点云对象
mesh: Open3D 网格对象
Returns:
distances: 每个点到最近三角形面的距离
"""
# 方法1:使用 Open3D 内置函数(新版本)
try:
distances = mesh.compute_point_cloud_distance(point_cloud)
return np.asarray(distances)
except AttributeError:
pass
# 方法2:使用 mesh 顶点最近邻(近似)
mesh_points = np.asarray(mesh.vertices)
points = np.asarray(point_cloud.points)
tree = KDTree(mesh_points)
distances, indices = tree.query(points)
return distances
def compute_statistics(distances):
"""计算距离统计信息"""
stats = {
'mean': np.mean(distances),
'rmse': np.sqrt(np.mean(distances ** 2)),
'max': np.max(distances),
'min': np.min(distances),
'std': np.std(distances),
'median': np.median(distances),
}
# 分位数
percentiles = [50, 75, 90, 95, 99]
for p in percentiles:
stats[f'percentile_{p}'] = np.percentile(distances, p)
return stats
def visualize_distance(point_cloud, mesh, distances, max_dist=None):
"""可视化距离(按距离着色)"""
pcd_colored = copy.deepcopy(point_cloud)
if max_dist is None:
max_dist = np.percentile(distances, 95)
# 生成颜色(红:远,蓝:近)
colors = np.zeros((len(distances), 3))
norm_dist = np.clip(distances / (max_dist + 1e-8), 0, 1)
colors[:, 0] = norm_dist # 红色通道(远)
colors[:, 2] = 1 - norm_dist # 蓝色通道(近)
pcd_colored.colors = o3d.utility.Vector3dVector(colors)
# Mesh 着色为灰色
mesh_colored = copy.deepcopy(mesh)
mesh_colored.paint_uniform_color([0.5, 0.5, 0.5])
# 可视化
o3d.visualization.draw_geometries(
[mesh_colored, pcd_colored],
window_name="Distance to Mesh (Blue=Near, Red=Far)",
width=1024,
height=768
)
def print_statistics(stats):
"""打印统计信息"""
print("=" * 60)
print("点云到 Mesh 距离统计")
print("=" * 60)
print(f"平均距离: {stats['mean']:.6f} m ({stats['mean'] * 1000:.3f} mm)")
print(f"RMSE: {stats['rmse']:.6f} m ({stats['rmse'] * 1000:.3f} mm)")
print(f"中位数: {stats['median']:.6f} m ({stats['median'] * 1000:.3f} mm)")
print(f"标准差: {stats['std']:.6f} m ({stats['std'] * 1000:.3f} mm)")
print(f"最大距离: {stats['max']:.6f} m ({stats['max'] * 1000:.3f} mm)")
print(f"最小距离: {stats['min']:.6f} m ({stats['min'] * 1000:.3f} mm)")
print("\n距离分位数:")
print(f" 50%: {stats['percentile_50']:.6f} m ({stats['percentile_50'] * 1000:.3f} mm)")
print(f" 75%: {stats['percentile_75']:.6f} m ({stats['percentile_75'] * 1000:.3f} mm)")
print(f" 90%: {stats['percentile_90']:.6f} m ({stats['percentile_90'] * 1000:.3f} mm)")
print(f" 95%: {stats['percentile_95']:.6f} m ({stats['percentile_95'] * 1000:.3f} mm)")
print(f" 99%: {stats['percentile_99']:.6f} m ({stats['percentile_99'] * 1000:.3f} mm)")
# ========== 主程序 ==========
if __name__ == "__main__":
# 读取文件(使用原始字符串避免转义问题)
file_path = r"E:\point_distance\data\40_noisy.ply"
mesh_path = r"E:\point_distance\data\40.stl"
print("读取点云...")
source_pcd = o3d.io.read_point_cloud(file_path)
print("读取 Mesh...")
mesh = o3d.io.read_triangle_mesh(mesh_path)
# 检查读取是否成功
if len(source_pcd.points) == 0:
print("错误:点云读取失败,请检查文件路径")
exit()
if len(mesh.vertices) == 0:
print("错误:Mesh 读取失败,请检查文件路径")
exit()
print(f"\n点云点数: {len(source_pcd.points)}")
print(f"Mesh 顶点数: {len(mesh.vertices)}")
print(f"Mesh 面数: {len(mesh.triangles)}")
# 可选:Mesh 法线计算(用于更好的可视化)
mesh.compute_vertex_normals()
# 计算距离
print("\n计算点云到 Mesh 的距离...")
distances = compute_point_to_mesh_distance(source_pcd, mesh)
# 计算统计信息
stats = compute_statistics(distances)
# 打印统计信息
print_statistics(stats)
# 可视化
print("\n可视化...")
visualize_distance(source_pcd, mesh, distances)