SLAM14讲-第七讲ORB(opencv)实现需要注意的点

 Ptr<DescriptorExtractor> descriptor = ORB::create();
 descriptor->compute(img_1, keypoints_1, descriptors_1); 

注意这里opencv默认描述子的特征维度为32位,每个元素是一个无符号8位整数,实际上就是256个二进制元素,即进行256次像素点的比较.

另外给出作者自己手搓代码的注释版本

与opencv不同的是作者采用了描述子的特征维度为8位,每个元素是一个无符号32位整数

// 定义一个函数ComputeORB,输入参数包括图像img、特征点集合keypoints和描述子集合descriptors。
void ComputeORB(const cv::Mat &img, vector<cv::KeyPoint> &keypoints, vector<DescType> &descriptors) {
  // 定义常量half_patch_size为8,表示在计算描述子时考虑的像素范围大小的一半。
  const int half_patch_size = 8;
  // 定义常量half_boundary为16,表示特征点距离图像边界的安全距离。
  const int half_boundary = 16;
  // 初始化计数器bad_points来记录位于图像边界附近的特征点数量。
  int bad_points = 0;
  
  // 遍历每一个特征点。
  for (auto &kp : keypoints) {
    // 如果特征点位于图像边界附近,则忽略该点并继续下一个特征点。
    if (kp.pt.x < half_boundary || kp.pt.y < half_boundary ||
        kp.pt.x >= img.cols - half_boundary || kp.pt.y >= img.rows - half_boundary) {
      bad_points++; // 增加计数器
      descriptors.push_back({}); // 插入一个空描述子到描述子列表中
      continue; // 跳过此特征点
    }

    // 初始化两个矩形的矩m10和m01。
    float m01 = 0, m10 = 0;
    
    // 在特征点周围以half_patch_size为半径的窗口内计算矩。
    for (int dx = -half_patch_size; dx < half_patch_size; ++dx) {
      for (int dy = -half_patch_size; dy < half_patch_size; ++dy) {
        uchar pixel = img.at<uchar>(kp.pt.y + dy, kp.pt.x + dx); // 获取像素值
        m10 += dx * pixel; // 更新矩m10
        m01 += dy * pixel; // 更新矩m01
      }
    }

    // 计算角度的正弦和余弦值。
    float m_sqrt = sqrt(m01 * m01 + m10 * m10) + 1e-18; // 避免除以零的情况
    float sin_theta = m01 / m_sqrt;
    float cos_theta = m10 / m_sqrt;

    // 创建一个大小为8的描述子desc,并初始化为0。
    DescType desc(8, 0);
    
    // 对于每个特征点,计算32个方向的对比结果。
    for (int i = 0; i < 8; i++) {
      uint32_t d = 0; // 初始化方向对比结果d
      for (int k = 0; k < 32; k++) {
        int idx_pq = i * 32 + k;
        // 获取模式中的两个点p和q。
        cv::Point2f p(ORB_pattern[idx_pq * 4], ORB_pattern[idx_pq * 4 + 1]);
        cv::Point2f q(ORB_pattern[idx_pq * 4 + 2], ORB_pattern[idx_pq * 4 + 3]);

        // 使用旋转角度theta旋转点p和q。
        cv::Point2f pp = cv::Point2f(cos_theta * p.x - sin_theta * p.y, sin_theta * p.x + cos_theta * p.y) + kp.pt;
        cv::Point2f qq = cv::Point2f(cos_theta * q.x - sin_theta * q.y, sin_theta * q.x + cos_theta * q.y) + kp.pt;
        
        // 比较旋转后的点pp和qq处的像素强度,如果pp小于qq,则设置第k位为1。
        if (img.at<uchar>(pp.y, pp.x) < img.at<uchar>(qq.y, qq.x)) {
          d |= 1 << k;
        }
      }
      desc[i] = d; // 存储方向对比结果
    }
    descriptors.push_back(desc); // 将特征点的描述子添加到描述子列表中
  }

  // 打印位于图像边缘的特征点数量和总的特征点数量。
  cout << "bad/total: " << bad_points << "/" << keypoints.size() << endl;
}

注意:d |= 1 << k

这句代码 d |= 1 << k; 是用来构建一个整数 d 的二进制表示,在这里 d 是一个 uint32_t 类型的变量,用于存储特定的对比结果。

让我们分解一下这个表达式:

  1. 1 << k: 这是一个位运算符 <<,它将数字 1 向左移动 k 位。例如,如果 k3,那么 1 << 3 的结果就是 8(即二进制的 0000 1000)。这种操作通常用来创建一个只有第 k 位是 1 其他都是 0 的掩码。

  2. d |= ...: 这是一个复合赋值运算符 |=,它表示 d = d | ...。这里的 | 是按位或运算符,它对两个操作数进行逐位的逻辑或操作。也就是说,如果对应位上至少有一个 1,那么结果位就是 1;否则结果位就是 0

结合起来,d |= 1 << k 的作用是在 d 的第 k 位设置一个 1。如果 d 的第 k 位原本是 0,那么它会变成 1;如果已经是 1,则保持不变。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值