算法更新: delivery v1.2.1、平整度v1.5、测距v2.2、整车配准v1.3 适配器: vehicle-registration 新增、各算法 manifest 和 adapter 更新 后端: taskCadFiles/source 字段迁移、derivedPointClouds 注册、取消任务联动 前端: ElapsedTime 时长显示、两步算法流程、canRun 逻辑修复 文档: 新增5份算法性能分析报告
7.8 KiB
挠度算法性能分析报告
分析时间:2026-06-25
算法版本:delivery_v1.2.1 / underframe_deflection
输入规模:10 亿点 / 12 GB binary PLY(配准后全车点云)
测试环境:INTEL SSDSC2KB01(SATA SSD,实测顺序读速 ~394 MB/s);内存 1 TB;CPU 128 核
参照基准:算法方给出 2 min 28 s(不含点云加载时间)
执行流程总览
load_point_cloud()
└─ _read_binary_ply() # I/O 阶段
preprocess_point_cloud()
├─ estimate_spacing() # 采样估距
├─ statistical_outlier_filter()# ← 瓶颈1 全云 KNN k=18
├─ radius_filter() # ← 瓶颈2 全云 radius query
├─ bilateral_filter_points() # ← 瓶颈3 全云 KNN k=24
├─ voxel_downsample() # 10亿 → ~23万 work points
└─ estimate_normals() # 跑在 work points 上
infer_vehicle_frame() # 跳过(use_input_axes=true)
compute_deflection() # ← 算法方基准所测范围
├─ beam_tracking
├─ line_fitting
├─ reference detection
└─ deflection evaluation
write artifacts # PLY / Excel / JSON / CSV
各步骤详细说明与时间估算
第一阶段:I/O 与格式解析
| # | 步骤 | 代码位置 | 说明 | 估算时间 |
|---|---|---|---|---|
| 1 | 读取 12 GB binary PLY | io._read_binary_ply |
fh.read() 一次性读入,394 MB/s |
~31 s |
| 2 | np.frombuffer + column_stack → float64 |
io._read_binary_ply |
12 GB float32 → 24 GB float64,纯内存 | ~4 s |
| 3 | _swap_y_z 列置换(y ↔ z) |
pipeline._swap_y_z |
24 GB 数组列重排 | ~3 s |
小计:~38 s
这是算法方"没算点云加载时间"的部分。
第二阶段:预处理(真正瓶颈,全程作用于 10 亿原始点)
所有 KNN 滤波步骤均在 voxel_downsample 之前运行,每步都需对 10 亿点建 cKDTree + 全量查询。
| # | 步骤 | 代码位置 | 配置参数 | 说明 | 1000万点基线 | 10亿点估算 |
|---|---|---|---|---|---|---|
| 4 | estimate_spacing |
preprocess.py:22 |
sample_size=4000 |
只对 4000 个采样点建树,O(1) | ~1 s | ~1 s |
| 5 | statistical_outlier_filter |
preprocess.py:37 |
k=18, std_ratio=2.8 |
建 cKDTree(N) + query k=18,O(N·logN) | ~8 s | ~18 min |
| 6 | radius_filter |
preprocess.py:53 |
radius=281 mm, min_neighbors=4 |
再建 cKDTree + query_ball_point,比定 k 查询慢 2–3× |
~15 s | ~33 min |
| 7 | bilateral_filter_points |
preprocess.py:102 |
k=24, iterations=1 |
再建 cKDTree + query k=24,1 次迭代 | ~10 s | ~20 min |
| 8 | voxel_downsample |
preprocess.py:66 |
voxel=150 mm |
O(N) hash,numpy 向量化,10亿 → ~23万 work points | ~3 s | ~20 s |
| 9 | estimate_normals(work points,k=20) |
preprocess.py:78 |
normal_k=20 |
建树 + 逐点 PCA,但只跑在 ~23万 work points 上 | ~0.3 s | ~30 s |
小计:~72 min(步骤 5/6/7 贡献了 71 分钟)
第三阶段:坐标系变换
| # | 步骤 | 代码位置 | 说明 | 估算时间 |
|---|---|---|---|---|
| 10 | infer_vehicle_frame |
api.py:267 |
任务配置了 use_input_axes=true,直接用单位矩阵,跳过 PCA 帧推断 |
~0 s |
| 11 | transform_points(~9.9 亿 filtered points) |
api.py:289 |
R=I 单位变换,实际等同内存复制 | ~10 s |
小计:~10 s
第四阶段:挠度计算核心(算法方基准对应范围)
以下步骤均运行在 voxel 降采样后的 ~22,596 work points 上。
| # | 步骤 | 代码位置 | 说明 | 估算时间 |
|---|---|---|---|---|
| 12 | build_bottom_roi_mask |
frame.py |
ROI 掩码,O(work_n) | ~1 s |
| 13 | extract_beam_slices |
beams.py |
沿 X 轴分片,提取梁候选片段 | ~10 s |
| 14 | track_side_candidates × 2(左/右) |
beams.py |
左右梁候选追踪 | ~10 s |
| 15 | extract_representative_points × 2 |
fitting.py |
从 filtered_roi 提取代表点 | ~5 s |
| 16 | fit_representative_line × 2 |
fitting.py |
3D 直线拟合(最小二乘) | ~2 s |
| 17 | detect_reference_line × 2 |
reference.py |
端面参考线检测 | ~10 s |
| 18 | _evaluate_side_deflection × 2 |
api.py |
网格插值 + 最大挠度求解 | ~1 s |
小计:~40 s
算法方基准 2 min 28 s 是在已降采样的输入上测的,包含更多候选与迭代冗余,此处估算略低,属合理范围。
第五阶段:写输出
| # | 步骤 | 代码位置 | 说明 | 估算时间 |
|---|---|---|---|---|
| 19 | export_overlay_plys |
artifacts.py |
写 4 个 ASCII PLY 叠加层(共 ~1 GB) | ~3 min |
| 20 | render_plots |
artifacts.py |
matplotlib 生成挠度曲线图 | ~10 s |
| 21 | write_excel_report |
excel_report.py |
openpyxl 写 report.xlsx | ~5 s |
| 22 | write_report_json / CSV |
artifacts.py |
JSON + beam_tracks / deflection_curves CSV | ~2 s |
小计:~3 min 20 s
总时间汇总
| 阶段 | 估算耗时 | 备注 |
|---|---|---|
| I/O 与格式解析 | ~38 s | 算法方未计入的部分 |
| 预处理(三遍全云 KNN) | ~72 min | 主要瓶颈 |
| 坐标系变换 | ~10 s | — |
| 挠度计算(算法核心) | ~40 s | 与算法方基准吻合 |
| 写输出 | ~3 min | — |
| 估算总计 | ~77 min | 超时阈值 90 min |
根本原因分析
流水线顺序(当前):
10亿点
│
├─ statistical_outlier_filter (k=18) ─── 18 min ★
│ cKDTree build + query on 1B pts
│
├─ radius_filter (r=281mm) ─── 33 min ★
│ cKDTree build + query_ball on 1B pts
│
├─ bilateral_filter (k=24, iter=1) ─── 20 min ★
│ cKDTree build + query on 1B pts
│
└─ voxel_downsample (150mm) ─── 20 s
1B pts → ~23万 work points ← 早做就没事了
三个滤波步骤发生在 voxel_downsample 之前,对 10 亿点全云反复建 KD-Tree 并查询。
而 voxel 降采样之后只剩 ~23 万个 work points,在这个规模上做同样的滤波不超过 5 秒。
更重要的是,统计离群过滤在本次数据上只剔除了 0.65%(65 万 / 10 亿)的离群点,对最终 23 万个 work points 的质量几乎没有贡献,却花了 71 分钟。
优化建议
方案 A:适配器层预降采样(推荐,零侵入算法,可立即实施)
在 underframe_deflection_run.py 中,调用算法前先用 Open3D(C++ 实现)对 12 GB 输入做体素降采样,将 10 亿点压缩到 50–100 万点,再传入 run_measurement_pipeline:
# adapter 中新增,在调用 run_measurement_pipeline 之前
import open3d as o3d
pcd = o3d.io.read_point_cloud(pc_path) # Open3D 读 binary PLY,~15s
pcd_down = pcd.voxel_down_sample(voxel_size=30.0) # 30mm 体素,~5s,~100万点
# 写临时 binary PLY
tmp_path = out_dir / "_predown.ply"
o3d.io.write_point_cloud(str(tmp_path), pcd_down, write_ascii=False)
pc_path = str(tmp_path)
| 指标 | 当前 | 优化后 |
|---|---|---|
| 预处理 KNN 滤波 | ~71 min | < 30 s |
| 总耗时 | ~77 min | ~5 min |
| 改动范围 | — | 仅 adapter,不改算法 |
方案 B:算法层调整滤波顺序(需算法方配合)
将 preprocess.py 中 statistical_outlier_filter / radius_filter / bilateral_filter 三步移到 voxel_downsample 之后执行,从根本上解决问题,同时提升所有算法方自己的大点云场景性能。
注:上述时间为理论估算,基于 1000 万点实测基线(analysis/130)线性外推。实际误差在 ±30% 范围内,但数量级判断可靠。