三维重建SFM Stage 1-Feature Matching 特征匹配
本文章为电子科大大二数字动漫综合设计挑战项目,笔者主要对代码作解析,具体原理可以参考网上资料。
环境配置
系统:Linux Ubuntu
IDE: Clion
库:OpenCV 4.8.0
代码解析
图像读入
使用imread读取图像:
- filename:图像路径
- flags:图像读入方式。SIFT算法通常在灰度图像上运行,因为灰度图像更简单,计算效率更高,所以选择读入灰度图像
1 | cv::Mat image1 = cv::imread("/home/maric/CLionProjects/reconstruction/s2/B21.jpg",cv::IMREAD_GRAYSCALE); |
设置掩码
定义了一个与原始图像相同大小的二值图像,其中像素值为0或255。掩码的作用是限制特征点的检测和匹配区域,只对指定区域内的图像进行特征提取和匹配。
在这里,使用矩形区域定义了采样区域的ROI(Region of Interest),然后将掩码中对应区域的像素值设置为255,以便在该区域内进行特征点检测和匹配。
1 | // 定义掩码图像 |
SIFT(Scale-Invariant Feature Transform)算法
SIFT检测能够提取出具有唯一性和尺度不变性的图像特征点,并计算出描述符以进行特征匹配。这使得SIFT在计算机视觉领域的很多应用中都发挥着重要的作用,例如目标识别、图像拼接、三维重建等。
- SIFT参数:
- nfeatures:要检测的关键点数量,默认为0,设定为1000提高效率
- nOctaveLayers:每个金字塔组中的层数,设定为5提高精度
- contrastThreshold:用于过滤关键点的对比度阈值,默认为0.04。对于灰度图像,该值影响较小,若使用彩色图像,则可以对该值进行一定的调整。
- edgeThreshold:用于过滤边缘关键点的阈值,默认为10.0。
- sigma:高斯滤波器的初始方差,默认为1.6
1 | // 创建SIFT特征检测器 |
特征点检测
SIFT算法能够在图像中检测出具有显著特征的位置,这些位置被称为关键点(keypoints)。关键点通常位于图像中的角点、边缘、纹理等具有唯一性的位置,能够在不同尺度和旋转下保持稳定。通过检测关键点,可以在图像中找到具有显著特征的位置。
特征描述
对于每个检测到的关键点,SIFT算法计算一个描述符(descriptor),描述符是一个包含了关键点及其周围区域的局部特征向量。描述符编码了关键点周围区域的梯度方向、梯度大小等信息,具有较高的区分度和尺度不变性。通过计算描述符,可以将关键点转换为数值表示,用于后续的特征匹配和识别任务。
1 | //关键点列表: 图像中的特殊点,具有显著特征的位置,用于定位 |
特征匹配:
SIFT算法通过比较关键点的描述符,可以进行特征匹配。通过在两幅图像中找到具有相似特征描述符的关键点对,可以将两幅图像中的相应特征点进行匹配。特征匹配在图像拼接、目标跟踪、图像检索等应用中非常有用,目前openCV可以使用两种方法进行特征匹配:
Brute-Force
暴力匹配:Brute-Force匹配的优点是简单直观,易于实现。然而,它的计算复杂度较高,特别是在具有大量特征点的情况下。因此,在处理大规模数据时,Brute-Force匹配可能会变得相对缓慢。FLANN
最近邻匹配:使用cv::FlannBasedMatcher
类,它基于特征描述子之间的距离来找到最相似的k个特征点,并根据一定的阈值和筛选规则来决定是否进行匹配,这里笔者采用的是Flann匹配法。- 直接匹配:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 使用FLANN匹配器进行特征匹配
//匹配处理器
cv::Ptr<cv::FlannBasedMatcher> flann = cv::FlannBasedMatcher::create();
//匹配结果列表
std::vector<cv::DMatch> matches;
//匹配结果处理
flann->match(descriptors1, descriptors2, matches);
// 绘制匹配结果
cv::Mat matchImage;
cv::drawMatches(image1, keypoints1, image2, keypoints2, matches, matchImage);
// 显示匹配结果
cv::imshow("SIFT Matches", matchImage);
cv::waitKey(0);可以看到,直接匹配有太多的错误点,我们需要针对此进行优化
优化
距离比例筛选(Distance Ratio Filtering)
在特征匹配中,距离比例筛选是一种常用的筛选方法,用于排除一些不可靠的匹配结果。通过比较匹配结果中最近邻和次近邻之间的距离比例,可以判断匹配的可靠性。通常情况下,如果最近邻的距离远远小于次近邻的距离,那么可以认为这是一个可靠的匹配。
笔者通过遍历所有的匹配结果(matches
向量),对每个匹配结果进行以下判断:如果第一个最近邻的距离小于距离比例阈值k倍数的第二个最近邻的距离,将该匹配结果添加到新的匹配结果中,从而排除不可靠的匹配。
我们对flann匹配代码部分进行重写,首先我们需要计算出新的次近邻匹配
1
2
3
4
5
6//匹配处理器
cv::Ptr<cv::FlannBasedMatcher> flann = cv::FlannBasedMatcher::create();
//匹配结果列表
std::vector<std::vector<cv::DMatch>> matches;
//匹配结果处理
flann->knnMatch(descriptors1, descriptors2, matches,2);定义一个新的Dmatch变量good,用来存放好的匹配结果,同时设置距离比例阈值,通过调整阈值,我们可以对匹配结果进行筛选,最后通过这个good匹配结果生成匹配点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15std::vector<cv::DMatch> good;
float ratio = 0.65;
for (size_t i = 0; i < matches.size(); i++) {
if (matches[i][0].distance < ratio * matches[i][1].distance) {
good.push_back(matches[i][0]);
}
}
// 绘制匹配结果
cv::Mat matchImage;
cv::drawMatches(image1, keypoints1, image2, keypoints2, good, matchImage);
// 显示匹配结果
cv::imshow("SIFT Matches", matchImage);
cv::waitKey(0);
匹配结果
可以看到,匹配结果精确了很多。
源代码
1 | // |