云计算百科
云计算领域专业知识百科平台

卡尺算法流程详解

卡尺算法流程详解

对应 detection – EdgeDetection 模块的 RulerLinePoints / RulerCirclePoints 两个函数。

1. 卡尺算法是什么

卡尺算法(Caliper/Ruler Algorithm)是基于 1D 采样的边缘检测方法,沿指定方向(直线或圆周)定位边缘点。做法是:

  • 沿检测线等间距放置多个"卡尺"
  • 每个卡尺垂直于检测线方向采样图像像素
  • 对采样数据滤波、做峰值检测
  • 定位亚像素精度的边缘位置
  • 用得多的场合:工业测量的零件尺寸检测、边缘位置偏差类的缺陷检测、基于边缘点的图像配准、直线/圆形的几何参数估计。


    2. RulerLinePoints 流程

    2.1 完整流程

    开始

    ├─ 参数检查 ── 失败 → 返回错误
    │ 成功 → 初始化变量

    ├─ 需要显示结果?
    │ 是 → 先绘制主直线
    │ 否 → 跳过

    ├─ 创建滤波器内核

    ├─ 外层循环:n = 1 … ruler_nums,每个卡尺一轮
    │ │
    │ ├─ 计算卡尺中心位置
    │ │
    │ ├─ 采样,双重循环:
    │ │ 外层 m = 0 … ruler_width
    │ │ 内层 ir = -ruler_height … ruler_height
    │ │ 坐标在图像内?
    │ │ 否 → 采样值置 -1
    │ │ 是 → 双线性插值
    │ │ 存储采样值
    │ │ ir++ 扫完 ruler_height 后 m++,直到扫完 ruler_width
    │ │
    │ ├─ 计算投影平均值
    │ ├─ 应用滤波器
    │ ├─ 峰值检测
    │ │ 无峰值 → 直接进入下一个 n
    │ │ 有峰值 → 根据选择模式筛选峰值
    │ ├─ 选择峰值点
    │ ├─ 亚像素定位
    │ ├─ 计算边缘坐标,存储边缘点
    │ └─ 需要显示结果 → 绘制该卡尺及边缘

    │ n++;n > ruler_nums 时退出循环

    └─ 返回 SUCCESS

    2.2 核心步骤详解

    步骤 1: 参数检查与初始化

    // 参数验证
    CHECK_MAT_NOT_EMPTY(image); // 图像非空
    CHECK_MAT_TYPE(image, CV_8UC1); // 灰度图像
    CHECK_POINT_NOT_EMPTY(line.pt1); // 起点有效
    CHECK_POINT_NOT_EMPTY(line.pt2); // 终点有效

    // 初始化参数
    bool show_result = !result_image.empty(); // 是否显示结果
    ruler_width = ruler_width <= 0 ? 10 : ruler_width 1;
    ruler_height = ruler_height <= 0 ? 8 : ruler_height;
    ruler_nums = ruler_nums <= 0 ? 5 : ruler_nums;

    步骤 2: 计算直线参数

    // 转换坐标
    cv::Point line_st = cv2d::PointToCVPoint(line.pt1);
    cv::Point line_ed = cv2d::PointToCVPoint(line.pt2);

    // 计算直线角度
    double t = atan2(line_ed.y line_st.y, line_ed.x line_st.x);

    // 计算直线长度
    float L = (float)cv::norm(cv::Point2d(line_st.x, line_st.y)
    cv::Point2d(line_ed.x, line_ed.y));

    // 计算卡尺间距
    float plus_step = (L ruler_nums * ruler_width) / (ruler_nums + 1.0f);

    步骤 3: 创建滤波器

    // 差分滤波器(边缘检测)
    std::vector<float> filter_kernal(2 * filter_size + 1, 0.0f);
    for (int n = 0; n < filter_size; n++) {
    filter_kernal[n] = 1; // 正系数(左侧)
    filter_kernal[2 * filter_size n] = 1; // 负系数(右侧)
    }

    滤波器就是 [1, 1, …, -1, -1] 形式的差分核,用来检测灰度梯度变化——边缘处(灰度突变)会输出峰值。

    步骤 4: 循环处理每个卡尺

    for (int n = 1; n <= ruler_nums; n++) {
    // 4.1 计算卡尺中心位置
    float offset = n * (plus_step + ruler_width) ruler_width + ruler_width / 2.0f;
    cv::Point2f center(
    line_st.x + offset * cos(t),
    line_st.y + offset * sin(t)
    );

    // 4.2 沿垂直方向采样
    std::vector<float> samples;
    for (float m = ruler_width / 2.0f; m <= ruler_width / 2.0f; m += 1.0f) {
    for (int ir = ruler_height; ir <= ruler_height; ir++) {
    // 计算采样坐标
    float x = center.x + ir * cos(t + PI/2) + m * cos(t);
    float y = center.y + ir * sin(t + PI/2) + m * sin(t);

    // 双线性插值
    float value = BilinearInterpolation(image, x, y);
    samples.push_back(value);
    }
    }

    // 4.3 计算投影(沿卡尺宽度方向平均)
    std::vector<float> projection;
    for (int i = 0; i < 2 * ruler_height + 1; i++) {
    float sum = 0;
    for (int j = 0; j <= ruler_width; j++) {
    sum += samples[i * (ruler_width + 1) + j];
    }
    projection.push_back(sum / (ruler_width + 1));
    }

    // 4.4 应用滤波器
    std::vector<float> filtered = ApplyFilter(projection, filter_kernal);

    // 4.5 峰值检测
    std::vector<int> peeks = FindPeaks(filtered, threshold);

    // 4.6 选择最佳峰值
    int selected = SelectBestPeak(peeks, selection_mode);

    // 4.7 亚像素定位
    float subpixel_pos = SubpixelRefinement(filtered, selected);

    // 4.8 计算边缘坐标
    cv::Point2f edge_point(
    center.x + subpixel_pos * cos(t + PI/2),
    center.y + subpixel_pos * sin(t + PI/2)
    );

    edge_points.push_back(edge_point);
    }

    步骤 5: 亚像素定位

    对峰值点及其左右邻域做三点抛物线拟合:

    // 三点抛物线拟合
    int m = peak_index; // 峰值点
    int l = m 1; // 左邻域
    int r = m + 1; // 右邻域

    float a = filtered[l]; // 左点梯度
    float b = filtered[m]; // 峰值点梯度
    float c = filtered[r]; // 右点梯度

    // 亚像素偏移公式
    float u = 0.5f * (a c) / (a 2*b + c);

    // 最终位置(范围:m-0.5 到 m+0.5)
    float subpixel_pos = m + u;


    3. RulerCirclePoints 流程

    3.1 完整流程

    开始

    ├─ 参数检查 ── 失败 → 返回错误
    │ 成功 → 初始化变量

    ├─ 需要显示结果?
    │ 是 → 先绘制主圆
    │ 否 → 跳过

    ├─ 创建滤波器

    ├─ 计算角度步长 plus_step = 2π / ruler_nums

    ├─ 循环:t = -π … π,步长 plus_step
    │ │
    │ ├─ 计算卡尺中心 cx, cy = center + radius·(cos t, sin t)
    │ ├─ 采样圆周方向(径向)
    │ ├─ 计算投影平均值
    │ ├─ 应用滤波器
    │ ├─ 峰值检测
    │ │ 无峰值 → t += plus_step
    │ │ 有峰值 → 根据选择模式筛选峰值
    │ ├─ 亚像素定位
    │ ├─ 计算边缘坐标,存储边缘点
    │ └─ 需要显示结果 → 绘制该卡尺及边缘

    │ t += plus_step;t ≥ π 时退出循环

    └─ 返回 SUCCESS

    3.2 与直线卡尺的区别

    特性RulerLinePointsRulerCirclePoints
    检测路径 直线 圆周
    坐标计算 线性插值 极坐标转换
    角度计算 固定角度 t = atan2(dy, dx) 可变角度 t = -π to π
    卡尺间距 线性间距 角度间距 plus_step = 2π/ruler_nums
    卡尺方向 垂直于直线 径向(圆心向外)

    4. 卡尺生成过程可视化

    4.1 卡尺结构示意

    卡尺(Caliper)结构:

    ↑ 搜索方向
    |
    | ruler_height (高度)
    |

    ┌─────────────────────────────────────┐
    │ │ ruler_width (宽度)
    │ ──────────── 采样线 ────────── │
    │ │
    │ ──────────── 采样线 ────────── │
    │ │
    │ ──────────── 采样线 ────────── │
    │ │
    │ ──────────── 采样线 ────────── │
    │ │
    │ ──────────── 采样线 ────────── │
    │ │
    └─────────────────────────────────────┘

    |
    中心点
    (在检测线上)

    4.2 直线卡尺生成过程

    示例配置:图像 800 × 600,直线 (100, 300) → (700, 300),ruler_nums = 5,ruler_width = 10,ruler_height = 20。

    生成过程:

    步骤 1: 绘制主直线
    (100,300) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ (700,300)

    步骤 2: 计算卡尺间距
    直线长度: 600 像素
    ruler_nums: 5
    ruler_width: 10
    卡尺总宽度: 5 × 10 = 50
    可用长度: 600 – 50 = 550
    卡尺间距: 550 / 6 ≈ 91.67 像素

    步骤 3: 生成卡尺位置

    卡尺1: x = 100 + 91.67 + 5 = 196.67
    卡尺2: x = 100 + 2×91.67 + 15 = 298.34
    卡尺3: x = 100 + 3×91.67 + 25 = 400.00
    卡尺4: x = 100 + 4×91.67 + 35 = 501.67
    卡尺5: x = 100 + 5×91.67 + 45 = 603.34

    步骤 4: 沿垂直方向采样

    (100,300) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ (700,300)
    │ │ │ │ │
    │ │ │ │ └─ 卡尺5 (603.34, 300)
    │ │ │ └────── 卡尺4 (501.67, 300)
    │ │ └────────── 卡尺3 (400.00, 300)
    │ └────────────── 卡尺2 (298.34, 300)
    └────────────────── 卡尺1 (196.67, 300)

    每个卡尺的采样区域 (ruler_height = 20, ruler_width = 10):


    20px │ ● ● ● ● ● ● ● ● ● ●
    │ ● ● ● ● ● ● ● ● ● ●
    │ ● ● ● ● ● ● ● ● ● ●
    │ ● ● ● ● ● ● ● ● ● ●
    │ ● ● ● ● ● ● ● ● ● ●
    │ ● ● ● ● ● ● ● ● ● ●
    │ ● ● ● ● ● ● ● ● ● ●
    │ ● ● ● ● ● ● ● ● ● ●
    │ ● ● ● ● ● ● ● ● ● ●
    │ ● ● ● ● ● ● ● ● ● ●
    -20px ● ● ● ● ● ● ● ● ● ● ●
    └─────────────
    10px 宽度

    4.3 圆形卡尺生成过程

    示例配置:圆心 (400, 300),半径 150,ruler_nums = 8(每 45° 一个卡尺),ruler_width = 10,ruler_height = 20。

    生成过程:

    步骤 1: 绘制主圆
    ┌─────────────────┐
    ┌───┤ ├───┐
    ┌─┤ │ │ ├─┐
    ┌─┤ │ ●(400,300) │ ├─┐
    │ │ │ │ │ │
    └─┤ │ │ ├─┘
    └─┤ │ │ ├─┘
    └───┤ ├───┘
    └─────────────────┘

    步骤 2: 计算卡尺位置(圆周上8个点)

    角度步长: plus_step = 2π / 8 = 45°

    卡尺1: t = -180° → (-150, 300)
    卡尺2: t = -135° → (294, 194)
    卡尺3: t = -90° → (400, 150)
    卡尺4: t = -45° → (506, 194)
    卡尺5: t = 0° → (550, 300)
    卡尺6: t = 45° → (506, 406)
    卡尺7: t = 90° → (400, 450)
    卡尺8: t = 135° → (294, 406)

    步骤 3: 径向采样(沿圆心向外)

    卡尺3 (400,150)



    ────────┼────────●(400,300)────────┼────────



    卡尺7 (400,450)

    4.4 采样数据流

    原始图像 (灰度)

    采样 (沿卡尺方向)

    采样数据矩阵 (height × width)

    投影 (沿宽度方向平均)
    [v1, v2, v3, …, v(2*height+1)]

    滤波 (差分滤波器)
    [g1, g2, g3, …, g(2*height+1)]

    峰值检测
    找到梯度最大的位置

    亚像素定位
    抛物线拟合得到亚像素精度

    边缘点坐标
    转换回图像坐标系


    5. 关键算法详解

    5.1 双线性插值

    采样坐标大多不是整数,用周围 4 个像素加权取值:

    对于坐标 (x, y),设:
    – x0 = floor(x), x1 = x0 + 1
    – y0 = floor(y), y1 = y0 + 1
    – dx = x – x0, dy = y – y0

    插值值 = (1-dx)(1-dy)I(x0,y0) + dx(1-dy)I(x1,y0) +
    (1-dx)dy I(x0,y1) + dx dy I(x1,y1)

    5.2 峰值检测

    思路是相邻点做差分,检测符号变化(正→负),再筛掉不超过阈值的:

    void FindPeak(int size, float data[], float threshold, std::vector<int>& peaks) {
    std::vector<int> sign;
    for (int i = 1; i < size; i++) {
    float diff = data[i] data[i1];
    if (diff < 0.0f) sign.push_back(1);
    else if (diff > 0.0f) sign.push_back(1);
    else sign.push_back(0);
    }

    for (int j = 1; j < sign.size(); j++) {
    int diff = sign[j] sign[j1];
    if (diff != 0 && abs(data[j]) > threshold) {
    peaks.push_back(j);
    }
    }
    }

    5.3 亚像素定位

    用峰值点及其左右邻点三点拟合抛物线 y = Ax² + Bx + C,顶点在 x = -B/(2A),化简后就是 u = 0.5 × (a – c) / (a – 2b + c),精度能到 0.01 像素级。


    6. 性能

    采样数组改用 std::vector(分配次数减少约 95%),RAII 自动管理生命周期消除泄漏风险,容器用 reserve() 预分配减少扩容开销。


    7. 适用与局限

    精度到亚像素级,投影平均天然抗噪,峰值选择模式可扩展。代价是计算量与卡尺数量成正比,对图像质量有一定要求,参数(宽度、高度、数量、阈值)得按场景调。

    边缘清晰、形状规则、需要亚像素精度的检测用它合适;高度模糊或低对比度的图像、极度不规则的边缘,不要硬上。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 卡尺算法流程详解
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!