OpenCV 工件定位:PCA、旋转框与局部坐标系
同一枚螺钉转过 30°,轴对齐矩形的宽度明显变化。工件并没有变长,变化来自测量方向。要使尺寸跟随工件,需要先找到主轴,再把点转换到工件自己的坐标系。
本文对应 ScrewMetrology 6782dfd 的定位与投影测量实现,使用 C++17 / OpenCV。项目运行环境为 OpenCV 5.0.0;OpenCV 5 的二维几何类型位于 geometry 模块。返回系列导读。
1. 质心、旋转框中心不是同一个点
轮廓矩的面积质心为:
1 | O = (m10 / m00, m01 / m00) |
只有 m00 有效时才能除。大头部会使质心偏向头部;旋转框中心则是包围矩形中心。项目输出 center 表示质心,rotated_box.center 表示框中心,定位评价的中心误差使用后者。
最小面积框的角度还需要统一:RotatedRect.angle 对应 width 轴,若 height 更长,项目加 90° 后取长边方向。直接把 angle 当成螺钉方向容易在宽高交换时跳变。
2. 三种主轴都需要正确的输入
| 方法 | 使用的信息 | 可能的偏差 |
|---|---|---|
minAreaRect |
包住外轮廓的最小面积框 | 少量突出点改变长边方向 |
| PCA | 填充外轮廓后的全部前景像素 | 头部、遮挡或粘连改变面积分布 |
fitLine |
外轮廓点的拟合方向 | 采样密度和形状影响点分布 |
PCA 对中心化二维样本构造协方差矩阵,最大特征值对应的特征向量代表变化最大的方向。项目先在包围 ROI 内填充外轮廓,再对所有非零点做 PCA;这与直接对边界点做 PCA 不是同一份统计。
1 | // 核心片段:samples 每行是一个前景像素的 (x,y),类型 CV_64F。 |
两特征值之比接近 1,说明没有明显长轴。近圆螺母仍能返回一个数值角度,但小噪声就可能改变它。项目使用 axis_ratio 标记 axis_ambiguous,而不是把任何角度都视作可信姿态。
3. 从图像坐标到局部坐标
设原点是质心 O,沿主轴的单位向量为 eₓ,垂直向量为 eᵧ:
1 | eₓ = (cosθ, sinθ) |
图像 y 向下,atan2 的正角度在显示中表现为顺时针。项目把角度归一化为 [0°,180°),因为 PCA 返回的是无向主轴:0° 和 180° 是同一条轴。主轴正端不保证指向尖端,需要独立的头杆识别才能解释朝向。
3.1 可独立运行的坐标往返示例
把下面保存为 local_coordinates.cpp。它仅依赖 C++ 标准库,用于验证公式;运行失败时抛出异常,成功时误差应接近浮点舍入级别。
1 |
|
1 | c++ -std=c++17 -Wall -Wextra -pedantic local_coordinates.cpp -o local_coordinates |
4. 投影尺寸为何不受原点平移影响
轮廓点转换为 (s,t) 后,总长与最大宽度为:
1 | length = max(s) − min(s) |
更换原点只会让投影整体加上一个常数,最大值与最小值之差保持不变。更换主轴则会改变范围,因此轴向偏差会传递到尺寸。
最大宽度经常由头部决定,不能当成杆宽。项目把候选掩膜旋转到局部 ROI,按列提取横向范围得到 width(s),用中位数平滑后检查是否恰好一端明显变宽,再找连续变窄的位置作为头杆分界。
旋转二值掩膜使用最近邻插值,避免生成中间灰度;这一 ROI 用于几何剖面,亚像素测量仍回到原始灰度图。没有明显头杆差异时保留不可测字段,而不是强行套用螺钉模型。
5. 三种方法的同图对比
按导读构建并生成合成图后运行:
1 | build/screw_metrology measure --input data/synthetic/images/rotation_1.png \ |
对照 comparison_angles_deg、angle_deg、pixel,检查角度和尺寸如何联动。无向角误差应按 180° 周期计算,例如 179° 与 1° 相差 2°,不是 178°。
6. 定位成功以后仍可能不能测量
触及图像边缘的轮廓可能被截断,低 solidity 可能表示粘连,近圆形可能没有稳定主轴。这些目标仍可以画框、显示可见范围,但不应被当作完整工件作公差判定。
几何质量判断是启发式,可能漏掉一些重叠。换成另一种主轴不能恢复已经缺失的尖端,也不能拆开合并轮廓。遇到三种方法都异常时,优先返回掩膜检查输入。