最小二乘法拟合平面,附C++结合OpenCV的实现代码

问题:

给定n个三维点的坐标,根据这些点坐标由最小二乘法拟合平面。

分析推导:

平面一般方程为ax+by+cy+d=0;

这里为了便于描述,将平面方程变形为:z=Ax+By+C(相当于令上述平面一般方程中的c=-1,a=A,b=B,d=C)。因为已知了n个三维点的坐标,所以理想情况下将各点坐标代入平面方程中构建一个方程组,求解方程组即可。

\left\{\begin{matrix}z_0=Ax_0+By_0+C \\ z_1=Ax_1+By_1+C \\ \cdots \end{matrix}\right.

但是由于n个三维点的测量误差,点不一定在平面上,所以上述方程组是无解的。因此,采用最小二乘法求解上述线性方程组

下面对方程z=Ax+By+C根据最小二乘法求解其中的系数A,B,C。

构建方程的目标函数

J(A,B,C)=\sum_{i=0}^{n}(Ax_{i}+By_{i}+C-z_{i})^{2}

求解A,B,C使得损失函数J最小,采用微积分中的最小二乘法,即对A,B,C分别求偏导,令其偏导均为0,如下所示:

\left\{\begin{matrix} \partial J/\partial A=2*\sum_{i=0}^{n}(Ax_i+By_i+C-z_i)*x_i=0\\ \partial J/\partial B=2*\sum_{i=0}^{n}(Ax_i+By_i+C-z_i)*y_i=0 \\\partial J/\partial C=2*\sum_{i=0}^{n}(Ax_i+By_i+C-z_i)=0 \end{matrix}\right.

将上述方程组进行展开、变形,得到一个未知量为A,B,C的线性方程组:

\left\{\begin{matrix}A \sum_{}x_i^2+B\sum_{}x_iy_i+C\sum_{}x_i=\sum_{}x_iz_i \\ A \sum_{}x_iy_i+B\sum_{}y_i^2+C\sum_{}y_i=\sum_{}y_iz_i \\ A \sum_{}x_i+B\sum_{}y_i+C*n=\sum_{}z_i \end{matrix}\right.

构建上述方程组的系数矩阵Coeff,以及方程右边的常数向量v

Coeff=\begin{bmatrix} \sum_{}x_i^2 &\sum_{}x_iy_i &\sum_{}x_i \\ \sum_{}x_iy_i &\sum_{}y_i^2 &\sum_{}y_i \\ \sum_{}x_i &\sum_{}y_i &n \end{bmatrix}

v=\begin{Bmatrix}\sum_{}x_iz_i \\ \sum_{}y_iz_i \\ \sum_{}z_i \end{Bmatrix}

由此构建了形如Coeff *x=v的线性方程组,x=Coeff^{-1} * v,由此即可计算得到方程组的解\vec{x}

关于最小二乘法是否有解,以及最小二乘法的解是否为全局最优解,参考这篇文章的介绍:https://www.zhihu.com/question/427449730

上面完成了最小二乘法求解平面方程的理论推导,下面进行代码实现: C++结合OpenCV实现矩阵运算

cv::Vec4f fitplane(vector<cv::Point3f> ptsvec)
{
	int n = ptsvec.size();
	cv::Mat input_pts(n, 3, CV_32FC1);
	for (int i = 0; i < n; i++)
	{
		input_pts.at<float>(i, 0) = ptsvec[i].x;
		input_pts.at<float>(i, 1) = ptsvec[i].y;
		input_pts.at<float>(i, 2) = ptsvec[i].z;
	}
	double A11 = 0, A12 = 0, A13 = 0;
	double A21 = 0, A22 = 0, A23 = 0;
	double A31 = 0, A32 = 0, A33 = 0;
	double B1 = 0, B2 = 0, B3 = 0;
	for (int i = 0; i < n; i++)
	{
		A11 += input_pts.at<float>(i, 0)*input_pts.at<float>(i, 0);
		A12 += input_pts.at<float>(i, 0)*input_pts.at<float>(i, 1);
		A13 += input_pts.at<float>(i, 0);
		A21 += input_pts.at<float>(i, 0)*input_pts.at<float>(i, 1);
		A22 += input_pts.at<float>(i, 1)*input_pts.at<float>(i, 1);
		A23 += input_pts.at<float>(i, 1);
		A31 += input_pts.at<float>(i, 0);
		A32 += input_pts.at<float>(i, 1);
		B1 += input_pts.at<float>(i, 0)*input_pts.at<float>(i, 2);
		B2 += input_pts.at<float>(i, 1)*input_pts.at<float>(i, 2);
		B3 += input_pts.at<float>(i, 2);
	}
	A33 = n;

	cv::Mat A(3, 3, CV_32FC1);
	A.at<float>(0, 0) = A11; A.at<float>(0, 1) = A12; A.at<float>(0, 2) = A13;
	A.at<float>(1, 0) = A21; A.at<float>(1, 1) = A22; A.at<float>(1, 2) = A23;
	A.at<float>(2, 0) = A31; A.at<float>(2, 1) = A32; A.at<float>(2, 2) = A33;
	cv::Mat B(3, 1, CV_32FC1);
	B.at<float>(0, 0) = B1; B.at<float>(1, 0) = B2; B.at<float>(2, 0) = B3;
	cv::Mat X;
	X = A.inv()*B;//X为3*1解向量,分别对应平面方程z=ax+by+c中的abc
	cv::Vec4f v;
	v[0] = X.at<float>(0, 0);
	v[1] = X.at<float>(1, 0);
	v[2] = X.at<float>(2, 0);
	return v;
}

随机给一组点进行测试:(该点为三维坐标系中Oxy平面附近的一组点,坐标的z值在0附近小范围波动,拟合的平面法向量应该近似等于(0,0,1))。


int main()
{
	vector<cv::Point3f> pts;
	//pts.resize(5);
	pts.push_back(cv::Point3f(10.1, 20.5, 0.12));
	pts.push_back(cv::Point3f(15.1, 34.5, 0.1));
	pts.push_back(cv::Point3f(13.1, 7.5, -0.05));
	pts.push_back(cv::Point3f(10.1, 25.5, 0.03));
	pts.push_back(cv::Point3f(14.1, 10.5, 0.1));
	pts.push_back(cv::Point3f(16.1, 40.5, 0.2));
	pts.push_back(cv::Point3f(32.1, 10.5, -0.2));
	cv::Vec4f v = fitplane(pts);
    cout << "coeff: "<< v[0] << ", "<< v[1] <<", " << v[2] << endl;
	return 0; 
}

程序运行后的打印结果如下图:

  • 5
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值