13 KiB
coupler_panel — 集成开发者使用指南
高铁车钩面板偏转角与最大偏差测量算法
主输入:CAD STP + 点云 PLY + ROI JSON
主输出:两个方向偏转角
θ_right/θ_ref、最大 x 向偏差dx_y_max/dx_z_max,以及一份完整可序列化的report.json。
1. 一句话集成
from coupler_panel.pipeline import run_coupler_measurement_pipeline
result = run_coupler_measurement_pipeline(
input_cloud = "scan.ply", # 你的测量点云(已在 CAD 帧)
cad_stp_path = "panel.stp", # 同款面板的 CAD STP
roi_json = "roi.json", # ROI 描述(不裁剪就传 mode=all)
out_dir = "runs/today", # 中间产物 + report.json 落盘目录
)
print(result.theta_right_deg, result.theta_ref_deg) # 两个偏转角
print(result.dx_y_max_mm, result.dx_z_max_mm) # 最大 x 偏差
返回 CouplerMeasurement,全字段语义见 §4。out_dir 下额外得到 8 个文件(含 report.json,便于排查 / 后处理)。
2. 安装
# 全量安装(推荐)— core + 点云 IO + STP 解析 + 可视化 + Excel
python -m pip install -r requirements.txt
pip install -e ".[all]"
# 也可以按需取子集(见 pyproject.toml 的 extras):
pip install -e ".[cloud]" # 只要点云 + 去噪
pip install -e ".[cad]" # 需要解析 STP
pip install -e ".[report]" # 需要 Excel 报表
requirements.txt 锁定了端到端跑通过的版本(Python 3.10 验证)。
3. 三件输入的准备
3.1 CAD STP
- 提供面板的 STEP 文件路径。
- 算法用它做两件事:
- 从拓扑直接提 4 个定位孔 + 中心大孔 + 面板法向(写入
01_gt.json,作审计用) - 把 CAD 法向作为
panel_normal_seed喂给 hole-detection 的投影几何,加速并稳定 2D 网格化
- 从拓扑直接提 4 个定位孔 + 中心大孔 + 面板法向(写入
- 可以不传(
cad_stp_path=None)—— 此时 hole detection 走 PCA 自行估法向;适用于你已经预先把法向算好/扫描坐标系本来就规范的场景。建议生产里始终传,便于审计。
3.2 点云 PLY
- 支持
.ply/.pcd/.xyz/.txt/.csv(PCD 与二进制 PLY 需要 open3d)。 - 数组形状
(N, 3),单位 mm。 - 必须在 CAD 坐标系:
- x = 车体纵向,y = 车体宽度,z = 车体高度
- 算法默认面板法向
n_x > 0
- 不再做配准(参见 §6 "为什么不配准")。这意味着上游扫描端外参必须已经标定 —— 把点云对齐到 CAD 帧是扫描软件的职责,不是本算法的职责。
- 可以带颜色 / 法向等额外列;算法只读前 3 列。
3.3 ROI JSON
定义"PLY 里哪些点要纳入后续算法",**轴对齐 bounding box(AABB)**格式:
{
"x_min": -12500.0,
"x_max": 12500.0,
"y_min": 150.0,
"y_max": 4100.0,
"z_min": -1680.0,
"z_max": 200.0
}
六个 key 全部必填,单位 mm,坐标系 CAD 帧。点 (x, y, z) 被保留的条件是:
x_min <= x <= x_max ∧ y_min <= y <= y_max ∧ z_min <= z <= z_max
- 想保留全部点云:把 6 个边界写得比点云自身 bbox 还宽即可(不需要任何 mode 切换)。
- 想不写文件:传
roi_json=None,算法走"±∞" 默认(pass-through)。 - 想程序内构造:传
roi_spec=ROISpec(x_min=..., ...)。
4. 公共 API 详解
4.1 主入口 run_coupler_measurement_pipeline
def run_coupler_measurement_pipeline(
input_cloud : str | Path, # 必填:点云路径
out_dir : str | Path, # 必填:输出目录(自动创建)
*,
config_path : str | Path | None = None, # 可选:自定义 YAML
profile : str = "auto", # 可选:preset profile
cad_stp_path: str | Path | None = None, # 推荐:CAD STP 路径
roi_spec : ROISpec | None = None, # 二选一:内存 ROI 对象
roi_json : str | Path | None = None, # 二选一:ROI JSON 路径
progress : Callable[[str, float], None] | None = None,
) -> CouplerMeasurement
算法链路(固定不可绕过):
load_point_cloud → apply_roi → [optional CAD parse → panel_normal_seed]
→ denoise_cloud → compute_coupler_measurement
├─ validate
├─ detect 4 holes
├─ build reference frame
├─ extract panel ROI
├─ fit panel plane (RANSAC + SVD)
└─ compute metrics
Profile:传 "auto" 即可;若你的面板型号对应到一个特定 profile,可指定 profile="lu1_af4" 等(详见 configs/profiles/)。
配置覆盖:
result = run_coupler_measurement_pipeline(
input_cloud="scan.ply",
out_dir="runs/x",
config_path={
# 任何在 configs/default.yaml 里的字段都可以这样覆盖
"denoise": {"steps": [{"type": "voxel", "voxel_mm": 0.3}]},
"plane_fit": {"ransac_inlier_thresh_mm": 1.0},
"metrics": {"right_axis_sign": -1},
},
)
4.2 返回值 CouplerMeasurement
@dataclass
class CouplerMeasurement:
# 版本号
schema_version: str # 字段语义稳定性 → SCHEMA_VERSION
algorithm_version: str # 数值结果稳定性 → ALGORITHM_VERSION
# === 6 个主测量量 ===
theta_right_deg: float # 右偏转角 atan2(n_y, n_x),applied right_axis_sign
theta_ref_deg: float # 上偏转角 atan2(n_z, n_x)
D_y_max_mm: float # max |y_i - C_y|
D_z_max_mm: float # max |z_i - C_z|
dx_y_max_mm: float # |A| · D_y_max, A = -n_y/n_x
dx_z_max_mm: float # |B| · D_z_max, B = -n_z/n_x
# === 几何 ===
center_C: tuple[float, float, float] # 面板中心 (4 孔均值)
normal: tuple[float, float, float] # 拟合面板法向,n_x > 0
# === 详细子结构 ===
holes: HoleSet # 检测到的 4 个定位孔 + 可选中心孔
plane: PanelPlane # normal + offset + inlier_ratio + RMS
frame: ReferenceFrame # C + 参考面 z=C_z + 纵向中面 y=C_y
# === 诊断 ===
quality_flags: list[str] # 例如 'plane_inlier_ratio_low'
timings: list[StageTiming] # 每阶段秒数 + 峰值内存
report: dict # 全量可序列化,与 report.json 一致
result.to_dict() # 完全 JSON 化的 dict(落 DB / 网络传输用)
4.3 异常
CouplerError # 所有可预见异常的基类
├── InvalidPointCloudError # 输入形状/dtype/NaN 非法
│ └── UnsupportedFormatError # 文件后缀不支持
├── InsufficientPointsError # 点数 < 200 或 ROI 后 < 100
├── CADParseError # STP 解析失败 / 找不到 4 孔
├── HoleDetectionError # 点云上 4 孔检测失败
└── PlaneFitError # RANSAC 找不到平面
非致命问题通过 result.quality_flags 暴露:
| Flag | 含义 |
|---|---|
plane_inlier_ratio_low |
平面拟合内点率低于 plane_fit.min_inlier_ratio |
4.4 进度回调
def on_progress(stage: str, fraction: float) -> None:
print(f"[{fraction:5.1%}] {stage}")
result = run_coupler_measurement_pipeline(..., progress=on_progress)
阶段顺序:validate → detect_holes → build_frame → extract_roi → fit_plane → metrics。
5. ROI JSON schema 详细
完整字段(6 个 key,全部必填,单位 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
}
规则:
- 必须满足
x_min < x_max且y_min < y_max且z_min < z_max,否则load_roi_json抛ValueError - 不在 [min, max] 闭区间内的点被丢弃(用
>=与<=,包含边界) - 多余的 key(例如
comment、source)会被忽略,方便挂元数据
手动构造 ROI(不读文件):
from coupler_panel.roi_select import ROISpec, default_roi_spec
# 全保留默认(六个轴都是 ±∞)
spec = default_roi_spec()
# 自定义 bbox
spec = ROISpec(
x_min=-12000, x_max= 100,
y_min= -300, y_max= 1200,
z_min= -1000, z_max= 800,
)
result = run_coupler_measurement_pipeline(..., roi_spec=spec)
诊断输出:
每次运行后 out_dir/roi.json 写出实际生效的 spec + 应用诊断:
{
"spec": { "x_min": -12000.0, ... },
"diagnostics": {
"kind": "aabb",
"n_in": 96662,
"n_out": 96662,
"kept_ratio": 1.0,
"bounds": { "x_min": -12000.0, ... }
}
}
6. 为什么主路径不做配准
车钩面板上的 4 个定位孔与面板一起刚体旋转。如果用"扫到的 4 孔 → CAD 原始 4 孔"做 Kabsch 配准,数学上会精确解出"扫描器位姿误差 · 面板偏转"的复合刚体变换并反向作用 —— 把面板偏转随同位姿误差一起消掉,下游平面拟合再读不到偏转信号。
这是几何性质,不是实现 bug。
工程上,主算法假设扫描端外参标定让点云已经在 CAD 帧。如果你的设备做不到位姿标定,要么:
- 在面板外装基准(不动)做 Kabsch
- 用 ICP 或其他不依赖 4 孔的方法
研究模式(coupler_panel.pipeline.run_from_cad_pipeline)保留了 4 孔 Kabsch + 注入偏转 oracle 的能力,用于上限对照实验;生产集成请用 run_coupler_measurement_pipeline。
7. 落盘产物(out_dir/)
| 文件 | 内容 |
|---|---|
config.resolved.yaml |
解析合并后实际生效的完整配置 |
roi.json |
ROI spec + 应用诊断(n_in / n_out / kept_ratio) |
01_gt.json |
(传了 cad_stp_path 时)从 STP 提的审计用 GT |
02_sampled.ply |
ROI 裁剪后的点云 |
05_denoised.ply |
去噪后点云(喂给测量算法的最终点云) |
10_metrics.json |
等价于 report.json 的内部 report 部分 |
report.json |
完整结果,等于 result.to_dict();schema 见 §4.2 |
images/ |
占位空目录(仅 viz 模块写图时会用) |
8. 集成模板
import logging
from pathlib import Path
from coupler_panel.pipeline import run_coupler_measurement_pipeline
from coupler_panel.exceptions import CouplerError
log = logging.getLogger(__name__)
def measure_coupler(
cloud_path: Path,
cad_stp: Path,
roi_json: Path,
out_dir: Path,
) -> dict | None:
"""主集成入口。失败时返回 None;成功时返回 6 个测量量 + flags。"""
try:
result = run_coupler_measurement_pipeline(
input_cloud = cloud_path,
cad_stp_path = cad_stp,
roi_json = roi_json,
out_dir = out_dir,
progress = lambda s, f: log.debug("%s %.0f%%", s, f * 100),
)
except CouplerError as exc:
log.error("coupler measurement failed: %s", exc)
return None
if result.quality_flags:
log.warning("quality flags: %s", result.quality_flags)
return {
"schema_version": result.schema_version,
"algorithm_version": result.algorithm_version,
"theta_right_deg": result.theta_right_deg,
"theta_ref_deg": result.theta_ref_deg,
"dx_y_max_mm": result.dx_y_max_mm,
"dx_z_max_mm": result.dx_z_max_mm,
"center_C_mm": list(result.center_C),
"normal": list(result.normal),
"flags": result.quality_flags,
}
9. 命令行(备用,非主集成路径)
# 等价于 run_coupler_measurement_pipeline(input_cloud=..., out_dir=...)
coupler-measure --input-cloud scan.ply --out-dir runs/today
当前 CLI 暂未把
--cad与--roi暴露出来(设计上 CLI 主要给研究流程用);集成方请直接用 Python API。
10. 版本兼容性
| 标识 | 在哪 | 含义 |
|---|---|---|
coupler_panel.__version__ |
coupler_panel._version |
包版本,遵循语义化版本 |
coupler_panel.ALGORITHM_VERSION |
同上 | 算法版本;升级算法时递增,数值结果可能变化 |
coupler_panel.SCHEMA_VERSION |
同上 | CouplerMeasurement / report.json 字段结构;只在破坏性变更时递增;新增可选字段不递增 |
集成建议:
- 落库时把
algorithm_version写进每条记录,便于历史复现 - 反序列化
report.json时校验schema_version主版本号
11. 坐标 & 单位速查
| 量 | 单位 | 约定 |
|---|---|---|
| 点云坐标 | mm | x=纵向、y=宽度、z=高度 |
| 角度 | 度 (deg) | θ_right = atan2(n_y, n_x)、θ_ref = atan2(n_z, n_x) |
| 法向 | 单位向量 | 强制 n_x > 0 |
right_axis_sign |
±1 | 如果车体"右"是 +y 留 1;如果是 -y 则配置 -1 |