逆矩阵介绍及C++/OpenCV/Eigen的三种实现

逆矩阵(inverse matrix):在线性代数中,给定一个n阶方阵A,若存在一n阶方阵B,使得AB = BA =In,其中In为n阶单位矩阵,则称A是可逆的,且B是A的逆矩阵,记作A-1.只有正方形(n*n)的矩阵,亦即方阵,才可能、但非必然有逆矩阵。若方阵A的逆矩阵存在,则称A为非奇异方阵或可逆方阵。与行列式类似,逆矩阵一般常用于求解包含数个变数的数学方程式。

线性方程组:Ax = b ,如下,其中A是一个已知矩阵,b是一个已知向量,x是我们要求解的未知向量。向量x的每一个元素xi都是未知的。矩阵A的每一行和b中对应的元素构成一个约束。


线性代数提供了被称为矩阵逆(matrix inversion)的强大工具,对于大多数矩阵A,我们都能通过矩阵逆解析地求解式Ax=b。

单位矩阵(identity matrix):任意向量和单位矩阵相乘,都不会改变。我们将保持n维向量不变的单位矩阵记作In,形式上如下,

单位矩阵的结构很简单:所有沿主对角线的元素都是1,而所有其他位置的元素都是0。

矩阵A的矩阵逆(matrix inversion)记作A-1,其定义的矩阵满足如下条件:A-1A = In,可以通过A-1求解Ax = b,即x = A-1b。当然,这取决于我们能否找到一个逆矩阵A-1.

当逆矩阵A-1存在时,有几种不同的算法都能找到它的闭解形式。理论上,相同的逆矩阵可用于多次求解不同向量b的方程。

逆矩阵性质:

(1)、若矩阵A是可逆的,则A的逆矩阵是唯一的。

(2)、可逆矩阵一定是方阵。

(3)、A的逆矩阵的逆矩阵还是A,记作(A-1)-1=A.

(4)、可逆矩阵A的转置矩阵AT也可逆,并且(AT)-1=(A-1)T(转置的逆等于逆的转置)。

(5)、若矩阵A可逆,则矩阵A满足消去律,若AB=I(或BA=I),则B=I,若AB=AC(或BA=CA),则B=C。

(6)、两个可逆矩阵的乘积依然可逆。

(7)、矩阵可逆当且仅当它是满秩矩阵。

(8)、(λA)-1=(1/λ)*A-1

(9)、(AB)-1=B-1A-1

(10)、det(A-1)=1/det(A) (det为行列式)

(11)、方阵A可逆的充分必要条件是|A|≠0,并且A-1=(1/|A|)A*,其中A*是A的伴随矩阵。即可逆矩阵就是非奇异矩阵(当|A|=0时,A称为奇异矩阵)。

如果逆矩阵A-1存在,对于式Ax = b,肯定对于每一个向量b恰好存在一个解。

线性组合(linear combination):形式上,一组向量的线性组合,是指每个向量乘以对应标量系数之后的和,即:


生成子空间(span):一组向量的生成子空间是原始向量线性组合后所能抵达的点的集合。

确定Ax = b是否有解相当于确定向量b是否在A列向量的生成子空间中。这个特殊的生成子空间被称为A的列空间(column space)或者A的值域(range)。

线性无关(linearly independent):如果一组向量中的任意一个向量都不能表示成其他向量的线性组合,那么这组向量被称为线性无关,反之称为线性相关(linearly  dependent)。如果某个向量是一组向量中某些向量的线性组合,那么我们将这个向量加入到这组向量后不会增加这组向量的生成子空间。这意味着,如果一个矩阵的列空间涵盖整个Rm,那么该矩阵必须包含至少一组m个线性无关的向量。这是式Ax = b对于每一个向量b的取值都有解的充分必要条件。值得注意的是,这个条件是说该向量集恰好有m个线性无关的列向量,而不是至少m个。不存在一个m维向量的集合具有多于m个彼此线性不相关的列向量,但是一个有多于m个列向量的矩阵却有可能拥有不止一个大小为m的线性无关向量集。

要想使矩阵可逆,我们还需要保证式Ax = b对于每一个b值至多有一个解。为此,我们需要确保该矩阵至多有m个列向量。否则,该方程会有不止一个解。

综上所述,这意味着该矩阵必须是一个方阵(square),即m = n,并且所有列向量都是线性无关的。一个列向量线性相关的方阵被称为奇异的(singular)。

如果矩阵A不是一个方阵或者是一个奇异的方阵,该方程仍然可能有解。但是我们不能使用矩阵逆去求解。

对于方阵而言,矩阵的左逆和右逆是相等的:AA-1=A-1A=I

以上内容来自于 维基百科 和 《深度学习中文版》(https://github.com/exacity/deeplearningbook-chinese)

以下是分别用C++和OpenCV实现的矩阵求逆:

#include "funset.hpp"
#include <math.h>
#include <iostream>
#include <string>
#include <vector>
#include <opencv2/opencv.hpp>
#include "common.hpp"

#define EXP 1.0e-5

// 计算行列式
template<typename _Tp>
_Tp determinant(const std::vector<std::vector<_Tp>>& mat, int N)
{
	if (mat.size() != N) {
		fprintf(stderr, "mat must be square matrix\n");
		return -1;
	}
	for (int i = 0; i < mat.size(); ++i) {
		if (mat[i].size() != N) {
			fprintf(stderr, "mat must be square matrix\n");
			return -1;
		}
	}

	_Tp ret{ 0 };

	if (N == 1) return mat[0][0];

	if (N == 2) {
		return (mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0]);
	}
	else {
		// first col
		for (int i = 0; i < N; ++i) {
			std::vector<std::vector<_Tp>> m(N - 1);
			std::vector<int> m_rows;
			for (int t = 0; t < N; ++t) {
				if (i != t) m_rows.push_back(t);
			}
			for (int x = 0; x < N - 1; ++x) {
				m[x].resize(N - 1);
				for (int y = 0; y < N - 1; ++y) {
					m[x][y] = mat[m_rows[x]][y + 1];
				}
			}
			int sign = (int)pow(-1, 1 + i + 1);
			ret += mat[i][0] * sign * determinant<_Tp>(m, N - 1);
		}
	}

	return ret;
}

// 计算伴随矩阵
template<typename _Tp>
int adjoint(const std::vector<std::vector<_Tp>>& mat, std::vector<std::vector<_Tp>>& adj, int N)
{
	if (mat.size() != N) {
		fprintf(stderr, "mat must be square matrix\n");
		return -1;
	}
	for (int i = 0; i < mat.size(); ++i) {
		if (mat[i].size() != N) {
			fprintf(stderr, "mat must be square matrix\n");
			return -1;
		}
	}

	adj.resize(N);
	for (int i = 0; i < N; ++i) {
		adj[i].resize(N);
	}

	for (int y = 0; y < N; ++y) {
		std::vector<int> m_cols;
		for (int i = 0; i < N; ++i) {
			if (i != y) m_cols.push_back(i);
		}

		for (int x = 0; x < N; ++x) {
			std::vector<int> m_rows;
			for (int i = 0; i < N; ++i) {
				if (i != x) m_rows.push_back(i);
			}

			std::vector<std::vector<_Tp>> m(N - 1);
			for (int i = 0; i < N - 1; ++i) {
				m[i].resize(N - 1);
			}
			for (int j = 0; j < N - 1; ++j) {
				for (int i = 0; i < N - 1; ++i) {
					m[j][i] = mat[m_rows[j]][m_cols[i]];
				}
			}

			int sign = (int)pow(-1, x + y);
			adj[y][x] = sign * determinant<_Tp>(m, N-1);
		}
	}

	return 0;
}

template<typename _Tp>
void print_matrix(const std::vector<std::vector<_Tp>>& mat)
{
	int rows = mat.size();
	for (int y = 0; y < rows; ++y) {
		for (int x = 0; x < mat[y].size(); ++x) {
			fprintf(stderr, "  %f  ", mat[y][x]);
		}
		fprintf(stderr, "\n");
	}
	fprintf(stderr, "\n");
}

void print_matrix(const cv::Mat& mat)
{
	assert(mat.channels() == 1);

	for (int y = 0; y < mat.rows; ++y) {
		for (int x = 0; x < mat.cols; ++x) {
			if (mat.depth() == CV_8U) {
				unsigned char value = mat.at<uchar>(y, x);
				fprintf(stderr, "  %d  ", value);
			}
			else if (mat.depth() == CV_32F) {
				float value = mat.at<float>(y, x);
				fprintf(stderr, "  %f  ", value);
			}
			else if (mat.depth() == CV_64F) {
				double value = mat.at<double>(y, x);
				fprintf(stderr, "  %f  ", value);
			}
			else {
				fprintf(stderr, "don't support type: %d\n", mat.depth());
				return;
			}
		}
		fprintf(stderr, "\n");
	}
	fprintf(stderr, "\n");
}

// 求逆矩阵
template<typename _Tp>
int inverse(const std::vector<std::vector<_Tp>>& mat, std::vector<std::vector<_Tp>>& inv, int N)
{
	if (mat.size() != N) {
		fprintf(stderr, "mat must be square matrix\n");
		return -1;
	}
	for (int i = 0; i < mat.size(); ++i) {
		if (mat[i].size() != N) {
			fprintf(stderr, "mat must be square matrix\n");
			return -1;
		}
	}

	_Tp det = determinant(mat, N);
	if (fabs(det) < EXP) {
		fprintf(stderr, "mat's determinant don't equal 0\n");
		return -1;
	}

	inv.resize(N);
	for (int i = 0; i < N; ++i) {
		inv[i].resize(N);
	}

	double coef = 1.f / det;
	std::vector<std::vector<_Tp>> adj;
	if (adjoint(mat, adj, N) != 0) return -1;

	for (int y = 0; y < N; ++y) {
		for (int x = 0; x < N; ++x) {
			inv[y][x] = (_Tp)(coef * adj[y][x]);
		}
	}

	return 0;
}

int test_inverse_matrix()
{
	std::vector<float> vec{ 5, -2, 2, 7, 1, 0, 0, 3, -3, 1, 5, 0, 3, -1, -9, 4 };
	const int N{ 4 };
	if (vec.size() != (int)pow(N, 2)) {
		fprintf(stderr, "vec must be N^2\n");
		return -1;
	}

	std::vector<std::vector<float>> arr(N);
	for (int i = 0; i < N; ++i) {
		arr[i].resize(N);

		for (int j = 0; j < N; ++j) {
			arr[i][j] = vec[i * N + j];
		}
	}

	std::vector<std::vector<float>> inv1;
	int ret = inverse<float>(arr, inv1, N);

	fprintf(stderr, "source matrix: \n");
	print_matrix<float>(arr);
	fprintf(stderr, "c++ inverse matrix: \n");
	print_matrix<float>(inv1);

	cv::Mat mat(N, N, CV_32FC1, vec.data());
	cv::Mat inv2 = mat.inv();
	fprintf(stderr, "opencv inverse matrix: \n");
	print_matrix(inv2);

	return 0;
}
执行结果如下:

以下是用Eigen实现的矩阵求逆:

#include "funset.hpp"
#include <math.h>
#include <iostream>
#include <vector>
#include <string>
#include <opencv2/opencv.hpp>
#include <Eigen/Dense>
#include "common.hpp"

template<typename _Tp>
void print_matrix(const _Tp* data, const int rows, const int cols)
{
	for (int y = 0; y < rows; ++y) {
		for (int x = 0; x < cols; ++x) {
			fprintf(stderr, "  %f  ", static_cast<float>(data[y * cols + x]));
		}
		fprintf(stderr, "\n");
	}
	fprintf(stderr, "\n");
}

int test_inverse_matrix()
{
	std::vector<float> vec{ 5, -2, 2, 7, 1, 0, 0, 3, -3, 1, 5, 0, 3, -1, -9, 4 };
	const int N{ 4 };
	if (vec.size() != (int)pow(N, 2)) {
		fprintf(stderr, "vec must be N^2\n");
		return -1;
	}

	Eigen::Map<Eigen::MatrixXf> map(vec.data(), 4, 4);
	Eigen::MatrixXf inv = map.inverse();

	fprintf(stderr, "source matrix:\n");
	print_matrix<float>(vec.data(), N, N);
	fprintf(stderr, "eigen inverse matrix:\n");
	print_matrix<float>(inv.data(), N, N);

	return 0;
}
执行结果如下:


可见,三种实现方法结果是一致的。

GitHub

https://github.com/fengbingchun/NN_Test

https://github.com/fengbingchun/Eigen_Test

  • 6
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
手眼标定是指将机械臂的末端执行器与相机之间的相对姿态关系确定下来,这样可以用机械臂控制相机来实现精确的视觉引导。在手眼标定中,需要使用到相机的内外参数,机械臂的关节角度和末端执行器的位姿。下面介绍一种用C++实现手眼标定的方法。 1. 准备数据 首先需要获取机械臂的关节角度和末端执行器的位姿,以及相机的内外参数。可以通过机械臂的编程接口获取机械臂的状态信息,也可以手动记录机械臂运动时的关节角度和末端执行器位姿。相机的内外参数可以通过相机标定软件获取,例如OpenCV中的Camera Calibration。 2. 实现标定算法 手眼标定通常使用四元数来表示姿态关系,因为四元数可以避免万向锁问题。手眼标定的过程可以分为两步:计算机械臂的姿态变换矩阵和相机的姿态变换矩阵,然后通过求解手眼标定问题得到机械臂与相机的相对姿态矩阵。 计算机械臂的姿态变换矩阵可以通过机械臂的正运动学求解,计算相机的姿态变换矩阵可以通过相机的位姿信息求解,例如通过相机标定得到的旋转矩阵和平移矩阵。 通过求解手眼标定问题,可以得到机械臂与相机之间的相对姿态矩阵。手眼标定问题通常使用最小二乘法求解,可以使用OpenCV中的solvePnP函数求解。 3. 实现代码 下面是一个用C++OpenCV实现手眼标定的代码示例: ``` #include <opencv2/opencv.hpp> #include <opencv2/core/eigen.hpp> #include <Eigen/Dense> using namespace cv; void handEyeCalibration(const std::vector<Mat>& robotPoses, const std::vector<Mat>& cameraPoses, Mat& H) { int n = robotPoses.size(); Mat A = Mat::zeros(n * 6, 6, CV_64F); Mat B = Mat::zeros(n * 6, 1, CV_64F); for (int i = 0; i < n - 1; i++) { Mat Ri1, ti1, Ri2, ti2; cv::Matx33d R1(cameraPoses[i](cv::Rect(0, 0, 3, 3))), R2(cameraPoses[i + 1](cv::Rect(0, 0, 3, 3))); cv::Vec3d t1(cameraPoses[i](cv::Rect(3, 0, 1, 3))), t2(cameraPoses[i + 1](cv::Rect(3, 0, 1, 3))); cv::Rodrigues(R1, Ri1); cv::Rodrigues(R2, Ri2); cv::Matx33d Ri = R2 * R1.t(); cv::Vec3d ti = t2 - R2 * t1; cv::Matx33d Ti(robotPoses[i](cv::Rect(0, 0, 3, 3))), Hi(robotPoses[i + 1](cv::Rect(0, 0, 3, 3))); cv::Vec3d pi(robotPoses[i](cv::Rect(3, 0, 1, 3))), hi(robotPoses[i + 1](cv::Rect(3, 0, 1, 3))); cv::Matx33d HiTi = Hi * Ti.t(); cv::Vec3d hi_ti = hi - Hi * ti; cv::Matx33d A1 = Ri.t() - cv::Matx33d::eye(); cv::Matx33d A2 = HiTi - cv::Matx33d::eye(); cv::Matx33d A3 = Hi * Ti.t() - Ri.t() * Hi; cv::Matx33d A4 = Hi * Ti.t() - cv::Matx33d::eye(); cv::Vec3d b1 = hi_ti; cv::Vec3d b2 = Hi * pi - pi * Ri.t(); cv::Vec3d b3 = Hi * ti - ti * Ri.t(); Mat Ai, Bi; cv::vconcat(Mat(A1), Mat(A2), Ai); cv::vconcat(Ai, Mat(A3), Ai); cv::vconcat(Ai, Mat(A4), Ai); cv::vconcat(Mat(b1), Mat(b2), Bi); cv::vconcat(Bi, Mat(b3), Bi); int idx = i * 6; Ai.copyTo(A.rowRange(idx, idx + 6)); Bi.copyTo(B.rowRange(idx, idx + 6)); } Mat X; solve(A, B, X, DECOMP_SVD); H = Mat::eye(4, 4, CV_64F); X.rowRange(0, 3).col(0).copyTo(H.rowRange(0, 3).col(0)); X.rowRange(3, 6).col(0).copyTo(H.rowRange(0, 3).col(1)); X.rowRange(6, 9).col(0).copyTo(H.rowRange(0, 3).col(2)); X.rowRange(9, 12).col(0).copyTo(H.rowRange(0, 3).col(3)); } int main() { std::vector<Mat> robotPoses; // 机械臂位姿 std::vector<Mat> cameraPoses; // 相机位姿 // 加载机械臂位姿和相机位姿 Mat H; handEyeCalibration(robotPoses, cameraPoses, H); std::cout << "Hand-eye calibration result: " << std::endl << H << std::endl; return 0; } ``` 其中,robotPoses和cameraPoses分别是机械臂和相机的位姿序列,H是求解得到的机械臂与相机之间的相对姿态矩阵。通过solve函数求解线性方程组得到H,然后将H转换为4x4的变换矩阵即可。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值