orb的原理已经有很多博主已经解释过了,但是感觉大家都讲的很理论,有些具体的实现还是没有讲清楚,因此自己翻出了opencv的源码出来看。
源码的位置如图:
看了好几天之后,才大概弄清楚了orb算法的具体实现是怎样的,画个流程图给你们看。
至于orb描述子的生成呢,它是有2点对比较,3点对和4点对比较三种生成方式,就是说每次可以在特征点的邻域内随机选取两个,三个或者四个点作为一组进行比较。对于最常用的两点对,比较的结果就是0或1,即1个比特,而描述子作为矩阵,存储的数据类型为8位无符号整数,因此是在一次循环中取16个点,按顺序为8组点对,得到八个比特即一个字节的比较结果,源码如下:
if( wta_k == 2 )
{
for (i = 0; i < dsize; ++i, pattern += 16)
{
int t0, t1, val;
t0 = GET_VALUE(0); t1 = GET_VALUE(1);
val = t0 < t1;
t0 = GET_VALUE(2); t1 = GET_VALUE(3);
val |= (t0 < t1) << 1;
t0 = GET_VALUE(4); t1 = GET_VALUE(5);
val |= (t0 < t1) << 2;
t0 = GET_VALUE(6); t1 = GET_VALUE(7);
val |= (t0 < t1) << 3;
t0 = GET_VALUE(8); t1 = GET_VALUE(9);
val |= (t0 < t1) << 4;
t0 = GET_VALUE(10); t1 = GET_VALUE(11);
val |= (t0 < t1) << 5;
t0 = GET_VALUE(12); t1 = GET_VALUE(13);
val |= (t0 < t1) << 6;
t0 = GET_VALUE(14); t1 = GET_VALUE(15);
val |= (t0 < t1) << 7;
desc[i] = (uchar)val;
}
}
而对于三点对和四点对,则是找出点对中最大的点的编号(1,2或3)/(1,2,3或4),因此结果为2个比特,故一次循环中取12或者16个点比较,从而得到8比特即一字节的描述子,源码中表述如下(3点对的就不贴了,反正是一个意思):
else if( wta_k == 4 )
{
for (i = 0; i < dsize; ++i, pattern += 16)
{
int t0, t1, t2, t3, u, v, k, val;
t0 = GET_VALUE(0); t1 = GET_VALUE(1);
t2 = GET_VALUE(2); t3 = GET_VALUE(3);
u = 0, v = 2;
if( t1 > t0 ) t0 = t1, u = 1;
if( t3 > t2 ) t2 = t3, v = 3;
k = t0 > t2 ? u : v;
val = k;
t0 = GET_VALUE(4); t1 = GET_VALUE(5);
t2 = GET_VALUE(6); t3 = GET_VALUE(7);
u = 0, v = 2;
if( t1 > t0 ) t0 = t1, u = 1;
if( t3 > t2 ) t2 = t3, v = 3;
k = t0 > t2 ? u : v;
val |= k << 2;
t0 = GET_VALUE(8); t1 = GET_VALUE(9);
t2 = GET_VALUE(10); t3 = GET_VALUE(11);
u = 0, v = 2;
if( t1 > t0 ) t0 = t1, u = 1;
if( t3 > t2 ) t2 = t3, v = 3;
k = t0 > t2 ? u : v;
val |= k << 4;
t0 = GET_VALUE(12); t1 = GET_VALUE(13);
t2 = GET_VALUE(14); t3 = GET_VALUE(15);
u = 0, v = 2;
if( t1 > t0 ) t0 = t1, u = 1;
if( t3 > t2 ) t2 = t3, v = 3;
k = t0 > t2 ? u : v;
val |= k << 6;
desc[i] = (uchar)val;
}
}
那么就要问了,点对的选取方式是怎样的呢?对于两点对的orb描述子,通过神经网络的训练,已经发现了一种效果很好的点对pattern,适合的应用场景是31X31的特征点所在邻域,一共是256行,每行4个值,代表两个点的横纵坐标,通常orb描述子是32个字节的,即256比特,因此这个pattern对于2点对来说,是刚好的。而对于3/4点对生成方式,则是由这个已有的pattern来随机生成所需要的pattern。但是对于其他大小的邻域,这个pattern就不能使用了,因此就是随机生成的pattern。pattern在源码中的定义如下:
static int bit_pattern_31_[256*4]
最后提一句,在生成金字塔的时候,为了节省存储空间,各层图片都是放在一个大矩阵里面的,然而各层图片不是紧密连接挨着的,而是每一层图片外围都加上了一个固定宽度的边框,这是因为在之后的描述子计算前,要提取所在层的图像,然后对这一层的图像做了高斯模糊,这是为了提高对噪声的鲁棒性,因此加上边框便于模糊处理。而边框的添加方式,则是以边界为对称轴,镜像复制图像边缘的像素值到边框里面。源码中的体现如下:
copyMakeBorder(currImg, extImg, border, border, border, border,
BORDER_REFLECT_101+BORDER_ISOLATED);