一、直线拟合
最小二乘法直线拟合(
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:鲁棒性较强,可抑制部分异常点。
- 应用场景:适用于高精度直线参数提取(如工业检测中的边缘对齐)。
二、曲线拟合
多项式曲线拟合
- 原理:使用最小二乘法拟合多项式曲线(如二次、三次多项式),通过解线性方程组获取系数。
- 实现步骤:
- 调用
cv::solve
解线性方程组,生成多项式系数矩阵。 - 根据系数生成连续曲线点集,用于绘制拟合曲线。
- 调用
- 参数限制:多项式阶数需满足
阶数 ≤ 数据点数 - 1
。
基于Numpy的快速拟合(
np.polyfit
)- 优势:简化多项式拟合流程,直接生成多项式方程(需结合OpenCV绘图)。
- 示例:
coefficients = np.polyfit(x_points, y_points, degree) # 生成多项式系数
三、圆拟合
二维圆拟合
- 原理:通过最小二乘法或RANSAC算法,计算圆心坐标和半径。
- 实现函数:OpenCV未提供直接接口,需自行实现或结合第三方库(如
scikit-learn
)。
三维空间圆拟合
- 挑战:需处理三维点云数据,通常通过投影到二维平面或使用迭代优化方法实现。
四、代码
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;
}