先上Github地址
线性拟合的应用领域比较广泛, 如运动轨迹计算、数据分析、图像处理等领域, 故在此写一篇学习相关文章.
直线拟合
在说直线拟合前我们先来复习一下直线方程. 直线方程的表达式有以下几种(引用自百度):
-
一般式:Ax+By+C=0(A、B不同时为0)【适用于所有直线】
A1/A2=B1/B2≠C1/C2←→两直线平行
A1/A2=B1/B2=C1/C2←→两直线重合
横截距a=-C/A
纵截距b=-C/B -
点斜式:y-y0=k(x-x0) 【适用于不垂直于x轴的直线】
表示斜率为k,且过(x0,y0)的直线 -
截距式:x/a+y/b=1【适用于不过原点或不垂直于x轴、y轴的直线】
表示与x轴、y轴相交,且x轴截距为a,y轴截距为b的直线 -
斜截式:y=kx+b【适用于不垂直于x轴的直线】
表示斜率为k且y轴截距为b的直线 -
两点式:【适用于不垂直于x轴、y轴的直线】
表示过(x1,y1)和(x2,y2)的直线
**(y-y1)/(y2-y1)=(x-x1)/(x2-x1) **(****x1≠x2,y1≠y2****)**** - 交点式:f1(x,y) *m+f2(x,y)=0 【适用于任何直线】
表示过直线f1(x,y)=0与直线f2(x,y)=0的交点的直线 - 点平式:f(x,y) -f(x0,y0)=0【适用于任何直线】
表示过点(x0,y0)且与直线f(x,y)=0平行的直线 -
法线式:x·cosα+ysinα-p=0【适用于不平行于坐标轴的直线】
过原点向直线做一条的垂线段,该垂线段所在直线的倾斜角为α,p是该线段的长度 -
点向式:(x-x0)/u=(y-y0)/v (u≠0,v≠0)【适用于任何直线】
表示过点(x0,y0)且方向向量为(u,v )的直线 - 法向式:a(x-x0)+b(y-y0)=0【适用于任何直线】****
表示过点(x0,y0)且与向量(a,b)垂直的直线
在OpenCV中使用的是点向式:(x-x0)/u=(y-y0)/v (u≠0,v≠0)
在OpenCV中我们可以主要使用fitLine函数进行直线拟合, 其算法基于最小二乘法实现的, 关于最小二乘法后续再作详细介绍. fitLine函数定义如下:
void fitLine( InputArray points, OutputArray line, int distType, double param, double reps, double aeps );
该方法可以对二维或三维的点集进行拟合, 在二维模式下返回一个Vec4f的元素, 分别是点向式中的(u,v,x,y)
参数介绍:
points
输入一个二维或三维的向量, 数据结构可以是std::vector<> 或 Mat(推荐使用).
line
二维模式下返回Vec4f的元素(u,v,x,y), 三维模式下返回Vec6f的元素(vx, vy, vz, x0, y0, z0)
distType
拟合算法, 这里的拟合算法基于M-estimators实现, 有以下几种:
enum DistanceTypes {
DIST_USER = -1, //!< User defined distance
DIST_L1 = 1, //!< distance = |x1-x2| + |y1-y2|
DIST_L2 = 2, //!< the simple euclidean distance
DIST_C = 3, //!< distance = max(|x1-x2|,|y1-y2|)
DIST_L12 = 4, //!< L1-L2 metric: distance = 2(sqrt(1+x*x/2) - 1))
DIST_FAIR = 5, //!< distance = c^2(|x|/c-log(1+|x|/c)), c = 1.3998
DIST_WELSCH = 6, //!< distance = c^2/2(1-exp(-(x/c)^2)), c = 2.9846
DIST_HUBER = 7 //!< distance = |x|<c ? x^2/2 : c(|x|-c/2), c=1.345
};
其算法主要使用的是加权最小二乘法, 分别采用如下算法:
其中CV_DIST_L2为最简单快速的最小二乘法, 推荐使用.
param
拟合算法中的的C参, 设置为0时系统自动选用最优值.
reps
算法精度radius, 官方推荐0.01
aeps
算法精度angle, 官方推荐0.01
曲线拟合
首先,曲线的函数类型有:双曲线型、对数型、指数型、多项式型等。
对于最常见的曲线, 其特征接近于多项式型, 所以这里的曲线拟合问题就变成了多项式回归求解.
一般情况下,对实际的点数据进行求解是无解的, 所以我们需要引入最小二乘法来求解.(对于最小二乘法的原理和求解过程,后续再详细介绍)
在OpenCV中没有找到拟合曲线的算子, 这里使用C++来实现. 此算法迭代次数大于10时求解均为0. 若有更优的算法可以在评论中交流.
bool fittingCurve(vector<Point2f> &vec,int times,float *p) // 输入点数据, 迭代次数, 输出多项式参数
{
float *py = new float[vec.size()];
float *px = new float[times*vec.size()];
int i = 0;
for(vector<Point2f>::iterator itr = vec.begin();itr!=vec.end();++itr) {
py[i] = (*itr).y;
int j=0;
while (j<times)
{
px[times*i+j] = pow(((*itr).x),float(j));
j++;
}
i++;
}
Mat X = Mat((int)vec.size(),times,CV_32FC1,px);
Mat X_T;
transpose(X,X_T);
Mat Y = Mat((int)vec.size(),1,CV_32FC1,py);
Mat para = ((X_T*X).inv())*X_T*Y;
for (int s = 0; s<times;s++){
p[s] = para.at<float>(s);
}
delete[] px;
delete[] py;
return true;
}
下面我们来验证一组数据:
int x[]={50,100,150,200,250,300,350,400,450,500,550,600,650,700,750};
int y[]={428,454,480,506,532,458,384,210,636,662,688,778,504,430,456};
当迭代次数为3的时候求解结果为:
381.534
0.575073
-0.000505627
即:y = 381.534 + 0.575073x + -0.000505627x², 图像如下:
当迭代次数为7的时候求解结果为:
-210.757
17.1697
-0.132375
0.000430976
-6.52814e-07
4.45947e-10
-1.06088e-13
图像如下:
以下是一些自己测试的数据: