ORBSLAM2代码解析--ORBextractor

目录

ORBextractor.h

ExtractorNode 类

ORBextractor 类

构造函数和析构函数

ORBextractor.cc

(1) static float IC_Angle

详细参数说明

函数内部变量和步骤

(2) computeOrbDescriptor 函数

详细参数说明

函数内部变量和步骤

详细说明

(3)ORBextractor 构造函数

详细参数说明

构造函数内部变量和步骤

详细说明

(4)computeOrientation函数

详细注释代码

(5)DivideNode 函数

详细注释代码

详细说明

函数作用

相关方法

(6)DistributeOctTree 函数

详细说明

函数作用

相关方法

详细注释代码

(7)ComputeKeyPointsOctTree 函数

函数作用

传入参数

详细注释代码

关键步骤和说明

(8)ComputeKeyPointsOld 函数

函数作用

传入参数

详细注释代码

关键步骤和说明

(9)computeDescriptors 函数

(10)ORBextractor::operator() 函数

(11)ORBextractor::ComputePyramid 函数


ORBextractor.h

ExtractorNode 类

方法名

作用

传入参数

DivideNode

将当前节点分成四个子节点

ExtractorNode &n1ExtractorNode &n2ExtractorNode &n3ExtractorNode &n4

ORBextractor 类

方法名

作用

传入参数

ORBextractor

构造函数,初始化ORB提取器

int nfeatures:特征点数量
float scaleFactor:尺度因子
int nlevels:金字塔层数
int iniThFAST:初始FAST阈值
int minThFAST:最小FAST阈值

~ORBextractor

析构函数,销毁ORB提取器

operator()

计算图像的ORB特征和描述子

cv::InputArray image:输入图像
cv::InputArray mask:掩码
std::vector<cv::KeyPoint>& keypoints:关键点
cv::OutputArray descriptors:描述子

GetLevels

获取金字塔的层数

GetScaleFactor

获取金字塔的尺度因子

GetScaleFactors

获取每一层金字塔的尺度因子

GetInverseScaleFactors

获取每一层金字塔的尺度因子的倒数

GetScaleSigmaSquares

获取每一层金字塔的尺度方差

GetInverseScaleSigmaSquares

获取每一层金字塔的尺度方差的倒数

ComputePyramid

构建图像金字塔

cv::Mat image:输入图像

ComputeKeyPointsOctTree

在Octree中计算关键点

std::vector<std::vector<cv::KeyPoint>>& allKeypoints:所有关键点

ComputeKeyPointsOld

旧方法中计算关键点

std::vector<std::vector<cv::KeyPoint>>& allKeypoints:所有关键点

DistributeOctTree

在Octree中分布关键点

const std::vector<cv::KeyPoint>& vToDistributeKeys:待分布的关键点
const int &minX:最小X值
const int &maxX:最大X值
const int &minY:最小Y值
const int &maxY:最大Y值
const int &nFeatures:特征点数量
const int &level:金字塔层级

构造函数和析构函数

方法名

作用

传入参数

ORBextractor

构造函数,初始化ORB提取器

int nfeatures:特征点数量
float scaleFactor:尺度因子
int nlevels:金字塔层数
int iniThFAST:初始FAST阈值
int minThFAST:最小FAST阈值

~ORBextractor

析构函数,销毁ORB提取器

ORBextractor.cc

(1) static float IC_Angle+

// 计算关键点的主方向
static float IC_Angle(const Mat& image, Point2f pt, const vector<int> & u_max)
{
    // m_01 和 m_10 是图像的矩
    int m_01 = 0, m_10 = 0;

    // 获取图像中心点的指针
    const uchar* center = &image.at<uchar>(cvRound(pt.y), cvRound(pt.x));

    // 特别处理中心线,v = 0
    for (int u = -HALF_PATCH_SIZE; u <= HALF_PATCH_SIZE; ++u)
        m_10 += u * center[u];

    // 按行处理圆形区域
    int step = (int)image.step1();
    for (int v = 1; v <= HALF_PATCH_SIZE; ++v)
    {
        // 处理两行
        int v_sum = 0;
        int d = u_max[v];
        for (int u = -d; u <= d; ++u)
        {
            int val_plus = center[u + v*step], val_minus = center[u - v*step];
            v_sum += (val_plus - val_minus);
            m_10 += u * (val_plus + val_minus);
        }
        m_01 += v * v_sum;
    }

    // 计算并返回关键点的方向角度
    return fastAtan2((float)m_01, (float)m_10);
}

​​​​​​​

方法名

作用

传入参数

IC_Angle

计算关键点的主方向

const Mat& image:输入图像
Point2f pt:关键点坐标
const vector<int>& u_max:限制环形区域的最大 u 值

详细参数说明

参数

类型

说明

const Mat& image

cv::Mat

输入的图像

Point2f pt

cv::Point2f

关键点的坐标

const vector<int>& u_max

std::vector<int>

限制环形区域的最大 u 值,用于优化计算

函数内部变量和步骤

变量/步骤

类型

说明

int m_01

int

图像的矩之一,初始化为0

int m_10

int

图像的矩之一,初始化为0

const uchar* center

const uchar*

指向关键点位置的图像像素

int step

int

图像每行的步长

int v_sum

int

当前行的像素值和

int d

int

当前行的最大 u 值

循环中心线

for

遍历中心线上的像素值并计算 m_10

循环圆形区域

for

遍历圆形区域的每一行,内层循环计算每行像素的值并更新 m_10 和 m_01

返回值

float

关键点的方向角度

(2) computeOrbDescriptor 函数

const float factorPI = (float)(CV_PI / 180.f); // 将角度转换为弧度的因子

// 计算ORB特征描述子
static void computeOrbDescriptor(const KeyPoint& kpt,
                                 const Mat& img, const Point* pattern,
                                 uchar* desc)
{
    // 计算关键点的方向角度的弧度值
    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;

    // 宏定义,用于获取旋转后的图像块的像素值
    #define GET_VALUE(idx) \
        center[cvRound(pattern[idx].x * b + pattern[idx].y * a) * step + \
               cvRound(pattern[idx].x * a - pattern[idx].y * b)]

    // 计算特征描述子
    for (int i = 0; i < 32; ++i, pattern += 16)
    {
        int t0, t1, val;
        t0 = GET_VALUE(0); t1 = GET_VALUE(1);
        val = t0 < t1;
        t0 = GET_VALUE(2); t1 = GET_VALUE(3);
        val |= (t0 < t1) << 1;
        t0 = GET_VALUE(4); t1 = GET_VALUE(5);
        val |= (t0 < t1) << 2;
        t0 = GET_VALUE(6); t1 = GET_VALUE(7);
        val |= (t0 < t1) << 3;
        t0 = GET_VALUE(8); t1 = GET_VALUE(9);
        val |= (t0 < t1) << 4;
        t0 = GET_VALUE(10); t1 = GET_VALUE(11);
        val |= (t0 < t1) << 5;
        t0 = GET_VALUE(12); t1 = GET_VALUE(13);
        val |= (t0 < t1) << 6;
        t0 = GET_VALUE(14); t1 = GET_VALUE(15);
        val |= (t0 < t1) << 7;

        desc[i] = (uchar)val;
    }

    #undef GET_VALUE
}

详细参数说明

参数

类型

说明

const KeyPoint& kpt

cv::KeyPoint

输入的关键点

const Mat& img

cv::Mat

输入的图像

const Point* pattern

const cv::Point*

用于描述子计算的点模式

uchar* desc

uchar*

输出的特征描述子

函数内部变量和步骤

变量/步骤

类型

说明

float angle

float

关键点的角度(以弧度表示)

float a, b

float

计算方向角度的余弦和正弦值

const uchar* center

const uchar*

图像中关键点的中心像素指针

int step

int

图像每行的步长

宏定义 GET_VALUE(idx)

获取旋转后的图像块的像素值

for 循环

循环

计算特征描述子,每个描述子包含32个字节,每字节比较16对像素

详细说明

  1. computeOrbDescriptor 函数:用于计算给定关键点的ORB特征描述子。
    • 参数
      • const KeyPoint& kpt:输入的关键点。
      • const Mat& img:输入图像。
      • const Point* pattern:用于描述子计算的点模式。
      • uchar* desc:输出的特征描述子。
  1. 变量初始化
    • const float factorPI:将角度转换为弧度的因子。
    • float angle:关键点的角度(以弧度表示)。
    • float a, b:角度的余弦和正弦值。
    • const uchar* center:指向图像中关键点位置的像素。
    • int step:图像每行的步长。
  1. 宏定义 GET_VALUE(idx):用于获取旋转后的图像块的像素值。
  2. 计算描述子
    • 使用 for 循环计算描述子,每个描述子包含32个字节,每个字节比较16对像素。
    • 通过比较两个像素值,生成二进制值,并将其组合成一个字节,存储在 desc 数组中。

(3)ORBextractor 构造函数

ORBextractor::ORBextractor(int _nfeatures, float _scaleFactor, int _nlevels,
         int _iniThFAST, int _minThFAST):
    nfeatures(_nfeatures), scaleFactor(_scaleFactor), nlevels(_nlevels),
    iniThFAST(_iniThFAST), minThFAST(_minThFAST)
{
    // 初始化尺度因子和方差
    mvScaleFactor.resize(nlevels);
    mvLevelSigma2.resize(nlevels);
    mvScaleFactor[0] = 1.0f;
    mvLevelSigma2[0] = 1.0f;
    for(int i = 1; i < nlevels; i++)
    {
        mvScaleFactor[i] = mvScaleFactor[i-1] * scaleFactor;
        mvLevelSigma2[i] = mvScaleFactor[i] * mvScaleFactor[i];
    }

    // 计算尺度因子的倒数和方差的倒数
    mvInvScaleFactor.resize(nlevels);
    mvInvLevelSigma2.resize(nlevels);
    for(int i = 0; i < nlevels; i++)
    {
        mvInvScaleFactor[i] = 1.0f / mvScaleFactor[i];
        mvInvLevelSigma2[i] = 1.0f / mvLevelSigma2[i];
    }

    // 初始化图像金字塔
    mvImagePyramid.resize(nlevels);

    // 计算每个尺度上的特征点数量
    mnFeaturesPerLevel.resize(nlevels);
    float factor = 1.0f / scaleFactor;
    float nDesiredFeaturesPerScale = nfeatures * (1 - factor) / (1 - (float)pow((double)factor, (double)nlevels));

    int sumFeatures = 0;
    for(int level = 0; level < nlevels - 1; level++)
    {
        mnFeaturesPerLevel[level] = cvRound(nDesiredFeaturesPerScale);
        sumFeatures += mnFeaturesPerLevel[level];
        nDesiredFeaturesPerScale *= factor;
    }
    mnFeaturesPerLevel[nlevels - 1] = std::max(nfeatures - sumFeatures, 0);

    // 复制模式点
    const int npoints = 512;
    const Point* pattern0 = (const Point*)bit_pattern_31_;
    std::copy(pattern0, pattern0 + npoints, std::back_inserter(pattern));

    // 预计算圆形补丁中的行结束点
    umax.resize(HALF_PATCH_SIZE + 1);

    int v, v0, vmax = cvFloor(HALF_PATCH_SIZE * sqrt(2.f) / 2 + 1);
    int vmin = cvCeil(HALF_PATCH_SIZE * sqrt(2.f) / 2);
    const double hp2 = HALF_PATCH_SIZE * HALF_PATCH_SIZE;
    for(v = 0; v <= vmax; ++v)
        umax[v] = cvRound(sqrt(hp2 - v * v));

    // 确保对称性
    for(v = HALF_PATCH_SIZE, v0 = 0; v >= vmin; --v)
    {
        while(umax[v0] == umax[v0 + 1])
            ++v0;
        umax[v] = v0;
        ++v0;
    }
}

详细参数说明

参数

类型

说明

_nfeatures

int

所需特征点数量

_scaleFactor

float

尺度因子,用于图像金字塔

_nlevels

int

图像金字塔的层数

_iniThFAST

int

FAST特征检测初始阈值

_minThFAST

int

FAST特征检测的最小阈值

构造函数内部变量和步骤

变量/步骤

类型

说明

mvScaleFactor

std::vector<float>

尺度因子

mvLevelSigma2

std::vector<float>

尺度方差

mvInvScaleFactor

std::vector<float>

尺度因子的倒数

mvInvLevelSigma2

std::vector<float>

尺度方差的倒数

mvImagePyramid

std::vector<cv::Mat>

图像金字塔

mnFeaturesPerLevel

std::vector<int>

每层的特征点数量

pattern

std::vector<cv::Point>

特征点描述子的模式点

umax

std::vector<int>

预计算圆形补丁中的行结束点

计算尺度因子和方差

for 循环

计算每层的尺度因子和方差

计算尺度因子的倒数和方差的倒数

for 循环

计算每层的尺度因子倒数和方差倒数

初始化图像金字塔

resize 方法

初始化图像金字塔的层数

计算每层的特征点数量

for 循环

计算每层所需的特征点数量

复制模式点

std::copy 方法

复制用于描述子计算的模式点

预计算圆形补丁中的行结束点

for 循环

预计算圆形补丁中的行结束点,并确保对称性

详细说明

  1. 初始化
    • 构造函数接受五个参数:_nfeatures_scaleFactor_nlevels_iniThFAST_minThFAST
    • 使用这些参数初始化对应的成员变量。
  1. 计算尺度因子和方差
    • mvScaleFactormvLevelSigma2 用于存储每层的尺度因子和方差。
    • mvInvScaleFactormvInvLevelSigma2 存储尺度因子和方差的倒数。
  1. 初始化图像金字塔
    • mvImagePyramid 存储图像金字塔的每层。
  1. 计算每层的特征点数量
    • 根据尺度因子和所需的总特征点数量,计算每层的特征点数量并存储在 mnFeaturesPerLevel 中。
  1. 复制模式点
    • bit_pattern_31_ 中的模式点复制到 pattern 中,用于描述子计算。
  1. 预计算圆形补丁中的行结束点
    • umax 存储圆形补丁中每行的结束点,用于计算关键点的方向角度。
    • 确保对称性,使得圆形补丁在计算时更加准确。

通过这些步骤,ORBextractor 构造函数初始化了一个ORB特征提取器实例,准备好在后续步骤中提取特征点和计算描述子。

(4)computeOrientation函数

详细注释代码

/**
 * @brief 计算所有关键点的方向角度
 * 
 * 该函数对给定的图像中的所有关键点计算其方向角度。计算方法是基于图像块的灰度质心。
 * 
 * @param image 输入图像
 * @param keypoints 关键点列表,包含关键点的位置和其他信息
 * @param umax 预计算的行结束点,用于加速计算
 */
static void computeOrientation(const Mat& image, vector<KeyPoint>& keypoints, const vector<int>& umax)
{
    // 遍历所有关键点
    for (vector<KeyPoint>::iterator keypoint = keypoints.begin(),
         keypointEnd = keypoints.end(); keypoint != keypointEnd; ++keypoint)
    {
        // 计算每个关键点的方向角度,并存储在关键点的angle属性中
        keypoint->angle = IC_Angle(image, keypoint->pt, umax);
    }
}

这个函数的作用是在给定图像中计算所有关键点的方向角度,以便在特征描述时能够更好地对特征进行匹配和识别。具体实现是通过调用 IC_Angle 函数,计算图像块的灰度质心来确定方向角度。

以下是 DivideNode 函数的表格:

(5)DivideNode 函数

详细注释代码

/**
 * @brief 将当前节点分割成四个子节点
 * 
 * 该函数将当前节点分割成四个子节点,每个子节点代表当前节点的一个象限。函数首先计算当前节点的水平和垂直中点,然后定义每个子节点的边界。接着,将当前节点的关键点分配给对应的子节点。如果某个子节点只有一个关键点,则标记该子节点为 bNoMore。
 * 
 * @param n1 子节点 1,用于存储当前节点的左上部分。
 * @param n2 子节点 2,用于存储当前节点的右上部分。
 * @param n3 子节点 3,用于存储当前节点的左下部分。
 * @param n4 子节点 4,用于存储当前节点的右下部分。
 */
void ExtractorNode::DivideNode(ExtractorNode &n1, ExtractorNode &n2, ExtractorNode &n3, ExtractorNode &n4)
{
    // 计算当前节点的水平和垂直中点
    const int halfX = ceil(static_cast<float>(UR.x-UL.x)/2);
    const int halfY = ceil(static_cast<float>(BR.y-UL.y)/2);

    // 定义子节点 1 的边界并预分配内存
    n1.UL = UL;
    n1.UR = cv::Point2i(UL.x+halfX, UL.y);
    n1.BL = cv::Point2i(UL.x, UL.y+halfY);
    n1.BR = cv::Point2i(UL.x+halfX, UL.y+halfY);
    n1.vKeys.reserve(vKeys.size());

    // 定义子节点 2 的边界并预分配内存
    n2.UL = n1.UR;
    n2.UR = UR;
    n2.BL = n1.BR;
    n2.BR = cv::Point2i(UR.x, UL.y+halfY);
    n2.vKeys.reserve(vKeys.size());

    // 定义子节点 3 的边界并预分配内存
    n3.UL = n1.BL;
    n3.UR = n1.BR;
    n3.BL = BL;
    n3.BR = cv::Point2i(n1.BR.x, BL.y);
    n3.vKeys.reserve(vKeys.size());

    // 定义子节点 4 的边界并预分配内存
    n4.UL = n3.UR;
    n4.UR = n2.BR;
    n4.BL = n3.BR;
    n4.BR = BR;
    n4.vKeys.reserve(vKeys.size());

    // 将当前节点的关键点分配给对应的子节点
    for(size_t i = 0; i < vKeys.size(); i++)
    {
        const cv::KeyPoint &kp = vKeys[i];
        if (kp.pt.x < n1.UR.x)
        {
            if (kp.pt.y < n1.BR.y)
                n1.vKeys.push_back(kp);
            else
                n3.vKeys.push_back(kp);
        }
        else if (kp.pt.y < n1.BR.y)
            n2.vKeys.push_back(kp);
        else
            n4.vKeys.push_back(kp);
    }

    // 如果子节点只有一个关键点,则标记该子节点为 bNoMore
    if (n1.vKeys.size() == 1)
        n1.bNoMore = true;
    if (n2.vKeys.size() == 1)
        n2.bNoMore = true;
    if (n3.vKeys.size() == 1)
        n3.bNoMore = true;
    if (n4.vKeys.size() == 1)
        n4.bNoMore = true;
}

方法名

作用

传入参数

DivideNode

将当前节点分割成四个子节点

n1 (ExtractorNode&): 子节点 1
n2 (ExtractorNode&): 子节点 2
n3 (ExtractorNode&): 子节点 3
n4 (ExtractorNode&): 子节点 4

详细说明

传入参数名称

类型

描述

n1

ExtractorNode&

子节点 1,用于存储当前节点的左上部分。

n2

ExtractorNode&

子节点 2,用于存储当前节点的右上部分。

n3

ExtractorNode&

子节点 3,用于存储当前节点的左下部分。

n4

ExtractorNode&

子节点 4,用于存储当前节点的右下部分。

函数作用

DivideNode 函数将当前节点分割成四个子节点。每个子节点代表当前节点的一个象限。函数首先计算当前节点的水平和垂直中点,然后定义每个子节点的边界。接着,将当前节点的关键点分配给对应的子节点。如果某个子节点只有一个关键点,则标记该子节点为 bNoMore

相关方法

方法名

作用

传入参数

Reserve

预分配内存以避免重复分配

size (size_t): 要预分配的内存大小

(6)DistributeOctTree 函数

方法名

作用

传入参数

DistributeOctTree

使用四叉树算法将关键点分配到图像的不同区域中,然后选择响应值最大的关键点

vToDistributeKeys (const vectorcv::KeyPoint&): 需要分配的关键点向量
minX (const int&): 图像的最小 X 坐标
maxX (const int&): 图像的最大 X 坐标
minY (const int&): 图像的最小 Y 坐标
maxY (const int&): 图像的最大 Y 坐标
N (const int&): 需要的关键点数量
level (const int&): 当前图像金字塔的层级

详细说明

传入参数名称

类型

描述

vToDistributeKeys

const vector<cv::KeyPoint>&

需要分配的关键点向量。

minX

const int&

图像的最小 X 坐标。

maxX

const int&

图像的最大 X 坐标。

minY

const int&

图像的最小 Y 坐标。

maxY

const int&

图像的最大 Y 坐标。

N

const int&

需要的关键点数量。

level

const int&

当前图像金字塔的层级。

函数作用

DistributeOctTree 函数通过四叉树算法将关键点分配到图像的不同区域中,并从每个区域中选择响应值最大的关键点,以确保选出的关键点在图像上均匀分布。

相关方法

方法名

作用

传入参数

DivideNode

将当前节点分割成四个子节点

n1 (ExtractorNode&): 子节点 1
n2 (ExtractorNode&): 子节点 2
n3 (ExtractorNode&): 子节点 3
n4 (ExtractorNode&): 子节点 4

详细注释代码

/**
 * @brief 使用四叉树算法将关键点分配到图像的不同区域中,然后选择响应值最大的关键点
 * 
 * 该函数通过四叉树算法将关键点分配到图像的不同区域中,并从每个区域中选择响应值最大的关键点,以确保选出的关键点在图像上均匀分布。
 * 
 * @param vToDistributeKeys 需要分配的关键点向量。
 * @param minX 图像的最小 X 坐标。
 * @param maxX 图像的最大 X 坐标。
 * @param minY 图像的最小 Y 坐标。
 * @param maxY 图像的最大 Y 坐标。
 * @param N 需要的关键点数量。
 * @param level 当前图像金字塔的层级。
 * @return vector<cv::KeyPoint> 均匀分布且响应值最大的关键点向量。
 */
vector<cv::KeyPoint> ORBextractor::DistributeOctTree(const vector<cv::KeyPoint>& vToDistributeKeys, const int &minX,
                                       const int &maxX, const int &minY, const int &maxY, const int &N, const int &level)
{
    // 计算初始节点数量   
    const int nIni = round(static_cast<float>(maxX - minX) / (maxY - minY));
    const float hX = static_cast<float>(maxX - minX) / nIni;

    // 初始化节点列表
    list<ExtractorNode> lNodes;
    vector<ExtractorNode*> vpIniNodes(nIni);

    // 创建初始节点并分配关键点
    for (int i = 0; i < nIni; i++)
    {
        ExtractorNode ni;
        ni.UL = cv::Point2i(hX * static_cast<float>(i), 0);
        ni.UR = cv::Point2i(hX * static_cast<float>(i + 1), 0);
        ni.BL = cv::Point2i(ni.UL.x, maxY - minY);
        ni.BR = cv::Point2i(ni.UR.x, maxY - minY);
        ni.vKeys.reserve(vToDistributeKeys.size());

        lNodes.push_back(ni);
        vpIniNodes[i] = &lNodes.back();
    }

    // 将关键点分配给初始节点
    for (size_t i = 0; i < vToDistributeKeys.size(); i++)
    {
        const cv::KeyPoint &kp = vToDistributeKeys[i];
        vpIniNodes[kp.pt.x / hX]->vKeys.push_back(kp);
    }

    // 移除没有关键点的节点或仅包含一个关键点的节点
    for (auto lit = lNodes.begin(); lit != lNodes.end();)
    {
        if (lit->vKeys.size() == 1)
        {
            lit->bNoMore = true;
            ++lit;
        }
        else if (lit->vKeys.empty())
        {
            lit = lNodes.erase(lit);
        }
        else
        {
            ++lit;
        }
    }

    bool bFinish = false;
    int iteration = 0;
    vector<pair<int, ExtractorNode*>> vSizeAndPointerToNode;
    vSizeAndPointerToNode.reserve(lNodes.size() * 4);

    // 迭代分割节点直到满足条件
    while (!bFinish)
    {
        iteration++;
        int prevSize = lNodes.size();
        int nToExpand = 0;
        vSizeAndPointerToNode.clear();

        for (auto lit = lNodes.begin(); lit != lNodes.end();)
        {
            if (lit->bNoMore)
            {
                ++lit;
                continue;
            }

            // 分割节点
            ExtractorNode n1, n2, n3, n4;
            lit->DivideNode(n1, n2, n3, n4);

            // 添加子节点
            if (n1.vKeys.size() > 0)
            {
                lNodes.push_front(n1);
                if (n1.vKeys.size() > 1)
                {
                    nToExpand++;
                    vSizeAndPointerToNode.emplace_back(n1.vKeys.size(), &lNodes.front());
                    lNodes.front().lit = lNodes.begin();
                }
            }
            if (n2.vKeys.size() > 0)
            {
                lNodes.push_front(n2);
                if (n2.vKeys.size() > 1)
                {
                    nToExpand++;
                    vSizeAndPointerToNode.emplace_back(n2.vKeys.size(), &lNodes.front());
                    lNodes.front().lit = lNodes.begin();
                }
            }
            if (n3.vKeys.size() > 0)
            {
                lNodes.push_front(n3);
                if (n3.vKeys.size() > 1)
                {
                    nToExpand++;
                    vSizeAndPointerToNode.emplace_back(n3.vKeys.size(), &lNodes.front());
                    lNodes.front().lit = lNodes.begin();
                }
            }
            if (n4.vKeys.size() > 0)
            {
                lNodes.push_front(n4);
                if (n4.vKeys.size() > 1)
                {
                    nToExpand++;
                    vSizeAndPointerToNode.emplace_back(n4.vKeys.size(), &lNodes.front());
                    lNodes.front().lit = lNodes.begin();
                }
            }

            lit = lNodes.erase(lit);
        }

        // 判断是否完成
        if (lNodes.size() >= N || lNodes.size() == prevSize)
        {
            bFinish = true;
        }
        else if (lNodes.size() + nToExpand * 3 > N)
        {
            // 进一步分割
            while (!bFinish)
            {
                prevSize = lNodes.size();
                auto vPrevSizeAndPointerToNode = vSizeAndPointerToNode;
                vSizeAndPointerToNode.clear();
                sort(vPrevSizeAndPointerToNode.begin(), vPrevSizeAndPointerToNode.end());

                for (int j = vPrevSizeAndPointerToNode.size() - 1; j >= 0; j--)
                {
                    ExtractorNode n1, n2, n3, n4;
                    vPrevSizeAndPointerToNode[j].second->DivideNode(n1, n2, n3, n4);

                    // 添加子节点
                    if (n1.vKeys.size() > 0)
                    {
                        lNodes.push_front(n1

);
                        if (n1.vKeys.size() > 1)
                        {
                            vSizeAndPointerToNode.emplace_back(n1.vKeys.size(), &lNodes.front());
                            lNodes.front().lit = lNodes.begin();
                        }
                    }
                    if (n2.vKeys.size() > 0)
                    {
                        lNodes.push_front(n2);
                        if (n2.vKeys.size() > 1)
                        {
                            vSizeAndPointerToNode.emplace_back(n2.vKeys.size(), &lNodes.front());
                            lNodes.front().lit = lNodes.begin();
                        }
                    }
                    if (n3.vKeys.size() > 0)
                    {
                        lNodes.push_front(n3);
                        if (n3.vKeys.size() > 1)
                        {
                            vSizeAndPointerToNode.emplace_back(n3.vKeys.size(), &lNodes.front());
                            lNodes.front().lit = lNodes.begin();
                        }
                    }
                    if (n4.vKeys.size() > 0)
                    {
                        lNodes.push_front(n4);
                        if (n4.vKeys.size() > 1)
                        {
                            vSizeAndPointerToNode.emplace_back(n4.vKeys.size(), &lNodes.front());
                            lNodes.front().lit = lNodes.begin();
                        }
                    }

                    lNodes.erase(vPrevSizeAndPointerToNode[j].second->lit);

                    if (lNodes.size() >= N)
                        break;
                }

                if (lNodes.size() >= N || lNodes.size() == prevSize)
                    bFinish = true;
            }
        }
    }

    // 保留每个节点中响应值最大的关键点
    vector<cv::KeyPoint> vResultKeys;
    vResultKeys.reserve(nfeatures);
    for (auto lit = lNodes.begin(); lit != lNodes.end(); lit++)
    {
        auto &vNodeKeys = lit->vKeys;
        auto pKP = &vNodeKeys[0];
        float maxResponse = pKP->response;

        for (size_t k = 1; k < vNodeKeys.size(); k++)
        {
            if (vNodeKeys[k].response > maxResponse)
            {
                pKP = &vNodeKeys[k];
                maxResponse = vNodeKeys[k].response;
            }
        }

        vResultKeys.push_back(*pKP);
    }

    return vResultKeys;
}

(7)ComputeKeyPointsOctTree 函数

函数作用

ComputeKeyPointsOctTree 函数通过金字塔图像层次的方式,在每一层中检测并分配关键点,使用四叉树算法确保关键点在图像上均匀分布,然后计算每个关键点的方向。

传入参数

参数名称

类型

描述

allKeypoints

vector<vector<cv::KeyPoint>>&

存储每一层的所有关键点的向量。

详细注释代码

/**
 * @brief 通过金字塔图像层次检测并分配关键点,使用四叉树算法均匀分布关键点,最后计算每个关键点的方向。
 * 
 * @param allKeypoints 存储每一层的所有关键点的向量。
 */
void ORBextractor::ComputeKeyPointsOctTree(vector<vector<KeyPoint>>& allKeypoints)
{
    // 调整 allKeypoints 的大小为金字塔层数
    allKeypoints.resize(nlevels);

    const float W = 30;  // 分块大小

    // 遍历每一层金字塔
    for (int level = 0; level < nlevels; ++level)
    {
        const int minBorderX = EDGE_THRESHOLD - 3;
        const int minBorderY = minBorderX;
        const int maxBorderX = mvImagePyramid[level].cols - EDGE_THRESHOLD + 3;
        const int maxBorderY = mvImagePyramid[level].rows - EDGE_THRESHOLD + 3;

        vector<cv::KeyPoint> vToDistributeKeys;
        vToDistributeKeys.reserve(nfeatures * 10);

        const float width = (maxBorderX - minBorderX);
        const float height = (maxBorderY - minBorderY);

        const int nCols = width / W;
        const int nRows = height / W;
        const int wCell = ceil(width / nCols);
        const int hCell = ceil(height / nRows);

        // 遍历每一块
        for (int i = 0; i < nRows; i++)
        {
            const float iniY = minBorderY + i * hCell;
            float maxY = iniY + hCell + 6;

            if (iniY >= maxBorderY - 3)
                continue;
            if (maxY > maxBorderY)
                maxY = maxBorderY;

            for (int j = 0; j < nCols; j++)
            {
                const float iniX = minBorderX + j * wCell;
                float maxX = iniX + wCell + 6;
                if (iniX >= maxBorderX - 6)
                    continue;
                if (maxX > maxBorderX)
                    maxX = maxBorderX;

                vector<cv::KeyPoint> vKeysCell;
                // 在当前块中检测 FAST 关键点
                FAST(mvImagePyramid[level].rowRange(iniY, maxY).colRange(iniX, maxX),
                     vKeysCell, iniThFAST, true);

                // 如果当前块中没有检测到关键点,则降低阈值再次检测
                if (vKeysCell.empty())
                {
                    FAST(mvImagePyramid[level].rowRange(iniY, maxY).colRange(iniX, maxX),
                         vKeysCell, minThFAST, true);
                }

                // 如果检测到关键点,则将其加入到待分配关键点向量中
                if (!vKeysCell.empty())
                {
                    for (auto vit = vKeysCell.begin(); vit != vKeysCell.end(); ++vit)
                    {
                        (*vit).pt.x += j * wCell;
                        (*vit).pt.y += i * hCell;
                        vToDistributeKeys.push_back(*vit);
                    }
                }
            }
        }

        // 获取当前层的关键点向量引用
        vector<KeyPoint>& keypoints = allKeypoints[level];
        keypoints.reserve(nfeatures);

        // 使用四叉树算法分配关键点
        keypoints = DistributeOctTree(vToDistributeKeys, minBorderX, maxBorderX,
                                      minBorderY, maxBorderY, mnFeaturesPerLevel[level], level);

        const int scaledPatchSize = PATCH_SIZE * mvScaleFactor[level];

        // 为每个关键点添加边界和尺度信息
        const int nkps = keypoints.size();
        for (int i = 0; i < nkps; i++)
        {
            keypoints[i].pt.x += minBorderX;
            keypoints[i].pt.y += minBorderY;
            keypoints[i].octave = level;
            keypoints[i].size = scaledPatchSize;
        }
    }

    // 计算每个关键点的方向
    for (int level = 0; level < nlevels; ++level)
        computeOrientation(mvImagePyramid[level], allKeypoints[level], umax);
}

关键步骤和说明

  1. 初始化和边界计算
    • 函数开始时,初始化了一些变量,用于定义每一层金字塔图像的边界和分块大小。
  1. 遍历金字塔图像的每一层
    • 遍历每一层金字塔图像,计算该层的边界和分块大小。
  1. 遍历每一块并检测 FAST 关键点
    • 在每一块中使用 FAST 算法检测关键点。如果检测不到关键点,则降低阈值再次检测。
    • 将检测到的关键点加入到待分配关键点的向量中。
  1. 使用四叉树算法分配关键点
    • 调用 DistributeOctTree 函数,使用四叉树算法分配关键点,确保关键点在图像上均匀分布。
  1. 为每个关键点添加边界和尺度信息
    • 调整关键点的位置,添加边界和尺度信息。
  1. 计算关键点的方向
    • 调用 computeOrientation 函数,为每个关键点计算方向。

变量名称

类型

描述

allKeypoints

vector<vector<cv::KeyPoint>>&

存储每一层的所有关键点的向量。

W

const float

用于划分单元块的宽度,固定值为30。

level

int

当前处理的金字塔层级。

minBorderX

const int

当前层图像的最小 X 边界。

minBorderY

const int

当前层图像的最小 Y 边界。

maxBorderX

const int

当前层图像的最大 X 边界。

maxBorderY

const int

当前层图像的最大 Y 边界。

vToDistributeKeys

vector<cv::KeyPoint>

用于存储当前层需要分配的所有关键点。

width

const float

当前层图像的宽度(maxBorderX - minBorderX)。

height

const float

当前层图像的高度(maxBorderY - minBorderY)。

nCols

const int

图像划分的列数。

nRows

const int

图像划分的行数。

wCell

const int

每个单元块的宽度。

hCell

const int

每个单元块的高度。

iniY

float

当前处理的单元块的起始 Y 坐标。

maxY

float

当前处理的单元块的结束 Y 坐标。

iniX

float

当前处理的单元块的起始 X 坐标。

maxX

float

当前处理的单元块的结束 X 坐标。

vKeysCell

vector<cv::KeyPoint>

存储当前单元块检测到的关键点。

keypoints

vector<cv::KeyPoint>&

存储当前层的所有关键点。

scaledPatchSize

const int

当前层的关键点补丁大小,按层级缩放。

nkps

const int

当前层的关键点数量。

i

int

当前处理的行索引。

j

int

当前处理的列索引。

vit

vector<cv::KeyPoint>::iterator

当前关键点的迭代器。

umax

vector<int>

用于计算关键点方向的辅助变量,存储最大半径。

mnFeaturesPerLevel

vector<int>

每一层的期望关键点数量。

PATCH_SIZE

const int

关键点补丁大小的固定值。

mvScaleFactor

vector<float>

存储每一层的缩放因子。

mvImagePyramid

vector<cv::Mat>

存储图像金字塔的每一层。

iniThFAST

const int

初始 FAST 角点检测阈值。

minThFAST

const int

最小 FAST 角点检测阈值。

(8)ComputeKeyPointsOld 函数

函数作用

ComputeKeyPointsOld 函数在每一层金字塔图像中检测关键点,并使用分块策略确保关键点均匀分布。然后,根据得分保留最好的关键点,并计算每个关键点的方向。

传入参数

参数名称

类型

描述

allKeypoints

vector<vector<cv::KeyPoint>>&

存储每一层的所有关键点的向量。

详细注释代码

/**
 * @brief 在每一层金字塔图像中检测并均匀分配关键点,根据得分保留最佳关键点,最后计算每个关键点的方向。
 * 
 * @param allKeypoints 存储每一层的所有关键点的向量。
 */
void ORBextractor::ComputeKeyPointsOld(vector<vector<KeyPoint>>& allKeypoints)
{
    // 调整 allKeypoints 的大小为金字塔层数
    allKeypoints.resize(nlevels);

    float imageRatio = (float)mvImagePyramid[0].cols / mvImagePyramid[0].rows;

    // 遍历每一层金字塔
    for (int level = 0; level < nlevels; ++level)
    {
        const int nDesiredFeatures = mnFeaturesPerLevel[level];

        // 计算当前层的列数和行数
        const int levelCols = sqrt((float)nDesiredFeatures / (5 * imageRatio));
        const int levelRows = imageRatio * levelCols;

        const int minBorderX = EDGE_THRESHOLD;
        const int minBorderY = minBorderX;
        const int maxBorderX = mvImagePyramid[level].cols - EDGE_THRESHOLD;
        const int maxBorderY = mvImagePyramid[level].rows - EDGE_THRESHOLD;

        const int W = maxBorderX - minBorderX;
        const int H = maxBorderY - minBorderY;
        const int cellW = ceil((float)W / levelCols);
        const int cellH = ceil((float)H / levelRows);

        const int nCells = levelRows * levelCols;
        const int nfeaturesCell = ceil((float)nDesiredFeatures / nCells);

        vector<vector<vector<KeyPoint>>> cellKeyPoints(levelRows, vector<vector<KeyPoint>>(levelCols));

        vector<vector<int>> nToRetain(levelRows, vector<int>(levelCols, 0));
        vector<vector<int>> nTotal(levelRows, vector<int>(levelCols, 0));
        vector<vector<bool>> bNoMore(levelRows, vector<bool>(levelCols, false));
        vector<int> iniXCol(levelCols);
        vector<int> iniYRow(levelRows);
        int nNoMore = 0;
        int nToDistribute = 0;

        float hY = cellH + 6;

        // 遍历每一行
        for (int i = 0; i < levelRows; i++)
        {
            const float iniY = minBorderY + i * cellH - 3;
            iniYRow[i] = iniY;

            if (i == levelRows - 1)
            {
                hY = maxBorderY + 3 - iniY;
                if (hY <= 0)
                    continue;
            }

            float hX = cellW + 6;

            // 遍历每一列
            for (int j = 0; j < levelCols; j++)
            {
                float iniX;

                if (i == 0)
                {
                    iniX = minBorderX + j * cellW - 3;
                    iniXCol[j] = iniX;
                }
                else
                {
                    iniX = iniXCol[j];
                }

                if (j == levelCols - 1)
                {
                    hX = maxBorderX + 3 - iniX;
                    if (hX <= 0)
                        continue;
                }

                Mat cellImage = mvImagePyramid[level].rowRange(iniY, iniY + hY).colRange(iniX, iniX + hX);

                cellKeyPoints[i][j].reserve(nfeaturesCell * 5);

                // 在当前块中检测 FAST 关键点
                FAST(cellImage, cellKeyPoints[i][j], iniThFAST, true);

                // 如果当前块中没有检测到关键点,则降低阈值再次检测
                if (cellKeyPoints[i][j].size() <= 3)
                {
                    cellKeyPoints[i][j].clear();
                    FAST(cellImage, cellKeyPoints[i][j], minThFAST, true);
                }

                const int nKeys = cellKeyPoints[i][j].size();
                nTotal[i][j] = nKeys;

                if (nKeys > nfeaturesCell)
                {
                    nToRetain[i][j] = nfeaturesCell;
                    bNoMore[i][j] = false;
                }
                else
                {
                    nToRetain[i][j] = nKeys;
                    nToDistribute += nfeaturesCell - nKeys;
                    bNoMore[i][j] = true;
                    nNoMore++;
                }
            }
        }

        // 根据得分保留最佳关键点
        while (nToDistribute > 0 && nNoMore < nCells)
        {
            int nNewFeaturesCell = nfeaturesCell + ceil((float)nToDistribute / (nCells - nNoMore));
            nToDistribute = 0;

            for (int i = 0; i < levelRows; i++)
            {
                for (int j = 0; j < levelCols; j++)
                {
                    if (!bNoMore[i][j])
                    {
                        if (nTotal[i][j] > nNewFeaturesCell)
                        {
                            nToRetain[i][j] = nNewFeaturesCell;
                            bNoMore[i][j] = false;
                        }
                        else
                        {
                            nToRetain[i][j] = nTotal[i][j];
                            nToDistribute += nNewFeaturesCell - nTotal[i][j];
                            bNoMore[i][j] = true;
                            nNoMore++;
                        }
                    }
                }
            }
        }

        vector<KeyPoint>& keypoints = allKeypoints[level];
        keypoints.reserve(nDesiredFeatures * 2);

        const int scaledPatchSize = PATCH_SIZE * mvScaleFactor[level];

        // 根据得分保留最佳关键点并转换坐标
        for (int i = 0; i < levelRows; i++)
        {
            for (int j = 0; j < levelCols; j++)
            {
                vector<KeyPoint>& keysCell = cellKeyPoints[i][j];
                KeyPointsFilter::retainBest(keysCell, nToRetain[i][j]);
                if ((int)keysCell.size() > nToRetain[i][j])
                    keysCell.resize(nToRetain[i][j]);

                for (size_t k = 0, kend = keysCell.size(); k < kend; k++)
                {
                    keysCell[k].pt.x += iniXCol[j];
                    keysCell[k].pt.y += iniYRow[i];
                    keysCell[k].octave = level;
                    keysCell[k].size = scaledPatchSize;
                    keypoints.push_back(keysCell[k]);
                }
            }
        }

        if ((int)keypoints.size() > nDesiredFeatures)
        {
            KeyPointsFilter::retainBest(keypoints, nDesiredFeatures);
            keypoints.resize(nDesiredFeatures);
        }
    }

    // 计算每个关键点的方向
    for (int level = 0; level < nlevels; ++level)
        computeOrientation(mvImagePyramid[level], allKeypoints[level], umax);
}

关键步骤和说明

  1. 初始化和边界计算
    • 函数开始时,初始化了一些变量,用于定义每一层金字塔图像的边界和分块大小。
  1. 遍历金字塔图像的每一层
    • 遍历每一层金字塔图像,计算该层的边界和分块大小。
  1. 遍历每一块并检测 FAST 关键点
    • 在每一块中使用 FAST 算法检测关键点。如果检测不到关键点,则降低阈值再次检测。
    • 将检测到的关键点加入到待分配关键点的向量中。
  1. 根据得分保留最佳关键点
    • 调用 KeyPointsFilter::retainBest 函数,根据得分保留最佳关键点。
  1. 为每个关键点添加边界和尺度信息
    • 调整关键点的位置,添加边界和尺度信息。
  1. 计算关键点的方向
    • 调用 computeOrientation 函数,为每个关键点计算方向。

这个函数通过分块策略确保关键点均匀分布,然后根据得分保留最佳关键点,并计算每个关键点的方向信息。与 ComputeKeyPointsOctTree 不同的是,这个方法更注重在每个块内检测和分配关键点。

变量名称

类型

描述

allKeypoints

vector<vector<cv::KeyPoint>>&

存储每一层的所有关键点的向量。

imageRatio

float

图像的宽高比。

level

int

当前处理的金字塔层级。

nDesiredFeatures

int

当前层期望检测到的关键点数量。

levelCols

int

当前层划分的列数。

levelRows

int

当前层划分的行数。

minBorderX

const int

当前层图像的最小 X 边界。

minBorderY

const int

当前层图像的最小 Y 边界。

maxBorderX

const int

当前层图像的最大 X 边界。

maxBorderY

const int

当前层图像的最大 Y 边界。

W

const int

当前层图像的宽度。

H

const int

当前层图像的高度。

cellW

const int

每个单元块的宽度。

cellH

const int

每个单元块的高度。

nCells

const int

当前层图像划分的单元块总数。

nfeaturesCell

const int

每个单元块期望检测到的关键点数量。

cellKeyPoints

vector<vector<vector<cv::KeyPoint>>>

存储每个单元块检测到的关键点。

nToRetain

vector<vector<int>>

存储每个单元块中保留的关键点数量。

nTotal

vector<vector<int>>

存储每个单元块中检测到的总关键点数量。

bNoMore

vector<vector<bool>>

存储每个单元块是否需要继续分配关键点的标志。

iniXCol

vector<int>

存储每个单元块的起始 X 坐标。

iniYRow

vector<int>

存储每个单元块的起始 Y 坐标。

nNoMore

int

需要停止分配关键点的单元块数量。

nToDistribute

int

需要分配的关键点数量。

hY

float

当前处理的单元块的高度(包含边界调整)。

iniY

float

当前单元块的起始 Y 坐标。

hX

float

当前处理的单元块的宽度(包含边界调整)。

iniX

float

当前单元块的起始 X 坐标。

cellImage

cv::Mat

当前单元块的图像块。

nKeys

int

当前单元块中检测到的关键点数量。

keypoints

vector<cv::KeyPoint>&

存储当前层的所有关键点。

scaledPatchSize

const int

当前层的关键点补丁大小,按层级缩放。

keysCell

vector<cv::KeyPoint>&

存储当前单元块的所有关键点。

k

size_t

当前关键点的索引。

kend

size_t

当前单元块中关键点的数量。

umax

vector<int>

用于计算关键点方向的辅助变量,存储最大半径。

(9)computeDescriptors 函数

/**
 * @brief 计算输入关键点的描述符
 * 
 * @param image 输入图像,用于计算描述符
 * @param keypoints 输入的关键点向量
 * @param descriptors 输出的描述符矩阵,每个关键点对应一个 32 维的描述符
 * @param pattern ORB 描述符计算中使用的采样点模式
 */
static void computeDescriptors(const Mat& image, vector<KeyPoint>& keypoints, Mat& descriptors,
                               const vector<Point>& pattern)
{
    // 初始化描述符矩阵,大小为关键点数量 x 32,类型为 8 位无符号整型
    descriptors = Mat::zeros((int)keypoints.size(), 32, CV_8UC1);

    // 遍历所有关键点
    for (size_t i = 0; i < keypoints.size(); i++)
        // 计算每个关键点的 ORB 描述符,并存储到描述符矩阵中对应的行
        computeOrbDescriptor(keypoints[i], image, &pattern[0], descriptors.ptr((int)i));
}

变量名称

类型

描述

image

const cv::Mat&

输入图像,用于计算描述符。

keypoints

vector<cv::KeyPoint>&

输入的关键点向量。

descriptors

cv::Mat&

输出的描述符矩阵,每个关键点对应一个 32 维的描述符。

pattern

const vector<cv::Point>&

ORB 描述符计算中使用的采样点模式。

i

size_t

循环变量,用于遍历关键点。

(10)ORBextractor::operator() 函数

/**
 * @brief ORB特征点检测和描述符计算运算符重载函数
 * 
 * @param _image 输入图像
 * @param _mask 输入掩码(未使用)
 * @param _keypoints 输出关键点向量
 * @param _descriptors 输出描述符矩阵
 */
void ORBextractor::operator()( InputArray _image, InputArray _mask, vector<KeyPoint>& _keypoints,
                      OutputArray _descriptors)
{ 
    // 如果输入图像为空,则返回
    if(_image.empty())
        return;

    // 获取输入图像矩阵
    Mat image = _image.getMat();
    // 确保输入图像为8位单通道
    assert(image.type() == CV_8UC1 );

    // 预先计算尺度金字塔
    ComputePyramid(image);

    // 存储所有金字塔层的关键点
    vector < vector<KeyPoint> > allKeypoints;
    ComputeKeyPointsOctTree(allKeypoints);  // 采用八叉树分布方法计算关键点
    //ComputeKeyPointsOld(allKeypoints);   // 采用旧方法计算关键点(未使用)

    Mat descriptors;

    // 计算所有层的关键点总数
    int nkeypoints = 0;
    for (int level = 0; level < nlevels; ++level)
        nkeypoints += (int)allKeypoints[level].size();

    // 如果没有检测到关键点,则释放描述符矩阵
    if( nkeypoints == 0 )
        _descriptors.release();
    else
    {
        // 创建描述符矩阵,行数为关键点总数,列数为32(每个描述符的字节数),类型为8位无符号整型
        _descriptors.create(nkeypoints, 32, CV_8U);
        descriptors = _descriptors.getMat();
    }

    // 清空并预留关键点向量的空间
    _keypoints.clear();
    _keypoints.reserve(nkeypoints);

    // 偏移量初始化为0
    int offset = 0;

    // 遍历每一层
    for (int level = 0; level < nlevels; ++level)
    {
        // 获取当前层的关键点
        vector<KeyPoint>& keypoints = allKeypoints[level];
        int nkeypointsLevel = (int)keypoints.size();

        // 如果当前层没有关键点,继续下一层
        if(nkeypointsLevel==0)
            continue;

        // 预处理调整后的图像
        Mat workingMat = mvImagePyramid[level].clone();
        GaussianBlur(workingMat, workingMat, Size(7, 7), 2, 2, BORDER_REFLECT_101);

        // 计算描述符
        Mat desc = descriptors.rowRange(offset, offset + nkeypointsLevel);
        computeDescriptors(workingMat, keypoints, desc, pattern);

        // 更新偏移量
        offset += nkeypointsLevel;

        // 缩放关键点坐标
        if (level != 0)
        {
            float scale = mvScaleFactor[level]; // 获取当前层的缩放因子
            for (vector<KeyPoint>::iterator keypoint = keypoints.begin(),
                 keypointEnd = keypoints.end(); keypoint != keypointEnd; ++keypoint)
                keypoint->pt *= scale;
        }

        // 将当前层的关键点添加到输出关键点向量中
        _keypoints.insert(_keypoints.end(), keypoints.begin(), keypoints.end());
    }
}

这个函数的主要流程如下:

  1. 检查输入图像是否为空,若为空则返回。
  2. 获取输入图像并检查其类型是否为8位单通道。
  3. 计算尺度金字塔。
  4. 使用八叉树方法计算所有层的关键点。
  5. 如果没有检测到任何关键点,释放描述符矩阵。否则,创建描述符矩阵。
  6. 清空并预留输出关键点向量的空间。
  7. 遍历每一层,预处理图像,计算描述符,缩放关键点坐标,并将关键点添加到输出向量中。

(11)ORBextractor::ComputePyramid 函数

/**
 * @brief 计算图像金字塔的各个层
 * 
 * @param image 输入图像
 */
void ORBextractor::ComputePyramid(cv::Mat image)
{
    // 遍历每一层金字塔
    for (int level = 0; level < nlevels; ++level)
    {
        // 获取当前层的缩放因子
        float scale = mvInvScaleFactor[level];
        // 计算当前层的大小
        Size sz(cvRound((float)image.cols*scale), cvRound((float)image.rows*scale));
        // 计算带边界的完整大小
        Size wholeSize(sz.width + EDGE_THRESHOLD*2, sz.height + EDGE_THRESHOLD*2);
        // 创建带边界的临时图像
        Mat temp(wholeSize, image.type()), masktemp;
        // 存储当前层的图像(去除边界部分)
        mvImagePyramid[level] = temp(Rect(EDGE_THRESHOLD, EDGE_THRESHOLD, sz.width, sz.height));

        // 计算缩放后的图像
        if (level != 0)
        {
            // 将上一层的图像缩放至当前层大小
            resize(mvImagePyramid[level-1], mvImagePyramid[level], sz, 0, 0, INTER_LINEAR);

            // 添加边界
            copyMakeBorder(mvImagePyramid[level], temp, EDGE_THRESHOLD, EDGE_THRESHOLD, EDGE_THRESHOLD, EDGE_THRESHOLD,
                           BORDER_REFLECT_101 + BORDER_ISOLATED);            
        }
        else
        {
            // 对第一层图像添加边界
            copyMakeBorder(image, temp, EDGE_THRESHOLD, EDGE_THRESHOLD, EDGE_THRESHOLD, EDGE_THRESHOLD,
                           BORDER_REFLECT_101);            
        }
    }
}

这个函数的主要流程如下:

  1. 遍历金字塔的每一层。
  2. 获取当前层的缩放因子并计算当前层的图像大小。
  3. 计算带有边界的完整图像大小,并创建一个临时图像来存储带边界的图像。
  4. 存储去除边界后的当前层图像。
  5. 如果不是第一层,则将上一层图像缩放到当前层大小,并添加边界;否则,直接对输入图像添加边界。
  • 9
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值