有哪些方法拟合直线?
最小二乘法(Least Squares)
最小二乘法是一种常用的线性回归方法,用于找到使得数据点到拟合直线的垂直距离的平方和最小的直线。
适用场景:适用于误差主要集中在y方向的情况。
实现方法:
#include <opencv2/opencv.hpp>
#include <vector>
using namespace std;
using namespace cv;
Vec4f leastSquaresFit(const vector<Point2f>& points) {
Vec4f line;
fitLine(points, line, DIST_L2, 0, 0.01, 0.01);
return line;
}
RANSAC(Random Sample Consensus)
RANSAC是一种迭代方法,用于从包含异常值的数据中估计模型参数。它通过随机抽样找到内点最多的模型。
适用场景:适用于数据中存在较多异常点的情况。
实现方法:
#include <opencv2/opencv.hpp>
#include <vector>
#include <random>
using namespace std;
using namespace cv;
Vec4f ransacFit(const vector<Point2f>& points, int maxIterations = 1000, float distanceThreshold = 2.0) {
vector<Point2f> bestInliers;
Vec4f bestLine;
random_device rd;
mt19937 gen(rd());
uniform_int_distribution<> dis(0, points.size() - 1);
for (int i = 0; i < maxIterations; ++i) {
// 随机选择两个点
int idx1 = dis(gen);
int idx2 = dis(gen);
while (idx2 == idx1) {
idx2 = dis(gen);
}
Point2f p1 = points[idx1];
Point2f p2 = points[idx2];
// 计算直线方程
float a = p1.y - p2.y;
float b = p2.x - p1.x;
float c = p1.x * p2.y - p2.x * p1.y;
// 找到内点
vector<Point2f> inliers;
for (const auto& point : points) {
float dist = fabs(a * point.x + b * point.y + c) / sqrt(a * a + b * b);
if (dist < distanceThreshold) {
inliers.push_back(point);
}
}
// 更新最佳模型
if (inliers.size() > bestInliers.size()) {
bestInliers = inliers;
bestLine[0] = a;
bestLine[1] = b;
bestLine[2] = p1.x;
bestLine[3] = p1.y;
}
}
// 用最佳内点拟合直线
fitLine(bestInliers, bestLine, DIST_L2, 0, 0.01, 0.01);
return bestLine;
}
Theil-Sen估计是一种稳健的统计方法,通过计算所有点对斜率的中位数来拟合直线。
适用场景:适用于存在噪声和异常值的情况。
实现方法:
#include <opencv2/opencv.hpp>
#include <vector>
#include <algorithm>
using namespace std;
using namespace cv;
Vec4f theilSenEstimator(const vector<Point2f>& points) {
vector<float> slopes;
for (size_t i = 0; i < points.size(); ++i) {
for (size_t j = i + 1; j < points.size(); ++j) {
if (points[j].x != points[i].x) {
float slope = (points[j].y - points[i].y) / (points[j].x - points[i].x);
slopes.push_back(slope);
}
}
}
sort(slopes.begin(), slopes.end());
float medianSlope = slopes[slopes.size() / 2];
float medianIntercept = 0;
for (const auto& point : points) {
medianIntercept += point.y - medianSlope * point.x;
}
medianIntercept /= points.size();
Vec4f line;
line[0] = medianSlope;
line[1] = -1;
line[2] = 0;
line[3] = medianIntercept;
return line;
}
Hough变换(Hough Transform)
Hough变换是一种图像处理技术,通过在参数空间中投票找到最佳拟合直线。
适用场景:适用于图像中的线检测,尤其是在噪声较多的情况下。
实现方法:
#include <opencv2/opencv.hpp>
#include <vector>
using namespace std;
using namespace cv;
vector<Vec2f> houghTransform(const Mat& img, double rho = 1, double theta = CV_PI / 180, int threshold = 100) {
vector<Vec2f> lines;
HoughLines(img, lines, rho, theta, threshold);
return lines;
}
线性回归(Linear Regression)
线性回归是通过最小化误差平方和来找到最佳拟合直线的参数。
适用场景:适用于误差均匀分布的情况。
实现方法:
#include <opencv2/opencv.hpp>
#include <vector>
using namespace std;
using namespace cv;
Vec4f linearRegression(const vector<Point2f>& points) {
Mat X(points.size(), 2, CV_32F);
Mat y(points.size(), 1, CV_32F);
for (size_t i = 0; i < points.size(); ++i) {
X.at<float>(i, 0) = points[i].x;
X.at<float>(i, 1) = 1;
y.at<float>(i, 0) = points[i].y;
}
Mat w;
solve(X, y, w, DECOMP_SVD);
Vec4f line;
line[0] = w.at<float>(0, 0);
line[1] = -1;
line[2] = 0;
line[3] = w.at<float>(1, 0);
return line;
}
一个实战例程:
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import RANSACRegressor
# 1. 生成或读取数据
np.random.seed(0)
n_samples = 100
n_outliers = 20
# 生成直线上的点 y = 2 * x + 1
X = np.linspace(-5, 5, n_samples).reshape(-1, 1)
y = 2 * X.flatten() + 1
# 添加一些噪声
y += np.random.normal(size=y.shape)
# 添加一些异常点
X[:n_outliers] = np.random.uniform(-5, 5, n_outliers).reshape(-1, 1)
y[:n_outliers] = np.random.uniform(-20, 20, n_outliers)
# 2. 应用RANSAC算法去除异常点
ransac = RANSACRegressor()
ransac.fit(X, y)
inlier_mask = ransac.inlier_mask_
outlier_mask = np.logical_not(inlier_mask)
# 3. 用剩下的点拟合直线
line_X = np.arange(-5, 6)
line_y_ransac = ransac.predict(line_X.reshape(-1, 1))
# 4. 可视化结果
plt.scatter(X[inlier_mask], y[inlier_mask], color='yellowgreen', marker='.', label='Inliers')
plt.scatter(X[outlier_mask], y[outlier_mask], color='red', marker='.', label='Outliers')
plt.plot(line_X, line_y_ransac, color='cornflowerblue', linewidth=2, label='RANSAC regressor')
plt.legend(loc='best')
plt.xlabel('Input')
plt.ylabel('Response')
plt.show()
输出:
上述方法各有优劣,选择适当的方法需要根据具体应用场景和数据特性进行权衡。例如,RANSAC和Theil-Sen估计适用于异常值较多的情况,而最小二乘法和线性回归则适用于误差主要集中在y方向且误差较小的情况。Hough变换适用于图像中的线检测,尤其是在噪声较多的情况下。