先上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.000505627x2, 圖像如下:
當迭代次數為7的時候求解結果為:
-210.757
17.1697
-0.132375
0.000430976
-6.52814e-07
4.45947e-10
-1.06088e-13
圖像如下:
以下是一些自己測試的數據: