直线拟合推导

目标函数为:

\[ f(x)=\sum_{i=0}^{n-1}\left(Y_i-y_i\right)^2=\sum_{i=0}^{n-1}\left(ax_i+b-y_i\right)^2 \]

我们的目标是求当f(x)取得最小值时的a、b参数。于是分别求f(x)对于a、b的偏导数:

\[ \frac{\partial f}{\partial a}=\sum_{i=0}^{n-1}2\left(\alpha x_i+b-y_i\right)x_i = 2\sum_{i=0}^{n-1}\Big(ax_{i}^{2}+bx_{i}-x_{i}y_{i}\Big) = 2\left(a\sum_{i=0}^{n-1}x_{i}^{2}+b\sum_{i=0}^{n-1}x_{i}-\sum_{i=0}^{n-1}x_{i}y_{i}\right) \]
\[ \frac{\partial f}{\partial b} = \sum_{i=0}^{n-1} 2\left(\alpha x_i+b-y_i\right) = 2\left(a\sum_{i=0}^{n-1}x_{i}+\sum_{i=0}^{n-1}b-\sum_{i=0}^{n-1}y_{i}\right) = 2\left(a\sum_{i=0}^{n-1}x_{i}+bn-\sum_{i=0}^{n-1}y_{i}\right) \]

令以上偏导数为0;得到一个二元一次方程组:

\[ a\sum_{i=0}^{n-1}x_{i}^{2}+b\sum_{i=0}^{n-1}x_{i}=\sum_{i=0}^{n-1}x_{i}y_{i} \]
\[ a\sum_{i=0}^{n-1}x_{i}+bn=\sum_{i=0}^{n-1}y_{i} \]

记:

\[ A=\sum_{i=0}^{n-1}x_{i}^{2},B=\sum_{i=0}^{n-1}x_{i},C=\sum_{i=0}^{n-1}x_{i}y_{i},D=\sum_{i=0}^{n-1}y_{i} \]
\[ Aa + Bb = C \\ Ba + bn = D \]

解以上方程组得到a、b;就是我们要求的直线拟合参数;

\[ a = \frac{nC - BD}{nA -B^2} \\ b = \frac{DA - BC}{nA - B^2} \]
{
        float sum_x2 = 0.0;
        float sum_y = 0.0;
        float sum_x = 0.0;
        float sum_xy = 0.0;
        for(int pos = 0; pos < count; pos ++){
            sum_x2 += xs[pos] * xs[pos];
            sum_y += ys[pos];
            sum_x += xs[pos];
            sum_xy += xs[pos] * ys[pos];
        }


        float k = 0., b = 0.;
        float tmp = count * sum_x2 - sum_x * sum_x;
        if (std::abs(tmp) > 0.000001f){
            k = (count * sum_xy - sum_x * sum_y) / tmp;
            b = (sum_x2 * sum_y - sum_x * sum_xy) / tmp;
        }else{
            k = 0;
            b = 0;
        }
        std::cout << "cpp: k:" << k << " b:" << b << std::endl;
    }
}

直线极坐标表达式

如图所示,直线方程\(y = k * x + b\),其中:

\[ b = \frac{r}{\sin \theta} \\ k = \tan(\pi - (\pi/2 - \theta)) = \tan(\pi / 2 + \theta) = -\cot \theta = -\frac{\cos \theta}{\sin \theta} \]

得到:

\[ y = -\frac{\cos \theta}{\sin \theta} x + \frac{r}{\sin \theta} \]

因此得到直线方程极坐标表达式:

\[ x \cos \theta + y \sin \theta = r \]

其中\(r > 0\)为直线与圆心距离,\(\theta\)为垂线与x轴距离。

使用笛卡尔坐标系拟合直线


int main(int argc, const char **argv)
{
    // generate observation
    const int count = 1000;
    const float k = 0.5;
    const float b = 0.2;
    std::vector<float> xs, ys;
    for(int pos = 0; pos < count; pos ++){
        float x = pos / float(count);
        float y = k * x + b;
        xs.push_back(x);
        ys.push_back(y);
    }

    // 2. eigen
    // [x, 1] * ([k, b])^t = [y]
    {
        Eigen::MatrixXd A = Eigen::MatrixXd::Zero(count, 2);
        Eigen::VectorXd B = Eigen::VectorXd::Zero(count);
        for(int pos = 0; pos < count; pos ++){
            A.block<1, 2>(pos, 0)[0] = xs[pos];
            A.block<1, 2>(pos, 0)[1] = 1.;
            B[pos] = ys[pos];
        }
        Eigen::Vector2d solved = (A.transpose() * A).inverse() * (A.transpose() * B);
        //Eigen::Vector2d solved = A.bdcSvd(Eigen::ComputeThinU|Eigen::ComputeThinV).solve(B);
        //Eigen::Vector2d solved = (A.transpose() * A).ldlt().solve(A.transpose() * B);
        std::cout << "eigen: k:" << solved[0] << " b:" << solved[1] << std::endl;
    }

    return 0;
}


使用极坐标表达式拟合直线

\[ residual = \sum_0^N \{ x \cos \theta + y \sin \theta - r \} \]
\[ X = \begin{bmatrix} \theta \\ r \end{bmatrix}_{2\times1} \]
\[ J = \begin{bmatrix} x * -\sin \theta + y * \sin \theta & -1 \end{bmatrix}_{1\times2} \]
int main(int argc, const char **argv)
{
    const int count = 1000;
    for(int pos = 0; pos < count; pos ++){
        xs.push_back(1.);
        ys.push_back(pos / float(count));
    }
    {
        // polar line
        double theta = 0.;
        double r = 0.;
        for(int iter = 0; iter < 100; iter ++){
            Eigen::MatrixXd H = Eigen::MatrixXd::Zero(2, 2);
            Eigen::VectorXd B = Eigen::VectorXd::Zero(2);
            for(int pos = 0; pos < count; pos ++){
                double x = xs[pos], y = ys[pos];
                // x \cos \theta + y \sin \theta - r = 0
                double fx = x * std::cos(theta) + y * std::sin(theta) - r;
                Eigen::MatrixXd J = Eigen::MatrixXd::Zero(1, 2);
                J(0, 0) = x * -std::sin(theta) + y * std::cos(theta);
                J(0, 1) = -1.;
                H += J.transpose() * J;
                B += -J.transpose() * fx; 
            }
            // J^TJ \delta x = -J^T fx
            Eigen::Vector2d solved = H.ldlt().solve(B);
            theta += solved[0];
            r += solved[1];
        }
        std::cout << "polar:" << " theta:" << theta << " r:" << r << " k:" << -std::cos(theta)/std::sin(theta) << " b:" << r/std::sin(theta) << std::endl;
    }
    return 0;
}


Reference

[0]