OpenCV中直线、曲线和圆的拟合方法

发布于:2025-03-22 ⋅ 阅读:(18) ⋅ 点赞:(0)

一、直线拟合

  1. 最小二乘法直线拟合(cv::fitLine

    • 原理‌:通过最小化点到直线的垂直距离平方和,计算最优直线参数‌。
    • 函数参数说明‌:
      void cv::fitLine(
          InputArray points,    // 输入点集(2D或3D)
          OutputArray line,    // 输出直线参数(2D为Vec4f,3D为Vec6f)
          int distType,        // 距离类型(如DIST_L2、DIST_L1)
          double param,        // 距离权重参数(通常设为0)
          double reps,         // 径向精度阈值(推荐0.01)
          double aeps          // 角度精度阈值(推荐0.01)
      );
      ‌distType‌:
      DIST_L2:对噪声敏感,适合无异常值数据‌。
      DIST_HUBER:鲁棒性较强,可抑制部分异常点‌。
      
    • 应用场景‌:适用于高精度直线参数提取(如工业检测中的边缘对齐)‌。

二、曲线拟合

  1. 多项式曲线拟合

    • 原理‌:使用最小二乘法拟合多项式曲线(如二次、三次多项式),通过解线性方程组获取系数‌。
    • 实现步骤‌:
      • 调用cv::solve解线性方程组,生成多项式系数矩阵‌。
      • 根据系数生成连续曲线点集,用于绘制拟合曲线‌。
    • 参数限制‌:多项式阶数需满足阶数 ≤ 数据点数 - 1‌。
  2. 基于Numpy的快速拟合(np.polyfit

    • 优势‌:简化多项式拟合流程,直接生成多项式方程(需结合OpenCV绘图)‌。
    • 示例‌:
      coefficients = np.polyfit(x_points, y_points, degree) # 生成多项式系数

三、圆拟合

  1. 二维圆拟合

    • 原理‌:通过最小二乘法或RANSAC算法,计算圆心坐标和半径‌。
    • 实现函数‌:OpenCV未提供直接接口,需自行实现或结合第三方库(如scikit-learn)‌。
  2. 三维空间圆拟合

    • 挑战‌:需处理三维点云数据,通常通过投影到二维平面或使用迭代优化方法实现‌。

四、代码

1.‌最小二乘法直线拟合
#include <opencv2/opencv.hpp>
#include <vector>

using namespace cv;
using namespace std;

int main() {
    // 模拟输入点集(可替换为实际图像边缘点)
    vector<Point2f> points = {{50, 100}, {100, 150}, {150, 200}, {200, 250}};

    // 直线拟合
    Vec4f lineParams;
    fitLine(points, lineParams, DIST_L2, 0, 0.01, 0.01);

    // 解析直线参数:vx, vy 是方向向量,x0, y0 是直线上一点
    float vx = lineParams;
    float vy = lineParams;
    float x0 = lineParams;
    float y0 = lineParams;

    // 计算直线端点(延伸100像素)
    Point pt1(x0 - 100*vx, y0 - 100*vy);
    Point pt2(x0 + 100*vx, y0 + 100*vy);

    // 可视化
    Mat img = Mat::zeros(300, 300, CV_8UC3);
    for (const auto& p : points) {
        circle(img, p, 3, Scalar(0, 255, 0), FILLED); // 绘制输入点
    }
    line(img, pt1, pt2, Scalar(0, 0, 255), 2); // 绘制拟合直线
    imshow("Line Fitting", img);
    waitKey(0);
    return 0;
}
2. 曲线拟合
#include <opencv2/opencv.hpp>
#include <vector>
#include <cmath>

using namespace cv;
using namespace std;

int main() {
    // 输入点集(二次曲线y = ax^2 + bx + c 比如:y = 0.5x² + 3x + 10 的采样)
    vector<Point2f> points;
    for (int x = 0; x <= 20; x += 2) {
        float y = 0.5*pow(x,2) + 3*x + 10;
        points.emplace_back(x*10, y*10); // 放大坐标便于显示
    }

    // 构建矩阵方程 Ax = b
    Mat A(points.size(), 3, CV_32F);
    Mat b(points.size(), 1, CV_32F);
    for (size_t i = 0; i < points.size(); i++) {
        float x = points[i].x;
        A.at<float>(i, 0) = x*x;
        A.at<float>(i, 1) = x;
        A.at<float>(i, 2) = 1;
        b.at<float>(i) = points[i].y;
    }

    // 解方程 (A^T * A) * coeff = A^T * b
    Mat coeff;
    solve(A, b, coeff, DECOMP_NORMAL | DECOMP_SVD);

    // 生成拟合曲线点
    vector<Point> curvePoints;
    for (int x = 0; x <= 200; x += 2) {
        float y = coeff.at<float>(0)*x*x + coeff.at<float>(1)*x + coeff.at<float>(2);
        curvePoints.emplace_back(x, cvRound(y));
    }

    // 可视化
    Mat img = Mat::zeros(800, 800, CV_8UC3);
    for (const auto& p : points) {
        circle(img, p, 5, Scalar(0, 255, 0), FILLED);
    }
    polylines(img, curvePoints, false, Scalar(0, 0, 255), 2);
    imshow("Curve Fitting", img);
    waitKey(0);
    return 0;
}

关键点‌:

  • 使用 solve() 解多项式方程组
  • 支持任意次多项式(需修改矩阵维度)
  • 通过 DECOMP_SVD 提高数值稳定性

在C++中,我们可以使用Eigen库或手动实现最小二乘法:

#include <iostream>
#include <vector>
#include <Eigen/Dense>
 
struct Point {
    double x, y;
};
 
int main() {
    std::vector<Point> points = {{1, 2}, {2, 5}, {3, 10}, {4, 17}};
    int n = points.size();
    Eigen::MatrixXd A(n, 3); // 二次方程有3个参数a, b, c
    Eigen::VectorXd b(n); // y值
    Eigen::Vector3d coeffs; // 系数向量
 
    for (int i = 0; i < n; ++i) {
        A(i, 0) = points[i].x * points[i].x; // x^2
        A(i, 1) = points[i].x;               // x
        A(i, 2) = 1;                        // 1 (constant term)
        b(i) = points[i].y;                // y值
    }
 
    coeffs = A.bdcSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(b);
    std::cout << "Coefficients: " << coeffs.transpose() << std::endl; // a, b, c
    return 0;
}
3. 圆拟合
#include <opencv2/opencv.hpp>
#include <vector>

using namespace cv;
using namespace std;

int main() {
    // 生成模拟点集(实际应用中可通过findContours获取轮廓)
    vector<Point2f> points;
    for (int angle = 0; angle < 360; angle += 30) {
        float radian = angle * CV_PI / 180;
        points.emplace_back(100 + 50*cos(radian), 100 + 50*sin(radian));
    }

    // 最小包围圆拟合
    Point2f center;
    float radius;
    minEnclosingCircle(points, center, radius);

    // 可视化
    Mat img = Mat::zeros(200, 200, CV_8UC3);
    for (const auto& p : points) {
        circle(img, p, 3, Scalar(0, 255, 0), FILLED);
    }
    circle(img, center, cvRound(radius), Scalar(0, 0, 255), 1);
    imshow("Circle Fitting", img);
    waitKey(0);
    return 0;
}