algoLib/sourceCode/dataFitting.cpp

386 lines
9.1 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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;
}