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:
148
docs/平整度/平面度/平整度/平面度/平整度-平面度-轮廓测量轮廓测量算法分析.md
Normal file
148
docs/平整度/平面度/平整度/平面度/平整度-平面度-轮廓测量轮廓测量算法分析.md
Normal file
@@ -0,0 +1,148 @@
|
||||
# 平整度/平面度/轮廓测量算法性能分析报告
|
||||
|
||||
> 分析时间:2026-06-25
|
||||
> 算法版本:`平整度、平面度、轮廓测量v1.5`
|
||||
> 入口类:`MeasurementPipeline`(`pipeline.py`)
|
||||
> 测试环境:INTEL SSDSC2KB01(SATA SSD,~394 MB/s);**A100 40 GB GPU(轮廓计算可用)**;CPU 128 核
|
||||
|
||||
---
|
||||
|
||||
## 三种测量模式
|
||||
|
||||
| 模式(manifest `module` 参数) | 执行内容 |
|
||||
|---|---|
|
||||
| `all` | 平面拟合 + 曲率估计(无 CAD 时跳过轮廓) |
|
||||
| `plane` | 仅平面拟合(RANSAC) |
|
||||
| `curvature` | 仅曲率估计 |
|
||||
| `contour` | 点到网格距离(需要 CAD STL),GPU 加速 |
|
||||
|
||||
---
|
||||
|
||||
## 执行流程总览
|
||||
|
||||
```
|
||||
main() / run_*()
|
||||
├─ load_point_cloud() # Open3D 读 PLY
|
||||
├─ [可选] ROI 裁剪 # O(N) 掩码,adapter 提供
|
||||
├─ [可选] filters.run() # 去噪:SOR/ROR/VGF/PF(Open3D C++)
|
||||
│
|
||||
├─ run_plane() # module=plane 或 all
|
||||
│ └─ PlaneEstimator.estimate() # Open3D RANSAC 平面
|
||||
│
|
||||
├─ run_curvature() # module=curvature 或 all
|
||||
│ ├─ estimate_normals() # Open3D KDTree(C++ hybrid search)
|
||||
│ └─ 逐点 PCA 曲率 for loop ← 瓶颈
|
||||
│
|
||||
└─ run_contour() # module=contour(需 CAD)
|
||||
├─ load_mesh() # trimesh / Open3D 读 STL
|
||||
└─ point_to_triangles_distance_torch() # GPU 分块计算 ← 主耗时
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 各步骤详细说明与时间估算
|
||||
|
||||
### 第一阶段:I/O 与预处理
|
||||
|
||||
| # | 步骤 | 代码位置 | 说明 | 小规模(ROI后,~50万点) | 整车全量(10亿点) |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | `o3d.io.read_point_cloud` | `pipeline.py` | Open3D 读 binary PLY,C++ | **~3 s** | **~80 s** |
|
||||
| 2 | ROI 裁剪(adapter 注入) | `flatness_suite_run.py:126` | numpy 布尔掩码,O(N) | **< 1 s** | **~5 s** |
|
||||
| 3 | `VoxelGridFilter`(`voxel_size=0.05 m = 50 mm`) | `preprocessing/filters.py:91` | `o3d.voxel_down_sample`,C++ | **~1 s** | **~15 s** |
|
||||
| 4 | `SORFilter`(`nb_neighbors=20`) | `preprocessing/filters.py:33` | `o3d.remove_statistical_outlier`,C++ | **~2 s** | **~40 s** |
|
||||
|
||||
> adapter 中已有 ROI 裁剪逻辑,但需要前端传入 `roiXMin/Max` 等参数。若不传 ROI,输入直接是 10 亿点全量云,预处理即耗时 ~2 min。
|
||||
|
||||
---
|
||||
|
||||
### 第二阶段:平面拟合(`run_plane`)
|
||||
|
||||
| # | 步骤 | 代码位置 | 默认参数 | 说明 | 估算时间 |
|
||||
|---|---|---|---|---|---|
|
||||
| 5 | `PlaneEstimator.estimate` | `preprocessing/plane.py` | `threshold_mm=0.5, max_iter=2000` | Open3D `segment_plane` RANSAC | **~1–3 s**(百万点内) |
|
||||
|
||||
RANSAC 平面拟合使用 Open3D C++ 实现,时间与点数和迭代次数线性相关,百万点以内不是瓶颈。
|
||||
|
||||
---
|
||||
|
||||
### 第三阶段:曲率估计(`run_curvature`)
|
||||
|
||||
| # | 步骤 | 代码位置 | 默认参数 | 说明 | 估算时间 |
|
||||
|---|---|---|---|---|---|
|
||||
| 6 | `estimate_normals`(Open3D) | `preprocessing/curvature.py` | `radius=2.0 m, max_nn=30` | Open3D KDTree hybrid search,C++ | **~5 s(50万点)** |
|
||||
| 7 | **逐点 PCA 曲率 for loop** | `preprocessing/curvature.py:11` | 同上 | **Pure Python for loop,O(N) 次 numpy eigh**,无向量化 ← 瓶颈 | **~5 min(50万点)** / **~50 min(10亿点)** |
|
||||
|
||||
**曲率估计是最大的性能瓶颈**:
|
||||
|
||||
```python
|
||||
# 当前实现:每个点调用一次 Python,N 次 KDTree 查询 + N 次 eigh
|
||||
for i, point in enumerate(points): # ← N次 Python 调用
|
||||
_, indices, _ = kdtree.search_hybrid_vector_3d(point, radius, max_nn)
|
||||
neighbors = points[indices]
|
||||
cov = np.cov((neighbors - ...).T)
|
||||
eigenvalues = np.linalg.eigvalsh(cov) # ← N次 3x3 矩阵分解
|
||||
```
|
||||
|
||||
50 万点约需 5 分钟;10 亿点直接喂进去,约需数十小时。
|
||||
|
||||
---
|
||||
|
||||
### 第四阶段:轮廓测量(`run_contour`,GPU 加速)
|
||||
|
||||
| # | 步骤 | 代码位置 | 说明 | 估算时间 |
|
||||
|---|---|---|---|---|
|
||||
| 8 | `load_mesh`(trimesh / Open3D) | `pipeline.py` | 读 CAD STL,生成 TriangleMesh | **~5–30 s**(取决于 STL 大小) |
|
||||
| 9 | **`point_to_triangles_distance_torch`(GPU)** | `evaluation.py:67` | 分块(`point_chunk=2048, tri_chunk=4096`)点到三角形距离,A100 并行计算 | **~30–120 s(50万点,CAD三角形数取决于网格精度)** |
|
||||
| 10 | `colorize_distances` → PLY | `evaluation.py` | numpy 着色 + Open3D 写 PLY | **~5 s** |
|
||||
|
||||
轮廓计算使用 GPU 双重分块策略(点 × 三角形),A100 上对几十万点 × 几万三角形约 30–60 秒。若不配置 GPU 或回退到 CPU,同等规模约 30–60 分钟。
|
||||
|
||||
---
|
||||
|
||||
## 总时间汇总
|
||||
|
||||
| 场景 | 估算耗时 | 主要来源 |
|
||||
|---|---|---|
|
||||
| **有 ROI + module=plane(默认用法)** | **~5–10 s** | 基本无瓶颈 |
|
||||
| **有 ROI + module=curvature(50万点)** | **~5–6 min** | 曲率 for loop |
|
||||
| **有 ROI + module=contour(50万点 + GPU)** | **~3–5 min** | CAD 加载 + GPU 分块 |
|
||||
| **有 ROI + module=all(50万点)** | **~10–15 min** | 曲率 for loop + 轮廓 GPU |
|
||||
| 无 ROI 整车输入(10亿点) | **不可行** | 预处理 + 曲率直接 OOM 或数十小时 |
|
||||
|
||||
---
|
||||
|
||||
## 主要瓶颈与优化建议
|
||||
|
||||
### 瓶颈 1:曲率估计 Pure Python for loop
|
||||
|
||||
```
|
||||
修改成本:需要改算法代码
|
||||
快速缓解:在 adapter 对输入做更激进的体素降采样(1–5 mm),
|
||||
将 ROI 内点数压到 1–5 万以内,曲率循环变为秒级。
|
||||
根本解:用 scipy 或 sklearn 批量化 PCA,或用 Open3D 的
|
||||
compute_point_cloud_curvatures 替换 for loop。
|
||||
```
|
||||
|
||||
### 瓶颈 2:无 ROI 时整车输入
|
||||
|
||||
```
|
||||
现状:flatness_suite_run.py 已有 ROI 裁剪逻辑,
|
||||
但需要前端传入 roiXMin/Max 等参数。
|
||||
建议:在 manifest 中为常用测量面设置合理的默认 ROI,
|
||||
防止用户忘记填写时算法直接吃整车 10亿点。
|
||||
```
|
||||
|
||||
### 瓶颈 3:轮廓计算 chunk_size 过小
|
||||
|
||||
默认 `point_chunk_size=2048, triangle_chunk_size=4096`。A100 显存 40 GB 完全可以把 chunk_size 提到 65536 或更高,减少 GPU kernel 调度次数,轮廓计算可以进一步提速 3–5×。
|
||||
|
||||
---
|
||||
|
||||
## GPU 使用情况
|
||||
|
||||
| 模块 | GPU 使用 |
|
||||
|---|---|
|
||||
| 去噪滤波(SOR/ROR/VGF) | ❌ CPU(Open3D C++) |
|
||||
| 平面拟合(RANSAC) | ❌ CPU(Open3D C++) |
|
||||
| 曲率估计 | ❌ CPU(Pure Python) |
|
||||
| **轮廓计算(点到三角形距离)** | **✅ A100 GPU(torch + cuda)** |
|
||||
181
docs/挠度算法分析.md
Normal file
181
docs/挠度算法分析.md
Normal file
@@ -0,0 +1,181 @@
|
||||
# 挠度算法性能分析报告
|
||||
|
||||
> 分析时间: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% 范围内,但数量级判断可靠。*
|
||||
160
docs/整车配准算法性能分析报告.md
Normal file
160
docs/整车配准算法性能分析报告.md
Normal file
@@ -0,0 +1,160 @@
|
||||
# 整车配准算法性能分析报告
|
||||
|
||||
> 分析时间:2026-06-25
|
||||
> 算法版本:`整车配准v1.3`
|
||||
> 入口函数:`translate_align_by_front_tail`(`translate_align_pointcloud_to_cad.py`)
|
||||
> 测试环境:INTEL SSDSC2KB01(SATA SSD,~394 MB/s);A100 40 GB;CPU 128 核
|
||||
> **实测数据**:12 GB / 10 亿点 binary PLY 输入,总耗时 **10 min 24 s**
|
||||
|
||||
---
|
||||
|
||||
## 执行流程总览
|
||||
|
||||
```
|
||||
translate_align_by_front_tail()
|
||||
├─ 1. CAD STEP → mesh → 30k 点云采样 ← 实测 6 min 30 s(无 cache 时)
|
||||
│ └─ [cache 命中] 直接加载 .ply ← 实测 ~5 s
|
||||
├─ 2. 加载原始点云(Open3D binary PLY) ← 实测 1 min 17 s
|
||||
├─ 3. 车头 / 车尾裁剪 × 4 ← 实测 2 min 17 s
|
||||
├─ 4. 车头 / 车尾局部采样(1000 pts × 4)← 实测 7.6 ms
|
||||
├─ 5. 平移估计(点集质心差) ← 实测 1.3 ms
|
||||
├─ 6. ICP 精配准(采样 20 万点) ← 实测 1.9 s
|
||||
├─ 7. 应用变换 + 写入配准结果 PLY ← 实测 12.8 s
|
||||
└─ 8. 对齐误差统计(采样 10 万点 KNN) ← 实测 977 ms
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 各步骤详细说明与时间估算
|
||||
|
||||
### 第一阶段:CAD 准备(STEP → 采样点云)
|
||||
|
||||
| # | 步骤 | 代码位置 | 说明 | 无 cache | 有 cache |
|
||||
|---|---|---|---|---|---|
|
||||
| 1a | `read_step_file`(OCC 加载) | `translate_align...py:73` | OCC `STEPControl_Reader` 读 STEP | ~10 s | 跳过 |
|
||||
| 1b | `BRepMesh_IncrementalMesh`(tessellate) | `translate_align...py:147` | OCC 三角剖分,147,275 个面 | **~6 min** | 跳过 |
|
||||
| 1c | 提取顶点/三角形数组 | `translate_align...py:100` | Python for loop 遍历所有面 | ~15 s | 跳过 |
|
||||
| 1d | `o3d.PointCloud` + 均匀采样 30k 点 | `translate_align...py:155` | 从 mesh 均匀采样 `cad_sample_points=30000` | ~3 s | 跳过 |
|
||||
| 1e | **加载 cache PLY**(`useCadCache=true`) | `translate_align...py:510` | 直接读 1.4 MB PLY,30k 点 | — | **~1 s** |
|
||||
|
||||
> **CAD cache 机制**:首次运行时 STEP tessellate 需约 6 分钟,生成 `cad_cache/<hash>.ply`(1.4 MB)。后续所有运行直接加载 cache,耗时降至 1 秒以内。**务必开启 `useCadCache=true`(已默认开启)**,且不要删除 `cad_cache/` 目录。
|
||||
|
||||
---
|
||||
|
||||
### 第二阶段:加载原始点云
|
||||
|
||||
| # | 步骤 | 代码位置 | 说明 | 实测时间 |
|
||||
|---|---|---|---|---|
|
||||
| 2 | `o3d.io.read_point_cloud`(binary PLY) | `translate_align...py:547` | Open3D C++ 读 12 GB binary PLY,直接构造 PointCloud | **~1 min 17 s** |
|
||||
|
||||
Open3D 读取速度约 158 MB/s(受限于磁盘顺序读速 ~394 MB/s + float 转换)。12 GB 需要约 75 秒,无法进一步优化除非换更快的磁盘。
|
||||
|
||||
---
|
||||
|
||||
### 第三阶段:车头 / 车尾裁剪(主要瓶颈)
|
||||
|
||||
| # | 步骤 | 代码位置 | 说明 | 实测时间 |
|
||||
|---|---|---|---|---|
|
||||
| 3 | `crop_end_region_by_ratio` × 4 | `translate_align...py:180` | 对 10 亿点 Open3D PointCloud 做 4 次 `pcd.crop(AABB)`(CAD 前/后 + 扫描前/后) | **~2 min 17 s** |
|
||||
|
||||
虽然 `o3d.crop` 是 C++ 实现,但对 10 亿点的 PointCloud 内部仍需遍历全量点做 AABB 测试(O(N) × 4 次),实测每次约 34 秒,4 次共计 137 秒。
|
||||
|
||||
裁剪后车头/尾各只有 ~几千到几万点(ratio=0.01,即取前 1% 长度),所以裁剪结果本身非常小,耗时全部来自对全量 10 亿点的遍历。
|
||||
|
||||
---
|
||||
|
||||
### 第四阶段:局部采样 + 粗配准
|
||||
|
||||
| # | 步骤 | 代码位置 | 说明 | 实测时间 |
|
||||
|---|---|---|---|---|
|
||||
| 4 | `sample_farthest_points_simple` × 4 | `translate_align...py:235` | 随机采样 1000 点;裁剪后点数已很少,O(M) | **7.6 ms** |
|
||||
| 5 | 平移估计 | `translate_align...py:638` | 车头质心差 + 车尾质心差均值估计初始平移 | **1.3 ms** |
|
||||
|
||||
---
|
||||
|
||||
### 第五阶段:ICP 精配准
|
||||
|
||||
| # | 步骤 | 代码位置 | 说明 | 实测时间 |
|
||||
|---|---|---|---|---|
|
||||
| 6 | `sample_farthest_points_simple`(20 万点采样) | `translate_align...py:355` | 从 10 亿点随机采样 `icp_sample_points=200000` | **< 1 s** |
|
||||
| 7 | `o3d.registration.registration_icp` | `translate_align...py:375` | Point-to-point ICP,source 20 万点 → target CAD 30k 点,最多 50 次迭代;Open3D C++ | **1.9 s** |
|
||||
|
||||
ICP 本身非常快,因为:
|
||||
- source 只用 20 万点(采样)
|
||||
- target CAD 只有 30k 点
|
||||
- Open3D ICP 是 C++ SIMD 实现
|
||||
|
||||
---
|
||||
|
||||
### 第六阶段:应用变换 + 写入结果
|
||||
|
||||
| # | 步骤 | 代码位置 | 说明 | 实测时间 |
|
||||
|---|---|---|---|---|
|
||||
| 8 | `aligned_pcd.transform(T_final)` | `translate_align...py:704` | 对 10 亿点做 4×4 矩阵变换(Open3D C++,SIMD) | **~2 s** |
|
||||
| 9 | `write_point_cloud_float32_xyz` | `translate_align...py:314` | 分块写 binary PLY,float32,12 GB → 分 1000 个 chunk 写 | **~10 s** |
|
||||
| 10 | 误差统计(`compute_error_stats`) | `translate_align...py:740` | 采样 10 万点,KNN 建树 + 查询,scipy C++ | **977 ms** |
|
||||
|
||||
---
|
||||
|
||||
## 实测时间汇总
|
||||
|
||||
| 阶段 | 实测时间 | 占比 |
|
||||
|---|---|---|
|
||||
| CAD 采样(无 cache) | **6 min 30 s** | 63% |
|
||||
| CAD 采样(有 cache) | **~1 s** | <1% |
|
||||
| **加载原始点云(Open3D)** | **1 min 17 s** | 12% |
|
||||
| **车头/尾裁剪 × 4(Open3D C++ crop)** | **2 min 17 s** | 22% |
|
||||
| 局部采样 + 平移估计 | **<0.01 s** | <1% |
|
||||
| ICP 精配准(20 万点) | **1.9 s** | <1% |
|
||||
| 写 12 GB 结果 PLY | **12.8 s** | 2% |
|
||||
| 误差统计 | **977 ms** | <1% |
|
||||
| **总计(有 cache)** | **~10 min 24 s** | — |
|
||||
| **总计(无 cache,首次)** | **~17 min** | — |
|
||||
|
||||
---
|
||||
|
||||
## 主要瓶颈分析
|
||||
|
||||
### 瓶颈 1:车头/尾裁剪 2 min 17 s(无法简单绕过)
|
||||
|
||||
`pcd.crop(AABB)` 是 Open3D C++ 全量遍历,10 亿点 × 4 次不可避免。
|
||||
|
||||
```
|
||||
缓解方案:将 crop_ratio 从 0.01 增大到 0.02–0.05,
|
||||
提取更多点用于粗配准,同时不影响裁剪本身速度(依然 O(N))。
|
||||
根本方案:将 Open3D PointCloud 替换为 numpy 数组,
|
||||
4 次 crop 变为 4 次 numpy 布尔索引,
|
||||
并行化(4 个 crop 独立,可多线程),
|
||||
估计可从 2 min 17 s 降至 ~30 s。
|
||||
```
|
||||
|
||||
### 瓶颈 2:加载原始点云 1 min 17 s(I/O 受限)
|
||||
|
||||
Open3D 读 12 GB 文件,受限于 SATA SSD 394 MB/s 顺序读速,理论下限约 30 s(NVMe),实测 77 s 说明还有额外开销(float 类型转换)。升级到 NVMe 可降至 ~35 s。
|
||||
|
||||
### 瓶颈 3:CAD tessellate 6 min(首次,已有 cache 机制解决)
|
||||
|
||||
OCC 对 147,275 个面的 CAD 做三角剖分只需要跑一次,结果缓存为 1.4 MB PLY。**只要 `useCadCache=true` 且 CAD 文件未更换,后续每次配准这一步只需 1 秒**。
|
||||
|
||||
---
|
||||
|
||||
## 优化目标
|
||||
|
||||
| 场景 | 当前 | 优化后目标 |
|
||||
|---|---|---|
|
||||
| 有 cache,12 GB 输入 | 10 min 24 s | ~4 min(crop 多线程 + numpy 化) |
|
||||
| 首次运行(无 cache) | ~17 min | ~4 min + 6 min(CAD build 无法优化) |
|
||||
|
||||
---
|
||||
|
||||
## GPU 使用情况
|
||||
|
||||
| 模块 | GPU 使用 |
|
||||
|---|---|
|
||||
| STEP 解析 / tessellate(OCC) | ❌ CPU |
|
||||
| 点云读取(Open3D) | ❌ CPU(I/O 受限) |
|
||||
| crop / transform(Open3D) | ❌ CPU(C++ SIMD) |
|
||||
| **ICP 精配准(Open3D)** | ❌ CPU(Open3D ICP 不使用 CUDA) |
|
||||
| 误差统计(scipy KNN) | ❌ CPU |
|
||||
|
||||
**整车配准全程不使用 GPU**,A100 完全闲置。ICP 最有潜力用 GPU 加速(Open3D 有 `registration_colored_icp` + CUDA),但当前输入只有 20 万点,ICP 本身已是 1.9 s,瓶颈在 I/O 和 crop,GPU 化 ICP 收益有限。
|
||||
140
docs/点云测距算法性能分析报告.md
Normal file
140
docs/点云测距算法性能分析报告.md
Normal file
@@ -0,0 +1,140 @@
|
||||
# 点云测距算法性能分析报告
|
||||
|
||||
> 分析时间:2026-06-25
|
||||
> 算法版本:`train_measurement_system_v2.2`
|
||||
> 入口类:`TaskManager`(`src/task_manager.py`)
|
||||
> 测试环境:INTEL SSDSC2KB01(SATA SSD,~394 MB/s);CPU 128 核
|
||||
> 配置文件:`config/measurements_0618.yaml`(20 个测量任务)
|
||||
|
||||
---
|
||||
|
||||
## 执行流程总览
|
||||
|
||||
```
|
||||
train_measurement_run.py (adapter)
|
||||
├─ tm.pointcloud_path_override = pc_path
|
||||
├─ tm.load_config() # 读 YAML,解析 20 个任务
|
||||
├─ tm.load_point_cloud() # 一次性加载整车点云到内存 ← 瓶颈1
|
||||
└─ tm.run_all_tasks()
|
||||
├─ 任务1: plane_to_plane
|
||||
│ ├─ ROIExtractor.extract_bbox_points() # numpy 布尔索引,O(N)
|
||||
│ ├─ PlaneFitter.fit() × 2 # RANSAC,O(k×M)
|
||||
│ └─ distance calculation
|
||||
├─ 任务2: plane_to_plane ... (× 10 个)
|
||||
├─ 任务n: point_to_point ... (× 3 个)
|
||||
├─ 任务n: point_to_plane ... (× 3 个)
|
||||
├─ 任务n: plane_to_plane + 4_hole_center ... (× 2 个)
|
||||
└─ 任务n: point_to_point + hole_center ... (× 2 个)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 各步骤详细说明与时间估算
|
||||
|
||||
### 第一阶段:I/O — 加载点云
|
||||
|
||||
| # | 步骤 | 代码位置 | 说明 | 估算时间 |
|
||||
|---|---|---|---|---|
|
||||
| 1 | `PointCloudLoader.load_ply` | `pointcloud_loader.py:11` | 逐点 `struct.unpack` for loop,**O(N) 次 Python 调用** ← 瓶颈 | **~3 min(10亿点)** |
|
||||
|
||||
**关键问题**:当前 `_read_binary_ply` 使用 `struct.unpack` 逐点循环,实测 ~5 M 点/s。对比 `np.frombuffer`(~30 M 点/s)慢 6×,10 亿点需要约 3 分钟仅用于读取。
|
||||
|
||||
```python
|
||||
# 当前实现:每个点一次 Python 函数调用
|
||||
for _ in range(vertex_count): # ← N次循环
|
||||
data = struct.unpack(fmt, f.read(struct_size)) # ← N次 struct.unpack
|
||||
points.append(data[:3])
|
||||
|
||||
# 理想实现:一次性内存操作
|
||||
data = f.read(vertex_count * struct_size)
|
||||
arr = np.frombuffer(data, dtype=record_dtype) # ← 1次调用,~6× 更快
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 第二阶段:每个测量任务的执行(共 20 个)
|
||||
|
||||
每个任务共享同一份全量点云 `self.point_cloud`(已加载到内存),按序独立执行。
|
||||
|
||||
#### 子步骤 A:ROI 裁剪
|
||||
|
||||
| # | 步骤 | 代码位置 | 说明 | 估算时间(单任务) |
|
||||
|---|---|---|---|---|
|
||||
| 2 | `ROIExtractor.extract_bbox_points` | `roi_extractor.py:10` | numpy 全量布尔掩码,O(N) | **~2–5 s(10亿点)** |
|
||||
|
||||
> **重要**:每个任务都调用一次 `extract_bbox_points`,对 10 亿点全量 numpy 数组做 3 列比较。20 个任务 × 2–5 s = **~40–100 s 只用于 ROI 掩码**,而 ROI 内实际只有 100–10 万个点。
|
||||
|
||||
各任务 ROI 尺寸与预期点数(10 亿点密度下):
|
||||
|
||||
| 任务 | 类型 | source ROI 尺寸 (mm) | source 预期点数 |
|
||||
|---|---|---|---|
|
||||
| 任务21_侧门高度 | plane_to_plane | `[1000, 4, 20]` | ~221 pts |
|
||||
| 任务2_车体长度 | plane_to_plane | `[4, 400, 1600]` | ~7,091 pts |
|
||||
| 任务43_窗口下沿高度 | plane_to_plane | `[1210, 2, 5]` | ~33 pts |
|
||||
| 任务44_车顶高度 | plane_to_plane | `[10449, 2, 474]` | ~27,439 pts |
|
||||
|
||||
#### 子步骤 B:平面拟合(plane_to_plane / point_to_plane 任务)
|
||||
|
||||
| # | 步骤 | 代码位置 | 默认参数 | 说明 | 估算时间(单任务) |
|
||||
|---|---|---|---|---|---|
|
||||
| 3 | `PlaneFitter.fit`(RANSAC) | `plane_fitter.py:44` | `max_iter=1000, threshold=0.1, sample_pts=100` | RANSAC:1000 次随机抽 3 点 + 全 ROI 距离计算 | **< 0.1 s(ROI内千点级)** |
|
||||
| 4 | SVD 精确平面拟合 | `plane_fitter.py:137` | `max_svd_pts=10000` | 收敛后 SVD 精确拟合;>1 万点自动降采样 | **< 0.1 s** |
|
||||
| 5 | 计算平面间距离 | `plane_distance.py` | — | 点到平面距离 + 统计 | **< 0.01 s** |
|
||||
|
||||
#### 子步骤 C:孔心检测(含 `_hole_center` 的任务)
|
||||
|
||||
| # | 步骤 | 代码位置 | 说明 | 估算时间(单任务) |
|
||||
|---|---|---|---|---|
|
||||
| 6 | `HoleDetector.detect` | `hole_detect/hole_detector.py` | 圆心检测 + RANSAC 圆拟合 | **~2–5 s** |
|
||||
|
||||
---
|
||||
|
||||
### 第三阶段:结果输出
|
||||
|
||||
| # | 步骤 | 代码位置 | 说明 | 估算时间 |
|
||||
|---|---|---|---|---|
|
||||
| 7 | `ExcelExporter.export` | `excel_exporter.py` | openpyxl 生成 xlsx | **~5 s** |
|
||||
| 8 | `MeasurementLogger.save` | `measurement_logger.py` | JSON 结果落盘 | **< 1 s** |
|
||||
|
||||
---
|
||||
|
||||
## 总时间汇总(10 亿点输入)
|
||||
|
||||
| 阶段 | 估算耗时 | 说明 |
|
||||
|---|---|---|
|
||||
| **struct loop 读取 12 GB 点云** | **~3 min** | 10 亿次 Python 调用 |
|
||||
| **20 次全量 ROI 布尔索引** | **~40–100 s** | 每次 O(N),共 20 次 |
|
||||
| 20 个任务实际测量计算 | **~20 s** | ROI 内点数少,RANSAC 极快 |
|
||||
| 孔心检测(共 6 次) | **~20 s** | — |
|
||||
| 输出(Excel + JSON) | **~5 s** | — |
|
||||
| **估算总计** | **~5–6 min** | 远超算法方测试的 ROI 后场景 |
|
||||
|
||||
> 算法方基准(~2 min)是直接在 ROI 后的小点云上跑的结果,没有计入全量加载和 20 次全量 ROI 索引的开销。
|
||||
|
||||
---
|
||||
|
||||
## 主要瓶颈与优化建议
|
||||
|
||||
### 瓶颈 1:`struct.unpack` 逐点读取(改算法方可根治)
|
||||
|
||||
```
|
||||
当前:struct.unpack × 10亿次 → ~3 min
|
||||
优化:np.frombuffer 一次性读 → ~35 s
|
||||
速度提升:~5×,改动:pointcloud_loader.py _read_binary_ply 约 3 行
|
||||
```
|
||||
|
||||
### 瓶颈 2:20 次全量 numpy 布尔索引(适配器层可缓解)
|
||||
|
||||
```
|
||||
当前:每个任务都对 10亿点数组做 3 列 boolean 比较,共 20 次
|
||||
优化方案 A:adapter 预处理——用 Open3D 先按整车大 bbox 粗裁剪到 ~1亿点,
|
||||
再传给 TaskManager,ROI 索引代价降 10×
|
||||
优化方案 B:修改 TaskManager,第一次裁剪后缓存点云,
|
||||
下一个任务的 ROI 在上次结果上继续裁(层次裁剪)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GPU 使用情况
|
||||
|
||||
**不使用 GPU。** 所有计算为 CPU + numpy + scipy,无 torch/CUDA 依赖。
|
||||
111
docs/车钩面板算法分析.md
Normal file
111
docs/车钩面板算法分析.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# 车钩面板算法性能分析报告
|
||||
|
||||
> 分析时间:2026-06-25
|
||||
> 算法版本:`delivery_v1.2.1 / coupler_panel_release`
|
||||
> 适用场景:ROI 裁剪后的车钩面板局部点云(~数万点)
|
||||
> 测试环境:INTEL SSDSC2KB01(SATA SSD,~394 MB/s);A100 40 GB;CPU 128 核
|
||||
|
||||
---
|
||||
|
||||
## 执行流程总览
|
||||
|
||||
```
|
||||
run_coupler_measurement_pipeline()
|
||||
├─ load + ROI clip # 流式读取 / 全量加载后裁剪
|
||||
├─ select_panel_layer() # 可选层隔离(layer_select.enabled)
|
||||
├─ parse_cad() # 可选 STEP CAD 解析
|
||||
├─ autotune detection config # 可选参数自适应
|
||||
├─ denoise_cloud() # 去噪流水线(passthrough/voxel/SOR/ROR)
|
||||
└─ compute_coupler_measurement()
|
||||
├─ detect_four_holes() # 占位格 → 圆拟合 → 4 孔检测 ← 主要耗时
|
||||
├─ plane_fit() # 平面拟合
|
||||
└─ compute_deflection_metrics()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 各步骤详细说明与时间估算
|
||||
|
||||
### 第一阶段:I/O 与 ROI 裁剪
|
||||
|
||||
| # | 步骤 | 代码位置 | 说明 | 估算时间 |
|
||||
|---|---|---|---|---|
|
||||
| 1 | `crop_ply_bbox` 流式裁剪 | `pipeline.py:109` | **当提供 ROI 且输入为 binary PLY 时**,分块读取,无需全量加载(关键优化) | **~5–30 s**(取决于文件大小) |
|
||||
| 1b | 全量 `load_point_cloud` | `pipeline.py:117` | 无 ROI 时全量加载;1 亿点 ~2 GB binary PLY | **~15 s** |
|
||||
| 2 | `apply_roi` 掩码 | `pipeline.py:118` | O(N) numpy 布尔索引 | **< 1 s** |
|
||||
|
||||
> **重要**:`crop_ply_bbox` 是 v1.2.1 新增的流式裁剪,避免了整车大点云全量加载。必须在前端配置 ROI 参数,否则回退到全量加载再过滤。
|
||||
|
||||
---
|
||||
|
||||
### 第二阶段:可选层隔离与 CAD 解析
|
||||
|
||||
| # | 步骤 | 代码位置 | 说明 | 估算时间 |
|
||||
|---|---|---|---|---|
|
||||
| 3 | `select_panel_layer` | `pipeline.py:137` | PCA 法向 + 沿法向直方图取单层;仅当 `layer_select.enabled=true` 时运行 | **~2 s** |
|
||||
| 4 | `parse_cad`(STEP → mesh + GT) | `data_prep.py:111` | OCC tessellate STEP + 提取定位孔 GT;仅当传入 `cad_stp_path` 时运行 | **~5–30 s**(取决于 STEP 复杂度) |
|
||||
| 5 | `detection_overlay` autotune | `pipeline.py:190` | 从 CAD 孔半径 + 降采样间距自适应调整 detection 参数 | **< 1 s** |
|
||||
|
||||
---
|
||||
|
||||
### 第三阶段:去噪(`denoise_cloud`)
|
||||
|
||||
去噪流水线按 YAML config 中 `denoise.steps` 顺序执行,ROI 裁剪后的局部云通常只剩 **数千到数万点**,各步骤很快。
|
||||
|
||||
| # | 步骤 | 代码位置 | 使用 Open3D C++ 实现 | 说明 | 估算时间 |
|
||||
|---|---|---|---|---|---|
|
||||
| 6 | `passthrough_filter`(轴截取) | `denoise/passthrough.py` | 是(bbox crop) | 沿轴裁剪额外边界;O(N) | **< 0.1 s** |
|
||||
| 7 | `voxel_downsample`(VGF) | `denoise/voxel.py` | 是(`o3d.voxel_down_sample`) | 体素降采样,C++ 加速 | **< 0.5 s** |
|
||||
| 8 | `statistical_outlier_removal`(SOR) | `denoise/statistical.py` | 是(`o3d.remove_statistical_outlier`) | k-NN 统计离群,C++ 加速 | **< 1 s** |
|
||||
| 9 | `radius_outlier_removal`(ROR) | `denoise/radius.py` | 是(`o3d.remove_radius_outlier`) | 半径离群,C++ 加速 | **< 1 s** |
|
||||
|
||||
> 与 underframe-deflection 的 preprocess 对比:去噪用的是 Open3D C++ 实现,不是 scipy Pure Python,在局部云上几乎无延迟。
|
||||
|
||||
---
|
||||
|
||||
### 第四阶段:4 孔检测(主要耗时)
|
||||
|
||||
| # | 步骤 | 代码位置 | 说明 | 估算时间 |
|
||||
|---|---|---|---|---|
|
||||
| 10 | 投影到面板平面 + 占位格生成 | `detection/occupancy_grid.py` | PCA 面板法向投影 → 2D 栅格 | **~1 s** |
|
||||
| 11 | **孔形空腔检测** | `detection/detect_holes.py` | 连通分量 → 面积/圆度 筛选 4 孔;**若参数不匹配则抛 `HoleDetectionError`** | **~2–5 s** |
|
||||
| 12 | `circle_fit` 精确圆心 | `detection/circle_fit.py` | Taubin 圆拟合,O(M) per hole | **~1 s** |
|
||||
| 13 | `four_hole_selector` 4 孔配对 | `detection/four_hole_selector.py` | 象限排序 + 几何验证 | **< 1 s** |
|
||||
|
||||
> **瓶颈**:孔检测依赖 `grid_pixel_mm` / `hole_*_area_mm2` 参数匹配。参数不适配会触发 `HoleDetectionError`,需通过 `autotune` 或手动指定 profile 解决。
|
||||
|
||||
---
|
||||
|
||||
### 第五阶段:测量与输出
|
||||
|
||||
| # | 步骤 | 代码位置 | 说明 | 估算时间 |
|
||||
|---|---|---|---|---|
|
||||
| 14 | `plane_fit` 精确平面拟合 | `api.compute_coupler_measurement` | SVD 平面拟合 | **< 1 s** |
|
||||
| 15 | `compute_deflection_metrics` | `metrics.py` | 角度 / 偏移量计算 | **< 1 s** |
|
||||
| 16 | 写输出(JSON / PLY / Excel) | `pipeline.py` | report JSON + xlsx | **~2 s** |
|
||||
|
||||
---
|
||||
|
||||
## 总时间汇总
|
||||
|
||||
| 场景 | 估算耗时 | 说明 |
|
||||
|---|---|---|
|
||||
| **正常流程(有 ROI + 有 CAD)** | **~1–2 min** | 主要耗在 STEP 解析和孔检测 |
|
||||
| 无 ROI 的整车输入(1 亿点) | ~10 min+ | 全量加载 + 全量 SOR/ROR 过滤 |
|
||||
| 检测参数不匹配(`HoleDetectionError`) | 失败 | 需调整 `grid_pixel_mm` 或使用 autotune |
|
||||
|
||||
---
|
||||
|
||||
## 主要瓶颈与优化建议
|
||||
|
||||
| 问题 | 原因 | 解决方案 |
|
||||
|---|---|---|
|
||||
| `HoleDetectionError` 频繁发生 | `grid_pixel_mm` 过大或 `hole_area` 范围不含目标孔 | 提供 CAD STEP 启用 autotune,或在 manifest 配置 profile |
|
||||
| 整车云输入太慢 | 无 ROI 时全量加载 + 全量 SOR/ROR | **必须在前端配置 ROI**;或 manifest 中配置合理默认 ROI |
|
||||
| STEP 解析慢 | OCC tessellate 复杂曲面 | 可预先生成 STL 绕过 OCC;或启用 CAD sample 缓存 |
|
||||
|
||||
---
|
||||
|
||||
## GPU 使用情况
|
||||
|
||||
**不使用 GPU。** 所有计算为 CPU + numpy + Open3D,无 torch/CUDA 依赖。
|
||||
Reference in New Issue
Block a user