12 KiB
underframe_deflection 集成调用指南 (v1.1)
高铁底架纵梁挠度测量算法 —— 基于点云的计算。 本文档面向把本算法集成进上层软件的开发者:怎么准备 CAD 基准 + 扫描点云 + ROI JSON 三件套输入、怎么调用、怎么读结果。
0. TL;DR
from underframe_deflection import run_measurement_pipeline
report = run_measurement_pipeline(
input_cloud="scan.ply",
cad_path="reference.ply",
roi_path="roi.json",
out_dir="out/",
)
# 关心的两个核心数值
print(report["deflection"]["left"]["selected_measurement"]["signed_deflection_mm"])
print(report["deflection"]["right"]["selected_measurement"]["signed_deflection_mm"])
落盘后会得到:
out/
├── report.json # 完整结果
├── report.xlsx # 误差报表(Summary + 4 张中间表)
├── deflection_curves.csv # 沿 X 的挠度曲线
├── beam_tracks.csv # 候选梁切片诊断
├── images/ # 5 张诊断 PNG
└── ply/ # 4 个着色 PLY 叠加图
1. 算法的三个输入
| 输入 | 类型 | 必需 | 说明 |
|---|---|---|---|
| 扫描点云 | .ply / .xyz / .txt / .csv |
✅ 必须 | 现场采集得到的 (N, 3) XYZ 点云,单位 mm |
| CAD 基准 | .ply / .xyz / .stl / .obj |
可选但强烈建议 | 预先 tessellate / 采样过的 CAD 几何,作为配准基准坐标系;不直接吃 STEP/IGES |
| ROI JSON | .json |
可选但强烈建议 | 描述车底测量区域的轴对齐 bbox,坐标基于 CAD 坐标系 |
三者全给时走 v1.1 完整流程:去噪 → 配准到 CAD 系 → 按 ROI JSON 截区 → 算挠度 → 出 Excel。 只给扫描点云时走 legacy 流程,与本算法 v1.0 行为完全一致(仅多出 bilateral 这一步可用
--no-bilateral关掉)。
1.1 ROI JSON 文件格式
固定 6 个 float 字段,单位 mm,CAD 坐标系下:
{
"x_min": -12500.0,
"x_max": 12500.0,
"y_min": 150.0,
"y_max": 4100.0,
"z_min": -1680.0,
"z_max": 200.0
}
约束:每个轴 min < max,且都是有限实数;任一字段缺失或非法 → InvalidPointCloudError。
test_data/sample_roi.json 是一个可直接使用的样例。
1.2 CAD 基准
- 不解析 STEP / IGES。如果你只有
.stp,请用 CAD 软件或离线脚本先把它转成.stl/.ply/.obj,再喂给本算法。 - 三角网格的话只读顶点位置,face 信息忽略 —— 算法内部把 CAD 当作"参考点集"用 ICP 对齐。
test_data/m000004802409t02_asm_100000pts.ply是从原始 STEP 采样得到的 10 万点 PLY,可直接当 CAD 基准。
2. 三种调用方式
2.1 命令行(最简单的集成方式)
underframe-measure \
--input-cloud scan.ply \
--cad reference.ply \
--roi roi.json \
--out-dir out/
可选开关:
| 开关 | 含义 |
|---|---|
--config CONFIG.json |
覆盖部分内部超参数(结构同 DEFAULT_CONFIG) |
--profile {auto,M,TC,TP,custom} |
调参档位 |
--skip-registration |
给了 --cad 但不跑 ICP(适合调试或扫描已经预对齐) |
--no-bilateral |
关闭双边滤波(v1.0 行为) |
--skip-excel |
不写 .xlsx |
未安装本包时,等价命令:
python -m underframe_deflection.cli --input-cloud scan.ply ...
2.2 一次性 Python 函数(推荐)
from underframe_deflection import run_measurement_pipeline
report = run_measurement_pipeline(
input_cloud="scan.ply",
cad_path="reference.ply", # 可选
roi_path="roi.json", # 可选
out_dir="out/",
config_path=None, # 可选 JSON 配置覆盖
profile="auto", # 可选档位
skip_registration=False,
enable_bilateral=True,
skip_excel=False,
)
返回:report 是一个 dict,结构与 out/report.json 完全一致。
2.3 纯内存计算(不读写文件)
如果你已经从其它来源拿到了点云和 ROI(例如直接从相机驱动 / 数据库取数据),可以避开文件 I/O:
import numpy as np
from underframe_deflection import compute_deflection, load_point_cloud
points = load_point_cloud("scan.ply") # 或自己构造 (N, 3) ndarray
roi_aabb = { # 与 ROI JSON 同结构的 dict
"x_min": -12500, "x_max": 12500,
"y_min": 150, "y_max": 4100,
"z_min": -1680, "z_max": 200,
}
result = compute_deflection(points, roi_aabb=roi_aabb)
print(result.left.selected_signed_mm, result.right.selected_signed_mm)
注意:
compute_deflection本身不做 CAD 配准。如果你想自己控制配准过程,先调用register_to_cad,把变换应用到点云后再喂给compute_deflection,配合config["frame"]["use_input_axes"]=True(这本来就是默认值)即可:
from underframe_deflection import (
read_cad_reference, register_to_cad, compute_deflection,
)
from underframe_deflection.registration import apply_transform
scan = load_point_cloud("scan.ply")
cad = read_cad_reference("reference.ply")
# 你需要一份预解算好的 config(spacing 之类)。最简单的办法:
from underframe_deflection.config import load_config, resolve_numeric_parameters, cloud_geometry
from underframe_deflection.preprocess import estimate_spacing
cfg = load_config(None, "auto")
cfg = resolve_numeric_parameters(cfg, cloud_geometry(scan), estimate_spacing(scan))
reg = register_to_cad(scan, cad, cfg)
scan_aligned = apply_transform(scan, reg.R, reg.t)
result = compute_deflection(scan_aligned, roi_aabb=roi_aabb)
3. 读取输出
3.1 核心数值(最常用)
left = report["deflection"]["left"]["selected_measurement"]
right = report["deflection"]["right"]["selected_measurement"]
if left["available"]:
x_mm = left["x_mm"]
signed = left["signed_deflection_mm"] # 带符号,向下为负
absolute = left["absolute_deflection_mm"]
type_ = left["selected_type"] # "downward" / "upward"
beam_pt = left["beam_point"] # [x, y, z]
ref_pt = left["reference_point"] # [x, y, z]
如果你用的是 compute_deflection,公开数据类更直接:
result.left.selected_signed_mm
result.left.max_downward_mm
result.left.max_downward_x_mm
result.left.max_upward_mm
result.left.reference_confidence
result.curves.x_mm # ndarray
result.curves.left_deflection_mm # ndarray, NaN 表示该 X 处无效
3.2 质量警告 quality_flags
非致命问题列在这里,调用方应判断是否要重测:
| Flag | 含义 |
|---|---|
left_track_missing / right_track_missing |
单侧梁追踪失败 |
left_representative_line_missing / right_... |
单侧代表线拟合失败 |
left_reference_unavailable / right_... |
单侧参考线不可用 |
left_reference_low_confidence / right_... |
参考线置信度低 |
left_track_low_coverage / right_... |
单侧追踪覆盖率 < 45% |
left_right_symmetry_warning |
左右对称性偏差 > 18% |
registration_not_converged |
ICP 未收敛到容差内 |
registration_poor_quality |
ICP 最终 RMS > max(10 mm, 2×点间距) |
registration_low_inlier_ratio |
ICP 内点比例 < 50% |
passthrough_empty |
ROI passthrough 后点过少,已自动回退 |
bilateral_degraded |
bilateral k 邻域被裁剪 |
加粗的几项是 v1.1 新增。
3.3 配准信息
reg = report["registration"]
if reg["performed"]:
reg["rms_mm"] # 最终对齐残差
reg["initial_rms_mm"] # 粗对齐后的初始残差
reg["iterations"]
reg["inlier_ratio"]
reg["converged"] # bool
reg["R"] # 3x3 list
reg["t"] # length-3 list
3.4 Excel 误差报表
out/report.xlsx 5 个 sheet:
| Sheet | 内容 |
|---|---|
| Summary | key/value 形式:版本、输入路径、配准统计、预处理各阶段计数、左右挠度核心数值、质量警告 |
| Deflection_Curves | 沿 X 的逐点表:x / 左右 beam_y, beam_z / 左右 ref_y, ref_z / 左右 deflection / 左右 confidence |
| Beam_Tracks | 每片候选条带:slice_index、y_center、z_low、width、score、selected_rank 等 |
| Reference_Lines | 左右参考线 front/back 端点、direction、residual、confidence |
| Representative_Points | 左右每个代表点的 x/y/z/width/residual/confidence |
用
openpyxl、pandas.read_excel、Excel/LibreOffice 都可以直接读。
3.5 异常
| 异常类 | 触发条件 |
|---|---|
InvalidPointCloudError |
点云 / CAD / ROI JSON 文件格式非法、字段缺失、AABB 反转 |
UnsupportedFormatError |
文件后缀不在支持列表 |
InsufficientPointsError |
点数过少(点云 < 256)或 ROI 过滤后无重叠 |
FrameInferenceError |
自动坐标系推断失败(仅 legacy 路径) |
集成方应在外层 try / except DeflectionError 兜底,并把 quality_flags 落入日志或上层告警。
4. 配置覆盖(高级)
绝大多数场景不需要碰配置。需要微调时,通过 --config my.json 或 config= 传入嵌套 dict,与 DEFAULT_CONFIG 深度合并:
config = {
"preprocess": {
"bilateral": {"enabled": True, "k": 32, "sigma_n_deg": 10.0},
# 也可以从外部传 passthrough,但通常用 ROI JSON 即可
},
"registration": {
"icp_max_iter": 80,
"trim_ratio": 0.15,
},
"reference": {
"low_confidence_threshold": 0.5,
},
"deflection": {
"evaluation_grid_factor": 0.5, # 更密的挠度采样
},
}
完整字段定义见 underframe_deflection/config.py: DEFAULT_CONFIG。
5. 进度回调
compute_deflection 支持 progress 回调:
def on_progress(stage: str, fraction: float) -> None:
print(f"[{fraction:5.1%}] {stage}")
result = compute_deflection(points, roi_aabb=roi_aabb, progress=on_progress)
阶段顺序:preprocess → frame → beam_tracking → line_fitting → reference → deflection。
run_measurement_pipeline 当前未透传 progress 回调,需要时请直接调 compute_deflection。
6. 坐标系约定
输入点云必须满足以下约定,否则结果无意义:
- X 轴:纵向(车长方向)
- Y 轴:横向(车宽方向)
- Z 轴:高度方向,向上为正
- 单位:毫米
CAD 基准也必须遵循同样的轴向;ROI JSON 中的 bbox 字段在 CAD 坐标系下。 扫描点云轴向不同 → 调用前自行做置换 / 旋转。
7. 版本兼容性
__version__—— 包版本ALGORITHM_VERSION—— 算法版本(升级会改变数值结果)SCHEMA_VERSION——report.json结构版本(仅在破坏性变更时递增;新字段不递增)
v1.1 新增了非破坏性的可选字段(cad_reference / roi_source / roi_aabb / registration / preprocess.passthrough / preprocess.bilateral / roi.source / quality_flags 新值),SCHEMA_VERSION 保持 "1.0" 不变。
8. 集成模板
import logging
from underframe_deflection import (
run_measurement_pipeline,
DeflectionError,
)
log = logging.getLogger(__name__)
def measure(scan_path: str, cad_path: str, roi_path: str, out_dir: str) -> dict | None:
try:
report = run_measurement_pipeline(
input_cloud=scan_path,
cad_path=cad_path,
roi_path=roi_path,
out_dir=out_dir,
)
except DeflectionError as exc:
log.error("deflection failed: %s", exc)
return None
flags = report.get("quality_flags") or []
if flags:
log.warning("quality flags: %s", flags)
return {
"schema_version": report["schema_version"],
"algorithm_version": report["algorithm_version"],
"left_signed_mm": report["deflection"]["left"].get("selected_measurement", {}).get("signed_deflection_mm"),
"right_signed_mm": report["deflection"]["right"].get("selected_measurement", {}).get("signed_deflection_mm"),
"registration_rms_mm": report["registration"].get("rms_mm"),
"flags": flags,
}