//注意这是一个不属于任何类的全局静态函数,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
/**
* @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
// 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(row)[col]就是指向mat的第row行的第col列数据,数据类型为type
const int *pb = b.ptr<int32_t>();// b.ptr()是指向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;
}