几何尺寸与公差论坛

 找回密码
 注册
查看: 21|回复: 5

inspec里iwp的圆拟合边缘是如何提取的?

  [复制链接]
发表于 3 天前 | 显示全部楼层 |阅读模式
Crc 1 F85B1D0 (Name "1") (FtrTyp Hole) (PCS ((0.44880565 0.028924129 0 0) (0.35767046 0 0.028924129 0) (0 0 0 1)  (-15.516653 34.573211 0 0) (-12.365816 0 34.573211 0) (0 0 0 1)))
Constraints 1
TCon 1 F891200 (FitTp FitPts)
Tool 1
FSC 0 F84CFC8 (Sett ((DrvPos (0.44976505 0.3570983 0)) (Zoom 0.00035273369))) (OutDens 100) (Thresh 2.01704) (Patt 0.5 (1 1 1 1 0.988 0.769 0.256 0.0222 0.000635 0.000259 0.000198 0.000136)) (FakeVsn) (Zones ((NomCR (0.44976505 0.3570983 0) 0.02913917) (Width 0.0044091711))) (Geom ((0.44880565 0.028924129 0 0) (0.35767046 0 0.028924129 0) (0 0 0 1)  (-15.516653 34.573211 0 0) (-12.365816 0 34.573211 0) (0 0 0 1)))
 楼主| 发表于 3 天前 | 显示全部楼层
Tool 1
FSC 0 F84CFC8 (
  Sett (
    (DrvPos (0.44976505 0.3570983 0))     // 驱动位置,类似于起始点或参考中心
    (Zoom 0.00035273369))                // 缩放比例
  (OutDens 100)                          // 输出点密度
  (Thresh 2.01704)                       // 阈值,用于边缘检测
  (Patt 0.5
    (1 1 1 1 0.988 0.769 0.256 0.0222 0.000635 0.000259 0.000198 0.000136)) // 边缘梯度模板或滤波器响应曲线
  (FakeVsn)                              // 虚拟视觉标记位
  (Zones (
    (NomCR (0.44976505 0.3570983 0) 0.02913917)   // 名义圆心坐标 + 名义半径
    (Width 0.0044091711)))                        // 拟合带宽或检测带宽
 楼主| 发表于 3 天前 | 显示全部楼层
下面是根据你提供的 INSPEC 圆特征检测配置参数 重构的 C++ 圆边缘提取代码,包括:

    使用 INSPEC 的坐标系 → 像素坐标系转换;

    使用给定的 Patt 样本模板;

    按照名义圆中心和半径构造采样区域;

    计算梯度响应,提取边缘;

    拟合圆(可改为更高精度方法)。
 楼主| 发表于 3 天前 | 显示全部楼层
#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
#include <cmath>

int main() {
    // 读取图像(灰度)
    cv::Mat gray = cv::imread("2d_1920_1920.jpg", cv::IMREAD_GRAYSCALE);
    if (gray.empty()) {
        std::cerr << "图像加载失败" << std::endl;
        return -1;
    }

    // INSPEC 参数(单位 mm)
    const double zoom = 0.00035273369;  // mm/pixel
    const double scale = 1.0 / zoom;    // pixel/mm

    const double nomX = 0.44976505;
    const double nomY = 0.3570983;
    const double nomR = 0.02913917;
    const double bandWidth = 0.0044091711;

    // 转换为像素坐标
    const double centerX = nomX * scale;
    const double centerY = nomY * scale;
    const double radius = nomR * scale;
    const double bandPx = bandWidth * scale;

    // Patt 模板(来自 INSPEC)
    std::vector<double> patt = {1, 1, 1, 1, 0.988, 0.769, 0.256, 0.0222,
                                0.000635, 0.000259, 0.000198, 0.000136};
    const int kernelSize = patt.size();
    const double thresh = 2.01704;

    std::vector<cv::Point2f> edgePoints;
    int numSamples = 100;
    double sampleStep = 0.5;  // 每条射线上采样步长(像素)
    int stepDeg = 360 / numSamples;

    for (int i = 0; i < 360; i += stepDeg) {
        double theta = i * CV_PI / 180.0;
        std::vector<double> profile;

        // 在环带内沿径向采样
        for (double ro = -bandPx; ro <= bandPx; ro += sampleStep) {
            double x = centerX + (radius + ro) * cos(theta);
            double y = centerY + (radius + ro) * sin(theta);

            if (x >= 0 && x < gray.cols && y >= 0 && y < gray.rows) {
                profile.push_back(gray.at<uchar>(cvRound(y), cvRound(x)));
            } else {
                profile.push_back(0);  // 边界外填0
            }
        }

        // 卷积找最大响应
        double maxResp = 0;
        int bestIdx = -1;
        for (int j = 0; j <= profile.size() - kernelSize; ++j) {
            double resp = 0;
            for (int k = 0; k < kernelSize; ++k) {
                resp += profile[j + k] * patt[k];
            }
            if (resp > maxResp && resp > thresh) {
                maxResp = resp;
                bestIdx = j;
            }
        }

        if (bestIdx != -1) {
            double bestR = radius + (bestIdx * sampleStep) - bandPx;
            double ex = centerX + bestR * cos(theta);
            double ey = centerY + bestR * sin(theta);
            edgePoints.push_back(cv::Point2f(ex, ey));
        }
    }

    // 拟合圆
    cv::Point2f finalCenter;
    float finalRadius = 0;
    if (edgePoints.size() >= 3) {
        cv::minEnclosingCircle(edgePoints, finalCenter, finalRadius);
        std::cout << "拟合结果 圆心: (" << finalCenter.x << ", " << finalCenter.y
                  << "), 半径: " << finalRadius << " 像素" << std::endl;
    }

    // 可视化
    cv::Mat result;
    cv::cvtColor(gray, result, cv::COLOR_GRAY2BGR);
    for (auto& pt : edgePoints)
        cv::circle(result, pt, 1, cv::Scalar(0, 255, 255), -1);
    cv::circle(result, finalCenter, finalRadius, cv::Scalar(0, 255, 0), 2);
    cv::circle(result, finalCenter, 2, cv::Scalar(0, 0, 255), -1);

    cv::imshow("INSPEC Circle Edge Extraction", result);
    cv::waitKey(0);
    return 0;
}
 楼主| 发表于 3 天前 | 显示全部楼层
&#9989; 功能总览

    输入:灰度图像 + 圆初始估计(中心/半径)

    采样:圆周 100 个方向,环带内径向采样

    边缘检测:一维 DoG 模板进行卷积

    亚像素拟合:使用三点二次插值

    输出:检测边缘点,拟合最小外接圆(可用于后续几何公差分析)
 楼主| 发表于 3 天前 | 显示全部楼层
#include <opencv2/opencv.hpp>
#include <vector>
#include <cmath>
#include <iostream>

using namespace cv;
using namespace std;

// === 创建方向一致的 DoG 卷积模板 ===
vector<float> createDoGKernel(int size, float sigma) {
    vector<float> kernel(size);
    int half = size / 2;
    float norm = 0.0;

    for (int i = -half; i <= half; ++i) {
        float x = static_cast<float>(i);
        float val = -x * exp(-x * x / (2 * sigma * sigma));
        kernel[i + half] = val;
        norm += fabs(val);
    }

    // 归一化(可选)
    for (float& v : kernel)
        v /= norm;

    return kernel;
}

// === 主检测函数 ===
void detectCircularEdgeSubpixel(
    const Mat& gray,
    Point2f center,
    float radius,
    float bandWidth,
    float sampleStep,
    const vector<float>& kernel,
    float responseThresh,
    vector<Point2f>& edgePoints,
    int numAngles = 100
) {
    edgePoints.clear();
    int kernelSize = kernel.size();
    float angleStep = 360.0f / numAngles;

    for (int a = 0; a < numAngles; ++a) {
        double theta = a * CV_PI / 180.0;

        // 采样灰度值
        vector<uchar> profile;
        vector<Point2f> samplePoints;
        for (double rOffset = -bandWidth; rOffset <= bandWidth; rOffset += sampleStep) {
            double r = radius + rOffset;
            double x = center.x + r * cos(theta);
            double y = center.y + r * sin(theta);

            if (x >= 0 && x < gray.cols && y >= 0 && y < gray.rows) {
                profile.push_back(gray.at<uchar>(round(y), round(x)));
                samplePoints.push_back(Point2f(x, y));
            } else {
                profile.push_back(0);
                samplePoints.push_back(Point2f(x, y));
            }
        }

        // 卷积并找最大响应
        double maxResp = 0;
        int bestIdx = -1;
        for (int j = 0; j <= profile.size() - kernelSize; ++j) {
            double resp = 0;
            for (int k = 0; k < kernelSize; ++k) {
                resp += profile[j + k] * kernel[k];
            }
            if (resp > maxResp && resp > responseThresh) {
                maxResp = resp;
                bestIdx = j;
            }
        }

        // 亚像素拟合(仅在有效位置)
        if (bestIdx > 0 && bestIdx < profile.size() - kernelSize - 1) {
            double Rm1 = 0, R0 = 0, Rp1 = 0;
            for (int k = 0; k < kernelSize; ++k) {
                Rm1 += profile[bestIdx - 1 + k] * kernel[k];
                R0  += profile[bestIdx     + k] * kernel[k];
                Rp1 += profile[bestIdx + 1 + k] * kernel[k];
            }

            double delta = 0.0;
            double denom = Rm1 - 2 * R0 + Rp1;
            if (fabs(denom) > 1e-5) {
                delta = 0.5 * (Rm1 - Rp1) / denom;
            }

            double rFinal = radius + ((bestIdx + delta) * sampleStep) - bandWidth;
            double ex = center.x + rFinal * cos(theta);
            double ey = center.y + rFinal * sin(theta);
            edgePoints.push_back(Point2f(static_cast<float>(ex), static_cast<float>(ey)));
        }
    }
}

int main() {
    // === 加载图像 ===
    Mat gray = imread("circle.png", IMREAD_GRAYSCALE);
    if (gray.empty()) {
        cerr << "图像加载失败!" << endl;
        return -1;
    }

    // === 初始化参数 ===
    Point2f approxCenter(150.0f, 150.0f);  // 初始估计中心
    float approxRadius = 50.0f;
    float bandWidth = 5.0f;
    float sampleStep = 0.5f;
    int kernelSize = 5;
    float sigma = 1.0f;
    float responseThreshold = 50.0f;

    // === 构造 DoG 卷积模板 ===
    vector<float> patternKernel = createDoGKernel(kernelSize, sigma);

    // === 边缘检测 ===
    vector<Point2f> edgePoints;
    detectCircularEdgeSubpixel(gray, approxCenter, approxRadius, bandWidth,
                               sampleStep, patternKernel, responseThreshold, edgePoints);

    // === 拟合圆 ===
    Point2f fittedCenter;
    float fittedRadius = 0.0f;
    if (!edgePoints.empty()) {
        minEnclosingCircle(edgePoints, fittedCenter, fittedRadius);
        cout << "拟合圆心: " << fittedCenter << ", 半径: " << fittedRadius << endl;

        // 可视化
        Mat display;
        cvtColor(gray, display, COLOR_GRAY2BGR);
        for (auto& pt : edgePoints) {
            circle(display, pt, 1, Scalar(0, 0, 255), -1);
        }
        circle(display, fittedCenter, static_cast<int>(fittedRadius), Scalar(0, 255, 0), 2);
        imshow("Fitted Circle", display);
        waitKey(0);
    } else {
        cout << "未检测到边缘点。" << endl;
    }

    return 0;
}
您需要登录后才可以回帖 登录 | 注册

本版积分规则

QQ|Archiver|小黑屋|几何尺寸与公差论坛

GMT+8, 2025-5-24 05:15 , Processed in 0.038908 second(s), 19 queries .

Powered by Discuz! X3.4 Licensed

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表