特征提取和匹配是许多计算机视觉应用中的一个重要任务,广泛运用在运动结构、图像检索、目标检测等领域。每个计算机视觉初学者最先了解的特征检测器几乎都是1988年发布的harris。在之后的几十年时间内各种各样的特征检测器/描述符如雨后春笋般出现,特征检测的精度与速度都得到了提高。
特征提取和匹配由关键点检测,关键点特征描述和关键点匹配三个步骤组成。不同的检测器,描述符以及匹配器之间的组合往往是初学者疑惑的内容。本文将主要介绍关键点检测、描述以及匹配的背后原理,不同的组合方式之间的优劣,并提出几组根据实践结果得出的最佳组合。
特征(feature)
特征是与解决某个应用程序相关的计算任务有关的一条信息。特征可能是图像中的特定结构,例如点,边缘或对象。特征也可能是应用于图像的一般邻域操作或特征检测的结果。这些功能可以分为两大类: 1、图片中特定位置的特征,如山峰、建筑角落、门口或有趣形状的雪块。这种局部化的特征通常被称为关键点特征(或者甚至是角点) ,它们通常以点位置周围出现的像素块来描述,这个像素块往往被称作图像补丁(image patch)。 2、可以根据其方向和局部外观(边缘轮廓)进行匹配的特征称为边缘,它们也可以很好地指示图像序列中的对象边界和遮挡事件。特征点
边缘
特征提取和匹配的主要组成部分
1、检测(detection):识别感兴趣点 2、描述(description): 描述每个特征点周围的局部外观,这种描述在光照、平移、尺度和平面内旋转的变化下是(理想的)不变的。我们通常会为每个特征点提供一个描述符向量。 3、匹配(mataching): 通过比较图像中的描述符来识别相似的特征。对于两幅图像,我们可以得到一组对(xi,yi)->(xi’ ,yi’) ,其中(xi,yi)是一幅图像的特征,(xi’ ,yi’)是另一幅图像的特征.
detector
关键点/兴趣点(key point/ interest point)
关键点也称兴趣点,是纹理中表达的点。关键点往往是物体边界方向突然改变的点或两个或多个边缘段之间的交点。它在图像空间中具有明确的位置或很好地定位。即使图像域的局部或全局存在如光照和亮度变化等的扰动,关键点仍然是稳定,可以被重复可靠地计算出。除此之外它应该提供有效的检测。 关键点的计算方法有两种: 1、基于图像的亮度(通常通过图像导数)。 2、基于边界提取(通常通过边缘检测和曲率分析)。
关键点检测器光度和几何变化的不变性
在opencv库,我们可以选择很多特征检测器,特征检测器的选择取决于将要检测的关键点的类型以及图像的属性,需要考虑相应检测器在光度和几何变换方面的鲁棒性。 选择合适的关键点检测器时,我们需要考虑四种基本转换类型:1、旋转变换
2、 尺度变换
3、 强度变换
4、仿射变换
涂鸦序列是计算机视觉中使用的标准图像集之一,我们可以观察到第i+n帧的涂鸦图片包括了所有的变换类型。而对于高速公路序列,当专注于前面的车辆时,在第i帧和第i + n帧之间只有比例变化以及强度变化。
传统的harris传感器在旋转和加性强度偏移情况下具有较强的鲁棒性,但对尺度变化、乘性强度偏移(即对比度变化)和仿射变换敏感。自动尺度选择为了在理想尺度上检测关键点,我们必须知道(或找到)它们在图像中的各自维度,并适应本节前面介绍的高斯窗口 w (x,y) 的大小。如果关键点尺度是未知的或如果关键点与存在于不同的大小图像中,检测必须在多个尺度级连续执行。
基于相邻层之间的标准差增量,同一个关键点可能被多次检测到。这就提出了选择最能代表关键点的“正确”尺度的问题。1998年tony lindeberg 发表了一种“自动选择比例的特征提取(feature detection with automatic scale selection)”的方法。它提出了一个函数 f (x,y,scale) ,该函数可以用来选择在尺度上 ff 有稳定最大值的关键点。ff 最大化的尺度被称为各关键点的“特征尺度”。 如在下图中显示了这样一个函数 ff,它经过了几个尺度级别的评估,在第二张图中显示了一个清晰的最大值,可以看作是圆形区域内图像内容的特征尺度。
一个好的检测器能够根据局部邻域的结构特性自动选择关键点的特征尺度。现代关键点探测器通常具有这种能力,因此对图像尺度的变化具有很强的鲁棒性。
常见关键点检测器
关键点检测器是一个非常受欢迎的研究领域,因此这些年来已经开发了许多强大的算法。关键点检测的应用包括物体识别和跟踪,图像匹配和全景拼接以及机器人制图和3d建模等。检测器的选择除了需要比较上述转换中的不变性之外,还需要比较检测器的检测性能和处理速度。
经典关键点检测器
经典关键点检测器的目的是为了最大化检测精度,复杂度一般不是首要考虑因素。
harris- 1988 harris corner detector (harris, stephens)
shi, tomasi- 1996 good features to track (shi, tomasi)
sift- 1999 scale invariant feature transform (lowe) -none free
surt- 2006 speeded up robust features (bay, tuytelaars, van gool) -none free
现代关键点检测器
近年来,一些更快的探测器已经开发出来,用于智能手机和其他便携设备上的实时应用。下面的列表显示了属于这个组的最流行的检测器:
fast- 2006 features from accelerated segment test (fast) (rosten, drummond)
brief- 2010 binary robust independent elementary features (brief) (calonder, et al.)
orb- 2011 oriented fast and rotated brief (orb) (rublee et al.)
brisk- 2011 binary robust invariant scalable keypoints (brisk) (leutenegger, chli, siegwart)
freak- 2012 fast retina keypoint (freak) (alahi, ortiz, vandergheynst)
kaze- 2012 kaze (alcantarilla, bartoli, davidson)
feature descriptor
基于梯度与二进制的描述符
由于我们的任务是在图像序列中找到对应的关键点,因此我们需要一种基于相似性度量将关键点彼此可靠地分配的方法。很多文献中已经提出了各种各样的相似性度量(称为descriptor),并且在很多作者已经同时发布了一种用于关键点检测的新方法以及针对其关键点类型进行了优化的相似性度量。也就是说已经封装好的opencv关键点检测器函数大部分同样可以用来生成关键点描述符。 区别在于: 关键点检测器是一种根据函数的局部最大值从图像中选择点的算法,例如我们在harris检测器中看到的“角度”度量。 关键点描述符是用于描述关键点周围的图像补丁值的向量。描述方法有比较原始像素值的方法也有更复杂的方法,如梯度方向的直方图。 关键点检测器一般是从一个帧图片中寻找到特征点。而描述符帮助我们在“关键点匹配”步骤中将不同图像中的相似关键点彼此分配。如下图所示,一个帧中的一组关键点被分配给另一帧中的关键点,以使它们各自描述符的相似性最大化,并且这些关键点代表图像中的同一对象。除了最大化相似性之外,好的描述符还应该能够最大程度地减少不匹配的次数,即避免将彼此不对应于同一对象的关键点分配给彼此。
基于梯度hog描述符
虽然出现了越来越多快速的检测器/描述符组合,但是基于定向直方图(hog)描述符之一的尺度不变特征转换(sift)依然被广泛运用。hog的基本思想是通过物体在局部邻域中的强度梯度分布来描述物体的结构。为此,将图像划分为多个单元,在这些单元中计算梯度并将其收集到直方图中。然后,将所有单元格的直方图集用作相似性度量,以唯一地标识图像块或对象。 sift/surf使用hog作为描述符,既包括关键点检测器,也包括描述符,功能很强大,但是被专利保护。surf是在sift的基础上改进,不仅提高了计算速度,而且更加安全鲁棒性,两者的实现原理很相似。在此我先仅介绍sift。sift方法遵循五步过程,下面将对此进行简要概述。 首先,使用称为“拉普拉斯高斯(log)”的方法来检测图像中的关键点,该方法基于二阶强度导数。log应用于图像的各种比例级别,并且倾向于检测斑点而不是拐角。除了使用唯一的比例级别外,还根据关键点周围局部邻域中的强度梯度为关键点分配方向。 其次,对于每个关键点,其周围区域都会通过消除方向而改变,从而确保规范的方向。此外,该区域的大小将调整为16 x 16像素,从而提供了标准化的图像补丁。
第三,基于强度梯度_ix_和_iy_计算归一化图像补丁内每个像素的方向和大小。 第四,将归一化的贴片划分为4 x 4单元的网格。在每个单元内,超出幅度阈值的像素的方向收集在由8个bin组成的直方图中。
最后,将所有16个单元格的8柱状直方图连接到一个128维向量(描述符)中,该向量用于唯一表示关键点。
sift检测器/描述符即使在杂波中和部分遮挡下也能够可靠地识别物体。尺度,旋转,亮度和对比度的均匀变化是不变的,仿射失真甚至是不变的。 sift的缺点是速度低,这使其无法在智能手机等实时应用中使用。hog系列的其他成员(例如surf和gloh)已针对速度进行了优化。但是,它们仍然在计算上过于昂贵,因此不应在实时应用中使用。此外,sift和surf拥有大量专利,因此不能在商业环境中自由使用。为了在opencv中使用sift,必须使用#include ,并且需要安装opencv_contribute包,注意一定要在cmake选项中开启 opencv_enable_nonfree。 二进制binary描述符 基于hog的描述符的问题在于它们基于计算强度梯度,这是非常昂贵的操作。即使已进行了一些改进(例如surf),使用了积分图像,速度提高了,但这些方法仍然不适合处理能力有限的设备(例如智能手机)上的实时应用程序。二进制描述符家族是基于hog的方法的一种更快(免费)的替代方案,但准确性和性能稍差。 二进制描述符的核心思想是仅仅依赖强度信息(即图像本身) ,并将关键点周围的信息编码为一串二进制数字,当搜索相应关键点时,这些数字可以在匹配步骤中非常有效地进行比较。也就是说二进制描述符将兴趣点的信息编码成一系列数字,并作为一种数字“指纹” ,可用于区分一个特征和另一个特征。目前,最流行的二进制描述符是 brief、 brisk、 orb、 freak 和 kaze (所有这些都可以在 opencv 库中找到)。
二进制描述符
从高层次的角度来看,二进制描述符由三个主要部分组成: 1、一种描述样本点位于关键点附近的位置的采样模式( sampling pattern )。 2、一种消除了图像补丁围绕关键点位置旋转影响的方向补偿方法( orientation compensation)。 3、一种样本对选择的方法(ample-pair selection),它产生成对的样本点,这些样本点根据它们的强度值相互比较。如果第一个值大于第二个值,我们就在二进制字符串中写一个“1” ,否则就写一个“0”。在对采样模式中的所有点对执行此操作之后,将创建一个长的二进制链(或“ string”)(因此得到描述符类的族名)。brisk“二进制鲁棒不变可伸缩关键点”关键点检测器 / 描述符是二进制描述符的代表。在此我先仅介绍brisik。 2011年stefan leutenegger 提出的brisk是一个基于fast的检测器和一个binary描述符的组合,这个描述符由通过对每个关键点邻域进行专门采样而获得的强度比较创建。 brisk的采样模式由多个采样点(蓝色)组成,其中每个采样点周围的同心环(红色)表示应用高斯平滑的区域。与某些其他二进制描述符(例如orb或brief)相反,brisk采样模式是固定的。平滑对于避免混叠非常重要(这种效应会导致不同信号在采样时变得难以区分-或彼此混叠)。
在样本对选择期间,brisk算法会区分长距离对和短距离对。长距离对(即在样本图案上彼此之间具有最小距离的样本点)用于根据强度梯度估算图像补丁的方向,而短距离对用于对已组装的描述符字符串进行强度比较。在数学上,这些对表示如下:
首先,我们定义所有可能的采样点对的集合a。然后,我们从a提取子集l,子集l的欧氏距离大于上阈值。l是用于方向估计的长距离对。最后,我们从a提取欧氏距离低于下阈值的那些对。该集合s包含用于组装二进制描述符串的短距离对。 下图显示了短对(左)和长对(右)的采样模式上的两种距离对。
从长对中,关键点方向向量g 计算如下:
首先,根据归一化的单位矢量计算两个采样点之间的梯度强度,归一化的单位矢量给出两个点之间的方向,乘以两个点在各自比例下的强度差。然后在(2)中,关键点方向向量 g 从所有梯度强度的总和中计算出。 基于 g ,我们可以使用采样模式的方向重新排列短距离配对,从而确保旋转不变性。基于旋转不变的短距离配对,可以如下构建最终的二进制描述符:
从g计算出关键点的方位后,我们使用它使短距离配对旋转不变。然后,所有对之间的强度s被比较并用于组装可用于匹配的二进制描述符。
opencv detector/descriptor implementation
目前存在各种各样的特征点检测器/描述符,如 harris, shi-tomasi, fast, brisk, orb, akaze, sift, freak, brief。每一种都值得单独用一篇博客去描述,但是本文的目的是为了给大家一份综述,因此不详细的从原理上分析这些检测器/描述符。网上有大量描述这些检测器/描述符的文章,但是我还是建议大家先看opencv库的tutorial: how to detect and track object with opencv. 以下我会介绍各个特征点检测器/描述符的代码实现以及参数详解, 文章结尾会基于实际结果对这些组合进行评价。 有些opencv函数可以同时用于检测器/描述符,但是有的组合会出现问题。siftdetector/descriptor sift detector and orb descriptor do not work together
int nfeatures = 0;// the number of best features to retain.int noctavelayers = 3;// the number of layers in each octave. 3 is the value used in d. lowe paper.double contrastthreshold = 0.04;// the contrast threshold used to filter out weak features in semi-uniform (low-contrast) regions. double edgethreshold = 10;// the threshold used to filter out edge-like features. double sigma = 1.6; // the sigma of the gaussian applied to the input image at the octave #0.xxx=cv::create(nfeatures, noctavelayers, contrastthreshold, edgethreshold, sigma);harrisdetector
// detector parametersint blocksize = 2; // for every pixel, a blocksize × blocksize neighborhood is consideredint aperturesize = 3; // aperture parameter for sobel operator (must be odd)int minresponse = 100; // minimum value for a corner in the 8bit scaled response matrixdouble k = 0.04; // harris parameter (see equation for details)// detect harris corners and normalize outputcv::mat dst, dst_norm, dst_norm_scaled;dst = cv::zeros(img.size(), cv_32fc1);cv::cornerharris(img, dst, blocksize, aperturesize, k, cv::border_default);cv::normalize(dst, dst_norm, 0, 255, cv::norm_minmax, cv_32fc1, cv::mat());cv::convertscaleabs(dst_norm, dst_norm_scaled); // look for prominent corners and instantiate keypointsdouble maxoverlap = 0.0; // max. permissible overlap between two features in %, used during non-maxima suppressionfor (size_t j = 0; j < dst_norm.rows; j++) { for (size_t i = 0; i minresponse) { // only store points above a threshold cv::keypoint newkeypoint; newkeypoint.pt = cv::point2f(i, j); newkeypoint.size = 2 * aperturesize; newkeypoint.response = response; // perform non-maximum suppression (nms) in local neighbourhood around new key point bool boverlap = false; for (auto it = keypoints.begin(); it != keypoints.end(); ++it) { double kptoverlap = cv::overlap(newkeypoint, *it); if (kptoverlap > maxoverlap) { boverlap = true; if (newkeypoint.response > (*it).response) { // if overlap is >t and response is higher for new kpt *it = newkeypoint; // replace old key point with new one break; // quit loop over keypoints } } } if (!boverlap) { // only add new key point if no overlap has been found in previous nms keypoints.push_back(newkeypoint); // store new keypoint in dynamic list } } } // eof loop over cols} // eof loop over rowsshi-tomasidetector
int blocksize = 6; // size of an average block for computing a derivative covariation matrix over each pixel neighborhooddouble maxoverlap = 0.0; // max. permissible overlap between two features in %double mindistance = (1.0 - maxoverlap) * blocksize;int maxcorners = img.rows * img.cols / max(1.0, mindistance); // max. num. of keypointsdouble qualitylevel = 0.01; // minimal accepted quality of image cornersdouble k = 0.04;bool useharris = false;// apply corner detectionvector corners;cv::goodfeaturestotrack(img, corners, maxcorners, qualitylevel, mindistance, cv::mat(), blocksize, useharris, k); // add corners to result vectorfor (auto it = corners.begin(); it != corners.end(); ++it) { cv::keypoint newkeypoint; newkeypoint.pt = cv::point2f((*it).x, (*it).y); newkeypoint.size = blocksize; keypoints.push_back(newkeypoint);}brisikdetector/descriptor
int threshold = 30; // fast/agast detection threshold score.int octaves = 3; // detection octaves (use 0 to do single scale)float patternscale = 1.0f; // apply this scale to the pattern used for sampling the neighbourhood of a keypoint.xxx=cv::create(threshold, octaves, patternscale);freakdetector/descriptor
bool orientationnormalized = true;// enable orientation normalization.bool scalenormalized = true;// enable scale normalization.float patternscale = 22.0f;// scaling of the description pattern.int noctaves = 4;// number of octaves covered by the detected keypoints.const std::vector &selectedpairs = std::vector(); // (optional) user defined selected pairs indexes,xxx=cv::create(orientationnormalized, scalenormalized, patternscale, noctaves,selectedpairs);fastdetector/descriptor
int threshold = 30;// difference between intensity of the central pixel and pixels of a circle around this pixelbool nonmaxsuppression = true;// perform non-maxima suppression on keypointscv::detectortype type = cv::type_9_16;// type_9_16, type_7_12, type_5_8xxx=cv::create(threshold, nonmaxsuppression, type);orbdetector/descriptor sift detector and orb descriptor do not work together
int nfeatures = 500;// the maximum number of features to retain.float scalefactor = 1.2f;// pyramid decimation ratio, greater than 1.int nlevels = 8;// the number of pyramid levels.int edgethreshold = 31;// this is size of the border where the features are not detected.int firstlevel = 0;// the level of pyramid to put source image to.int wta_k = 2;// the number of points that produce each element of the oriented brief descriptor.auto scoretype = cv::harris_score;// the default harris_score means that harris algorithm is used to rank features.int patchsize = 31;// size of the patch used by the oriented brief descriptor.int fastthreshold = 20;// the fast threshold.xxx=cv::create(nfeatures, scalefactor, nlevels, edgethreshold, firstlevel, wta_k, scoretype,patchsize, fastthreshold);akazedetector/descriptor kaze/akaze descriptors will only work with kaze/akaze detectors.
auto descriptor_type = cv::descriptor_mldb;// type of the extracted descriptor: descriptor_kaze, descriptor_kaze_upright, descriptor_mldb or descriptor_mldb_upright.int descriptor_size = 0;// size of the descriptor in bits. 0 -> full sizeint descriptor_channels = 3;// number of channels in the descriptor (1, 2, 3)float threshold = 0.001f;// detector response threshold to accept pointint noctaves = 4;// maximum octave evolution of the imageint noctavelayers = 4;// default number of sublevels per scale levelauto diffusivity = cv::diff_pm_g2;// diffusivity type. diff_pm_g1, diff_pm_g2, diff_weickert or diff_charbonnierxxx=cv::create(descriptor_type, descriptor_size, descriptor_channels, threshold, noctaves,noctavelayers, diffusivity);briefdetector/descriptor
int bytes = 32;// legth of the descriptor in bytes, valid values are: 16, 32 (default) or 64 .bool use_orientation = false;// sample patterns using keypoints orientation, disabled by default.xxx=cv::create(bytes, use_orientation);
descriptor matching
特征匹配或一般意义上的图像匹配是图像配准、摄像机标定和目标识别等计算机视觉应用的一部分,是在同一场景 / 目标的两幅图像之间建立对应关系的任务。一种常用的图像匹配方法是从图像数据中检测出一组与图像描述符相关联的兴趣点。一旦从两个或更多的图像中提取出特征和描述符,下一步就是在这些图像之间建立一些初步的特征匹配。
一般来说,特征匹配方法的性能取决于基本关键点的性质和相关图像描述符的选择。 我们已经了解到关键点可以通过将其局部邻域转换为高维向量来描述,高维向量可以捕获梯度或强度分布的独特特征。
描述符之间的距离
特征匹配需要计算两个描述符之间的距离,这样它们之间的差异被转换成一个单一的数字,我们可以用它作为一个简单的相似性度量。 目前有三种距离度量:
绝对差之和(sad)-l1-norm
平方差之和(ssd)-l2-norm
汉明距离 (hamming distance)
sad和ssd之间的差异在于:首先两者之间的最短距离是一条直线,给定每个向量的两个分量,sad计算长度差之和,这是一维过程。而ssd计算平方和,遵循毕达哥拉斯定律,在一个矩形三角形中,宽边平方的总和等于斜边的平方。因此,就两个向量之间的几何距离而言,l2-norm是一种更准确的度量。注意,相同的原理适用于高维描述符。 而汉明距离对于仅由1和0组成的二进制描述符很适合,该距离通过使用xor函数计算两个向量之间的差,如果两个位相同,则返回零如果两位不同,则为1。因此,所有xor操作的总和就是两个描述符之间的不同位数。 值得注意的是必须根据所使用的描述符的类型选择合适距离度量。
binary descriptors :brisk, brief, orb, freak, and akaze-hamming distance
hog descriptors : sift (and surf and gloh, all patented)-l2-norm
寻找匹配对
让我们假设在一个图像中有n个关键点及其关联的描述符,在另一幅图像中有m个关键点。
蛮力匹配(brute force matching)
寻找对应对的最明显方法是将所有特征相互比较,即执行n x m比较。对于第一张图像中给定的关键点,它将获取第二张图像中的每个关键点并计算距离。距离最小的关键点将被视为一对。这种方法称为“蛮力匹配(brute force matching)”或“最近邻居匹配(nearest neighbor matching)”。opencv中蛮力匹配的输出是一个关键点对的列表,这些关键点对按其在所选距离函数下的描述符的距离进行排序。
快速最近邻(flann)
2014年,david lowe和marius muja发布了快速最近邻(fast library for approximate nearest neighbors(flann))。flann训练了一种索引结构,用于遍历使用机器学习概念创建的潜在匹配候选对象。该库构建了非常有效的数据结构(kd树)来搜索匹配对,并避免了穷举法的穷举搜索。因此,速度更快,结果也非常好,但是仍然需要调试匹配参数。 bfmatching和flann都接受描述符距离阈值t,该距离阈值t用于将匹配项的数量限制为“好”,并在匹配不对应的情况下丢弃匹配项。相应的“好”对称为“正阳性(tp)”,而错对称为“假阳性(fp)”。为t选择合适的值的任务是允许尽可能多的tp匹配,而应尽可能避免fp匹配。根据图像内容和相应的检测器/描述符组合,必须找到tp和fp之间的权衡点,以合理地平衡tp和fp之间的比率。下图显示了ssd上tp和fp的两种分布,以说明阈值选择。
第一阈值t1被设置为两个特征之间的最大允许的ssd,其方式是选择了一些正确的正匹配,而几乎完全避免了错误的正匹配。但是,使用此设置也将丢弃大多数tp匹配项。通过将匹配阈值增加到t2,可以选择更多的tp匹配,但是fp匹配的数量也将显着增加。在实践中,几乎没有找到tp和fp的清晰明了的分离,因此,设置匹配阈值始终是平衡“好”与“坏”匹配之间的折衷。尽管在大多数情况下都无法避免fp,但目标始终是尽可能降低fp次数。在下文中,提出了实现这一目标的两种策略。
选择匹配对
bfmatching- crosscheck
只要不超过所选阈值t,即使第二图像中不存在关键点,蛮力匹配也将始终返回与关键点的匹配。这不可避免地导致许多错误的匹配。抵消这种情况的一种策略称为交叉检查匹配,它通过在两个方向上应用匹配过程并仅保留那些在一个方向上的最佳匹配与在另一个方向上的最佳匹配相同的匹配来工作。交叉检查方法的步骤为: 1、对于源图像中的每个描述符,请在参考图像中找到一个或多个最佳匹配。 2、切换源图像和参考图像的顺序。 3、重复步骤1中源图像和参考图像之间的匹配过程。 4、选择其描述符在两个方向上最匹配的那些关键点对。 尽管交叉检查匹配会增加处理时间,但通常会消除大量的错误匹配,因此,当精度优于速度时,应始终执行交叉匹配。交叉匹配一般仅仅用于bfmatching。
nearest neighbor distance ratio (nn)/k-nearest-neighbor(knn)
减少误报数量的另一种非常有效的方法是为每个关键点计算最近邻距离比(nearest neighbor distance ratio)。knn与nn的区别在与nn每个特征点只保留一个最好的匹配 (keeping only the best match),而knn每个特征点保留k个最佳匹配(keeping the best k matches per keypoint). k一般为2. 主要思想是不要将阈值直接应用于ssd。相反,对于源图像中的每个关键点,两个(k=2)最佳匹配位于参考图像中,并计算描述符距离之间的比率。然后,将阈值应用于比率,以筛选出模糊匹配。下图说明了原理。
在该示例中,将具有关联描述符da的图像补丁与其他两个具有描述符的图像补丁d_ b1 和 d_b2进行比较 。可以看出,图像补丁看起来非常相似,并且会导致模棱两可,因此不可靠。通过计算最佳匹配与次佳匹配之间的ssd比值,可以过滤掉这些较弱的候选对象。 在实践中,已证明阈值0.8可以在tp和fp之间提供良好的平衡。在原始sift中检查的图像序列中,使用此设置可以消除90%的错误匹配,而丢失少于5%的正确匹配。注意,只有knn能设置阈值0.8。nn只会提供一个最佳匹配。 以下是匹配的执行代码:
void matchdescriptors(std::vector &kptssource, std::vector &kptsref, cv::mat &descsource,cv::mat &descref,std::vector &matches, std::string descriptorclass, std::string matchertype,std::string selectortype) { // configure matcher bool crosscheck = false; cv::ptr matcher; int normtype; if (matchertype.compare(mat_bf) == 0) { int normtype = descriptorclass.compare(des_binary) == 0 ? cv::norm_hamming : cv::norm_l2; matcher = cv::create(normtype, crosscheck); } else if (matchertype.compare(mat_flann) == 0) { // opencv bug workaround : convert binary descriptors to floating point due to a bug in current opencv implementation if (descsource.type() !=cv_32f) { descsource.convertto(descsource, cv_32f); // descref.convertto(descref, cv_32f); } if (descref.type() !=cv_32f) { descref.convertto(descref, cv_32f); } matcher = cv::flannbased); } // perform matching task if (selectortype.compare(sel_nn) == 0) { // nearest neighbor (best match) matcher->match(descsource, descref, matches); // finds the best match for each descriptor in desc1 } else if (selectortype.compare(sel_knn) == 0) { // k nearest neighbors (k=2) vector knn_matches; matcher->knnmatch(descsource, descref, knn_matches, 2); //-- filter matches using the lowe's ratio test double mindescdistratio = 0.8; for (auto it = knn_matches.begin(); it != knn_matches.end(); ++it) { if ((*it)[0].distance brisk>surf>sift>akaze>kaze 每个特征点的特征检测描述器的计算效率顺序为: orb>orb (1000) >brisk>brisk (1000) >surf (64d)>surf (128d) >akaze>sift>kaze 每个特征点的有效特征匹配顺序为: orb (1000) >brisk (1000) >akaze>kaze>surf (64d)>orb>brisk>sift>surf (128d) 特征检测描述器的整体图像匹配速度顺序为: orb (1000) >brisk (1000) >akaze>kaze>surf (64d)>sift>orb>brisk>surf (128d) 备注:不同检测器的检测图像,从中可以看出它们关键点邻域的大小和分布。harris
shi-tomasi
fast
brisik
orb
akaze
sift
北京京剧院除采用沉浸式演出展览等多种形式向观众展示京剧艺术魅力
官方优化调整石墨物项临时出口管制 12月1日正式施行
终于,苹果还是打了“灭霸的响指”,但那些严肃、重要且专业的问题需要讨论
典型案例分析:车速表指针跳动故障排除方法
快商通首席科学家:语音识别的后半段路,从语言处理走向语言理解
关键点检测器光度和几何变化的不变性
新基建背景下,如何利用数据实现价值提升
提高机器人的动作精度要从这些方面入手
UWA推出全新GPU性能测评工具,支持多款PowerVR芯片优化
看索尼电视魅力有多大?让球王奥斯卡和宝宝其乐无穷
重点用能单位能耗在线监测系统开发能源管控平台开发
一加手机三周岁活动,新款一加3T将现发现售
MPM3805A汽车级降压模块变换器产品特性和优势
MAX1792 500MA,低压差线性稳压器
三星Galaxy Mini:小身材也能完成大功能
瑞基执行器的工作原理是怎样的
e络盟新添Fluke多功能钳表系列
如何利用1200 V EliteSiC MOSFET 模块,打造充电更快的车载充电器?
SpaceX Falcon 9火箭克服引擎故障部署卫星
飞凌嵌入式受邀亮相2023统信UOS生态大会,开启国产OS新探索