极线的绘制(已知相机的内外参数,极线几何)

极线几何与立体视觉
目标:
极限几何的约束网上有很多资料,但是大多数是理论知识和一些推导。但是在具体的应用中,**常常拿极限约束来判断相机的位姿是否准确,对于这个介绍相对较少。**本博客将介绍在一直相机的内外参数的情况下,怎么实现两个相机的极限约束。

双目视觉对应关系,同时也可用于相邻两帧间的运动估计。见下图:
在这里插入图片描述
配点必须在极线上
基线:左右像机光心连线(黄色线段 O 1 O 2 O_1O_2 O1O2);
极平面:空间点,两像机光心决定的平面(灰色平面 O 1 P O 2 O_1PO_2 O1PO2);
极点:基线与两摄像机图像平面的交点;
极线:极平面与图像平面交线(蓝色线段)

基于极限的几何关系,需要知道它们的参数关系如下。
两个相机的转化为 R , T R,T R,T.
如果知道两个相机的内外参数。可以计算它们的转化矩阵。假设中左边的相机为世界坐标系。它们可以表示为:
在这里插入图片描述

上述是假设两个相机之前的关系。其中世界坐标系为左边相机的坐标系。它们重合。因此通过 R , T R,T R,T计算得到的第二个相机的参数(它是相机位姿态,它是指的是在第一个坐标系下的 R , T R,T R,T),
具体的计算可以参考如下。
p 2 p_2 p2坐标点(原本在 O 2 O_2 O2坐标系)转化为在 O 1 O_1 O坐标系 p 1 p_1 p的( p 1 p_1 p1表示它在 O 1 O_1 O1坐标系下的坐标)坐标为:
p 1 = R p 2 + T = > p 2 = R T p 1 − R T T      ( 1 ) p_1=Rp_2+T=>p_2=R^Tp_1-R^TT\space \space\space \space (1) p=Rp+T=>p=RTpRTT    (1)
上述可以得到,从 O 1 O_1 O坐标系转换到 O 2 O_2 O坐标系为:
[ R T    − R T T ] [R^T \space \space -R^TT] [RT  RTT]

因此两个相互转换的矩阵为:
[ R , T ] [R,T] [R,T]表示从 O 2 O_2 O坐标系到 O 1 O_1 O坐标系的转换
[ R T    − R T T ] [R^T \space \space -R^TT] [RT  RTT]表示从 O 1 O_1 O坐标系到 O 2 O_2 O坐标系的转换

最后加上相机的内参矩阵 K , K ′ K,K' K,K可以得到整体的矩阵转换如下:

M = K [ I    0 ] ,    M ′ = K ′ [ R T    − R T T ] M=K[I \space \space 0] ,\space \space M'=K'[R^T \space \space -R^TT] M=K[I  0],  M=K[RT  RTT]

为了方便计算,假设相机的内参矩阵 K , K ′ K,K' K,K为单位矩阵 I I I。因此
M = [ I    0 ] ,    M ′ = [ R T    − R T T ] M=[I \space \space 0] ,\space \space M'=[R^T \space \space -R^TT] M=[I  0],  M=[RT  RTT]

从图二可以知道 p ′ p' p在坐标系 O 1 O_1 O1的坐标为: R p ′ + T Rp'+T Rp+T,因为 R p ′ + T Rp'+T Rp+T这个点在 O 1 O_1 O1坐标系的极线 O 1 P O_1P O1P上,因为极限平面 O 1 O 2 P O_1O_2P O1O2P是平面:
T × ( R p ′ + T ) = T × ( R p ′ )      ( 2 ) T\times(Rp'+T)=T\times(Rp')\space \space\space \space (2) T×(Rp+T)=T×(Rp)    (2)
向量 T × ( R p ′ ) T\times(Rp') T×(Rp)表示垂直于平面 O 1 O 2 P O_1O_2P O1O2P
利用叉乘得到:
p T ⋅ [ T × ( R p ′ ) ] = 0      ( 3 ) p^T \cdot[T\times(Rp')]=0 \space \space\space \space (3) pT[T×(Rp)]=0    (3)
其中叉乘 T T 可以表示为矩阵形式,一般表示为 s k e w ( T ) skew(T) skew(T)或者 [ T × ] [T_{\times}] [T×]
[ T × ] = [ 0 − T [ 2 ] T [ 1 ] T [ 2 ] 0 − T [ 0 ] − T [ 1 ] T [ 0 ] 0 ]      ( 4 ) [T_{\times}]=\begin{bmatrix} 0 & -T[2] & T[1] \\ T[2] & 0 & -T[0] \\ -T[1] & T[0] & 0 \end{bmatrix} \space \space\space \space (4) [T×]=0T[2]T[1]T[2]0T[0]T[1]T[0]0    (4)
公式 ( 3 ) (3) (3)写成矩阵的形式为:
p T ⋅ [ T × ( R p ′ ) ] = 0 = > p T [ T × ] R p ′ = 0      ( 5 ) p^T \cdot[T\times(Rp')]=0=>p^T[T_{\times}]Rp'=0 \space \space\space \space (5) pT[T×(Rp)]=0=>pT[T×]Rp=0    (5)
从公式 ( 5 ) (5) (5)得到 [ T × ] R [T_{\times}]R [T×]R为Essential Matrix,这个矩阵写成: [ T × ] R = E [T_{\times}]R=E [T×]R=E,因此:
p T E p ′ = 0      ( 6 ) p^TEp'=0 \space \space\space \space (6) pTEp=0    (6)
直线的表达为 l ⋅ p = 0 l\cdot p=0 lp=0(在齐次坐标表示下)
可以把 E p ′ Ep' Ep看做是直线的方程, p p p看做是直线上的点,也就是说 E p ′ Ep' Ep就是以 O 1 O_1 O1为原点坐标系中的极线了(图一 O 1 e O_1e O1e线段),反之也可。

上述都是将 K , K ′ K,K' K,K设置为单位矩阵。当不再是单位矩阵的时候。得到矩阵为”
M = K [ I    0 ] ,    M ′ = K ′ [ R T    − R T T ]      ( 7 ) M=K[I \space \space 0] ,\space \space M'=K'[R^T \space \space -R^TT]\space \space\space \space (7) M=K[I  0],  M=K[RT  RTT]    (7)
因为 K , K ′ K,K' K,K是相机内参数,它表示从相机坐标系转换为图像坐标系上。因此: p I = K − 1 p p_I=K^{-1}p pI=K1p; p I ′ = K ′ − 1 p ′ p_I'=K'^{-1}p' pI=K1p
因为公式:
p T E p ′ = 0      ( 6 ) p^TEp'=0 \space \space\space \space (6) pTEp=0    (6)
p I = K − 1 p p_I=K^{-1}p pI=K1p; p I ′ = K ′ − 1 p ′ p_I'=K'^{-1}p' pI=K1p 带入 ( 6 ) (6) (6)得到:

p T K − T [ T × ] R K ′ − 1 p ′ = 0      ( 8 ) p^TK^{-T}[T_{\times}]RK'^{-1}p'=0 \space \space\space \space (8) pTKT[T×]RK1p=0    (8)
F = K − T [ T × ] R K ′ − 1 F=K^{-T}[T_{\times}]RK'^{-1} F=KT[T×]RK1,这就是Fundamental Matrix了。也是opencv里面计算的Fundametal矩阵

上述都是将第一个相机设置为和世界坐标系一致。但是在一直两个相机的情况下。可能需要计算 R , T R,T R,T矩阵。它们是:表示从 O 2 O_2 O坐标系到 O 1 O_1 O坐标系的转换
已知两个相机(都是相机的外参)为:
M 1 = [ R 1 T 1 0 1 ] M 2 = [ R 2 T 2 0 1 ] M_1=\begin{bmatrix} R_1 & T_1 \\ 0 & 1 \end{bmatrix} \\ \\ \\ M_2=\begin{bmatrix} R_2 & T_2 \\ 0 & 1 \end{bmatrix} M=[R10T11]M2=[R20T21]
需要计算从 O 2 O_2 O坐标系到 O 1 O_1 O坐标系的转换的矩阵 R , T R,T R,T
推导:
假设两个对应点在各自的相机坐标系为 p 1 p_1 p1, p 2 p_2 p2
p 1 p_1 p1在世界坐标系的顶点为
p w = M 1 p 1      ( 9 ) p_w=M_1p_1 \space \space\space \space (9) pw=M1p1    (9)
p 2 p_2 p2在世界坐标系的顶点为
p w = M 2 p 2      ( 10 ) p_w=M_2p_2 \space \space\space \space (10) pw=M2p2    (10)
( 9 ) ( 10 ) (9)(10) (9)(10)公式得到:
p 1 = M 1 − 1 M 2 p 2 p_1=M^{-1}_1M_2p_2 p=M11M2p2
其中 R , T R,T R,T M 1 − 1 M 2 M^{-1}_1M_2 M11M2旋转矩阵和平移向量。
代码如下:

void RunTwoImagesCorrspondEpilinesWithPath(int src_idx, int tgt_idx, const std::string& dir)
{
    
    
	std::cerr << "the cams num: " << cams_modes_.size() << std::endl;
	if (src_idx < 0 || tgt_idx < 0 ||
		src_idx >= cams_modes_.size() || tgt_idx >= cams_modes_.size())
	{
    
    
		std::cerr << "RunTwoImagesCorrespondEpilines invalid idx..." << std::endl;
		return;
	}
	if (!cams_modes_[src_idx].valid_ || !cams_modes_[tgt_idx].valid_)
	{
    
    
		std::cerr << "RunTwoImagesCorrespondEpilines invalid cams existed..." << std::endl;
		return;
	}
	CameraModel src_cam_model = cams_modes_[src_idx];
	CameraModel tgt_cam_model = cams_modes_[tgt_idx];
	std::string src_name = images_files_path_[src_idx];
	std::string tgt_name = images_files_path_[tgt_idx];
	std::cerr << "src_name: " << src_name << std::endl;
	std::cerr << "tgt_name: " << tgt_name << std::endl;
	std::string src_file_name = src_name.substr(src_name.find_last_of("/") + 1, src_name.find_last_of(".") - src_name.find_last_of("/") - 1);
	std::string tgt_file_name = tgt_name.substr(tgt_name.find_last_of("/") + 1, tgt_name.find_last_of(".") - tgt_name.find_last_of("/") - 1);
	cv::Mat src_rgb = cv::imread(src_name);
	cv::Mat tgt_rgb = cv::imread(tgt_name);
	//cv::imshow("tgt_rgb", tgt_rgb);
	//cv::waitKey(0);
	// used in OpenCV3
	cv::Ptr<cv::FeatureDetector> detector = cv::ORB::create();
	cv::Ptr<cv::DescriptorExtractor> descriptor = cv::ORB::create();
	std::vector< cv::KeyPoint > src_kp, tgt_kp;
	detector->detect(src_rgb, src_kp);
	detector->detect(tgt_rgb, tgt_kp);
	// ¼ÆËãÃèÊö×Ó
	cv::Mat src_desp, tgt_desp;
	descriptor->compute(src_rgb, src_kp, src_desp);
	descriptor->compute(tgt_rgb, tgt_kp, tgt_desp);
	// Æ¥ÅäÃèÊö×Ó
	std::vector< cv::DMatch > matches;
	cv::BFMatcher matcher;
	matcher.match(src_desp, tgt_desp, matches);
	cout << "Find total " << matches.size() << " matches." << endl;
	if (matches.size() < match_pnts_num_)
	{
    
    
		std::cerr << "match num is too much fewer..." << std::endl;
		return;
	}
	// ɸѡƥÅä¶Ô
	std:vector< cv::DMatch > goodMatches;
	double minDis = 9999;
	for (size_t i = 0; i < matches.size(); i++)
	{
    
    
		if (matches[i].distance < minDis)
			minDis = matches[i].distance;
	}
	for (size_t i = 0; i < matches.size(); i++)
	{
    
    
		if (matches[i].distance < 10 * minDis)
			goodMatches.push_back(matches[i]);
	}
	std::vector< cv::Point2f > src_pts, tgt_pts;
	for (size_t i = 0; i<goodMatches.size(); i++)
	{
    
    
		src_pts.push_back(src_kp[goodMatches[i].queryIdx].pt);
		tgt_pts.push_back(tgt_kp[goodMatches[i].trainIdx].pt);
	}
	//通过两个相机的位姿计算它们之间的e_matrix
	Eigen::Matrix4f src_pose = src_cam_model.cam_pose_;
	Eigen::Matrix4f tgt_pose = tgt_cam_model.cam_pose_;
	Eigen::Matrix4f src_extr = src_pose.inverse();
	Eigen::Matrix4f tgt_extr = tgt_pose.inverse();
	Eigen::Matrix4f relative_extr = tgt_extr*src_pose;
	Eigen::Matrix3f r_extr = relative_extr.topLeftCorner(3, 3);
	Eigen::Vector3f t_extr = relative_extr.topRightCorner(3, 1);
	//
	Eigen::Matrix3f em = ComputeCrossMatrixFromLVector(t_extr)*r_extr;
	//已知e_matrix,计算fundamental_matrix
	Eigen::Matrix3f src_km = Eigen::Matrix3f::Identity();
	Eigen::Matrix3f tgt_km = Eigen::Matrix3f::Identity();	
	src_km(0, 0) = src_cam_model.fx_;
	src_km(1, 1) = src_cam_model.fy_;
	src_km(0, 2) = src_cam_model.cx_;
	src_km(1, 2) = src_cam_model.cy_;
	tgt_km(0, 0) = src_cam_model.fx_;
	tgt_km(1, 1) = src_cam_model.fy_;
	tgt_km(0, 2) = src_cam_model.cx_;
	tgt_km(1, 2) = src_cam_model.cy_;
	Eigen::Matrix3f fm = (tgt_km.inverse()).transpose()*em*src_km.inverse();
	std::cerr << "fm: \n" << fm << std::endl;
	Eigen::Matrix3f fmt = fm;
	//²âÊÔ
	cv::Mat fundm = (cv::Mat_<float>(3, 3)
		<< fmt(0, 0), fmt(0, 1), fmt(0, 2),
		fmt(1, 0), fmt(1, 1), fmt(1, 2),
		fmt(2, 0), fmt(2, 1), fmt(2, 2));
	std::cerr << fundm << std::endl;
	std::vector<cv::Vec<float, 3>> epilines1, epilines2;
	computeCorrespondEpilines(src_pts, 1, fundm, epilines1);
	computeCorrespondEpilines(tgt_pts, 2, fundm, epilines2);
	cv::RNG &rng = cv::theRNG();
	for (int i = 0; i < match_pnts_num_; ++i) {
    
    
		//Ëæ»ú²úÉúÑÕÉ«
		std::cerr << "epilines1: " << i << ": " << epilines1[i] << std::endl;
		std::cerr << "epilines2: " << i << ": " << epilines2[i] << std::endl;
		cv::Scalar color = cv::Scalar(rng(255), rng(255), rng(255));
		circle(src_rgb, src_pts[i], 5, color, 3);
		//»æÖÆÍ⼫ÏßµÄʱºò£¬Ñ¡ÔñÁ½¸öµã£¬Ò»¸öÊÇx=0´¦µÄµã£¬Ò»¸öÊÇxΪͼƬ¿í¶È´¦
		line(src_rgb, cv::Point(0, -epilines2[i][2] / epilines2[i][1]), cv::Point(src_rgb.cols, -(epilines2[i][2] + epilines2[i][0] * src_rgb.cols) / epilines2[i][1]), color);
		circle(tgt_rgb, tgt_pts[i], 5, color, 3);
		line(tgt_rgb, cv::Point(0, -epilines1[i][2] / epilines1[i][1]), cv::Point(tgt_rgb.cols, -(epilines1[i][2] + epilines1[i][0] * tgt_rgb.cols) / epilines1[i][1]), color);
	}
	std::string abs_src_file_name = dir + "/" + src_file_name + ".png";
	std::string abs_tgt_file_name = dir + "/" + tgt_file_name + ".png";
	std::cerr << "abs_src_file_name: " << abs_src_file_name << std::endl;
	std::cerr << "abs_tgt_file_name: " << abs_tgt_file_name << std::endl;
	//std::system("pause");
	//cv::imshow("epiline1", tgt_rgb);
	//cv::waitKey(0);
	//std::cerr << "tgt_rgb rows, cols: " << tgt_rgb.rows << ", " << tgt_rgb.cols << std::endl;
	//std::system("pause");
	cv::imwrite(abs_tgt_file_name, tgt_rgb);
	//std::system("pause");
	//cv::imshow("epiline2", src_rgb);
	cv::imwrite(abs_src_file_name, src_rgb);
	cv::waitKey(0);
}

测试的结果如下:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_43851636/article/details/126783484