OpenCV实战——二值特征描述符
- 0. 前言
- 1. ORB 和 BRISK 二值描述符
- 1. ORB 特征描述符
- 1.2 ORB 与 BRISK 算法
- 2. FREAK 二值描述符
- 3. 二值描述符采样模式
- 4. 完整代码
- 相关链接
0. 前言
在《特征描述符》一节中,我们学习了如何使用从图像强度梯度中提取的描述符来描述关键点,这些描述符可以是 64
、128
或更多维的浮点向量。这使得使用这些描述符的算法计算代价较高,为了减少与这些描述符相关的内存和计算负载,引入了二值描述符,使它们易于计算的同时保持对场景和视角变化的鲁棒性。本节,我们将学习一些常见的二值描述符,包括 ORB
(Oriented FAST and Rotated BRIEF
)和 BRISK
(Binary Robust Invariant Scalable Keypoints
) 描述符。
1. ORB 和 BRISK 二值描述符
1. ORB 特征描述符
由于在 OpenCV
检测器和描述符模块顶层定义了通用接口,使用 ORB
等二值描述符与使用 SURF 和 SIFT 等描述符并没有什么不同。
(1) 首先,定义关键点向量:
std::vector<cv::KeyPoint> keypoints1;
std::vector<cv::KeyPoint> keypoints2;
(2) 创建 cv::Mat
结构存储描述符:
cv::Mat descriptors1;
cv::Mat descriptors2;
(3) 创建指向 ORB
检测器的指针:
cv::Ptr<cv::Feature2D> feature = cv::ORB::create(60);
(4) 检测并计算每个图像的关键点和描述符:
feature->detectAndCompute(image1, cv::noArray(), keypoints1, descriptors1);
feature->detectAndCompute(image2, cv::noArray(), keypoints2, descriptors2);
(5) 创建匹配器,并匹配两个图像描述符:
// 构建检测器
cv::BFMatcher matcher(cv::NORM_HAMMING);
// 匹配两张图像描述符
std::vector<cv::DMatch> matches;
matcher.match(descriptors1, descriptors2, matches);
在匹配器中使用汉明范数 (cv::NORM_HAMMING
标志),通过计算对应位置值不同的位数来测量两个二值描述符之间的距离。在多数处理器上,通过使用异或运算和位计数可以高效地实现此操作。
匹配的结果如下图所示:
使用另一种流行的二元特征检测器/描述符 BRISK
可以得到类似的结果,只需调用 cv::BRISK(40)
创建 cv::DescriptorExtractor
实例,cv::BRISK()
的第一个参数是一个用于控制检测点数量的阈值。
1.2 ORB 与 BRISK 算法
ORB
算法在多个尺度上检测定向特征点,基于这个结果,ORB
描述符通过使用强度比较来提取每个关键点的表示。ORB
的构建基于 BRIEF
(Binary Robust Invariant Scalable Keypoints
) 描述符,通过选择关键点周围定义的邻域内的一对随机点创建二值描述符,然后比较两个像素点的强度值,如果第一个点的强度更高,则将值 1
分配给相应的描述符位值,否则,赋值为 0
。对多个随机点对重复以上比较过程可以得到由多个二进制位组成的描述符;通常,使用 128
到 512
位。
ORB
使用以上方案,接下来要决定使用哪组点对来构建描述符,即使点对是随机选择的,一旦它们被选中,就必须执行二值测试来构建所有关键点的描述符,以确保结果的一致性。为了使描述符更加独特,它们必须具有可比较性。此外,由于每个关键点方向的确定性,当强度模式分布相对于该方向被归一化时(即当点坐标相对于该关键点方向给出时),会在强度模式分布中引入一些偏差。根据这些考虑和实验验证,ORB
确定了一组具有高方差和最小相关性的 256
个点对。换言之,所选的二值测试在各种关键点上有相等机会为 0
或 1
,即尽可能彼此独立的测试。
除了控制特征检测过程的参数外,cv::ORB
构造函数还包括两个与其描述符相关的参数。第一个参数用于指定在其中选择点对的图像块大小(默认值为 31x31
);第二个参数用于指定使用三元组或四元组点而非默认的点对执行测试。
BRISK
的描述符与 ORB
非常相似,但其强度比较方法具有以下两个主要差异。首先,BRISK并不是从邻域的 31x31
点中随机选择点,而是从具有等距位置的一组同心圆(由 60
个点组成)中选择点;其次,每个样本点的强度是一个高斯平滑值,其 σ
值与到中心关键点的距离成正比,从这些点中,BRISK
选择 512
个点对。
2. FREAK 二值描述符
除了以上二值描述符外,在 OpenCV
中也实现了其他二值描述符,接下来,我们将介绍另一个描述符 FREAK
。
FREAK
(Fast Retina Keypoint
) 也是一个二值描述符,但它没有关联的检测器,可以应用于检测到的任何一组关键点,例如 SIFT
、SURF
或 ORB
。与 BRISK
一样,FREAK
描述符也基于同心圆上定义的采样模式。但其设计思想借鉴了人眼的工作原理,在视网膜上,神经细胞的密度程度随着到中央凹距离的增加而降低。因此,FREAK
构建了一个由 43
个点组成的采样模式,其中点的密度在中心点附近较多,为了获得其强度,每个点都用高斯核滤波,高斯核的大小也随着到中心的距离而增加。
为了确定成对比较的数量,通过遵循类似于 ORB
的策略进行了实验验证。通过分析数千个关键点,保留了方差最大和相关性最低的二值测试,得到最佳比较数量为 512
对。
FREAK
还引入了级联执行描述符比较的想法。也就是说,首先执行表示粗粒度信息的前 128
位(对应于在外围对较大高斯核进行的测试),只有当比较的描述符通过这个初始步骤时,才会执行剩余的测试。
使用 ORB
检测到的关键点,通过创建 cv::DescriptorExtractor
实例来提取 FREAK
描述符:
feature = cv::xfeatures2d::FREAK::create();
匹配结果如下所示:
3. 二值描述符采样模式
下图说明了本节中介绍的三个二值描述符的采样模式:
第一个采样模式表示 ORB
描述符,其在正方形网格上随机选择点对,每对由一条线连接的点代表一个可能的测试来比较两个像素强度,上图中只展示了八个这样的点对,ORB
默认使用 256
对;第二个表示 BRISK
采样模式,点在显示的圆圈上均匀采样(为清楚起见,我们在图中只标示了第一个圆圈上的点);第三个表示 FREAK
的对数极坐标采样网格,BRISK
的点分布均匀,而 FREAK
靠近中心的点密度更高,例如,在 BRISK
中,可能在外圆上找到 20
个点,而在 FREAK
中,外圆可能仅包含 6
个点。
4. 完整代码
完整代码 binaryDescriptors.cpp
如下所示:
#include <iostream>
#include <vector>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/objdetect/objdetect.hpp>
#include <opencv2/xfeatures2d.hpp>
int main() {
// 图像匹配
// 1. 读取图像
cv::Mat image1 = cv::imread("1.png", cv::IMREAD_GRAYSCALE);
cv::Mat image2 = cv::imread("2.png", cv::IMREAD_GRAYSCALE);
// 2. 定义关键点向量和描述符
std::vector<cv::KeyPoint> keypoints1;
std::vector<cv::KeyPoint> keypoints2;
cv::Mat descriptors1;
cv::Mat descriptors2;
// 3. 定义特征检测器/描述符
cv::Ptr<cv::Feature2D> feature = cv::ORB::create(60);
// cv::Ptr<cv::Feature2D> feature = cv::BRISK::create(80);
// 4. 关键点检测及描述符
feature->detectAndCompute(image1, cv::noArray(), keypoints1, descriptors1);
feature->detectAndCompute(image2, cv::noArray(), keypoints2, descriptors2);
// 绘制特征点
cv::Mat featureImage;
cv::drawKeypoints(image1,keypoints1,featureImage,cv::Scalar(255,255,255),cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
cv::namedWindow("ORB");
cv::imshow("ORB",featureImage);
std::cout << "Number of ORB keypoints (image 1): " << keypoints1.size() << std::endl;
std::cout << "Number of ORB keypoints (image 2): " << keypoints2.size() << std::endl;
// FREAK
// feature = cv::xfeatures2d::FREAK::create();
// feature->compute(image1, keypoints1, descriptors1);
// feature->compute(image1, keypoints2, descriptors2);
// 构建检测器
cv::BFMatcher matcher(cv::NORM_HAMMING);
// 匹配两张图像描述符
std::vector<cv::DMatch> matches;
matcher.match(descriptors1, descriptors2, matches);
// 绘制匹配
cv::Mat imageMatches;
cv::drawMatches(image1, keypoints1, // 第一张图像及其关键点
image2, keypoints2, // 第二张图像及其关键点
matches, // 匹配
imageMatches, // 生成结果
cv::Scalar(255, 255, 255), // 线颜色
cv::Scalar(255, 255, 255), // 点颜色
std::vector<char>(), // 掩码
cv::DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS | cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
cv::namedWindow("ORB Matches");
cv::imshow("ORB Matches", imageMatches);
// cv::namedWindow("FREAK Matches");
// cv::imshow("FREAK Matches", imageMatches);
std::cout << "Number of matches: " << matches.size() << std::endl;
cv::waitKey();
return 0;
}
相关链接
OpenCV实战(1)——OpenCV与图像处理基础
OpenCV实战(2)——OpenCV核心数据结构
OpenCV实战(3)——图像感兴趣区域
OpenCV实战(4)——像素操作
OpenCV实战(5)——图像运算详解
OpenCV实战(6)——OpenCV策略设计模式
OpenCV实战(7)——OpenCV色彩空间转换
OpenCV实战(8)——直方图详解
OpenCV实战(9)——基于反向投影直方图检测图像内容
OpenCV实战(10)——积分图像详解
OpenCV实战(11)——形态学变换详解
OpenCV实战(12)——图像滤波详解
OpenCV实战(13)——高通滤波器及其应用
OpenCV实战(14)——图像线条提取
OpenCV实战(15)——轮廓检测详解
OpenCV实战(16)——角点检测详解
OpenCV实战(17)——FAST特征点检测
OpenCV实战(18)——特征匹配