Files
massive-cloud/docs/挠度算法分析.md
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

182 lines
7.8 KiB
Markdown
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.
# 挠度算法性能分析报告
> 分析时间: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% 范围内,但数量级判断可靠。*