将拟合类函数独立成一个模块
This commit is contained in:
parent
09de9f850d
commit
971c7021e9
385
sourceCode/dataFitting.cpp
Normal file
385
sourceCode/dataFitting.cpp
Normal file
@ -0,0 +1,385 @@
|
|||||||
|
#include "SG_baseDataType.h"
|
||||||
|
#include "SG_baseAlgo_Export.h"
|
||||||
|
#include <vector>
|
||||||
|
#ifdef __WIN32
|
||||||
|
#include <corecrt_math_defines.h>
|
||||||
|
#endif // __WIN32
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <Eigen/dense>
|
||||||
|
|
||||||
|
void lineFitting(std::vector< SVzNL3DPoint>& inliers, double* _k, double* _b)
|
||||||
|
{
|
||||||
|
//最小二乘拟合直线参数
|
||||||
|
double xx_sum = 0;
|
||||||
|
double x_sum = 0;
|
||||||
|
double y_sum = 0;
|
||||||
|
double xy_sum = 0;
|
||||||
|
int num = 0;
|
||||||
|
for (int i = 0; i < inliers.size(); i++)
|
||||||
|
{
|
||||||
|
x_sum += inliers[i].x; //x的累加和
|
||||||
|
y_sum += inliers[i].y; //y的累加和
|
||||||
|
xx_sum += inliers[i].x * inliers[i].x; //x的平方累加和
|
||||||
|
xy_sum += inliers[i].x * inliers[i].y; //x,y的累加和
|
||||||
|
num++;
|
||||||
|
}
|
||||||
|
*_k = (num * xy_sum - x_sum * y_sum) / (num * xx_sum - x_sum * x_sum); //根据公式求解k
|
||||||
|
*_b = (-x_sum * xy_sum + xx_sum * y_sum) / (num * xx_sum - x_sum * x_sum);//根据公式求解b
|
||||||
|
}
|
||||||
|
|
||||||
|
//拟合成通用直线方程ax+by+c=0,包括垂直
|
||||||
|
void lineFitting_abc(std::vector< SVzNL3DPoint>& inliers, double* _a, double* _b, double* _c)
|
||||||
|
{
|
||||||
|
//判断是否为垂直
|
||||||
|
int dataSize = (int)inliers.size();
|
||||||
|
if (dataSize < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
double deltaX = abs(inliers[0].x - inliers[dataSize - 1].x);
|
||||||
|
double deltaY = abs(inliers[0].y - inliers[dataSize - 1].y);
|
||||||
|
std::vector< SVzNL3DPoint> fittingData;
|
||||||
|
if (deltaX < deltaY)
|
||||||
|
{
|
||||||
|
//x=ky+b 拟合
|
||||||
|
for (int i = 0; i < dataSize; i++)
|
||||||
|
{
|
||||||
|
SVzNL3DPoint a_fitPt;
|
||||||
|
a_fitPt.x = inliers[i].y;
|
||||||
|
a_fitPt.y = inliers[i].x;
|
||||||
|
a_fitPt.z = inliers[i].z;
|
||||||
|
fittingData.push_back(a_fitPt);
|
||||||
|
}
|
||||||
|
double k = 0, b = 0;
|
||||||
|
lineFitting(fittingData, &k, &b);
|
||||||
|
//ax+by+c
|
||||||
|
*_a = 1.0;
|
||||||
|
*_b = -k;
|
||||||
|
*_c = -b;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//y = kx+b拟合
|
||||||
|
double k = 0, b = 0;
|
||||||
|
lineFitting(inliers, &k, &b);
|
||||||
|
//ax+by+c
|
||||||
|
*_a = k;
|
||||||
|
*_b = -1;
|
||||||
|
*_c = b;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//圆最小二乘拟合
|
||||||
|
double fitCircleByLeastSquare(
|
||||||
|
const std::vector<SVzNL3DPoint>& pointArray,
|
||||||
|
SVzNL3DPoint& center,
|
||||||
|
double& radius)
|
||||||
|
{
|
||||||
|
int N = pointArray.size();
|
||||||
|
if (N < 3) {
|
||||||
|
return std::numeric_limits<double>::max();
|
||||||
|
}
|
||||||
|
|
||||||
|
double sumX = 0.0;
|
||||||
|
double sumY = 0.0;
|
||||||
|
double sumX2 = 0.0;
|
||||||
|
double sumY2 = 0.0;
|
||||||
|
double sumX3 = 0.0;
|
||||||
|
double sumY3 = 0.0;
|
||||||
|
double sumXY = 0.0;
|
||||||
|
double sumXY2 = 0.0;
|
||||||
|
double sumX2Y = 0.0;
|
||||||
|
|
||||||
|
for (int pId = 0; pId < N; ++pId) {
|
||||||
|
sumX += pointArray[pId].x;
|
||||||
|
sumY += pointArray[pId].y;
|
||||||
|
|
||||||
|
double x2 = pointArray[pId].x * pointArray[pId].x;
|
||||||
|
double y2 = pointArray[pId].y * pointArray[pId].y;
|
||||||
|
sumX2 += x2;
|
||||||
|
sumY2 += y2;
|
||||||
|
|
||||||
|
sumX3 += x2 * pointArray[pId].x;
|
||||||
|
sumY3 += y2 * pointArray[pId].y;
|
||||||
|
sumXY += pointArray[pId].x * pointArray[pId].y;
|
||||||
|
sumXY2 += pointArray[pId].x * y2;
|
||||||
|
sumX2Y += x2 * pointArray[pId].y;
|
||||||
|
}
|
||||||
|
|
||||||
|
double C, D, E, G, H;
|
||||||
|
double a, b, c;
|
||||||
|
|
||||||
|
C = N * sumX2 - sumX * sumX;
|
||||||
|
D = N * sumXY - sumX * sumY;
|
||||||
|
E = N * sumX3 + N * sumXY2 - (sumX2 + sumY2) * sumX;
|
||||||
|
G = N * sumY2 - sumY * sumY;
|
||||||
|
H = N * sumX2Y + N * sumY3 - (sumX2 + sumY2) * sumY;
|
||||||
|
|
||||||
|
a = (H * D - E * G) / (C * G - D * D);
|
||||||
|
b = (H * C - E * D) / (D * D - G * C);
|
||||||
|
c = -(a * sumX + b * sumY + sumX2 + sumY2) / N;
|
||||||
|
|
||||||
|
center.x = -a / 2.0;
|
||||||
|
center.y = -b / 2.0;
|
||||||
|
radius = sqrt(a * a + b * b - 4 * c) / 2.0;
|
||||||
|
|
||||||
|
double err = 0.0;
|
||||||
|
double e;
|
||||||
|
double r2 = radius * radius;
|
||||||
|
for (int pId = 0; pId < N; ++pId) {
|
||||||
|
e = pow(pointArray[pId].x - center.x, 2) + pow(pointArray[pId].y - center.y, 2) - r2;
|
||||||
|
if (e > err) {
|
||||||
|
err = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
bool leastSquareParabolaFit(const std::vector<cv::Point2d>& points,
|
||||||
|
double& a, double& b, double& c,
|
||||||
|
double& mse, double& max_err)
|
||||||
|
{
|
||||||
|
// 校验点集数量(至少3个点才能拟合抛物线)
|
||||||
|
int n = points.size();
|
||||||
|
if (n < 3) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化各项求和参数
|
||||||
|
double sum_x = 0.0, sum_x2 = 0.0, sum_x3 = 0.0, sum_x4 = 0.0;
|
||||||
|
double sum_y = 0.0, sum_xy = 0.0, sum_x2y = 0.0;
|
||||||
|
|
||||||
|
// 计算各项求和
|
||||||
|
for (const auto& p : points) {
|
||||||
|
double x = p.x;
|
||||||
|
double y = p.y;
|
||||||
|
double x2 = x * x;
|
||||||
|
double x3 = x2 * x;
|
||||||
|
double x4 = x3 * x;
|
||||||
|
|
||||||
|
sum_x += x;
|
||||||
|
sum_x2 += x2;
|
||||||
|
sum_x3 += x3;
|
||||||
|
sum_x4 += x4;
|
||||||
|
sum_y += y;
|
||||||
|
sum_xy += x * y;
|
||||||
|
sum_x2y += x2 * y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建线性方程组 M * [a,b,c]^T = N
|
||||||
|
// M矩阵:3x3
|
||||||
|
double M[3][3] = {
|
||||||
|
{sum_x4, sum_x3, sum_x2},
|
||||||
|
{sum_x3, sum_x2, sum_x},
|
||||||
|
{sum_x2, sum_x, (double)n}
|
||||||
|
};
|
||||||
|
|
||||||
|
// N向量:3x1
|
||||||
|
double N[3] = { sum_x2y, sum_xy, sum_y };
|
||||||
|
|
||||||
|
// 高斯消元法求解线性方程组(3元一次方程组)
|
||||||
|
// 步骤1:将M转化为上三角矩阵
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
// 选主元(避免除数为0)
|
||||||
|
int pivot = i;
|
||||||
|
for (int j = i; j < 3; j++) {
|
||||||
|
if (fabs(M[j][i]) > fabs(M[pivot][i])) {
|
||||||
|
pivot = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 交换行
|
||||||
|
std::swap(M[i], M[pivot]);
|
||||||
|
std::swap(N[i], N[pivot]);
|
||||||
|
|
||||||
|
// 归一化主元行
|
||||||
|
double div = M[i][i];
|
||||||
|
if (fabs(div) < 1e-10) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (int j = i; j < 3; j++) {
|
||||||
|
M[i][j] /= div;
|
||||||
|
}
|
||||||
|
N[i] /= div;
|
||||||
|
|
||||||
|
// 消去下方行
|
||||||
|
for (int j = i + 1; j < 3; j++) {
|
||||||
|
double factor = M[j][i];
|
||||||
|
for (int k = i; k < 3; k++) {
|
||||||
|
M[j][k] -= factor * M[i][k];
|
||||||
|
}
|
||||||
|
N[j] -= factor * N[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 步骤2:回代求解
|
||||||
|
c = N[2];
|
||||||
|
b = N[1] - M[1][2] * c;
|
||||||
|
a = N[0] - M[0][1] * b - M[0][2] * c;
|
||||||
|
|
||||||
|
// 计算拟合误差
|
||||||
|
mse = 0.0;
|
||||||
|
max_err = 0.0;
|
||||||
|
for (const auto& p : points) {
|
||||||
|
double y_fit = a * p.x * p.x + b * p.x + c;
|
||||||
|
double err = y_fit - p.y;
|
||||||
|
double err_abs = fabs(err);
|
||||||
|
mse += err * err;
|
||||||
|
if (err_abs > max_err) {
|
||||||
|
max_err = err_abs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mse /= n; // 均方误差
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
//抛物线最小二乘拟合 y=ax^2 + bx + c
|
||||||
|
bool leastSquareParabolaFitEigen(
|
||||||
|
const std::vector<cv::Point2d>& points,
|
||||||
|
double& a, double& b, double& c,
|
||||||
|
double& mse, double& max_err)
|
||||||
|
{
|
||||||
|
int n = points.size();
|
||||||
|
if (n < 3) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建系数矩阵A和目标向量B
|
||||||
|
Eigen::MatrixXd A(n, 3);
|
||||||
|
Eigen::VectorXd B(n);
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
double x = points[i].x;
|
||||||
|
A(i, 0) = x * x;
|
||||||
|
A(i, 1) = x;
|
||||||
|
A(i, 2) = 1.0;
|
||||||
|
B(i) = points[i].y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最小二乘求解:Ax = B,直接调用Eigen的求解器
|
||||||
|
Eigen::Vector3d coeffs = A.bdcSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(B);
|
||||||
|
a = coeffs(0);
|
||||||
|
b = coeffs(1);
|
||||||
|
c = coeffs(2);
|
||||||
|
|
||||||
|
// 计算误差
|
||||||
|
mse = 0.0;
|
||||||
|
max_err = 0.0;
|
||||||
|
for (const auto& p : points) {
|
||||||
|
double y_fit = a * p.x * p.x + b * p.x + c;
|
||||||
|
double err = y_fit - p.y;
|
||||||
|
double err_abs = fabs(err);
|
||||||
|
mse += err * err;
|
||||||
|
if (err_abs > max_err) {
|
||||||
|
max_err = err_abs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mse /= n;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//计算面参数: z = Ax + By + C
|
||||||
|
//res: [0]=A, [1]= B, [2]=-1.0, [3]=C,
|
||||||
|
void vzCaculateLaserPlane(std::vector<cv::Point3f> Points3ds, std::vector<double>& res)
|
||||||
|
{
|
||||||
|
//最小二乘法拟合平面
|
||||||
|
//获取cv::Mat的坐标系以纵向为x轴,横向为y轴,而cvPoint等则相反
|
||||||
|
//系数矩阵
|
||||||
|
cv::Mat A = cv::Mat::zeros(3, 3, CV_64FC1);
|
||||||
|
//
|
||||||
|
cv::Mat B = cv::Mat::zeros(3, 1, CV_64FC1);
|
||||||
|
//结果矩阵
|
||||||
|
cv::Mat X = cv::Mat::zeros(3, 1, CV_64FC1);
|
||||||
|
double x2 = 0, xiyi = 0, xi = 0, yi = 0, zixi = 0, ziyi = 0, zi = 0, y2 = 0;
|
||||||
|
for (int i = 0; i < Points3ds.size(); i++)
|
||||||
|
{
|
||||||
|
x2 += (double)Points3ds[i].x * (double)Points3ds[i].x;
|
||||||
|
y2 += (double)Points3ds[i].y * (double)Points3ds[i].y;
|
||||||
|
xiyi += (double)Points3ds[i].x * (double)Points3ds[i].y;
|
||||||
|
xi += (double)Points3ds[i].x;
|
||||||
|
yi += (double)Points3ds[i].y;
|
||||||
|
zixi += (double)Points3ds[i].z * (double)Points3ds[i].x;
|
||||||
|
ziyi += (double)Points3ds[i].z * (double)Points3ds[i].y;
|
||||||
|
zi += (double)Points3ds[i].z;
|
||||||
|
}
|
||||||
|
A.at<double>(0, 0) = x2;
|
||||||
|
A.at<double>(1, 0) = xiyi;
|
||||||
|
A.at<double>(2, 0) = xi;
|
||||||
|
A.at<double>(0, 1) = xiyi;
|
||||||
|
A.at<double>(1, 1) = y2;
|
||||||
|
A.at<double>(2, 1) = yi;
|
||||||
|
A.at<double>(0, 2) = xi;
|
||||||
|
A.at<double>(1, 2) = yi;
|
||||||
|
A.at<double>(2, 2) = (double)((int)Points3ds.size());
|
||||||
|
B.at<double>(0, 0) = zixi;
|
||||||
|
B.at<double>(1, 0) = ziyi;
|
||||||
|
B.at<double>(2, 0) = zi;
|
||||||
|
//计算平面系数
|
||||||
|
X = A.inv() * B;
|
||||||
|
//A
|
||||||
|
res.push_back(X.at<double>(0, 0));
|
||||||
|
//B
|
||||||
|
res.push_back(X.at<double>(1, 0));
|
||||||
|
//Z的系数为-1
|
||||||
|
res.push_back(-1.0);
|
||||||
|
//C
|
||||||
|
res.push_back(X.at<double>(2, 0));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 空间直线最小二乘拟合
|
||||||
|
* @param points 输入的三维点集(至少2个点,否则无意义)
|
||||||
|
* @param center 输出:拟合直线的质心(基准点P0)
|
||||||
|
* @param direction 输出:拟合直线的方向向量v(单位化)
|
||||||
|
* @return 拟合是否成功(点集有效返回true)
|
||||||
|
*/
|
||||||
|
bool fitLine3DLeastSquares(const std::vector<SVzNL3DPoint>& points, SVzNL3DPoint& center, SVzNL3DPoint& direction)
|
||||||
|
{
|
||||||
|
// 检查点集有效性
|
||||||
|
if (points.size() < 2) {
|
||||||
|
std::cerr << "Error: 点集数量必须大于等于2!" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int n = points.size();
|
||||||
|
Eigen::MatrixXd A(n, 3); // 点集矩阵:每行一个点的(x,y,z)
|
||||||
|
|
||||||
|
// 1. 计算质心(center)
|
||||||
|
double cx = 0.0, cy = 0.0, cz = 0.0;
|
||||||
|
for (const auto& p : points) {
|
||||||
|
cx += p.x;
|
||||||
|
cy += p.y;
|
||||||
|
cz += p.z;
|
||||||
|
A.row(points.size() - n) << p.x, p.y, p.z; // 填充点集矩阵
|
||||||
|
n--;
|
||||||
|
}
|
||||||
|
cx /= points.size();
|
||||||
|
cy /= points.size();
|
||||||
|
cz /= points.size();
|
||||||
|
center = { cx, cy, cz };
|
||||||
|
|
||||||
|
// 2. 构造去中心化的协方差矩阵(3x3)
|
||||||
|
// 关键修复:使用RowVector3d(行向量)做rowwise减法,匹配维度
|
||||||
|
Eigen::RowVector3d centroid_row(cx, cy, cz);
|
||||||
|
Eigen::MatrixXd centered = A.rowwise() - centroid_row; // 维度匹配,无报错
|
||||||
|
|
||||||
|
// 协方差矩阵计算(n-1为无偏估计,工程中也可直接用n)
|
||||||
|
Eigen::Matrix3d cov = centered.transpose() * centered; // / (points.size() - 1);
|
||||||
|
// 3. 特征值分解:求协方差矩阵的特征值和特征向量
|
||||||
|
Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> eigensolver(cov);
|
||||||
|
if (eigensolver.info() != Eigen::Success) {
|
||||||
|
std::cerr << "Error: 特征值分解失败!" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最大特征值对应的特征向量即为方向向量(Eigen默认按特征值升序排列,取最后一个)
|
||||||
|
Eigen::Vector3d dir = eigensolver.eigenvectors().col(2);
|
||||||
|
// 单位化方向向量(可选,但工程中通常标准化)
|
||||||
|
dir.normalize();
|
||||||
|
|
||||||
|
direction = { dir(0), dir(1), dir(2) };
|
||||||
|
return true;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user