计算computeDescriptors函数传进来的每个特征点对应的描述子。
点v 绕 原点旋转θ 角,得到点v’,假设 v点的坐标是(x, y) ,那么可以推导得到 v’点的坐标(x’, y’)
计算描述子时要考虑旋转不变性:比如同一幅图像在不同事件光照效果不同可能英雄特征点的提取,但是相对亮度一般不变(相对暗的地方永远是相对暗的)。
在特征点匹配时,主要是根据描述子的汉明距离进行匹配,我们要保证两个特征点是相同角度进行匹配的(转到相同的角度),但如何确定这个角度呢?ORBSLAM2中采用了都让特征点的angle属性指向灰度质心的办法实现,我们通过前面已经通过AC_Angle函数求得了angle的值,这个角度值即PQ与x轴的夹角,我们要旋转这个angle角度使特征点指向灰度质心的向量与x轴重合。
ORB-SLAM2 ---- IC_Angle函数_Courage2022的博客-CSDN博客
我们观察上图推导得到旋转后的坐标关系变换。
/** * @brief 计算ORB特征点的描述子。注意这个是全局的静态函数,只能是在本文件内被调用 * @param[in] kpt 特征点对象 * @param[in] img 提取特征点的图像 * @param[in] pattern 预定义好的采样模板 * @param[out] desc 用作输出变量,保存计算好的描述子,维度为32*8 = 256 bit */ static void computeOrbDescriptor(const KeyPoint& kpt, const Mat& img, const Point* pattern, uchar* desc)
//得到特征点的角度,用弧度制表示。其中kpt.angle是角度制,范围为[0,360)度 float angle = (float)kpt.angle*factorPI; //计算这个角度的余弦值和正弦值 float a = (float)cos(angle), b = (float)sin(angle); //获得图像中心指针 const uchar* center = &img.at(cvRound(kpt.pt.y), cvRound(kpt.pt.x)); //获得图像的每行的字节数 const int step = (int)img.step;const float factorPI = (float)(CV_PI/180.f);我们先将角度转化为弧度制方便计算,并计算cos angle = a,sin angle = b。
获取特征点kpt中心指针center,获取每行的字节数step。
#define GET_VALUE(idx) center[cvRound(pattern[idx].x*b + pattern[idx].y*a)*step + cvRound(pattern[idx].x*a - pattern[idx].y*b)]
![]()
观察这个宏定义,cvRound(pattern[idx].x*b + pattern[idx].y*a)*step + cvRound(pattern[idx].x*a - pattern[idx].y*b)其实是在算
,即pattern点集下的(x,y)点的坐标。center取其灰度值,这个其实就是算(x,y)点的灰度值。
//brief描述子由32*8位组成 //其中每一位是来自于两个像素点灰度的直接比较,所以每比较出8bit结果,需要16个随机点,这也就是为什么pattern需要+=16的原因 for (int i = 0; i < 32; ++i, pattern += 16) { int t0, //参与比较的第1个特征点的灰度值 t1, //参与比较的第2个特征点的灰度值 val; //描述子这个字节的比较结果,0或1 t0 = GET_VALUE(0); t1 = GET_VALUE(1); val = t0 < t1; //描述子本字节的bit0 t0 = GET_VALUE(2); t1 = GET_VALUE(3); val |= (t0 < t1) << 1; //描述子本字节的bit1 t0 = GET_VALUE(4); t1 = GET_VALUE(5); val |= (t0 < t1) << 2; //描述子本字节的bit2 t0 = GET_VALUE(6); t1 = GET_VALUE(7); val |= (t0 < t1) << 3; //描述子本字节的bit3 t0 = GET_VALUE(8); t1 = GET_VALUE(9); val |= (t0 < t1) << 4; //描述子本字节的bit4 t0 = GET_VALUE(10); t1 = GET_VALUE(11); val |= (t0 < t1) << 5; //描述子本字节的bit5 t0 = GET_VALUE(12); t1 = GET_VALUE(13); val |= (t0 < t1) << 6; //描述子本字节的bit6 t0 = GET_VALUE(14); t1 = GET_VALUE(15); val |= (t0 < t1) << 7; //描述子本字节的bit7 //保存当前比较的出来的描述子的这个字节 desc[i] = (uchar)val; } //为了避免和程序中的其他部分冲突在,在使用完成之后就取消这个宏定义 #undef GET_VALUE这里bit_pattern_31_[256*4]是256对点,每一对点要比较灰度值,因此一个特征点的描述子为256bit。这个一次循环计算8bit存储在desc[i]中,一共循环256/8=32次即可计算出该特征点的描述子。