本文章是综合书籍以及相关资料的一点个人总结。
1、定义
单应矩阵的模式图:(引用自opencv docs)
观测物平面π上的一个点X(齐次坐标)映射到相机平面π'的点X’(齐次坐标
),存在如下的一种转换关系:
其中H就是单应矩阵,Opencv Docs还定义一般的应用场景:
a)同一平面被两个处于不同位置的相机观测,或者同一个相机观测到的两个相同的,但位置不同的物体;
b)旋转相机拍摄的任意图片,考虑的是因为只有旋转,则所有点可以近似于处在距离相机原点无穷远的平面上
2、模型条件
- 基于的是相机的针孔模型,所以想得到精确解,必须对相机进行标定,获得相机内外参数;
- 单应矩阵是两个平面间点的转换矩阵,如果观测物的点不处于同一平面则单应矩阵不成立,这也是实际使用中的误差来源;
3、公式推导及对应src范例
a)对于相机坐标系下的H:
设平面在第一个相机坐标系下的单位法向量为
,其到第一个相机中心(坐标原点)的距离为d,则平面
可表示为:
(1)
根据一般的两个平面间的旋转变换R和平移变换t,有第一个相机坐标系下的平面至第二相机坐标系下变换公式:
(2)
结合上面两式可得:
将(1)式变形为:,带入(2):
即, (3)
opencv有src详细论证使用此公式求解以及直接用函数求解的结果对比:
int main()
{
Mat img1 = imread(img1Path);
Mat img2 = imread(img2Path);
//寻找棋盘点
vector<Point2f> corners1, corners2;
bool found1 = findChessboardCorners(img1, patternSize, corners1);
bool found2 = findChessboardCorners(img2, patternSize, corners2);
if (!found1 || !found2)
{
cout << "Error, cannot find the chessboard corners in both images." << endl;
return;
}
vector<Point3f> objectPoints;
calcChessboardCorners(patternSize, squareSize, objectPoints);
//加载相机内参以及畸变参数
FileStorage fs(intrinsicsPath, FileStorage::READ);
Mat cameraMatrix, distCoeffs;
fs["camera_matrix"] >> cameraMatrix;
fs["distortion_coefficients"] >> distCoeffs;
//利用函数solvePnp可以计算出棋盘对应图片1和图片2的旋转矩阵和平移矩阵
Mat rvec1, tvec1;
solvePnP(objectPoints, corners1, cameraMatrix, distCoeffs, rvec1, tvec1);
Mat rvec2, tvec2;
solvePnP(objectPoints, corners2, cameraMatrix, distCoeffs, rvec2, tvec2);
//! [compute-poses]
//将结果显示在图片上
Mat img1_copy_pose = img1.clone(), img2_copy_pose = img2.clone();
Mat img_draw_poses;
drawFrameAxes(img1_copy_pose, cameraMatrix, distCoeffs, rvec1, tvec1, 2 * squareSize);
drawFrameAxes(img2_copy_pose, cameraMatrix, distCoeffs, rvec2, tvec2, 2 * squareSize);
hconcat(img1_copy_pose, img2_copy_pose, img_draw_poses);
imshow("Chessboard poses", img_draw_poses);
//! [compute-camera-displacement]
Mat R1, R2;
Rodrigues(rvec1, R1);
Rodrigues(rvec2, R2);
//根据R1,R2计算图片1至图片2的旋转,平移矩阵
Mat R_1to2, t_1to2;
computeC2MC1(R1, tvec1, R2, tvec2, R_1to2, t_1to2);
Mat rvec_1to2;
Rodrigues(R_1to2, rvec_1to2);
//! [compute-camera-displacement]
//! [compute-plane-normal-at-camera-pose-1]
Mat normal = (Mat_<double>(3, 1) << 0, 0, 1);
Mat normal1 = R1 * normal;
//! [compute-plane-normal-at-camera-pose-1]
//! [compute-plane-distance-to-the-camera-frame-1]
Mat origin(3, 1, CV_64F, Scalar(0));
Mat origin1 = R1 * origin + tvec1;
double d_inv1 = 1.0 / normal1.dot(origin1);
//! [compute-plane-distance-to-the-camera-frame-1]
//! [compute-homography-from-camera-displacement]
Mat homography_euclidean = computeHomography(R_1to2, t_1to2, d_inv1, normal1);
Mat homography = cameraMatrix * homography_euclidean * cameraMatrix.inv();
homography /= homography.at<double>(2, 2);
// homography_euclidean 就是根据公式(3)计算得到H
homography_euclidean /= homography_euclidean.at<double>(2, 2);
//! [compute-homography-from-camera-displacement]
//Same but using absolute camera poses instead of camera displacement, just for check
Mat homography_euclidean2 = computeHomography(R1, tvec1, R2, tvec2, d_inv1, normal1);
Mat homography2 = cameraMatrix * homography_euclidean2 * cameraMatrix.inv();
homography_euclidean2 /= homography_euclidean2.at<double>(2, 2);
homography2 /= homography2.at<double>(2, 2);
cout << "\nEuclidean Homography:\n" << homography_euclidean << endl;
cout << "Euclidean Homography 2:\n" << homography_euclidean2 << endl << endl;
//直接采用函数findHomography的计算结果
undistortPoints(corners1, corners1_undist, cameraMatrix, distCoeffs);
undistortPoints(corners2, corners2_undist, cameraMatrix, distCoeffs);
Mat H = findHomography(corners1_undist, corners2_undist);
cout << "\nfindHomography H_undist:\n" << H << endl;
return 1;
}
//! [compute-homography]
Mat computeHomography(const Mat &R_1to2, const Mat &tvec_1to2, const double d_inv, const Mat &normal)
{
Mat homography = R_1to2 + d_inv * tvec_1to2*normal.t();
return homography;
}
//! [compute-homography]
Mat computeHomography(const Mat &R1, const Mat &tvec1, const Mat &R2, const Mat &tvec2,
const double d_inv, const Mat &normal)
{
Mat homography = R2 * R1.t() + d_inv * (-R2 * R1.t() * tvec1 + tvec2) * normal.t();
return homography;
}
//! [compute-c2Mc1]
void computeC2MC1(const Mat &R1, const Mat &tvec1, const Mat &R2, const Mat &tvec2,
Mat &R_1to2, Mat &tvec_1to2)
{
//c2Mc1 = c2Mo * oMc1 = c2Mo * c1Mo.inv()
R_1to2 = R2 * R1.t();
tvec_1to2 = R2 * (-R1.t()*tvec1) + tvec2;
}
计算结果:
以下是3D坐标轴绘图:
b)图像平面间的转换矩阵H:
基于针孔模型: 对于相机坐标平面上的一个点对应的图像像素平面
有:
(4)
其中K是相机内参矩阵,R是旋转矩阵,t是平移矩阵;
结合H的定义:,左乘
,即:
则对于图像像素坐标系下的H:
其中H'就是相机坐标系下的单应矩阵。
实际运用中,如果只需要求解图像坐标系下的相关内容,则直接利用H即可,而如果需要求解实际世界坐标系下的相关内容就需要根据H,以及相机内参K,求解H',进而求解相关的R,t等。
Opencv中分解H获取R t的计算过程就是如此,这个下次分析。
对应的程式资源: