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 << k
: 这是一个位运算符<<
,它将数字1
向左移动k
位。例如,如果k
是3
,那么1 << 3
的结果就是8
(即二进制的0000 1000
)。这种操作通常用来创建一个只有第k
位是1
其他都是0
的掩码。 -
d |= ...
: 这是一个复合赋值运算符|=
,它表示d = d | ...
。这里的|
是按位或运算符,它对两个操作数进行逐位的逻辑或操作。也就是说,如果对应位上至少有一个1
,那么结果位就是1
;否则结果位就是0
。
结合起来,d |= 1 << k
的作用是在 d
的第 k
位设置一个 1
。如果 d
的第 k
位原本是 0
,那么它会变成 1
;如果已经是 1
,则保持不变。