对输入的图像提取特征点、计算描述子。
- /**
- * @brief 用仿函数(重载括号运算符)方法来计算图像特征点
- *
- * @param[in] _image 输入原始图的图像
- * @param[in] _mask 掩膜mask
- * @param[in & out] _keypoints 存储特征点关键点的向量
- * @param[in & out] _descriptors 存储特征点描述子的矩阵
- */
- void ORBextractor::operator()( InputArray _image, InputArray _mask, vector
& _keypoints, - OutputArray _descriptors)
- if(_image.empty())
- return;
-
- //获取图像的大小
- Mat image = _image.getMat();
- //判断图像的格式是否正确,要求是单通道灰度值
- assert(image.type() == CV_8UC1 );
ComputePyramid(image);
ORB-SLAM2 ---- ORBextractor::ComputePyramid函数_Courage2022的博客-CSDN博客
// 存储所有的特征点,注意此处为二维的vector,第一维存储的是金字塔的层数,第二维存储的是那一层金字塔图像里提取的所有特征点 vector < vector> allKeypoints; //使用四叉树的方式计算每层图像的特征点并进行分配 ComputeKeyPointsOctTree(allKeypoints);ORB-SLAM2 ---- ORBextractor::ComputeKeyPointsOctTree函数_Courage2022的博客-CSDN博客
Mat descriptors; //统计整个图像金字塔中的特征点 int nkeypoints = 0; //开始遍历每层图像金字塔,并且累加每层的特征点个数 for (int level = 0; level < nlevels; ++level) nkeypoints += (int)allKeypoints[level].size(); //如果本图像金字塔中没有任何的特征点 if( nkeypoints == 0 ) //通过调用cv::mat类的.realse方法,强制清空矩阵的引用计数,这样就可以强制释放矩阵的数据了 //参考[https://blog.csdn.net/giantchen547792075/article/details/9107877] _descriptors.release(); else { //如果图像金字塔中有特征点,那么就创建这个存储描述子的矩阵,注意这个矩阵是存储整个图像金字塔中特征点的描述子的 _descriptors.create(nkeypoints, //矩阵的行数,对应为特征点的总个数 32, //矩阵的列数,对应为使用32*8=256位描述子 CV_8U); //矩阵元素的格式 //获取这个描述子的矩阵信息 // ?为什么不是直接在参数_descriptors上对矩阵内容进行修改,而是重新新建了一个变量,复制矩阵后,在这个新建变量的基础上进行修改? descriptors = _descriptors.getMat(); } //清空用作返回特征点提取结果的vector容器 _keypoints.clear(); //并预分配正确大小的空间 _keypoints.reserve(nkeypoints); //因为遍历是一层一层进行的,但是描述子那个矩阵是存储整个图像金字塔中特征点的描述子,所以在这里设置了Offset变量来保存“寻址”时的偏移量, //辅助进行在总描述子mat中的定位 int offset = 0; //开始遍历每一层图像 for (int level = 0; level < nlevels; ++level) { //获取在allKeypoints中当前层特征点容器的句柄 vector& keypoints = allKeypoints[level]; //本层的特征点数 int nkeypointsLevel = (int)keypoints.size(); //如果特征点数目为0,跳出本次循环,继续下一层金字塔 if(nkeypointsLevel==0) continue; // preprocess the resized image这里我们新建了一个描述子矩阵descriptors,统计所有层的已提取特征点数目存放在nkeypoints里(为什么要建立这个变量,配置文件里面不是已经有要分配的特征点数量了吗?ORBextractor.nFeatures: 1000?因为可能由于图片原因未必能找出那么多特征点....), 在判断特征点数目是否为空?(SLAM系统BUG或者图片原因);
若为空则清空用作返回特征点提取结果的vector容器,若不为空那么就创建这个存储描述子的矩阵,注意这个矩阵是存储整个图像金字塔中特征点的描述子的。
对每层图像金子塔进行遍历,取出当前层的特征点存储到keypoints容器中,并存储该层金字塔中特征点的数量到变量nkeypointsLevel 中,如果特征点数目 = 0,跳过该层金字塔。
Mat workingMat = mvImagePyramid[level].clone(); // 注意:提取特征点的时候,使用的是清晰的原图像;这里计算描述子的时候,为了避免图像噪声的影响,使用了高斯模糊 GaussianBlur(workingMat, //源图像 workingMat, //输出图像 Size(7, 7), //高斯滤波器kernel大小,必须为正的奇数 2, //高斯滤波在x方向的标准差 2, //高斯滤波在y方向的标准差 BORDER_REFLECT_101);//边缘拓展点插值类型高斯模糊意义:去掉干扰点
参数意义:我们高斯模糊原图像(金字塔第i层的图像,深拷贝到了workingMat 变量中),并将高斯模糊后的图像也放入到该变量中。
- Mat desc = descriptors.rowRange(offset, offset + nkeypointsLevel);
- computeDescriptors(workingMat, //高斯模糊之后的图层图像
- keypoints, //当前图层中的特征点集合
- desc, //存储计算之后的描述子
- pattern); //随机采样模板
-
- // 更新偏移量的值
- offset += nkeypointsLevel;
// 对于第0层的图像特征点,他们的坐标就不需要再进行恢复了 if (level != 0) { // 获取当前图层上的缩放系数 float scale = mvScaleFactor[level]; // 遍历本层所有的特征点 for (vector::iterator keypoint = keypoints.begin(), keypointEnd = keypoints.end(); keypoint != keypointEnd; ++keypoint) // 特征点本身直接乘缩放倍数就可以了 keypoint->pt *= scale; } // And add the keypoints to the output // 将keypoints中内容插入到_keypoints 的末尾 // keypoint其实是对allkeypoints中每层图像中特征点的引用,这样allkeypoints中的所有特征点在这里被转存到输出的_keypoints _keypoints.insert(_keypoints.end(), keypoints.begin(), keypoints.end());将特征点乘以缩放系数得到对应层的特征点坐标。
将提取到的特征点放入_keypoints容器交付上层。