【数学和算法】最小二乘法理论(附c++代码)
插值:是給出一些點,求出這些點的表達式,表達式一定經過這些點,然后利用表達式就可以得到某點的值。
插值應用場景:在數據點稀少的時候,也可以用來插值,使得數據點密集。
擬合:是給出一些點,求出最接近這些點的曲線表達式,表達式不要求穿過這些點,只要求最貼合這些點,這就是擬合。
擬合應用場景:用來使得這些點的曲線更平滑。
注意多項式插值與多項式擬合的區別:
- 多項式插值的話不需要用到最小二乘法,比如三次多項式插值,你只需要四個點,四個方程,組成由4個多項式系數作為未知數的四元一次方程組,就可以求出三次多項式的四個系數;
- 多項式擬合,例如三次多項式擬合,可能會給你幾十個幾百個上千個點,然后使用最小二乘法,求出最貼合這些點的曲線來擬合這些點。
看了上面的區別,應該很明了了,如果使用多項式插值,每增加一個點,多項式次數就會增加,就需要重新計算,而且次數也并非越大越好,會有龍格現象,在實際工程中,這顯然不合理,所以多項式插值一般只是理論中一帶而過。實際工程中我們通常使用多項式擬合。當數據點本身很多的話,多項式插值則次數很高,龍格現象會造成不準確插值。當然這一點可以通過分段低次插值克服,而不會直接用到高次數的多項式插值。
下面只介紹擬合中最常用的最小二乘法,理論部分直接參考知乎大佬的博客: 最小二乘法
1.最小二乘法 理論部分
知乎大佬的博客中講到了最小二乘法推導,以及選擇算術平均數、一次多項式曲線、二次多項式曲線等進行擬合的效果。
- 如果使用算術平均數來擬合,得到的擬合曲線的所有點的y值都相等,即平行于x軸的直線(斜率為0);
- 如果使用一次多項式來擬合,得到的擬合曲線的所有點的y值不相等,即一條斜率不為0的直線;
- 如果使用二次多項式來擬合,得到的擬合曲線是一個二次函數的曲線;
- 如果使用n次多項式來擬合,得到的擬合曲線是一個n次函數的曲線;
也就是說,給你一系列可能是曲線的點,你可以選擇不同的函數來擬合這些點,得到擬合曲線函數。
注意看,上面兩個偏導=0的方程組求解,是把所有樣本代入進去進行求和:并不是一個樣本代入一個方程,而是所有樣本一起代入。
上面就是使用一次多項式曲線,注意,不是二次多項式,因為最小二乘法就是求距離的平方和最小值,所以接了個平方。
只需要記住,最小二乘法是用來求多項式的系數,所以求導是分別求平方和對各個系數的偏導,令偏導為0,就得到了方程組,把各個點的(xi,yi)一起代入方程組進行求解,就得到了多項式的系數。不過這個方程組可以使用矩陣的形式A*X=B來求解,利用opencv中的cv::solve函數可以求解。cv::solve函數可參考這篇博客:https://blog.csdn.net/nienelong3319/article/details/80855077
下面的截圖是推導:
其中公式里面的i=0有誤,都改為i=1,因為點是從(x1,y1)開始的。W3改為Wm
代碼部分
2.1 多項式擬合曲線C++代碼:
// 用最小二乘法根據點集擬合多項式系數 // order是設置的多項式次數 // is_x_axis是根據x軸的方向。因為像素坐標系的x,y和車身坐標系的x,y方向不一樣 // coeff是求出的系數向量(order+1行,1列) bool PolyCurveFit(const std::vector<Point2D<float>> &point_set, const int &order,const bool &is_x_axis, cv::Mat *coeff) {if (coeff == nullptr) {SERROR << "The pointer of coefficient matrix is null!";return false;}const int n = static_cast<int>(point_set.size());if (n <= order) {SERROR << "The number of points should be larger than the order.""The number of points = "<< n;return false;}cv::Mat A = cv::Mat::zeros(order + 1, order + 1, CV_64FC1);cv::Mat B = cv::Mat::zeros(order + 1, 1, CV_64FC1);// Fill matrix Afor (int i = 0; i < order + 1; ++i) {for (int j = 0; j < order + 1; ++j) {for (int k = 0; k < n;++k) { // 遍歷點,把每個點的x或y坐標都進行求i+j次方// 矩陣A的每個元素都是[x的冪],或[y的冪],最大冪為2*orderA.at<double>(i, j) +=std::pow(is_x_axis ? static_cast<double>(point_set.at(k).x): static_cast<double>(point_set.at(k).y),i + j);}}}// Fill matrix Bfor (int i = 0; i < order + 1; ++i) {// 遍歷點,把每個點都進行(x坐標求i次方)*y 或 (y坐標求i次方)*xfor (int k = 0; k < n; ++k) {// 矩陣B的每個元素都是[x的冪*y],或[y的冪*x],最大冪為orderB.at<double>(i, 0) +=std::pow(is_x_axis ? static_cast<double>(point_set.at(k).x): static_cast<double>(point_set.at(k).y),i) *(is_x_axis ? static_cast<double>(point_set.at(k).y): static_cast<double>(point_set.at(k).x));}}(*coeff) = cv::Mat::zeros(order + 1, 1, CV_64FC1);// 使用最小二乘法,最后優化問題化簡為,求A*X=B, 即 A*coeff=B,// 矩陣方程式有了,求未知系數矩陣X,使用的是LU矩陣分解算法// 這里就是求解order+1元order次方程組,求得就是系數if (!cv::solve(A, B, *coeff, cv::DECOMP_LU)) {SERROR << "Cannot solve polynomial coefficient!";return false;}return true; }上面代碼是使用opencv的庫函數解出Ax=b中的x,這里的x就是系數coef。
如果是使用Eigen矩陣的話,那么代碼這樣的:
2.2 求出一系列點的多項式之后,給出一個點的x值,就可以根據求出的多項式系數,求出該點的y值:
// 模板參數中的N是N次多項式的最高次數,N次多項式有N+1個多項式系數 // std::array<float, N + 1> poly_coeff是通過上面函數求出的多項式系數,可由Mat中轉存到array中 // 下面函數就是以 y=a+b*x+c*x^2+d*x^3+...的形式計算x值對應的y值 template <typename T, std::size_t N> T GetPolyValue(const std::array<float, N + 1> &poly_coeff, T x) {T value = 0;for (int i = 0; i < N+1; ++i) {value += poly_coeff[i] * std::pow(x, i);}return value; }2.3 畫出x從0~100擬合曲線,x采樣間隔為0.3,使用圓圈在圖像上畫出這些點
- (1) 如果擬合的多項式的點(x,y)本來就是像素坐標系的點,可以直接在圖像中畫出來:
- (2) 如果x,y是車身坐標系的點,那么需要先轉化為像素坐標系的點,然后再到圖像中標出這些點:
總結
以上是生活随笔為你收集整理的【数学和算法】最小二乘法理论(附c++代码)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【C++】39. std::ofstre
- 下一篇: 【数学与算法】KMeans聚类代码