视觉SLAM ch7代码总结(一)

目录

一、OpenCV的 ORB特征

二、手写ORB特征


一、OpenCV的 ORB特征

1.Keypoint类

opencv中Keypoint类的默认构造函数为:

CV_WRAP KeyPoint() : pt(0,0), size(0), angle(-1), response(0), octave(0), class_id(-1) {} 

pt(x,y):关键点的点坐标,是像素坐标

size():该关键点邻域直径大小;angle:角度,表示关键点的方向,值为[0,360),负值表示不使用。 response:响应强度,选择响应最强的关键点; octacv:从哪一层金字塔得到的此关键点。 class_id:当要对图片进行分类时,用class_id对每个关键点进行区分,默认为-1。

 2.Ptr智能指针模板类

Ptr  detector = ORB::create();  
//等价于
FeatureDetector * detector = ORB::create();
//创建一个Ptr类型的detector,用于接收ORB类中create()函数的返回值

Ptr是OpenCV中使用的智能指针模板类,可以轻松管理各种类型的指针。

 3.  ORB::create()函数

opencv中的ORB类是一个纯虚类,继承于Feature2D类,无法进行实例化创建对象。提供静态成员函数ORB::create()供调用。

static Ptr< ORB >   create (int nfeatures=500, float scaleFactor=1.2f, 
int nlevels=8, int edgeThreshold=31, int firstLevel=0, int WTA_K=2, 
int scoreType=ORB::HARRIS_SCORE, int patchSize=31, int fastThreshold=20)

nfeatures - 最多提取的特征点的数量;

scaleFactor - 金字塔图像之间的尺度参数;

nlevels – 高斯金字塔的层数;

edgeThreshold – 边缘阈值,这个值主要是根据后面的patchSize来定的,靠近边缘edgeThreshold以内的像素是不检测特征点的。

firstLevel - 第一层的索引值,这里默认为0。

WET_K - 用于产生BIREF描述子的 点对的个数,一般为2个,也可以设置为3个或4个,那么这时候描述子之间的距离计算就不能用汉明距离了,而是应该用一个变种。OpenCV中,如果设置WET_K = 2,则选用点对就只有2个点,匹配的时候距离参数选择NORM_HAMMING,如果WET_K设置为3或4,则BIREF描述子会选择3个或4个点,那么后面匹配的时候应该选择的距离参数为NORM_HAMMING2。

scoreType - 用于对特征点进行排序的算法,你可以选择HARRIS_SCORE,也可以选择FAST_SCORE,但是它也只是比前者快一点点而已。

patchSize – 用于计算BIREF描述子的特征点邻域大小。

 4. 特征检测器FeatureDetector 和 描述子提取器DescriptorExtractor类 和 描述子匹配器DescriptorMatcher

 特征检测器FeatureDetector是虚类,通过定义FeatureDetector的对象可以使用多种特征检测及匹配方法,通过create()函数调用。

class CV_EXPORTS FeatureDetector
{
public:
	virtual ~FeatureDetector();
	void detect( const Mat& image, vector& keypoints,
		const Mat& mask=Mat() ) const;
	void detect( const vector& images,
		vector >& keypoints,
		const vector& masks=vector() ) const;
	virtual void read(const FileNode&);
	virtual void write(FileStorage&) const;
	static Ptr create( const string& detectorType );
protected:
	...
};

描述子提取器DescriptorExtractor是提取关键点的描述子抽象基类。

class CV_EXPORTS DescriptorExtractor
{
public:
    virtual ~DescriptorExtractor();

    void compute( const Mat& image, vector& keypoints,
                  Mat& descriptors ) const;
    void compute( const vector& images, vector >& keypoints,
                  vector& descriptors ) const;

    virtual void read( const FileNode& );
    virtual void write( FileStorage& ) const;

    virtual int descriptorSize() const = 0;
    virtual int descriptorType() const = 0;

    static Ptr create( const string& descriptorExtractorType );

protected:
    ...
};

描述子匹配器DescriptorMatcher用于特征关键点描述子匹配的抽象基类.

有两类匹配任务:匹配两个图像之间的特征描述子,或者匹配一个图像与另外一个图像集的特征描述子,

"BruteForce-Hamming"表示使用汉明距离进行匹配。

class cv::DescriptorMatcher {
public:
	virtual void add(InputArrayOfArrays descriptors); // Add train descriptors
	virtual void clear(); // Clear train descriptors
	virtual bool empty() const; // true if no descriptors
	void train(); // Train matcher
	virtual bool isMaskSupported() const = 0; // true if supports masks
	const vector& getTrainDescriptors() const; // Get train descriptors
 
// methods to match descriptors from one list vs. "trained" set (recognition)
	void match(
		InputArray queryDescriptors,
		vector& matches,
		InputArrayOfArrays masks = noArray()
	);
	void knnMatch(
		InputArray queryDescriptors,
		vector< vector >& matches,
		int k,
		InputArrayOfArrays masks = noArray(),
		bool compactResult = false
	);
	void radiusMatch(
		InputArray queryDescriptors,
		vector< vector >& matches,
		float maxDistance,
		InputArrayOfArrays masks = noArray(),
		bool compactResult = false
	);
 
 
	// methods to match descriptors from two lists (tracking)
	//
	// Find one best match for each query descriptor
	void match(
		InputArray queryDescriptors,
		InputArray trainDescriptors,
		vector& matches,
		InputArray mask = noArray()
	) const;
	// Find k best matches for each query descriptor (in increasing order of distances)
	void knnMatch(
		InputArray queryDescriptors,
		InputArray trainDescriptors,
		vector< vector >& matches,
		int k,
		InputArray mask = noArray(),
		bool compactResult = false
	) const;
	// Find best matches for each query descriptor with distance less than maxDistance
	void radiusMatch(
		InputArray queryDescriptors,
		InputArray trainDescriptors,
		vector< vector >& matches,
		float maxDistance,
		InputArray mask = noArray(),
		bool compactResult = false
	) const;
	virtual void read(const FileNode&); // Reads matcher from a file node
	virtual void write(FileStorage&) const; // Writes matcher to a file storage
	virtual cv::Ptr clone(
		bool emptyTrainData = false
	) const = 0;
	static cv::Ptr create(
		const string& descriptorMatcherType
	);
	...
};

 每个函数及其参数的意思直接看文档吧。重点看下create()和match()即可

4.ORB特征检测与匹配步骤如下:

第一步: 检测Oriented FAST角点位置,使用detect()函数

CV_WRAP virtual void detect( InputArray image,
                                 CV_OUT std::vector& keypoints,
                                 InputArray mask=noArray() );

detect()方法的原型参数:需要检测的图像,关键点数组,第三个参数为默认值

第二步:根据角点位置计算BRIEF描述子 ,使用compute()函数

CV_WRAP virtual void compute( InputArray image,
                                  CV_OUT CV_IN_OUT std::vector& keypoints,
                                  OutputArray descriptors );

compute()原型参数:图像,图像的关键点数组,Mat类型的描述子

 关于使用该函数得到的描述子为什么是Mat矩阵?

用矩阵用来存储n行(假设有n个特征点)32列(ORB特征)的描述子,即每行代表一个特征点的描述子。 

第三步:定义输出标记关键点的图像,使用drawKeypoints()函数

CV_EXPORTS_W void drawKeypoints( InputArray image,
                                     const std::vector& keypoints,
                                     InputOutputArray outImage,
                                     const Scalar& color=Scalar::all(-1),
                                     int flags=DrawMatchesFlags::DEFAULT );

drawKeypoints()函数原型参数:原图,原图关键点,带有关键点的输出图像,后面两个为默认值

后两个参数 

第四步:对两幅图像中的BRIEF描述子进行匹配,使用match()函数

void DescriptorMatcher::match(
                    const Mat& queryDescriptors,
                    const Mat& trainDescriptors, 
                    vector& matches, 
                    const Mat& mask=Mat() ) const

queryDescriptors – 特征描述子查询集.

trainDescriptors – 待训练的特征描述子集. 

matches –匹配点数组,元素类型为DMatch

mask – 特定的在输入查询和训练特征描述子集之间的Mask permissible匹配.

DMatch类  (在OpenCV2.x中Dmatch为结构体数据类型,在OpenCV3.x中为Class类型)

class CV_EXPORTS_W_SIMPLE DMatch
{
public:
    CV_WRAP DMatch();
    CV_WRAP DMatch(int _queryIdx, int _trainIdx, float _distance);
    CV_WRAP DMatch(int _queryIdx, int _trainIdx, int _imgIdx, float _distance);

    CV_PROP_RW int queryIdx; // query descriptor index
    CV_PROP_RW int trainIdx; // train descriptor index
    CV_PROP_RW int imgIdx;   // train image index

    CV_PROP_RW float distance;

    // less is better
    bool operator<( const DMatch &m ) const
    {
        return distance < m.distance;
    }
};

前三行是构造函数,

int queryIdx –>query描述子下标,即match函数中位于前面的描述子

int trainIdx –>  train描述子下标,match函数中位于后面的描述子

int imgIdx –>训练图像的索引(若有多个才有用)

float distance –>两个特征向量之间的欧氏距离,越小表明匹配度越高。

最后,是一个小于操作符的重载,用于比较和排序。 比较的是上述的distance,

queryIdx是图1中匹配的关键点的对应编号

trainIdx是图2中匹配的关键点的对应编号

 画出所有的匹配结果,使用drawMatches()函数

CV_EXPORTS_W void drawMatches( InputArray img1,
                                   const std::vector& keypoints1,
                                   InputArray img2,
                                   const std::vector& keypoints2,
                                   const std::vector& matches1to2,
                                   InputOutputArray outImg,
                                   const Scalar& matchColor=Scalar::all(-1),
                                   const Scalar& singlePointColor=Scalar::all(-1),
                                   const std::vector& matchesMask=std::vector(),
                                   int flags=DrawMatchesFlags::DEFAULT );

drawMatches()原型参数,简单用法就是:图1,图1关键点,图2,图2关键点,两张图片的关键点匹配数组(DMatch类型),承接图像,后面参数有默认值。 

总结一下:到这里位置所有的匹配结果就已经完成从了,但是有大量的误匹配,因此需要对匹配结果进行筛选。

第五步:对匹配点进行筛选

当描述子之间的距离大于两倍的最小距离认为匹配有误;如果最小距离非常小也不可以,30是下限。

然后就是画出筛选后的匹配图片,同样使用drawMatches()函数



minmax_element()  是返回指向范围内最小和最大元素的一对迭代器。

default (1)      template 
                         pair
                           minmax_element (ForwardIterator first, ForwardIterator last);

custom (2)      template 
                         pair
                  minmax_element (ForwardIterator first, ForwardIterator last, Compare comp);

 第二个是经常用到的,

first:一个输入迭代器,指示要比较的范围的第一个位置。

last:一个输入迭代器,指示要比较的范围中过去的最后一个元素。

comp: 一个用户定义的二元谓词函数,它接受两个参数,如果两个参数按顺序返回真,否则返回假。它遵循严格的弱排序来对元素进行排序。

 它将较小的值作为第一个元素返回(first表示),较大的值作为的第二个元素返回(second表示)。

Lambda表达式(匿名函数)  c++11特性

[外部变量访问方式说明符] (参数表) -> 返回值类型
{
   语句块
}

“外部变量访问方式说明符” 可以是 或 &,表示{}中用到的、定义在{}外面的变量在{}中是否允许被改变。表示不允许,&表示允许。当然,在{}中也可以不使用定义在外面的变量。  “-> 返回值类型”可以省略。

int a[4] = {2, 11, 444, 3};
sort(a, a+4, [=](int x, int y) -> bool { return x%10 < y%10; } );
for_each(a, a+4, [=](int x) { cout << x << " ";} );

输出结果是 11 2 3 444

程序第 2 行使得数组 a 按个位数从小到大排序;

for_each 的第 3 个参数是一个 Lambda 表达式。for_each 执行过程中会依次以每个元素作为参数调用它,因此每个元素都被输出。

匿名函数详解

 总结一下:minmax_element()实际上是对描述子之间的距离进行了排序。

二、手写ORB特征

写这段代码前建议先看下164页的一些总结,先了解下使用OpenCV和手写的区别。

OpenCV中描述子使用Mat来存储的,这里使用256位的二进制描述,即对应到8个32位的unsigned int 数据,用typedef 将它表示成DescType。

ORB特征点的计算使用FAST()函数,描述子的计算是自定义函数ComputeORB(),描述子的匹配也是自定义函数BfMatch(),在这个函数中使用了SSE指令集中的_mm_popcnt_u32函数计算一个unsigned int 变量中的一个数,从而达到汉明距离的效果。

1.检测FAST关键点使用 cv::FAST()函数  

CV_EXPORTS void FAST( InputArray image, CV_OUT std::vector& keypoints,
                      int threshold, bool nonmaxSuppression=true );

image: 检测的灰度图像

keypoints: 在图像上检测到的关键点

threshold: 中心像素和围绕该像素的圆的像素之间的强度差阈值

nonmaxSuppression: 参数非最大值抑制,默认为真,对检测到的角点应用非最大值抑制。

2.函数ComputeORB() 输入图像和Fast角点,输出描述子

① 关于badpoints的筛选:

函数中利用if语句来进行badpoints的计数,在计算BRIEF描述子时,如果以该keypoints为中心的图像块,超出了图像范围,那么,在进行随机选取像素对时,其计算出的描述子将会是错误的,即该keypoints为badpoints。而以该keypoints为中心的图像块,其长和宽应该满足什么条件时,才能够超出图像呢?主要有四种况:

1、当kp.pt.x < half_boundary时,边长为boundary的图像块将在-x轴上超出图像。
2、当kp.pt.y < half_boundary时,边长为boundary的图像块将在-y轴上超出图像。
3、当kp.pt.x >= img.cols - half_boundary(kp.pt.x>= 640-16)时,边长为boundary(32)的图像块将在+x轴上超出图像。
4、当kp.pt.y >= img.rows - half_boundary(kp.pt.y>= 480-16)时,边长为boundary(32)的图像块将在+y轴上超出图像。

参考:ORB中的badpoints检测

灰度质心法

为了保证旋转不变性,所以要在圆内计算。这是因为圆具备旋转不变的性质。比如说,一张图像中确定一个像素的质心之后,图像发生了旋转。这个时候,如果我们选择以该像素为中心,计算正方形区域内的像素的矩 m00、m01、m10、m11 就会发生变化,这样质心就发生改变。但是如果计算圆内的像素。只要半径保持不变,那么他的像素就不会发生变化。

half_patch_size表示半径

参考:

灰度质心法 

灰度质心法的代码实现

③坐标旋转

在平面坐标上,任意点P(x1,y1),绕一个坐标点Q(x2,y2)旋转θ角度后,新的坐标设为(x, y)的计算公式如下:
x= (x1 - x2)* cos(θ) - (y1 - y2)* sin(θ) + x2 = x1* cos(θ) - y1* sin(θ)
y= (x1 - x2)* sin(θ) + (y1 - y2)* cos(θ) + y2 = x1* sin(θ) + y1* cos(θ)

④位运算

a |= b  等价于  a = a | b    | 是位运算,两个都为0时,结果才为0

3.函数BfMatch 输入描述子desc1和描述子desc2,输出匹配对matches

SSE指令集

_mm_popcnt_u32 数32位 unsigned int中1的个数,而一个描述子由 8 * 32组成,因此距离为计算8个 (desc1[i1][k] ^ desc2[i2][k] , 32位 ) 中1的个数。

参考:

手写ORB特征代码解析

你可能感兴趣的:(视觉SLAM,opencv,计算机视觉,slam)