• ORB-SLAM2 ---- ORBextractor::operator()仿函数


    1.这个仿函数做了什么?

            对输入的图像提取特征点、计算描述子。

    2.代码解析

    1. /**
    2. * @brief 用仿函数(重载括号运算符)方法来计算图像特征点
    3. *
    4. * @param[in] _image 输入原始图的图像
    5. * @param[in] _mask 掩膜mask
    6. * @param[in & out] _keypoints 存储特征点关键点的向量
    7. * @param[in & out] _descriptors 存储特征点描述子的矩阵
    8. */
    9. void ORBextractor::operator()( InputArray _image, InputArray _mask, vector& _keypoints,
    10. OutputArray _descriptors)

    2.1 检查图像有效性。如果图像为空,那么就直接返回

    1. if(_image.empty())
    2. return;
    3. //获取图像的大小
    4. Mat image = _image.getMat();
    5. //判断图像的格式是否正确,要求是单通道灰度值
    6. assert(image.type() == CV_8UC1 );

    2.2 构建图像金字塔 

    ComputePyramid(image);

    ORB-SLAM2 ---- ORBextractor::ComputePyramid函数_Courage2022的博客-CSDN博客

    2.3 计算图像的特征点,并且将特征点进行均匀化。均匀的特征点可以提高位姿计算精度 

    1. // 存储所有的特征点,注意此处为二维的vector,第一维存储的是金字塔的层数,第二维存储的是那一层金字塔图像里提取的所有特征点
    2. vector < vector > allKeypoints;
    3. //使用四叉树的方式计算每层图像的特征点并进行分配
    4. ComputeKeyPointsOctTree(allKeypoints);

    ORB-SLAM2 ---- ORBextractor::ComputeKeyPointsOctTree函数_Courage2022的博客-CSDN博客

    2.4 拷贝图像描述子到新的矩阵descriptors 

    1. Mat descriptors;
    2. //统计整个图像金字塔中的特征点
    3. int nkeypoints = 0;
    4. //开始遍历每层图像金字塔,并且累加每层的特征点个数
    5. for (int level = 0; level < nlevels; ++level)
    6. nkeypoints += (int)allKeypoints[level].size();
    7. //如果本图像金字塔中没有任何的特征点
    8. if( nkeypoints == 0 )
    9. //通过调用cv::mat类的.realse方法,强制清空矩阵的引用计数,这样就可以强制释放矩阵的数据了
    10. //参考[https://blog.csdn.net/giantchen547792075/article/details/9107877]
    11. _descriptors.release();
    12. else
    13. {
    14. //如果图像金字塔中有特征点,那么就创建这个存储描述子的矩阵,注意这个矩阵是存储整个图像金字塔中特征点的描述子的
    15. _descriptors.create(nkeypoints, //矩阵的行数,对应为特征点的总个数
    16. 32, //矩阵的列数,对应为使用32*8=256位描述子
    17. CV_8U); //矩阵元素的格式
    18. //获取这个描述子的矩阵信息
    19. // ?为什么不是直接在参数_descriptors上对矩阵内容进行修改,而是重新新建了一个变量,复制矩阵后,在这个新建变量的基础上进行修改?
    20. descriptors = _descriptors.getMat();
    21. }
    22. //清空用作返回特征点提取结果的vector容器
    23. _keypoints.clear();
    24. //并预分配正确大小的空间
    25. _keypoints.reserve(nkeypoints);
    26. //因为遍历是一层一层进行的,但是描述子那个矩阵是存储整个图像金字塔中特征点的描述子,所以在这里设置了Offset变量来保存“寻址”时的偏移量,
    27. //辅助进行在总描述子mat中的定位
    28. int offset = 0;
    29. //开始遍历每一层图像
    30. for (int level = 0; level < nlevels; ++level)
    31. {
    32. //获取在allKeypoints中当前层特征点容器的句柄
    33. vector& keypoints = allKeypoints[level];
    34. //本层的特征点数
    35. int nkeypointsLevel = (int)keypoints.size();
    36. //如果特征点数目为0,跳出本次循环,继续下一层金字塔
    37. if(nkeypointsLevel==0)
    38. continue;
    39. // preprocess the resized image

            这里我们新建了一个描述子矩阵descriptors,统计所有层的已提取特征点数目存放在nkeypoints里(为什么要建立这个变量,配置文件里面不是已经有要分配的特征点数量了吗?ORBextractor.nFeatures: 1000?因为可能由于图片原因未必能找出那么多特征点....), 在判断特征点数目是否为空?(SLAM系统BUG或者图片原因);

            若为空则清空用作返回特征点提取结果的vector容器,若不为空那么就创建这个存储描述子的矩阵,注意这个矩阵是存储整个图像金字塔中特征点的描述子的。

            对每层图像金子塔进行遍历,取出当前层的特征点存储到keypoints容器中,并存储该层金字塔中特征点的数量到变量nkeypointsLevel 中,如果特征点数目 = 0,跳过该层金字塔。

    2.5 对图像进行高斯模糊

    1. Mat workingMat = mvImagePyramid[level].clone();
    2. // 注意:提取特征点的时候,使用的是清晰的原图像;这里计算描述子的时候,为了避免图像噪声的影响,使用了高斯模糊
    3. GaussianBlur(workingMat, //源图像
    4. workingMat, //输出图像
    5. Size(7, 7), //高斯滤波器kernel大小,必须为正的奇数
    6. 2, //高斯滤波在x方向的标准差
    7. 2, //高斯滤波在y方向的标准差
    8. BORDER_REFLECT_101);//边缘拓展点插值类型

            高斯模糊意义:去掉干扰点

            参数意义:我们高斯模糊原图像(金字塔第i层的图像,深拷贝到了workingMat 变量中),并将高斯模糊后的图像也放入到该变量中。

    2.6 计算高斯模糊后图像的描述子 

    1. Mat desc = descriptors.rowRange(offset, offset + nkeypointsLevel);
    2. computeDescriptors(workingMat, //高斯模糊之后的图层图像
    3. keypoints, //当前图层中的特征点集合
    4. desc, //存储计算之后的描述子
    5. pattern); //随机采样模板
    6. // 更新偏移量的值
    7. offset += nkeypointsLevel;

    ORB-SLAM2 ---- computeDescriptors函数_Courage2022的博客-CSDN博客

    2.7 对非第0层图像中的特征点的坐标恢复到第0层图像(原图像)的坐标系下 

    1. // 对于第0层的图像特征点,他们的坐标就不需要再进行恢复了
    2. if (level != 0)
    3. {
    4. // 获取当前图层上的缩放系数
    5. float scale = mvScaleFactor[level];
    6. // 遍历本层所有的特征点
    7. for (vector::iterator keypoint = keypoints.begin(),
    8. keypointEnd = keypoints.end(); keypoint != keypointEnd; ++keypoint)
    9. // 特征点本身直接乘缩放倍数就可以了
    10. keypoint->pt *= scale;
    11. }
    12. // And add the keypoints to the output
    13. // 将keypoints中内容插入到_keypoints 的末尾
    14. // keypoint其实是对allkeypoints中每层图像中特征点的引用,这样allkeypoints中的所有特征点在这里被转存到输出的_keypoints
    15. _keypoints.insert(_keypoints.end(), keypoints.begin(), keypoints.end());

            将特征点乘以缩放系数得到对应层的特征点坐标。

            将提取到的特征点放入_keypoints容器交付上层。

  • 相关阅读:
    博捷芯:半导体芯片切割,一道精细工艺的科技之门
    【X806开发板试用】串口数据透传阿里云IOT
    css样式在弹性盒元素在必要的时候拆行
    vivo 制品管理在 CICD 落地实践
    YC-性能测试
    数据结构与算法——串
    麒麟系统开发笔记(十二):在国产麒麟系统上编译GDAL库、搭建基础开发环境和基础Demo
    做PPT绝对不能错过这5个网站
    KubeEdge 1.12版本发布,稳定性、安全性、可扩展性均带来大幅提升
    Sui Gaming AMA精彩内容集锦
  • 原文地址:https://blog.csdn.net/qq_41694024/article/details/126310734