算法更新: delivery v1.2.1、平整度v1.5、测距v2.2、整车配准v1.3 适配器: vehicle-registration 新增、各算法 manifest 和 adapter 更新 后端: taskCadFiles/source 字段迁移、derivedPointClouds 注册、取消任务联动 前端: ElapsedTime 时长显示、两步算法流程、canRun 逻辑修复 文档: 新增5份算法性能分析报告
182 lines
7.8 KiB
Markdown
182 lines
7.8 KiB
Markdown
# 挠度算法性能分析报告
|
||
|
||
> 分析时间: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`:
|
||
|
||
```python
|
||
# 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% 范围内,但数量级判断可靠。*
|