【ORB SLAM 2的描述子计算和描述子汉明距离的计算代码解析】

computeDescriptors计算描述子函数

//注意这是一个不属于任何类的全局静态函数,static修饰符限定其只能够被本文件中的函数调用
/**
 * @brief 计算某层金字塔图像上特征点的描述子
 * 
 * @param[in] image                 某层金字塔图像
 * @param[in] keypoints             特征点vector容器
 * @param[out] descriptors          描述子
 * @param[in] pattern               计算描述子使用的固定随机点集
 */
static void computeDescriptors(const Mat& image, vector<KeyPoint>& keypoints, Mat& descriptors,
                               const vector<Point>& pattern)
{
	//清空保存描述子信息的容器
    descriptors = Mat::zeros((int)keypoints.size(), 32, CV_8UC1);

	//开始遍历特征点
    for (size_t i = 0; i < keypoints.size(); i++)
		//计算这个特征点的描述子
        computeOrbDescriptor(keypoints[i], 				//要计算描述子的特征点
							 image, 					//以及其图像
							 &pattern[0], 				//随机点集的首地址
							 descriptors.ptr((int)i));	//提取出来的描述子的保存位置 //返回指向指定矩阵行的指针。
}

描述子的形式

descriptors = Mat::zeros((int)keypoints.size(), 32, CV_8UC1);

描述子矩阵:行是(int)keypoints.size()特征点的数量,列是32,每个元素就是CV_8UC1

CV_8UC1 : 8bite unsigned int,channels = 1,即8bite无符号整形单通道矩阵,故字节数=1字节=8位(bite)

例如某个特征点的描述子a:

[116,  89,  30,  96,   9, 205,  83, 164, 235, 184, 174,   8, 246, 243,  65, 114, 128, 244, 108,  74,   9,  33, 218,  48, 249, 185,  37,  89, 192,  51,  66,  35]

1*32,每个元素是8bite uint,116=01110100

computeOrbDescriptor计算ORB描述子函数

/**
 * @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<uchar>(cvRound(kpt.pt.y), cvRound(kpt.pt.x));
	//获得图像的每行的字节数
    const int step = (int)img.step;
    // cv::Mat::step详解
/*
step的几个类别区分:
    step:矩阵第一行元素的字节数
    step[0]:矩阵第一行元素的字节数
    step[1]:矩阵中一个元素的字节数
    step1(0):矩阵中一行有几个通道数
    step1(1):一个元素有几个通道数(channel())
*/
	//原始的BRIEF描述子没有方向不变性,通过加入关键点的方向来计算描述子,称之为Steer BRIEF,具有较好旋转不变特性
	//具体地,在计算的时候需要将这里选取的采样模板中点的x轴方向旋转到特征点的方向。
	//获得采样点中某个idx所对应的点的灰度值,这里旋转前坐标为(x,y), 旋转后坐标(x',y'),他们的变换关系:
    // x'= xcos(θ) - ysin(θ),  y'= xsin(θ) + ycos(θ)
    // 下面表示 y'* step + x'
    #define GET_VALUE(idx) center[cvRound(pattern[idx].x*b + pattern[idx].y*a)*step + cvRound(pattern[idx].x*a - pattern[idx].y*b)]        
    
	//brief描述子由32*8位组成
	//其中每一位是来自于两个像素点灰度的直接比较,所以每比较出8bit结果,需要16个随机点,这也就是为什么pattern需要+=16的原因
    for (int i = 0; i < 32; ++i, pattern += 16) // 最后的描述子矩阵是1x32的uint8,一行共32个字节
    {
		
        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;//8位=1字节,循环32次后就是一行32个字节
    }

    //为了避免和程序中的其他部分冲突在,在使用完成之后就取消这个宏定义
    #undef GET_VALUE
}

描述子计算过程

desc[i] = (uchar)val;//8位=1字节,循环32次后就是一行32个字节,就是某个特征点的描述子

例如某个特征点的描述子a:

[116,  89,  30,  96,   9, 205,  83, 164, 235, 184, 174,   8, 246, 243,  65, 114, 128, 244, 108,  74,   9,  33, 218,  48, 249, 185,  37,  89, 192,  51,  66,  35]
desc[0] = 01110100 = 116
desc[1] = 01011001 = 89
desc[2] = 01110100 = 30
.
.
.
desc[30] = 01000010 = 66
desc[31] = 00100011 = 35

DescriptorDistance计算描述子汉明距离函数

// Bit set count operation from
// Hamming distance:两个二进制串之间的汉明距离,指的是其不同位数的个数
// http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel
int ORBmatcher::DescriptorDistance(const cv::Mat &a, const cv::Mat &b)
{
    const int *pa = a.ptr<int32_t>();// mat.ptr<type>(row)[col]就是指向mat的第row行的第col列数据,数据类型为type
    const int *pb = b.ptr<int32_t>();// b.ptr<int32_t>()是指向b矩阵 前4字节 的指针,4字节*8bite=int32_t
//    cout << "a:" << a << endl;
//    cout << "b:" << b << endl;
//    a是1x32的矩阵,一行共32个字节(因每个元素是uint8,1字节=8位):
//    例如a=[217,  84, 251, 239,  36,  79, 127,  20, 125,  78, 118,  28, 255,  21, 155, 117, 182, 204,  29, 110, 233, 180, 113, 170, 247, 249,  32, 128,  98, 250,  71, 121]
//    cout << "*pa:" << *pa << endl;// *pa:-1814382077
//    cout << "pa:" << pa << endl;// pa:0x7f27680e4da0
//    cout << "指向图像数据的首地址a.data:" << a.data << endl;//Mat.data的Mat类成员,是指向图像数据的首地址,地址类型是uchar.打印会乱码
//    cout << "每一行所占的字节数a.step.p[0]:" << a.step.p[0] << endl; //每一行所占的字节数a.step.p[0]=32
//    cout << "每个点所占的字节数a.step.p[1]:" << a.step.p[1] << endl;
//    每个点所占的字节数a.step.p[1]=1.因为描述自矩阵的type是CV_8UC1:8bite unsigned int,channels = 1,即8bite无符号整形单通道矩阵,故字节数=1字节=8位(bite)
//    cout << "uint8_t的字节数:" << sizeof(uint8_t) << endl;// uint8_t的字节数:1字节.即1个字节=8位(bite)
//    cout << "uint16_t的字节数:" << sizeof(uint16_t) << endl;// uint16_t的字节数:2字节
//    cout << "uint32_t的字节数:" << sizeof(uint32_t) << endl;// uint32_t的字节数:4字节
//    cout << "uint64_t的字节数:" << sizeof(uint64_t) << endl;// uint64_t的字节数:8字节

    int dist=0;

    // 8bite*32=256bit

// 例如
//        a:[116,  89,  30,  96,   9, 205,  83, 164, 235, 184, 174,   8, 246, 243,  65, 114, 128, 244, 108,  74,   9,  33, 218,  48, 249, 185,  37,  89, 192,  51,  66,  35]
//        b:[241,  76,  91, 110, 124, 141,  82, 220, 231,  40, 176,  72, 119, 208, 161,  48, 154, 160,  78, 106, 207,  39, 104,  63, 242, 221,  33,  56, 120, 155,  66,   9]
//        dist:12
//        dist:23
//        dist:32
//        dist:42
//        dist:51
//        dist:65
//        dist:75
//        dist:85
//看一下具体的过程:

//    i=0,pa指向 第一个4字节[ 116,  89,  30,  96],共32位; pb指向 第一个4字节[ 241,  76,  91, 110],共32位;  计算第一个4字节(4个8bite=32bite)的汉明距离
//        [ 116,  89,  30,  96] = [01110100 01011001 00011110 01100000]
//        [ 241,  76,  91, 110] = [11110001 01001100 01011011 01101110] 第一个4字节的汉明距离是12
//        dist=12

//    i=1,pa指向 第二个4字节[ 9, 205,  83, 164],共32位; pb指向 第二个4字节[ 124, 141,  82, 220],共32位;
//        [ 9, 205,  83, 164]   = [00001001 11001101 01010011 10100100]
//        [ 124, 141,  82, 220] = [01111100 10001101 01010010 11011100] 第二个4字节的汉明距离是11
//        dist=12+11 = 23
//    i=2,pa指向 第三个4字节[ 235, 184, 174,   8],共32位; pb指向 第三个4字节[ 231,  40, 176,  72],共32位;
//    i=3,pa指向 第四个4字节[ 246, 243,  65, 114],共32位; pb指向 第四个4字节[ 119, 208, 161,  48],共32位;
//    i=4,pa指向 第五个4字节[ 128, 244, 108,  74],共32位; pb指向 第五个4字节[ 154, 160,  78, 106],共32位;
//    i=5,pa指向 第六个4字节[  9,  33, 218,  48],共32位; pb指向 第六个4字节[ 207,  39, 104,  63],共32位;
//    i=6,pa指向 第七个4字节[ 249, 185,  37,  89],共32位; pb指向 第七个4字节[ 242, 221,  33,  56],共32位;
//    i=7,pa指向 第八个4字节[ 192,  51,  66,  35],共32位; pb指向 第八个4字节[ 120, 155,  66,   9],共32位;
//    以上8个32bite的汉明距离之和 就是 描述子a和描述子b 共32个8bite=256位



    for(int i=0; i<8; i++, pa++, pb++)
    {
        unsigned  int v = *pa ^ *pb;        // 相等为0,不等为1
        // 下面的操作就是计算其中bit为1的个数了,这个操作看上面的链接就好
        // 其实我觉得也还阔以直接使用8bit的查找表,然后做32次寻址操作就完成了;不过缺点是没有利用好CPU的字长
        v = v - ((v >> 1) & 0x55555555);
        v = (v & 0x33333333) + ((v >> 2) & 0x33333333);
        dist += (((v + (v >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24;
        cout << "dist:" << dist<< endl;
    }
//    cout << "dist total:" << dist<< endl;
//    cout << "***********************************"  << endl;
    return dist;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

楚歌again

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

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

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

打赏作者

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

抵扣说明:

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

余额充值