SLAM练习题(七)—— 对极约束

SLAM 学习笔记

对极约束

以下题目来自计算机视觉life从零开始一起学习SLAM系列
题目: 证明:对三维列向量下面等式恒成立。其中等式左边 X 表示叉乘,等式右边上三角符号表示反对称矩阵。
a ⃗ × b ⃗ = a ⃗ ∧ b ⃗ \vec{a} \times \vec{b}=\vec{a}^{\wedge} \vec{b} a ×b =a b
在这里插入图片描述

这个证明还是比较简单的,利用叉乘和^ 的定义即可。

讨论

对极约束的推导:

在极平面中:

在这里插入图片描述

由于叉乘只在三维空间里有定义,所以把二维点p0,p1看成三维的方向向量来考虑。设归一化平面的焦距为1,则:

在这里插入图片描述

在C0坐标系下, R p 1 Rp_1 Rp1 是p1 在以C0为原点的C0坐标系下的方向向量。所以上述结论可化为:
在这里插入图片描述
该式子即为对极约束

极线方程:

对极约束中,把中间部分拿出来,记为本质矩阵或本征矩阵(Essential Matrix)

E = t ∧ R E=t^{\wedge} R E=tR

所以上述结论可化为:

p 0 T E p 1 = 0 \mathbf{p}_{0}^{T} \mathbf{E} \mathbf{p}_{1}=0 p0TEp1=0

又因为点p在直线l上的充分必要条件就是 直线l 的系数与p的齐次坐标p’的内积为0,所以E*p1就可以看成直线的方程,该直线就是极线,如下图的红线:
在这里插入图片描述

问:为什么Ep1就一定是极线?p0可以在很多条线上,E不是单单为了简化公式而引入的吗?

书上的公式推导是严格证明,这里不是严格的证明,是从几何关系来理解对极约束和极线。这里讨论的前提就是C0,C1p构成的极平面和成像平面切割。这里有个隐含条件就是p0,p1的来源,他们是同一个空间点在两个相机里的成像点。C0,C1,p这个平面和左右两个成像平面的交线就是极线。

???再仔细品品

实践

题目: 现有一个运动着的相机拍摄的连续两张图片,其中特征点匹配部分已经完成。请根据两帧图像对应的匹配点计算基础矩阵,并利用该矩阵绘制出前10个特征点对应的极线。
代码框架下载 链接:https://pan.baidu.com/s/1NYeREXHgyuz-f46ASXUjzw 提取码:eiia
**知识点:**极点和对应极线
参考答案:

#include<iostream>
#include <vector>

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/calib3d/calib3d.hpp>

using namespace std;
using namespace cv;
int main( int argc, char** argv )
{

    Mat rgb1 = imread( "./rgb1.ppm");
    Mat rgb2 = imread( "./rgb2.ppm");

    Ptr<FeatureDetector> detector = ORB::create();
    Ptr<DescriptorExtractor> descriptor = ORB::create();
    
    vector< KeyPoint > kp1, kp2;
    detector->detect( rgb1, kp1 );
    detector->detect( rgb2, kp2 );

    // 计算描述子
    Mat desp1, desp2;
    descriptor->compute( rgb1, kp1, desp1 );
    descriptor->compute( rgb2, kp2, desp2 );

    // 匹配描述子
    vector< DMatch > matches;
    BFMatcher matcher;
    matcher.match( desp1, desp2, matches );
    cout<<"Find total "<<matches.size()<<" matches."<<endl;

    // 筛选匹配对
    vector< 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] );
    }


    vector< Point2f > pts1, pts2;
    for (size_t i=0; i<goodMatches.size(); i++)
    {
        pts1.push_back(kp1[goodMatches[i].queryIdx].pt);
        pts2.push_back(kp2[goodMatches[i].trainIdx].pt);
    }

    // 请先计算基础矩阵并据此绘制出前10个匹配点对应的对极线,可以调用opencv函数
    // ----------- 开始你的代码 --------------//
    Mat F =findFundamentalMat(pts1,pts2,CV_FM_8POINT);
    //计算对应点的外极线epilines是一个三元组(a,b,c),表示点在另一视图中对应的外极线ax+by+c=0;
    std::vector<Vec<float,3>> epilines1, epilines2;
    computeCorrespondEpilines(pts1,1,F,epilines1);
    computeCorrespondEpilines(pts2,2,F,epilines2);

    RNG rng;
    for (uint i=0;i<10;i++)
    {
        Scalar color =Scalar(rng(256),rng(256),rng(256));//产生随机颜色
        // 在视图2中把关键点用圆圈画出来,然后再绘制对应点处的外极线
        circle(rgb2,pts2[i],3,color,2);
        line (rgb2, Point(0, -epilines1[i][2] / epilines1[i][1]), Point(rgb2.cols, -
        (epilines1[i][2] + epilines1[i][0] * rgb2.cols) / epilines1[i][1]), color);
        //绘制外极线的时候,选择两个点,一个是x=0处的点,一个是x为图片宽度处,即由ax+by+c=0解出y=-(c+ax)/b,把x=0,x=rgb2.cols代入即可算出两点的坐标。
        circle(rgb1, pts1[i], 3, color, 2);
        line(rgb1, Point(0, -epilines2[i][2] / epilines2[i][1]), Point(rgb1.cols, -
(epilines2[i][2] + epilines2[i][0] * rgb1.cols) / epilines2[i][1]), color);
    }
    // ----------- 结束你的代码 --------------//
    imshow("epiline1", rgb2);
    imshow("epiline2", rgb1);
    waitKey(0);
    return 0;
}

程序运行结果:
在这里插入图片描述
附:CMakeLists.txt:

cmake_minimum_required( VERSION 2.8 )
project( epipolar )

set( CMAKE_CXX_FLAGS "-std=c++11" )

find_package( OpenCV REQUIRED )

include_directories( ${OpenCV_INCLUDE_DIRS} )

add_executable( epipolar epipolar.cpp )

target_link_libraries( epipolar ${OpenCV_LIBS} )

总结

六哥给的代码框架是OpenCV 2.4下的,我装的是OpenCV3.3.1的,有些函数还是有些区别:

// 特征提取器和描述子提取器的实例化方式 2.4版本中:
Ptr<FeatureDetector> detector;
Ptr<DescriptorExtractor> descriptor;
detector = FeatureDetector::create("ORB");
descriptor = DescriptorExtractor::create("ORB");

//在3.3.1版本中,应该这样写
Ptr<FeatureDetector> detector = ORB::create();
Ptr<DescriptorExtractor> descriptor = ORB::create();

还有就是如果要使用circle()和line()函数,这两个函数的引用在头文件#include<opencv2/imgproc/imgproc.hpp>中,引入该头文件即可。或者引入#include<opencv2/opencv.hpp>,这个头文件几乎把OpenCV所有的头文件都引入了,会降低加载速度。

除此之外,感觉是遇到了vscode代码提示的bug, circle()和line()两个函数可以查看注释,但是无法定位引用,编译倒是没问题,不影响大局,但是第一次碰到这个问题还是头疼了很久,也没解决。
参考:
从零开始一起学习SLAM | 不推公式,如何真正理解对极约束?

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

薛定猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值