features2d_1_特征点检测与匹配

来源:互联网 发布:网络常用英文缩写 编辑:程序博客网 时间:2024/05/17 18:25

一角点检测

兴趣点=关键点=特征点,检测足够多的点,同时他们的区分度很高,则可以精确定位稳定的特征
特征类型分为:
[1]边缘
[2]角点(感兴趣关键点)
[3]斑点(Blobs) (感兴趣区域)

角点是在任意方向的一个微小变动都会引起灰度很大的变化的点。
角点:
[1]一阶导数(灰度的梯度)的局部最大所对应的像素点
[2]两条及两条以上边缘的交点
[3]图像中梯度和梯度方向的变化速率都很高的点
[4]角点处的一阶导数最大,二阶导数为0,它指示了物体边缘变化不连续的方向

1,Harris角点检测
基于灰度图像的角点提取算法,稳定性高。采用高斯滤波,速度较慢,角点信息有丢失和位置偏移的现象,有聚簇现象。
针对每一个像素,在blockSize×blockSize的邻域内,计算
这里写图片描述
这里写图片描述
根据设定的阈值threshold,如果R>threshold,则判断为角点

void cornerHarris(InputArray src, OutputArray dst,                   int blockSize, int ksize, double k,                   int borderType=BORDER_DEFAULT )//src,单通道8-bit或浮点型图像//dst,与src同size,type为CV_32FC1,用来存储R值//blockSize,针对每一个像素的邻域大小//ksize,进行Sobel边缘检测的ksize//k,计算公式中的参数k//borderType,边界拓展类型                  

应用:

//转化成灰度图cv::cvtColor(image,Image,CV_BGR2GRAY);//进行角点检测cv::Mat process;int blockSize=2;int ksize=3;double k=0.04;cv::cornerHarris(Image,process,blockSize,ksize,k);//进行归一化,使值落在0~255cv::normalize(process,process,0,255,cv::NORM_MINMAX,              CV_32FC1);//对于大于设定阈值的点,判断为角点int thresh=120;for(int i=0;i<result.rows;i++)for(int j=0;j<result.cols;j++)if(process.at<float>(i,j)>thresh){cv::circle(result,cv::Point(j,i),5,cv::Scalar(0,0,255),2);}

效果图:
这里写图片描述

2,Shi-Tomasi角点检测
用于强角点的检测,是Harris算法的改进版,两个特征值中较小的一个大于最小阈值,会得到强角点
角点检测原理:
[1]首先利用cornerHarris()或者cornerMnEigenVal(),计算角点的质量
[2]执行非极大抑制,在3*3邻域内局部最大值被保留
[3]有最小特征值比这里写图片描述还要小的点被抛弃
[4]剩下的角点,根据质量进行递减排序
[5]根据角点间距离minDistance,留下质量好的角点

void goodFeaturesToTrack(InputArray image,                          OutputArray corners,                          int maxCorners,                          double qualityLevel,                          double minDistance,                          InputArray mask=noArray(),                          int blockSize=3,                          bool useHarrisDetector=false,                          double k=0.04 )/*image,单通道,8-bit或者32位浮点型 *corners,输出被检测到的角点向量,vector<Point2f> *maxCorners,角点的最大数量 *qualityLevel,用于计算最小特征值的参数,最小特征值 *=qualityLevel×最大特征值,一般为0.01/0.1 *minDistance,角点间的最小距离 *mask,掩码 *blockSize,每个像素邻域 *useHarrisDetector,如果true使用cornerHarris(),否则 *cornerMnEigenVal() *k,Harris的参数 */                         

应用:

//转化成灰度图cv::cvtColor(image,Image,CV_BGR2GRAY);//查找角点std::vector<cv::Point2f> corners;cv::goodFeaturesToTrack(Image,corners,100,0.01,8);cv::Size winSize(5,5);cv::Size zeroZone(-1,-1);cv::TermCriteria criteria=cv::TermCriteria(CV_TERMCRIT_EPS+CV_TERMCRIT_ITER,40,0.01);//进行亚像素精确化cv::cornerSubPix(Image,corners,winSize,zeroZone,criteria);//进行标记for(int i=0;i<corners.size();i++){cv::circle(result,corners[i],5,cv::Scalar(0,0,255),2);}

效果图:
这里写图片描述

3,创建自己的角点检测
1>计算角点检测特征值和特征向量
对于每一个像素p,在blockSize×blockSize区域内,通过Sobel计算协变矩阵:
这里写图片描述

这里写图片描述为M的非排序特征值。
x1,y1为这里写图片描述的特征向量
x2,y2为这里写图片描述的特征向量
存储在dst中这里写图片描述

void cornerEigenValsAndVecs(InputArray src, OutputArray dst,                             int blockSize, int ksize,                             int borderType=BORDER_DEFAULT )//src,单通道,8-bit或浮点型图像//dst,与src同size,type为CV_32FC(6)//blockSize,邻域大小//ksize,Sobel边缘检测的ksize//borderType,边界拓展类型                            

2>为角点检测,计算梯度矩阵的最小的特征值
用来寻找这里写图片描述中的最小值

void cornerMinEigenVal(InputArray src, OutputArray dst,                        int blockSize, int ksize=3,                        int borderType=BORDER_DEFAULT )//src,单通道,8-bit或浮点型图像//dst,存储最小的特征值,与src同size,type为CV_32FC1//blockSize,邻域大小//ksize,Sobel边缘检测的ksize//borderType,边界拓展类型                       

[1]应用_Harris角点检测:

//转化成灰度图cv::cvtColor(image,Image,CV_BGR2GRAY);//找到特征值cv::Mat dst;cv::cornerEigenValsAndVecs(Image,dst,3,3);//将特征值带入公式,得到Mcint k=0.04;cv::Mat Mc(dst.size(),CV_32FC1);for(int i=0;i<dst.rows;i++)for(int j=0;j<dst.cols;j++){float lam1=dst.at<cv::Vec6f>(i,j)[0];float lam2=dst.at<cv::Vec6f>(i,j)[1];Mc.at<float>(i,j)=lam1*lam2-k*std::pow((lam1+lam2),2);}//找到Mc中的最大值,最小值double minVal,maxVal;cv::minMaxLoc(Mc,&minVal,&maxVal,0,0);int max_qualityLevel=100,qualityLevel=30;//对每一点是否达到角点标准进行判断for(int i=0;i<Mc.rows;i++)for(int j=0;j<Mc.cols;j++)if(Mc.at<float>(i,j)>minVal+(maxVal-minVal)*                     qualityLevel/max_qualityLevel){         cv::circle(result,cv::Point(j,i),4,cv::Scalar(0,0,255),2);}

效果图:
这里写图片描述

[2]应用_Shi_Tomashi角点检测:

//转化成灰度图cv::cvtColor(image,Image,CV_BGR2GRAY);//得到特征值,并且计算得到最小值cv::Mat dst;cv::cornerMinEigenVal(Image,dst,3,3);//找到dst中,最大值,最小值double minVal,maxVal;cv::minMaxLoc(dst,&minVal,&maxVal,0,0);int max_qualityLevel=100,qualityLevel=30;//对每一点是否达到,角点标准进行判断for(int i=0;i<dst.rows;i++)for(int j=0;j<dst.cols;j++)if(dst.at<float>(i,j)>minVal+(maxVal-minVal)                     *qualityLevel/max_qualityLevel){cv::circle(result,cv::Point(j,i),4,cv::Scalar(0,0,255),2);}

效果图:
这里写图片描述

二特征检测与匹配

1,基类和数据结构
1>KeyPoint类

class KeyPoint{Point2f pt      //坐标float size      //特征点的邻域直径float angle     //特征点的方向float response  //特征点的强度反应,可用于sortint octave      //特征点所在的金字塔的组int class_id    }

2>FeatureDetector类
二维图像中用于特征探测的抽象基类

[1]关键点探测

//从image中探测关键点void FeatureDetector::detect(const Mat& image,                              vector<KeyPoint>& keypoints,                              const Mat& mask=Mat() ) const//image图像//keypoints探测到的关键点//mask,掩码,8-bit                             

[2]特征探测器的创建

Ptr<FeatureDetector> FeatureDetector::create(const string& detectorType)/*返回相应名字的特征探测器的指针 *有以下特征探测名称: *"FAST"FastFeatureDetector *"STAR"StarFeatureDetector *"SIFT"SIFT (nonfree module) *"SURF"SURF (nonfree module) *"ORB"ORB *"BRISK"BRISK *"MSER"MSER *"GFTT"GoodFeaturesToTrackDetector *"HARRIS"GoodFeaturesToTrackDetector(Harris)    *"Dense"DenseFeatureDetector *"SimpleBlob"SimpleBlobDetector */

3>DescriptorExtractor类
用于计算图像关键点描述符的抽象基类

[1]计算图像中关键点的描述符

void DescriptorExtractor::compute(const Mat& image,                                  vector<KeyPoint>& keypoints,                                  Mat& descriptors) const//image,图像//keypoints,输入的关键点,不能计算描述符的关键点被删除,剩下的被//排序,还有部分点被添加进来//descriptors,描述符                                 

[2]描述符提取类的创建

Ptr<DescriptorExtractor> DescriptorExtractor              ::create(const string& descriptorExtractorType)/*返回相应描述符提取类的指针 *有以下提取类名称: *"SIFT" – SIFT *"SURF" – SURF *"BRIEF" – BriefDescriptorExtractor *"BRISK" – BRISK *"ORB" – ORB *"FREAK" – FREAK */              

4>DMatch类
用于匹配关键点描述符的类

struct DMatch{    int queryIdx;   //查找描述索引    int trainIdx;   //训练描述索引    int imgIdx;     //训练图像索引    float distance; //描述符之间的距离};

5>DescriptorMatcher类
用于图像匹配的抽象基类

[1]从查询集中为每个描述符找到最好的匹配

void DescriptorMatcher::match(const Mat& queryDescriptors,                               const Mat& trainDescriptors,                               vector<DMatch>& matches,                               const Mat& mask=Mat() ) const//queryDescriptors,查询集的描述符//trainDescriptors,训练集的描述符,这一部分是没有添加到类中的描述符//matches,匹配结果//mask,掩码                              

[2]从查询集中为每个描述符找到k个最好的匹配

void DescriptorMatcher     ::knnMatch(const Mat& queryDescriptors,                 const Mat& trainDescriptors,                 vector<vector<DMatch>>& matches, int k,                 const Mat& mask=Mat(),                 bool compactResult=false ) const//queryDescriptors,查询集的描述符//trainDescriptors,训练集的描述符,这一部分是没有添加到类中的描述符//matches,匹配结果,每一个matches[i]都有k个或者少于k个的匹配,//针对相应的queryDescriptors//k,找到k个最好结果,并且以质量递减返回//compactResult,if false,matches的size与//queryDescriptors的rows相同,if true,不包含完全掩盖查询符的//匹配结果

[3]针对每一个查询集描述符,查找不超过最远距离的匹配

void DescriptorMatcher     ::radiusMatch(const Mat& queryDescriptors,                    const Mat& trainDescriptors,                              vector<vector<DMatch>>& matches,                    float maxDistance,                    const Mat& mask=Mat(),                    bool compactResult=false ) const//queryDescriptors,查询集的描述符//trainDescriptors,训练集的描述符,这一部分是没有添加到类中的描述符//matches,匹配结果,matches[i]是queryDescriptors中对应的匹//配结果,并且匹配描述符间的距离小于maxDistance,返回的顺序是以距//离递减排序//maxDistance,匹配描述符间的最远距离

[4]描述符匹配类的创建

Ptr<DescriptorMatcher> DescriptorMatcher::create(const string& descriptorMatcherType)/*返回相应描述符匹配类的指针 *描述符匹配类的名称: *BruteForce(默认L2) *BruteForce-L1 *BruteForce-Hamming *BruteForce-Hamming(2) *FlannBased */

[5]向类中增加用于训练的描述符

void DescriptorMatcher::add(const vector<Mat>& descriptors)//descriptors,每一个descriptors[i]来自同一张train image的描述符

[6]训练描述符匹配类

void DescriptorMatcher::train()//训练描述符匹配类,针对所有匹配方法,在matching之前train都会运//行

6>DrawMatchesFlags
matches和Keypoints的flag类

struct DrawMatchesFlags{    enum    {        DEFAULT = 0,         //输出图像会被创建,源图像,matches,keypoints会被绘制        DRAW_OVER_OUTIMG = 1,        //输出图像不会被创建,在已有图像上绘制                    NOT_DRAW_SINGLE_POINTS = 2,         //未匹配关键点不会被绘制        DRAW_RICH_KEYPOINTS = 4        //关键点的方向和大小都会被绘制     };};                   

2,特征点检测
1>drawKeypoints
绘制检测到的关键点

void drawKeypoints(const Mat& image,                    const vector<KeyPoint>& keypoints,                      Mat& outImage,                    const Scalar& color=Scalar::all(-1),                    int flags=DrawMatchesFlags::DEFAULT )//image,源图像//keypoints,源图像中的关键点//outImage,输出图像//color,颜色//flags,绘制标记

2>FastFeatureDetector
使用FAST算法检测关键点
检测步骤:
[1]以某个点为中心做一个圆,根据圆上的像素值判断该店是否为关键点
[2]如果存在这样一段圆弧,它的连续长度超过周长的3/4,并且他上面的所有像素强度都与圆心强度值明显不同,则认定这是一个关键点

class FastFeatureDetector : public FeatureDetector

应用:

//创建关键点探测器cv::Ptr<cv::FeatureDetector> detector=cv::FeatureDetector::create("FAST");//也可以(效果相同)://FastFeatureDetector dector//关键点检测std::vector<cv::KeyPoint> keypoint;detector->detect(Image,keypoint);//关键点绘制cv::drawKeypoints(Image,keypoint,result,cv::Scalar::all(-1));

原图与效果图:
这里写图片描述 这里写图片描述
time(s):0.00469645

2>BRISK
多尺度FAST特征检测,既可以快速检测,又不随尺度改变而改变。
检测步骤:
[1]首先通过两个下采样过程构建一个图像金字塔
[2]在图像金字塔上应用FAST特征检测器,只有局部最大值的像素才可能称为关键点
[3]这个点与上下两层的相邻像素比较评分,如果它的评分在尺度上也更高,则它是个关键点

应用:

//另外一种特征点探测器的创建方法cv::Ptr<cv::FeatureDetector> detector=new cv::BRISK(1,3);//通过BRISK的构造函数,来创建特征探测器//有两个参数://[1]thresh,为FAST/AGAST的阈值,是中央像素与周围像素之间的区别//[2]octaves,检测八度,金字塔的多尺度数目

效果图:
这里写图片描述
time(s):0.793073

3>SIFT
尺度不变特征转换,采用图像空间和尺度空间的局部最大值,使用拉普拉斯滤波器响应。
SIFT基于浮点内核计算特征,则在空间和尺度上更加精确,计算效率更低。
效果图:
这里写图片描述
time(s):0.0790308

4>SURF
是SIFT的加速版,尺度不变的特征检测,不仅在任何尺度下拍摄的物体都能够检测到相同的特征点,而且每个被检测的特征都对应一个尺度因子。
检测步骤:
[1]针对每个像素计算Hessian矩阵,该矩阵衡量了一个函数的局部曲率。
[2]根据矩阵的行列式,得到曲率的强度,在普通空间和尺度空间,Hessian行列式达到了局部最大值,则认为是尺度不变特征
应用:

 cv::Ptr<cv::FeatureDetector> detector=                              new cv::SURF(900);//hessianThreshold,为hessian矩阵的阈值 

效果图:
这里写图片描述
time(s):0.0227669

5>ORB
定向FAST和旋转BRISK
检测步骤:
[1]创建图像金字塔,在具有关键点评分的位置接受N个强度最大的关键点(使用Harris角点强度衡量方法)
[2]每个被检测的关键点总是关联了一个方向,ORB使用关键点周围的圆形邻域的重心方向

缺点:
[1]不具备旋转不变性
[2]对噪声敏感
[3]不具备尺度不变性

应用:

cv::Ptr<cv::FeatureDetector> detector=            new cv::ORB(200,1.2,8);//参数://[1]nfeatures,最多的关键点总数//[2]scaleFactor,图层之间的缩放因子//[3]nlevels,金字塔的图层数量            

效果图:
这里写图片描述
time(s):0.00400557

3,特征匹配
1>drawMatches
两张image中匹配结果的绘制

void drawMatches(const Mat& img1, const vector<KeyPoint>& keypoints1,  const Mat& img2, const vector<KeyPoint>& keypoints2,  const vector<DMatch>& matches1to2, Mat& outImg,  const Scalar& matchColor=Scalar::all(-1),  const Scalar& singlePointColor=Scalar::all(-1),  const vector<char>& matchesMask=vector<char>(),  int flags=DrawMatchesFlags::DEFAULT ) /*img1,第一张图像,keypoints1,第一张图像的关键点  *img2,第二张图像,keypoints2,第二张图像的关键点  *matches1to2,从第一张图像到第二张图像的匹配结果  *outImg,输出图像  *matchColor,匹配上的颜色,singlePointColor,关键点没有匹配  *上的颜色  */

2>图像匹配

[1]BFMatcher类
蛮力描述符匹配器,针对第一组每一个descriptor,在第二组与每一个descriptor进行比较得到最有可能的

class BFMatcher : public DescriptorMatcher//构造函数BFMatcher::BFMatcher(int normType=NORM_L2,                      bool crossCheck=false)/*normType(其中之一): *NORM_L1/NORM_L2,针对SIFT/SURF的descriptors *NORM_HAMMING,针对ORB/BRISK/BRIEF的descriptors *NORM_HAMMING2,针对ORB,并且WTA_K==3/4(ORB构造函数) *crossCheck,if false,针对每个描述符,找到k个最近邻 *if true,knnMatch()方法k=1时,返回pair(i,j),则两组描述符中 *第i,和第j个最相似 */                     

[2]FlannBasedMatcher类
基于Flann的描述符匹配器,当描述符很多时比BFMatcher更有效,但是不支持允许匹配的描述符集

[3]说明
SIFT,SURF描述符类型float
ORB,BRIFT描述符类型uchar

支持float有FlannBasedMatcher,BFMatcher
支持uchar有BFMatcher

所以ORB,BRIFT的描述符类型只可使用BFMatcher

应用:
[1]常规match

int min=1000;cv::SurfFeatureDetector detector(min);std::vector<cv::KeyPoint> keypoint1;std::vector<cv::KeyPoint> keypoint2;//image均为灰度图detector.detect(Image1,keypoint1);detector.detect(Image2,keypoint2);//计算关键点描述符cv::SurfDescriptorExtractor extractor;cv::Mat descriptor1,descriptor2;extractor.compute(Image1,keypoint1,descriptor1);extractor.compute(Image2,keypoint2,descriptor2);//进行匹配cv::BFMatcher matcher(cv::NORM_L2);std::vector<cv::DMatch> matches;matcher.match(descriptor1,descriptor2,matches);//绘制匹配结果cv::drawMatches(Image1,keypoint1,Image2,keypoint2,                 matches,result);

效果图:
这里写图片描述
错误匹配较多,速度较慢

[2]match匹配改进

//利用Flann进行第一步匹配SURF的描述符cv::FlannBasedMatcher matcher;std::vector<cv::DMatch> matches;matcher.match(descriptor1,descriptor2,matches);double min_distance=1000,max_distance=0;for(int i=0;i<matches.size();i++){  //在已经match好的结果中,找到描述符之间距离的最大值和最小值  double dist=matches[i].distance;  if(dist>max_distance) max_distance=dist;  if(dist<min_distance) min_distance=dist;}//对于描述符之间距离超过2倍最小距离的进行排除std::vector<cv::DMatch> rmatches;for(int i=0;i<matches.size();i++)if(matches[i].distance<std::max(2*min_distance,0.02)){ rmatches.push_back(matches[i]);}

效果图:
这里写图片描述

[3]knnMatch

//knnMatch针对每一个描述符,找到2个最好的匹配,并且距离递增std::vector<std::vector<cv::DMatch>> matches;matcher.knnMatch(descriptor1,descriptor2,matches,2);//对于matches[i][0].distance<0.7*matches[i][1].distance进行采纳std::vector<cv::DMatch > rmatches;for(int i=0;i<matches.size();i++)if(matches[i][0].distance<0.7*matches[i][1].distance)rmatches.push_back(matches[i][0]);//进行绘制cv::drawMatches(Image1,keypoint1,Image2,keypoint2,                rmatches,result);

效果图:
这里写图片描述

3>寻找已知物体

[1]findHomography
通过匹配的关键点,找出相应变换矩阵H
这里写图片描述
反向投影误差:
这里写图片描述

Mat findHomography(InputArray srcPoints,                    InputArray dstPoints, int method=0,                    double ransacReprojThreshold=3,                    OutputArray mask=noArray() )/*srcPoints,dstPoints,分别为源图像和目标图像上的对应点,为Mat *的CV_32FC2或者vector<Point2f> *method,用于计算H矩阵的方法: *0,常规方法使用所有点 *CV_RANSAC,使用RANSAC鲁棒性方法 *CV_LMEDS,最小中值鲁棒性方法 *ransacReprojThreshold,处理点对为内围层时,允许重投影误差的最 *大值。 */                   

RANSAC:处理几乎任何比例异常,但需要阈值
LMEDS:只有超过50%的可利用点,才能确定
如果影响很小就用default

[2]perspectiveTransform
执行向量的透视矩阵变换
变换每一个src元素
这里写图片描述
这里写图片描述
这里写图片描述

void perspectiveTransform(InputArray src, OutputArray dst,                           InputArray m)//src,dst同size和type//m,变换矩阵,3*3或者4*4矩阵

应用:

//在匹配完成的基础上,如果已匹配数目达到一定数量,则允许查找//防止出现bugif(rmatches.size()>4){  std::vector<cv::Point2f> obj;  std::vector<cv::Point2f> scene;  //对于已匹配点,对点进行分组  for(int i=0;i<rmatches.size();i++)  {   obj.push_back(keypoint1[rmatches[i].queryIdx].pt);   scene.push_back(keypoint2[rmatches[i].trainIdx].pt);  }  //得到变换矩阵H  cv::Mat H=cv::findHomography(obj,scene,CV_RANSAC);  std::vector<cv::Point2f> obj_corner(4);  //将obj的图像的四个角进行存储  obj_corner[0]=cv::Point2f(0,0);  obj_corner[1]=cv::Point2f(Image1.cols,0);  obj_corner[2]=cv::Point2f(Image1.cols,Image1.rows);  obj_corner[3]=cv::Point2f(0,Image1.rows);  //进行转换,得到scene中的查找物体  std::vector<cv::Point2f> scene_corner(4);  cv::perspectiveTransform(obj_corner,scene_corner,H);//进行标记cv::line(result,scene_corner[0]+cv::Point2f(Image1.cols,0),         scene_corner[1]+cv::Point2f(Image1.cols,0),         cv::Scalar(0,0,255),2);cv::line(result,scene_corner[1]+cv::Point2f(Image1.cols,0),         scene_corner[2]+cv::Point2f(Image1.cols,0),         cv::Scalar(0,0,255),2);cv::line(result,scene_corner[2]+cv::Point2f(Image1.cols,0),         scene_corner[3]+cv::Point2f(Image1.cols,0),         cv::Scalar(0,0,255),2);cv::line(result,scene_corner[3]+cv::Point2f(Image1.cols,0),         scene_corner[0]+cv::Point2f(Image1.cols,0),         cv::Scalar(0,0,255),2);}

效果图:
这里写图片描述

0 0
原创粉丝点击