极线的绘制(已知相机的内外参数,极线几何)
但是在具体的应用中,**常常拿极限约束来判断相机的位姿是否准确,对于这个介绍相对较少。**本博客将介绍在一直相机的内外参数的情况下,怎么完成相机的极限约束。假设中左边的相机为世界坐标系。上述是假设两个相机之前的关系。其中世界坐标系为左边相机的坐标系。计算得到的第二个相机的参数(它是相机位姿态,它是指的是在第一个坐标系下的。是相机内参数,它表示从相机坐标系转换为图像坐标系上。基于极限的几何关系,需要
极线几何与立体视觉
目标:
极限几何的约束网上有很多资料,但是大多数是理论知识和一些推导。但是在具体的应用中,**常常拿极限约束来判断相机的位姿是否准确,对于这个介绍相对较少。**本博客将介绍在一直相机的内外参数的情况下,怎么实现两个相机的极限约束。
双目视觉对应关系,同时也可用于相邻两帧间的运动估计。见下图:
配点必须在极线上
基线:左右像机光心连线(黄色线段O1O2O_1O_2O1O2);
极平面:空间点,两像机光心决定的平面(灰色平面O1PO2O_1PO_2O1PO2);
极点:基线与两摄像机图像平面的交点;
极线:极平面与图像平面交线(蓝色线段)
基于极限的几何关系,需要知道它们的参数关系如下。
两个相机的转化为R,TR,TR,T.
如果知道两个相机的内外参数。可以计算它们的转化矩阵。假设中左边的相机为世界坐标系。它们可以表示为:
上述是假设两个相机之前的关系。其中世界坐标系为左边相机的坐标系。它们重合。因此通过R,TR,TR,T计算得到的第二个相机的参数(它是相机位姿态,它是指的是在第一个坐标系下的R,TR,TR,T),
具体的计算可以参考如下。
p2p_2p2坐标点(原本在O2O_2O2坐标系)转化为在O1O_1O1坐标系p1p_1p1的(p1p_1p1表示它在O1O_1O1坐标系下的坐标)坐标为:
p1=Rp2+T=>p2=RTp1−RTT (1) p_1=Rp_2+T=>p_2=R^Tp_1-R^TT\space \space\space \space (1) p1=Rp2+T=>p2=RTp1−RTT (1)
上述可以得到,从O1O_1O1坐标系转换到O2O_2O2坐标系为:
[RT −RTT][R^T \space \space -R^TT][RT −RTT]
因此两个相互转换的矩阵为:
[R,T][R,T][R,T]:表示从O2O_2O2坐标系到O1O_1O1坐标系的转换。
[RT −RTT][R^T \space \space -R^TT][RT −RTT]:表示从O1O_1O1坐标系到O2O_2O2坐标系的转换。
最后加上相机的内参矩阵K,K′K,K'K,K′可以得到整体的矩阵转换如下:
M=K[I 0], M′=K′[RT −RTT] 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′为单位矩阵III。因此
M=[I 0], M′=[RT −RTT] M=[I \space \space 0] ,\space \space M'=[R^T \space \space -R^TT] M=[I 0], M′=[RT −RTT]
从图二可以知道p′p'p′在坐标系O1O_1O1的坐标为:Rp′+TRp'+TRp′+T,因为Rp′+TRp'+TRp′+T这个点在O1O_1O1坐标系的极线O1PO_1PO1P上,因为极限平面O1O2PO_1O_2PO1O2P是平面:
T×(Rp′+T)=T×(Rp′) (2) T\times(Rp'+T)=T\times(Rp')\space \space\space \space (2) T×(Rp′+T)=T×(Rp′) (2)
向量T×(Rp′)T\times(Rp')T×(Rp′)表示垂直于平面O1O2PO_1O_2PO1O2P
利用叉乘得到:
pT⋅[T×(Rp′)]=0 (3) p^T \cdot[T\times(Rp')]=0 \space \space\space \space (3) pT⋅[T×(Rp′)]=0 (3)
其中叉乘TTT可以表示为矩阵形式,一般表示为skew(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)写成矩阵的形式为:
pT⋅[T×(Rp′)]=0=>pT[T×]Rp′=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,因此:
pTEp′=0 (6) p^TEp'=0 \space \space\space \space (6) pTEp′=0 (6)
直线的表达为l⋅p=0l\cdot p=0l⋅p=0(在齐次坐标表示下)
可以把Ep′Ep'Ep′看做是直线的方程,ppp看做是直线上的点,也就是说Ep′Ep'Ep′就是以O1O_1O1为原点坐标系中的极线了(图一O1eO_1eO1e线段),反之也可。
上述都是将K,K′K,K'K,K′设置为单位矩阵。当不再是单位矩阵的时候。得到矩阵为”
M=K[I 0], M′=K′[RT −RTT] (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′是相机内参数,它表示从相机坐标系转换为图像坐标系上。因此:pI=K−1pp_I=K^{-1}ppI=K−1p; pI′=K′−1p′p_I'=K'^{-1}p'pI′=K′−1p′
因为公式:
pTEp′=0 (6) p^TEp'=0 \space \space\space \space (6) pTEp′=0 (6)
将pI=K−1pp_I=K^{-1}ppI=K−1p; pI′=K′−1p′p_I'=K'^{-1}p'pI′=K′−1p′ 带入(6)(6)(6)得到:
pTK−T[T×]RK′−1p′=0 (8) p^TK^{-T}[T_{\times}]RK'^{-1}p'=0 \space \space\space \space (8) pTK−T[T×]RK′−1p′=0 (8)
将F=K−T[T×]RK′−1F=K^{-T}[T_{\times}]RK'^{-1}F=K−T[T×]RK′−1,这就是Fundamental Matrix了。也是opencv里面计算的Fundametal矩阵
上述都是将第一个相机设置为和世界坐标系一致。但是在一直两个相机的情况下。可能需要计算R,TR,TR,T矩阵。它们是:表示从O2O_2O2坐标系到O1O_1O1坐标系的转换。
已知两个相机(都是相机的外参)为:
M1=[R1T101]M2=[R2T201] M_1=\begin{bmatrix} R_1 & T_1 \\ 0 & 1 \end{bmatrix} \\ \\ \\ M_2=\begin{bmatrix} R_2 & T_2 \\ 0 & 1 \end{bmatrix} M1=[R10T11]M2=[R20T21]
需要计算从O2O_2O2坐标系到O1O_1O1坐标系的转换的矩阵R,TR,TR,T
推导:
假设两个对应点在各自的相机坐标系为p1p_1p1,p2p_2p2
p1p_1p1在世界坐标系的顶点为
pw=M1p1 (9) p_w=M_1p_1 \space \space\space \space (9) pw=M1p1 (9)
p2p_2p2在世界坐标系的顶点为
pw=M2p2 (10) p_w=M_2p_2 \space \space\space \space (10) pw=M2p2 (10)
(9)(10)(9)(10)(9)(10)公式得到:
p1=M1−1M2p2 p_1=M^{-1}_1M_2p_2 p1=M1−1M2p2
其中R,TR,TR,T为M1−1M2M^{-1}_1M_2M1−1M2旋转矩阵和平移向量。
代码如下:
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);
}
测试的结果如下:
更多推荐
所有评论(0)