特征检测与特征匹配方法笔记+代码分享

news/2024/11/8 11:42:24/

        在一幅图像中,总能发现其独特的像素点,这些点可以被视为该图像的特征,我们称之为特征点。在计算机视觉领域中,基于特征点的图像特征匹配是一项至关重要的任务,因此,如何定义并识别一幅图像中的特征点显得尤为重要。本文旨在总结计算机视觉领域中最常用的几种特征点及其特征匹配方法。

        在计算机视觉领域,兴趣点(亦称关键点或特征点)的概念已被广泛采纳,应用于目标识别、图像配准、视觉跟踪、三维重建等多个方面。其原理在于,通过对图像中的某些特征点进行局部分析,而非全面审视整幅图像,只要图像中存在足够多可检测且互不相同的稳定兴趣点,并能被精确定位,上述方法便能发挥显著效果。

        以下用于实验的两幅图像分别是:一幅手机抓拍的风景照,以及一幅遥感图像。

 

 1、SURF

        视觉不变性在特征检测中扮演着举足轻重的角色,其中尺度不变性问题的解决尤为棘手。为了攻克这一难题,计算机视觉领域引入了尺度不变特征的概念。其核心思想在于,无论物体在何种尺度下被拍摄,都能检测到一致的关键点,并且每个检测到的特征点都配备了一个尺度因子。在理想状况下,对于两幅图像中不同尺度的同一物体点,其计算所得的尺度因子之比应等于图像尺度的比率。

        近年来,多种尺度不变特征被相继提出,本节将介绍其中的一种:SURF特征。SURF,全称“加速稳健特征”(Speeded Up Robust Feature),它不仅具备尺度不变性,还拥有较高的计算效率。

        接下来,我们将进行常规的特征提取和特征点匹配,以观察其实际效果。

#include "highgui/highgui.hpp"    
#include "opencv2/nonfree/nonfree.hpp"    
#include "opencv2/legacy/legacy.hpp"   
#include <iostream>  using namespace cv;
using namespace std;int main()
{Mat image01 = imread("2.jpg", 1);    //右图Mat image02 = imread("1.jpg", 1);    //左图namedWindow("p2", 0);namedWindow("p1", 0);imshow("p2", image01);imshow("p1", image02);//灰度图转换  Mat image1, image2;cvtColor(image01, image1, CV_RGB2GRAY);cvtColor(image02, image2, CV_RGB2GRAY);//提取特征点    SurfFeatureDetector surfDetector(800);  // 海塞矩阵阈值,在这里调整精度,值越大点越少,越精准 vector<KeyPoint> keyPoint1, keyPoint2;surfDetector.detect(image1, keyPoint1);surfDetector.detect(image2, keyPoint2);//特征点描述,为下边的特征点匹配做准备    SurfDescriptorExtractor SurfDescriptor;Mat imageDesc1, imageDesc2;SurfDescriptor.compute(image1, keyPoint1, imageDesc1);SurfDescriptor.compute(image2, keyPoint2, imageDesc2);//获得匹配特征点,并提取最优配对     FlannBasedMatcher matcher;vector<DMatch> matchePoints;matcher.match(imageDesc1, imageDesc2, matchePoints, Mat());cout << "total match points: " << matchePoints.size() << endl;Mat img_match;drawMatches(image01, keyPoint1, image02, keyPoint2, matchePoints, img_match);namedWindow("match", 0);imshow("match",img_match);imwrite("match.jpg", img_match);waitKey();return 0;
}

        从上述特征点匹配的效果来看,匹配结果并不理想。如果我们基于这样的匹配结果去实现图像拼接或物体追踪,效果肯定会大打折扣。因此,我们需要进一步筛选匹配点,以获取更优质的匹配点,这一过程被称为“去粗取精”。在这里,我们采用了Lowe’s算法来进一步优化匹配点的选择。

        为了剔除因图像遮挡和背景杂乱而产生的无匹配关系的关键点,SIFT算法的提出者Lowe提出了一种基于最近邻距离与次近邻距离比较的SIFT匹配方法。具体而言,对于一幅图像中的一个SIFT关键点,我们在另一幅图像中找出与其欧式距离最近的前两个关键点。如果这两个关键点中,最近距离与次近距离的比率(即ratio)小于某个阈值T,则接受这一对匹配点。由于特征空间的高维性,错误匹配的特征点之间往往存在大量相似的距离,因此其ratio值通常较高。通过降低比例阈值T,虽然SIFT匹配点的数量会减少,但匹配结果会更加稳定;反之,则可能增加错误匹配的数量。

        Lowe推荐的ratio阈值为0.8,但经过对大量存在尺度、旋转和亮度变化的图像进行匹配实验,我们发现ratio的取值在0.4~0.6之间时效果最佳。当ratio小于0.4时,匹配点数量稀少;而当ratio大于0.6时,则可能出现大量错误匹配点。因此,我们建议根据实际需求选择合适的ratio值:

  • 当对匹配准确度要求较高时,ratio可取0.4;
  • 当需要较多的匹配点时,ratio可取0.6;
  • 在一般情况下,ratio可取0.5作为折中选择。
#include "highgui/highgui.hpp"    
#include "opencv2/nonfree/nonfree.hpp"    
#include "opencv2/legacy/legacy.hpp"   
#include <iostream>  using namespace cv;
using namespace std;int main()
{Mat image01 = imread("g2.jpg", 1);    Mat image02 = imread("g4.jpg", 1);    imshow("p2", image01);imshow("p1", image02);//灰度图转换  Mat image1, image2;cvtColor(image01, image1, CV_RGB2GRAY);cvtColor(image02, image2, CV_RGB2GRAY);//提取特征点    SurfFeatureDetector surfDetector(2000);  // 海塞矩阵阈值,在这里调整精度,值越大点越少,越精准 vector<KeyPoint> keyPoint1, keyPoint2;surfDetector.detect(image1, keyPoint1);surfDetector.detect(image2, keyPoint2);//特征点描述,为下边的特征点匹配做准备    SurfDescriptorExtractor SurfDescriptor;Mat imageDesc1, imageDesc2;SurfDescriptor.compute(image1, keyPoint1, imageDesc1);SurfDescriptor.compute(image2, keyPoint2, imageDesc2);FlannBasedMatcher matcher;vector<vector<DMatch> > matchePoints;vector<DMatch> GoodMatchePoints;vector<Mat> train_desc(1, imageDesc1);matcher.add(train_desc);matcher.train();matcher.knnMatch(imageDesc2, matchePoints, 2);cout << "total match points: " << matchePoints.size() << endl;// Lowe's algorithm,获取优秀匹配点for (int i = 0; i < matchePoints.size(); i++){if (matchePoints[i][0].distance < 0.6 * matchePoints[i][1].distance){GoodMatchePoints.push_back(matchePoints[i][0]);}}Mat first_match;drawMatches(image02, keyPoint2, image01, keyPoint1, GoodMatchePoints, first_match);imshow("first_match ", first_match);waitKey();return 0;
}

2、SIFT算法

        SURF算法作为SIFT算法的加速版本,在特征点检测速度上有了显著提升,因此在实时视频流物体匹配等应用中具有显著优势。相比之下,SIFT(尺度不变特征转换,Scale-Invariant Feature Transform)算法虽然特征计算量大,导致特征点提取过程耗时较长,在一些追求速度的场合难以应用,但其基于浮点内核计算特征点的特性,使得SIFT算法在空间和尺度上的定位通常更为精确。因此,在匹配精度要求极高且对匹配速度无严格要求的场合,SIFT算法仍是一个值得考虑的选择。

        值得一提的是,SIFT特征检测的代码实现与SURF算法的代码实现非常相似,仅需要对SURF代码进行微小的修改即可适用于SIFT特征检测。

#include "highgui/highgui.hpp"    
#include "opencv2/nonfree/nonfree.hpp"    
#include "opencv2/legacy/legacy.hpp"   
#include <iostream>  using namespace cv;
using namespace std;int main()
{Mat image01 = imread("1.jpg", 1);    //右图Mat image02 = imread("2.jpg", 1);    //左图namedWindow("p2", 0);namedWindow("p1", 0);imshow("p2", image01);imshow("p1", image02);//灰度图转换  Mat image1, image2;cvtColor(image01, image1, CV_RGB2GRAY);cvtColor(image02, image2, CV_RGB2GRAY);//提取特征点    SiftFeatureDetector siftDetector(2000);  // 海塞矩阵阈值,在这里调整精度,值越大点越少,越精准 vector<KeyPoint> keyPoint1, keyPoint2;siftDetector.detect(image1, keyPoint1);siftDetector.detect(image2, keyPoint2);//特征点描述,为下边的特征点匹配做准备    SiftDescriptorExtractor SiftDescriptor;Mat imageDesc1, imageDesc2;SiftDescriptor.compute(image1, keyPoint1, imageDesc1);SiftDescriptor.compute(image2, keyPoint2, imageDesc2);//获得匹配特征点,并提取最优配对     FlannBasedMatcher matcher;vector<DMatch> matchePoints;matcher.match(imageDesc1, imageDesc2, matchePoints, Mat());cout << "total match points: " << matchePoints.size() << endl;Mat img_match;drawMatches(image01, keyPoint1, image02, keyPoint2, matchePoints, img_match);imshow("match",img_match);imwrite("match.jpg", img_match);waitKey();return 0;
}

没有经过点筛选的匹配效果同样糟糕。下面继续采用Lowe‘s的算法选出优秀匹配点。

#include "highgui/highgui.hpp"    
#include "opencv2/nonfree/nonfree.hpp"    
#include "opencv2/legacy/legacy.hpp"   
#include <iostream>  using namespace cv;
using namespace std;int main()
{Mat image01 = imread("1.jpg", 1);Mat image02 = imread("2.jpg", 1);imshow("p2", image01);imshow("p1", image02);//灰度图转换  Mat image1, image2;cvtColor(image01, image1, CV_RGB2GRAY);cvtColor(image02, image2, CV_RGB2GRAY);//提取特征点    SiftFeatureDetector siftDetector(800);  // 海塞矩阵阈值,在这里调整精度,值越大点越少,越精准 vector<KeyPoint> keyPoint1, keyPoint2;siftDetector.detect(image1, keyPoint1);siftDetector.detect(image2, keyPoint2);//特征点描述,为下边的特征点匹配做准备    SiftDescriptorExtractor SiftDescriptor;Mat imageDesc1, imageDesc2;SiftDescriptor.compute(image1, keyPoint1, imageDesc1);SiftDescriptor.compute(image2, keyPoint2, imageDesc2);FlannBasedMatcher matcher;vector<vector<DMatch> > matchePoints;vector<DMatch> GoodMatchePoints;vector<Mat> train_desc(1, imageDesc1);matcher.add(train_desc);matcher.train();matcher.knnMatch(imageDesc2, matchePoints, 2);cout << "total match points: " << matchePoints.size() << endl;// Lowe's algorithm,获取优秀匹配点for (int i = 0; i < matchePoints.size(); i++){if (matchePoints[i][0].distance < 0.6 * matchePoints[i][1].distance){GoodMatchePoints.push_back(matchePoints[i][0]);}}Mat first_match;drawMatches(image02, keyPoint2, image01, keyPoint1, GoodMatchePoints, first_match);imshow("first_match ", first_match);imwrite("first_match.jpg", first_match);waitKey();return 0;
}

3、ORB算法

        ORB,全称Oriented BRIEF,是BRIEF算法的改进版本。与SIFT算法相比,ORB算法的运行速度快了100倍;与SURF算法相比,则快了10倍。在计算机视觉领域,ORB算法因其在各种测评中展现出的卓越综合性能,而被广泛认为是一种顶尖的特征提取算法。

        既然ORB算法是对BRIEF算法的改进,我们首先要了解BRIEF算法的不足之处。BRIEF算法的优势在于其极高的运行速度,但其缺点也较为明显:不具备旋转不变性,对噪声敏感,以及缺乏尺度不变性。针对其中的前两个缺点,ORB算法提出了一种全新的解决方案。然而,值得注意的是,ORB算法并未解决尺度不变性的问题。

#include "highgui/highgui.hpp"    
#include "opencv2/nonfree/nonfree.hpp"    
#include "opencv2/legacy/legacy.hpp"   
#include <iostream>  using namespace cv;
using namespace std;int main()
{Mat image01 = imread("g2.jpg", 1);Mat image02 = imread("g4.jpg", 1);imshow("p2", image01);imshow("p1", image02);//灰度图转换  Mat image1, image2;cvtColor(image01, image1, CV_RGB2GRAY);cvtColor(image02, image2, CV_RGB2GRAY);//提取特征点    OrbFeatureDetector OrbDetector(1000);  // 在这里调整精度,值越小点越少,越精准 vector<KeyPoint> keyPoint1, keyPoint2;OrbDetector.detect(image1, keyPoint1);OrbDetector.detect(image2, keyPoint2);//特征点描述,为下边的特征点匹配做准备    OrbDescriptorExtractor OrbDescriptor;Mat imageDesc1, imageDesc2;OrbDescriptor.compute(image1, keyPoint1, imageDesc1);OrbDescriptor.compute(image2, keyPoint2, imageDesc2);flann::Index flannIndex(imageDesc1, flann::LshIndexParams(12, 20, 2), cvflann::FLANN_DIST_HAMMING);vector<DMatch> GoodMatchePoints;Mat macthIndex(imageDesc2.rows, 2, CV_32SC1), matchDistance(imageDesc2.rows, 2, CV_32FC1);flannIndex.knnSearch(imageDesc2, macthIndex, matchDistance, 2, flann::SearchParams());// Lowe's algorithm,获取优秀匹配点for (int i = 0; i < matchDistance.rows; i++){if (matchDistance.at<float>(i,0) < 0.6 * matchDistance.at<float>(i, 1)){DMatch dmatches(i, macthIndex.at<int>(i, 0), matchDistance.at<float>(i, 0));GoodMatchePoints.push_back(dmatches);}}Mat first_match;drawMatches(image02, keyPoint2, image01, keyPoint1, GoodMatchePoints, first_match);imshow("first_match ", first_match);imwrite("first_match.jpg", first_match);waitKey();return 0;
}

4、FAST特征检测算子

       FAST,全称Features from Accelerated Segment Test,是一种专门用于快速检测兴趣点的算子。其工作原理极为简洁,仅通过对比少数几个像素的强度值,即可迅速判断一个点是否为关键点。

        与Harris检测器类似,FAST算法同样基于对角点的定义进行工作。它通过对候选特征点周围的图像强度值进行分析,来判断该点是否为关键点。具体而言,以某个点为中心作一个圆,然后检查圆上像素的强度值。如果存在一段连续长度超过圆周长3/4的圆弧,且该圆弧上所有像素的强度值都与圆心的强度值存在显著差异(要么全部更暗,要么全部更亮),则认定该点为关键点。

        由于FAST算法检测兴趣点的速度极快,因此非常适合那些对速度有严格要求的应用场景,如实时视觉跟踪和目标识别等。在这些应用中,需要在实时视频流中快速跟踪或匹配多个点,FAST算法的高效性显得尤为重要。

        在OpenCV中,我们可以使用FastFeatureDetector进行特征点提取。然而,由于OpenCV并未提供针对FAST的专用描述子提取器,因此我们可以借助SiftDescriptorExtractor来实现描述子的提取。

#include "highgui/highgui.hpp"    
#include "opencv2/nonfree/nonfree.hpp"    
#include "opencv2/legacy/legacy.hpp"   
#include <iostream>  using namespace cv;
using namespace std;int main()
{Mat image01 = imread("1.jpg", 1);Mat image02 = imread("2.jpg", 1);imshow("p2", image01);imshow("p1", image02);//灰度图转换  Mat image1, image2;cvtColor(image01, image1, CV_RGB2GRAY);cvtColor(image02, image2, CV_RGB2GRAY);//提取特征点    FastFeatureDetector Detector(50);  //阈值 vector<KeyPoint> keyPoint1, keyPoint2;Detector.detect(image1, keyPoint1);Detector.detect(image2, keyPoint2);//特征点描述,为下边的特征点匹配做准备    SiftDescriptorExtractor   Descriptor;Mat imageDesc1, imageDesc2;Descriptor.compute(image1, keyPoint1, imageDesc1);Descriptor.compute(image2, keyPoint2, imageDesc2);BruteForceMatcher< L2<float> > matcher;   vector<vector<DMatch> > matchePoints;vector<DMatch> GoodMatchePoints;vector<Mat> train_desc(1, imageDesc1);matcher.add(train_desc);matcher.train();matcher.knnMatch(imageDesc2, matchePoints, 2);cout << "total match points: " << matchePoints.size() << endl;// Lowe's algorithm,获取优秀匹配点for (int i = 0; i < matchePoints.size(); i++){if (matchePoints[i][0].distance < 0.6 * matchePoints[i][1].distance){GoodMatchePoints.push_back(matchePoints[i][0]);}}Mat first_match;drawMatches(image02, keyPoint2, image01, keyPoint1, GoodMatchePoints, first_match);imshow("first_match ", first_match);imwrite("first_match.jpg", first_match);waitKey();return 0;
}

如果我们把描述子换成SurfDescriptorExtractor,即FastFeatureDetector + SurfDescriptorExtractor的组合,看看效果

可以看出,这种组合下的特征点匹配并不精确。

如果我们把描述子换成SurfDescriptorExtractor,即FastFeatureDetector + BriefDescriptorExtractor 的组合,看看效果

        尽管FAST特征点检测速度飞快,但其精度却略显不足。这可能会引发一些疑问:既然FAST特征点检测速度这么快,为什么我们还需要借助SiftDescriptorExtractor或其他描述子提取器来提取特征呢?

        对此,我有以下理解:特征点匹配的首要步骤是识别并提取每幅图像的特征点,这被称为特征检测。例如,我们使用FastFeatureDetector或SiftFeatureDetector等工具来完成这一任务。然而,仅仅识别特征点并不足以实现精确匹配,我们还需要对这些特征点进行更深入的分析,通过数学特征(如梯度直方图、局部随机二值特征等)进行描述。在这一步骤中,我们可以选择使用不同的描述子提取器来对特征点进行描述,从而更精确地实现特征点的匹配。

        在OpenCV库中,SURF、ORB和SIFT算法不仅包含特征检测器(FeatureDetector),还配备了描述子提取器(DescriptorExtractor)。因此,在使用这些算法进行特征匹配时,我们通常会直接使用它们自带的配套方法。

        那么,如果我们希望使用FAST角点检测来进行特征点匹配,应该怎么办呢?此时,我们可以采用FastFeatureDetector与BriefDescriptorExtractor的组合方式,这种组合实际上就是著名的ORB算法。因此,特征点检测和特征点匹配是两个独立的步骤,我们可以根据项目需求自由组合这两个步骤的方法。

5、Harris角点检测

        在图像中搜寻有价值的特征点时,角点无疑是一种高效且实用的方法。角点作为图像中易于定位的局部特征,广泛存在于人造物体中,如墙壁、门、窗户、桌子等形成的角落。这些角点之所以具有价值,是因为它们作为两条边缘线的交汇点,不仅是一种显著的二维特征,而且能够被精确地定位,甚至达到子像素级别的精度。

        相比之下,位于图像均匀区域或物体轮廓上的点,以及在同一物体的不同图像上难以重复精确定位的点,其应用价值就相对较低。为了有效地检测这些角点,Harris特征检测算法应运而生,它成为了检测角点的经典方法之一。

        在这里,我们将展示如何利用GoodFeaturesToTrackDetector(一个用于检测图像中可跟踪特征点的OpenCV工具)与SiftDescriptorExtractor(一个用于提取SIFT描述子的OpenCV工具)的组合方式来进行角点检测和描述子提取。当然,除了这种组合方式,还存在其他多种可能的组合,但在此我们不再一一展示。

#include "highgui/highgui.hpp"    
#include "opencv2/nonfree/nonfree.hpp"    
#include "opencv2/legacy/legacy.hpp"   
#include <iostream>  using namespace cv;
using namespace std;int main()
{Mat image01 = imread("1.jpg", 1);Mat image02 = imread("2.jpg", 1);imshow("p2", image01);imshow("p1", image02);//灰度图转换  Mat image1, image2;cvtColor(image01, image1, CV_RGB2GRAY);cvtColor(image02, image2, CV_RGB2GRAY);//提取特征点    GoodFeaturesToTrackDetector Detector(500);  //最大点数,值越大,点越多vector<KeyPoint> keyPoint1, keyPoint2;Detector.detect(image1, keyPoint1);Detector.detect(image2, keyPoint2);//特征点描述,为下边的特征点匹配做准备    SiftDescriptorExtractor  Descriptor;Mat imageDesc1, imageDesc2;Descriptor.compute(image1, keyPoint1, imageDesc1);Descriptor.compute(image2, keyPoint2, imageDesc2);BruteForceMatcher< L2<float> > matcher;   vector<vector<DMatch> > matchePoints;vector<DMatch> GoodMatchePoints;vector<Mat> train_desc(1, imageDesc1);matcher.add(train_desc);matcher.train();matcher.knnMatch(imageDesc2, matchePoints, 2);cout << "total match points: " << matchePoints.size() << endl;// Lowe's algorithm,获取优秀匹配点for (int i = 0; i < matchePoints.size(); i++){if (matchePoints[i][0].distance < 0.6 * matchePoints[i][1].distance){GoodMatchePoints.push_back(matchePoints[i][0]);}}Mat first_match;drawMatches(image02, keyPoint2, image01, keyPoint1, GoodMatchePoints, first_match);imshow("first_match ", first_match);imwrite("first_match.jpg", first_match);waitKey();return 0;
}

 


http://www.ppmy.cn/news/1545343.html

相关文章

【vue2.0入门】认识vue工程

目录 引言一、工程目录介绍1. package.json文件2. src\App.vue3. src\components 文件夹4. src\assets 文件夹5. node_modules 文件夹6. 其他 二、安装 vuejs devtools 插件1. 下载插件2. 配置插件3. 使用插件 三、总结 引言 本系列教程旨在帮助一些零基础的玩家快速上手前端开…

物联优化汽车齿轮锻造

在汽车齿轮的锻造工艺中&#xff0c;锻造温度、锻造压力与行程、锻造速度与锤击方式以及热处理工艺等核心参数扮演着举足轻重的角色。这些参数的精准控制与实时监测&#xff0c;对于提升生产效率、确保产品质量、削减生产成本以及推动生产智能化转型具有不可估量的价值。明达技…

遥测终端机RTU产品如何选型和配置

在配置远程终端单元&#xff08;RTU&#xff09;时&#xff0c;首先需要确认其支持的数据接口类型&#xff0c;例如常见的RS-232、RS-485接口&#xff0c;以及以太网连接方式。这些接口类型将决定RTU如何与其他设备进行数据交换和通信。 接下来&#xff0c;需要确定RTU支持的输…

【Rust中多线程同步机制】

Rust中多线程同步机制 多线程编程Rust中的多线程编程thread::spawnmove Rust中的线程间同步机制Rust线程间同步机制之MutexRust线程间同步机制之读写锁Rust线程同步机制之条件变量Rust中的信号量Rust中的Atomic Rust中线程间的数据传递总结 多线程编程 多线程编程&#xff0c;…

【C/C++】strncpy函数的模拟实现

零.导言 之前我们学习了strncpy函数&#xff0c;不妨我们现在尝试模拟实现strncpy函数的功能。 一.实现strncpy函数的要点 strncpy函数是一种字符串函数&#xff0c;可以按字节拷贝字符类型的数组&#xff0c;因此我们自定义的模拟函数需要两个char类型的指针参数&#xff1b;…

html5拖放

1、什么是拖放&#xff08;Drag 和 Drop&#xff09; 拖放&#xff0c;字面意思就是拖动&#xff0c;放置 在编程里面也是如此,拖放是一种常见的特性&#xff0c;即抓取对象以后拖到另一个位置。 在 HTML5 中&#xff0c;拖放是标准的一部分&#xff0c;任何元素都能够拖放。…

【论文复现】自动化细胞核分割与特征分析

本文所涉及所有资源均在这里可获取。 作者主页&#xff1a; 七七的个人主页 文章收录专栏&#xff1a; 论文复现 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01;&#x1f496;&#x1f496; 自动化细胞核分割与特征分析 引言效果展示HoverNet概述HoverNet原理分析整…

25国考照片处理器使用流程图解❗

1、打开“国家公务员局”网站&#xff0c;进入2025公务员专题&#xff0c;找到考生考务入口 2、点击下载地址 3、这几个下载链接都可以 4、下载压缩包 5、解压后先看“使用说明”&#xff0c;再找到“照片处理工具”双击。 6、双击后会进入这样的界面&#xff0c;点击&…