Opencv特征检测之ORB算法原理及应用详解

Opencv特征检测之ORB算法原理及应用详解

  • 特征是图像信息的另一种数字表达形式。一组好的特征对于在指定
    任务上的最终表现至关重要。
  • 视觉里程 (VO) 的主要问题是如何根据图像特征来估计相机运动。但是,整幅图像用来计算分析通常比较耗时,故而转换为分析图像中的特征点的运动。
    在这里插入图片描述
  • 计算机视觉领域的研究者们在长年的研究中,设计了许多更加稳定的局部图像特征,如著名的SIFT, SURF,ORB 等等。相比于朴素的角点,这些人工设计的特征点能够拥有如下的性质:
    1. 可重复性(Repeatability):相同的“区域”可以在不同的图像中被找到。
    2. 可区别性(Distinctiveness):不同的“区域”有不同的表达。
    3. 高效率(Efficiency):同一图像中,特征点的数量应远小于像素的数量。
    4. 本地性(Locality):特征仅与一小片图像区域相关。
  • 特征点由关键点(Key-point)和描述子(Descriptor)两部分组成。比方说,当我们谈论 SIFT 特征时,是指“提取 SIFT 关键点,并计算 SIFT 描述子”两件事情。关键点是指该特征点在图像里的位置,有些特征点还具有朝向、大小等信息。描述子通常是一个向量,按照某种人为设计的方式,描述了该关键点周围像素的信息。描述子是按照“外观相似的特征应该有相似的描述子”的原则设计的。因此,只要两个特征点的描述子在向量空间上的距离相近,就可以认为它们是同样的特征点。
  • 下文将详细讲述ORB算法(关键点+特征描述子)原理。

1. ORB 简介

  • ORB论文:https://www.gwylab.com/download/ORB_2012.pdf
  • 历史上,研究者提出过许多图像特征。它们有些很精确,在相机的运动和光照变化下仍具有相似表达,但相应地需要较大的计算量。其中,SIFT(尺度不变特征变换,Scale Invariant FeatureTransform) 当属最为经典的一种。它充分考虑了在图像变换过程中出现的光照,尺度,旋转等变化,但随之而来的是极大的计算量。由于整个 SLAM 过程中,图像特征的提取与匹配仅仅是诸多环节中的一个,到目前(2016 年)为止,普通 PC 的 CPU还无法实时地计算 SIFT 特征,进行定位与建图。所以在 SLAM 中我们甚少使用这种“奢侈”的图像特征。
  • 另一些特征,则考虑适当降低精度和鲁棒性,提升计算的速度。例如 FAST 关键点属于计算特别快的一种特征点(注意这里“关键点”的用词,说明它没有描述子)。而 ORB(Oriented FAST and Rotated BRIEF)特征则是目前看来非常具有代表性的实时图像特征。它改进了 FAST 检测子 [33] 不具有方向性的问题,并采用速度极快的二进制描述子BRIEF,使整个图像特征提取的环节大大加速。根据作者在论文中的测试,在同一幅图像中同时提取约 1000 个特征点的情况下,ORB 约要花费 15.3ms,SURF 约花费 217.3ms,SIFT 约花费 5228.7ms。由此可以看出 ORB 在保持了特征子具有旋转,尺度不变性的同时,速度方面提升明显,对于实时性要求很高的 SLAM 来说是一个很好的选择。大部分特征提取都具有较好的并行性,可以通过 GPU 等设备来加速计算。经过 GPU加速后的 SIFT,就可以满足实时计算要求。但是,引入 GPU 将带来整个 SLAM 成本的提升。由此带来的性能提升,是否足以抵去付出的计算成本,需要系统的设计人员仔细考量。在目前的 SLAM 方案中,ORB 是质量与性能之间较好的折中,因此我们以 ORB 为代表,介绍提取特征的整个过程。

2. ORB原理

在这里插入图片描述
\quad \quad ORB特征亦由关键点和描述子两部分组成。它的关键点称为“OrientedFAST”,是一种改进的FAST角点,什么是FAST角点我们将在下文介绍。它的描述子称为BRIEF (BinaryRobustIndependentElementaryFeatures)。因此,提取ORB特征分为两个步骤:

  1. FAST 角点提取:找出图像中的” 角点”。相较于原版的 FAST, ORB 中计算了特征
    点的主方向,为后续的 BRIEF 描述子增加了旋转不变特性。
  2. BRIEF 描述子:对前一步提取出特征点的周围图像区域进行描述。
    下面我们分别介绍 FAST 和 BRIEF。

2.1 FAST关键点

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.2 BRIEF 描述子

在提取 Oriented FAST 关键点后,我们对每个点计算其描述子。ORB 使用改进的BRIEF 特征描述。我们先来讲 BRIEF 是什么。
在这里插入图片描述

  • 具体实现:
    在这里插入图片描述
  • 比较好的博客:ORB算法与opencv实现

2.3 ORB算法具体实现

在这里插入图片描述

2.4 Opencv源码解析

  • API简介
 //ORB类定义:位置..\features2d.hppstatic Ptr<ORB> cv::ORB::create (int     nfeatures = 500,             //需要的特征点总数;float   scaleFactor = 1.2f,         //尺度因子;       int     nlevels = 8,                //金字塔层数;int     edgeThreshold = 31,         //边界阈值;int     firstLevel = 0,             //起始层;int     WTA_K = 2,                  //描述子形成方法,WTA_K=2表示,采用两两比较;int     scoreType = ORB::HARRIS_SCORE,  //角点响应函数,可以选择Harris或者Fast的方法;                   int     patchSize = 31,            //特征点邻域大小;int     fastThreshold = 20)        //FAST阈值
  • 源码:
  • 头文件类定义如下:
/*!ORB implementation.
*/
class CV_EXPORTS_W ORB : public Feature2D
{
public:// the size of the signature in bytesenum { kBytes = 32, HARRIS_SCORE=0, FAST_SCORE=1 };CV_WRAP explicit ORB(int nfeatures = 500, float scaleFactor = 1.2f, int nlevels = 8, int edgeThreshold = 31,//构造函数int firstLevel = 0, int WTA_K=2, int scoreType=ORB::HARRIS_SCORE, int patchSize=31 );// returns the descriptor size in bytesint descriptorSize() const;   //描述子占用的字节数,默认32字节// returns the descriptor typeint descriptorType() const;//描述子类型,8位整形数// Compute the ORB features and descriptors on an imagevoid operator()(InputArray image, InputArray mask, vector<KeyPoint>& keypoints) const;// Compute the ORB features and descriptors on an imagevoid operator()( InputArray image, InputArray mask, vector<KeyPoint>& keypoints,    //提取特征点与形成描述子OutputArray descriptors, bool useProvidedKeypoints=false ) const;AlgorithmInfo* info() const;protected:void computeImpl( const Mat& image, vector<KeyPoint>& keypoints, Mat& descriptors ) const;//计算描述子void detectImpl( const Mat& image, vector<KeyPoint>& keypoints, const Mat& mask=Mat() ) const;//检测特征点CV_PROP_RW int nfeatures;//特征点总数CV_PROP_RW double scaleFactor;//尺度因子CV_PROP_RW int nlevels;//金字塔内层数CV_PROP_RW int edgeThreshold;//边界阈值CV_PROP_RW int firstLevel;//开始层数CV_PROP_RW int WTA_K;//描述子形成方法,默认WTA_K=2,两两比较CV_PROP_RW int scoreType;//角点响应函数CV_PROP_RW int patchSize;//邻域Patch大小
};
  • 特征提取及形成描述子:通过这个函数对图像提取Fast特征点或者计算特征描述子
_image:输入图像;
_mask:掩码图像;
_keypoints:输入角点;
_descriptors:如果为空,只寻找特征点,不计算特征描述子;
_useProvidedKeypoints:如果为true,函数只计算特征描述子;
/** Compute the ORB features and descriptors on an image* @param img the image to compute the features and descriptors on* @param mask the mask to apply* @param keypoints the resulting keypoints* @param descriptors the resulting descriptors* @param do_keypoints if true, the keypoints are computed, otherwise used as an input* @param do_descriptors if true, also computes the descriptors*/
void ORB::operator()( InputArray _image, InputArray _mask, vector<KeyPoint>& _keypoints,OutputArray _descriptors, bool useProvidedKeypoints) const
{CV_Assert(patchSize >= 2);bool do_keypoints = !useProvidedKeypoints;bool do_descriptors = _descriptors.needed();if( (!do_keypoints && !do_descriptors) || _image.empty() )return;//ROI handlingconst int HARRIS_BLOCK_SIZE = 9;//Harris角点响应需要的边界大小int halfPatchSize = patchSize / 2;.//邻域半径int border = std::max(edgeThreshold, std::max(halfPatchSize, HARRIS_BLOCK_SIZE/2))+1;//采用最大的边界Mat image = _image.getMat(), mask = _mask.getMat();if( image.type() != CV_8UC1 )cvtColor(_image, image, CV_BGR2GRAY);//转灰度图int levelsNum = this->nlevels;//金字塔层数if( !do_keypoints )   //不做特征点检测{// if we have pre-computed keypoints, they may use more levels than it is set in parameters// !!!TODO!!! implement more correct method, independent from the used keypoint detector.// Namely, the detector should provide correct size of each keypoint. Based on the keypoint size// and the algorithm used (i.e. BRIEF, running on 31x31 patches) we should compute the approximate// scale-factor that we need to apply. Then we should cluster all the computed scale-factors and// for each cluster compute the corresponding image.//// In short, ultimately the descriptor should// ignore octave parameter and deal only with the keypoint size.levelsNum = 0;for( size_t i = 0; i < _keypoints.size(); i++ )levelsNum = std::max(levelsNum, std::max(_keypoints[i].octave, 0));//提取特征点的最大层数levelsNum++;}// Pre-compute the scale pyramidsvector<Mat> imagePyramid(levelsNum), maskPyramid(levelsNum);//创建尺度金字塔图像for (int level = 0; level < levelsNum; ++level){float scale = 1/getScale(level, firstLevel, scaleFactor);  //每层对应的尺度/*static inline float getScale(int level, int firstLevel, double scaleFactor){return (float)std::pow(scaleFactor, (double)(level - firstLevel));}	*/Size sz(cvRound(image.cols*scale), cvRound(image.rows*scale));//每层对应的图像大小Size wholeSize(sz.width + border*2, sz.height + border*2);Mat temp(wholeSize, image.type()), masktemp;imagePyramid[level] = temp(Rect(border, border, sz.width, sz.height));if( !mask.empty() ){masktemp = Mat(wholeSize, mask.type());maskPyramid[level] = masktemp(Rect(border, border, sz.width, sz.height));}// Compute the resized imageif( level != firstLevel )    //得到金字塔每层的图像{if( level < firstLevel ){resize(image, imagePyramid[level], sz, 0, 0, INTER_LINEAR);if (!mask.empty())resize(mask, maskPyramid[level], sz, 0, 0, INTER_LINEAR);}else{resize(imagePyramid[level-1], imagePyramid[level], sz, 0, 0, INTER_LINEAR);if (!mask.empty()){resize(maskPyramid[level-1], maskPyramid[level], sz, 0, 0, INTER_LINEAR);threshold(maskPyramid[level], maskPyramid[level], 254, 0, THRESH_TOZERO);}}copyMakeBorder(imagePyramid[level], temp, border, border, border, border,//扩大图像的边界BORDER_REFLECT_101+BORDER_ISOLATED);if (!mask.empty())copyMakeBorder(maskPyramid[level], masktemp, border, border, border, border,BORDER_CONSTANT+BORDER_ISOLATED);}else{copyMakeBorder(image, temp, border, border, border, border,//扩大图像的四个边界BORDER_REFLECT_101);if( !mask.empty() )copyMakeBorder(mask, masktemp, border, border, border, border,BORDER_CONSTANT+BORDER_ISOLATED);}}// Pre-compute the keypoints (we keep the best over all scales, so this has to be done beforehandvector < vector<KeyPoint> > allKeypoints;if( do_keypoints )//提取角点{// Get keypoints, those will be far enough from the border that no check will be required for the descriptorcomputeKeyPoints(imagePyramid, maskPyramid, allKeypoints,  //对每一层图像提取角点,见下面(1)的分析nfeatures, firstLevel, scaleFactor,edgeThreshold, patchSize, scoreType);// make sure we have the right number of keypoints keypoints/*vector<KeyPoint> temp;for (int level = 0; level < n_levels; ++level){vector<KeyPoint>& keypoints = all_keypoints[level];temp.insert(temp.end(), keypoints.begin(), keypoints.end());keypoints.clear();}KeyPoint::retainBest(temp, n_features_);for (vector<KeyPoint>::iterator keypoint = temp.begin(),keypoint_end = temp.end(); keypoint != keypoint_end; ++keypoint)all_keypoints[keypoint->octave].push_back(*keypoint);*/}else  //不提取角点{// Remove keypoints very close to the borderKeyPointsFilter::runByImageBorder(_keypoints, image.size(), edgeThreshold);// Cluster the input keypoints depending on the level they were computed atallKeypoints.resize(levelsNum);for (vector<KeyPoint>::iterator keypoint = _keypoints.begin(),keypointEnd = _keypoints.end(); keypoint != keypointEnd; ++keypoint)allKeypoints[keypoint->octave].push_back(*keypoint);    //把角点信息存入allKeypoints内// Make sure we rescale the coordinatesfor (int level = 0; level < levelsNum; ++level)   //把角点位置信息缩放到指定层位置上{if (level == firstLevel)continue;vector<KeyPoint> & keypoints = allKeypoints[level];float scale = 1/getScale(level, firstLevel, scaleFactor);for (vector<KeyPoint>::iterator keypoint = keypoints.begin(),keypointEnd = keypoints.end(); keypoint != keypointEnd; ++keypoint)keypoint->pt *= scale;   //缩放}}Mat descriptors;        vector<Point> pattern;if( do_descriptors ) //计算特征描述子{int nkeypoints = 0;for (int level = 0; level < levelsNum; ++level)nkeypoints += (int)allKeypoints[level].size();//得到所有层的角点总数if( nkeypoints == 0 )_descriptors.release();else{_descriptors.create(nkeypoints, descriptorSize(), CV_8U);//创建一个矩阵存放描述子,每一行表示一个角点信息descriptors = _descriptors.getMat();}const int npoints = 512;//取512个点,共256对,产生256维描述子,32个字节Point patternbuf[npoints];const Point* pattern0 = (const Point*)bit_pattern_31_;//训练好的256对数据点位置if( patchSize != 31 ){pattern0 = patternbuf;makeRandomPattern(patchSize, patternbuf, npoints);}CV_Assert( WTA_K == 2 || WTA_K == 3 || WTA_K == 4 );if( WTA_K == 2 )  //WTA_K=2使用两个点之间作比较std::copy(pattern0, pattern0 + npoints, std::back_inserter(pattern));else{int ntuples = descriptorSize()*4;initializeOrbPattern(pattern0, pattern, ntuples, WTA_K, npoints);}}_keypoints.clear();int offset = 0;for (int level = 0; level < levelsNum; ++level)//依次计算每一层的角点描述子{// Get the features and compute their orientationvector<KeyPoint>& keypoints = allKeypoints[level];int nkeypoints = (int)keypoints.size();//本层内角点个数// Compute the descriptorsif (do_descriptors){Mat desc;if (!descriptors.empty()){desc = descriptors.rowRange(offset, offset + nkeypoints);}offset += nkeypoints;  //偏移量// preprocess the resized imageMat& workingMat = imagePyramid[level];//boxFilter(working_mat, working_mat, working_mat.depth(), Size(5,5), Point(-1,-1), true, BORDER_REFLECT_101);GaussianBlur(workingMat, workingMat, Size(7, 7), 2, 2, BORDER_REFLECT_101);//高斯平滑图像computeDescriptors(workingMat, keypoints, desc, pattern, descriptorSize(), WTA_K);//计算本层内角点的描述子,(3)}// Copy to the output dataif (level != firstLevel)  //角点位置信息返回到原图上{float scale = getScale(level, firstLevel, scaleFactor);for (vector<KeyPoint>::iterator keypoint = keypoints.begin(),keypointEnd = keypoints.end(); keypoint != keypointEnd; ++keypoint)keypoint->pt *= scale; }// And add the keypoints to the output_keypoints.insert(_keypoints.end(), keypoints.begin(), keypoints.end());//存入描述子信息,返回}
}

2.4.1 提取角点

imagePyramid:即构造好的金字塔

/** Compute the ORB keypoints on an image* @param image_pyramid the image pyramid to compute the features and descriptors on* @param mask_pyramid the masks to apply at every level* @param keypoints the resulting keypoints, clustered per level*/
static void computeKeyPoints(const vector<Mat>& imagePyramid,const vector<Mat>& maskPyramid,vector<vector<KeyPoint> >& allKeypoints,int nfeatures, int firstLevel, double scaleFactor,int edgeThreshold, int patchSize, int scoreType )
{int nlevels = (int)imagePyramid.size();  //金字塔层数vector<int> nfeaturesPerLevel(nlevels);// fill the extractors and descriptors for the corresponding scalesfloat factor = (float)(1.0 / scaleFactor);float ndesiredFeaturesPerScale = nfeatures*(1 - factor)/(1 - (float)pow((double)factor, (double)nlevels));//int sumFeatures = 0;for( int level = 0; level < nlevels-1; level++ )   //对每层图像上分配相应角点数{nfeaturesPerLevel[level] = cvRound(ndesiredFeaturesPerScale);sumFeatures += nfeaturesPerLevel[level];ndesiredFeaturesPerScale *= factor;}nfeaturesPerLevel[nlevels-1] = std::max(nfeatures - sumFeatures, 0);//剩下角点数,由最上层图像提取// Make sure we forget about what is too close to the boundary//edge_threshold_ = std::max(edge_threshold_, patch_size_/2 + kKernelWidth / 2 + 2);// pre-compute the end of a row in a circular patchint halfPatchSize = patchSize / 2;           //计算每个特征点圆邻域的位置信息vector<int> umax(halfPatchSize + 2);int v, v0, vmax = cvFloor(halfPatchSize * sqrt(2.f) / 2 + 1);int vmin = cvCeil(halfPatchSize * sqrt(2.f) / 2);for (v = 0; v <= vmax; ++v)           //umax[v] = cvRound(sqrt((double)halfPatchSize * halfPatchSize - v * v));// Make sure we are symmetricfor (v = halfPatchSize, v0 = 0; v >= vmin; --v){while (umax[v0] == umax[v0 + 1])++v0;umax[v] = v0;++v0;}allKeypoints.resize(nlevels);for (int level = 0; level < nlevels; ++level){int featuresNum = nfeaturesPerLevel[level];allKeypoints[level].reserve(featuresNum*2);vector<KeyPoint> & keypoints = allKeypoints[level];// Detect FAST features, 20 is a good thresholdFastFeatureDetector fd(20, true);      fd.detect(imagePyramid[level], keypoints, maskPyramid[level]);//Fast角点检测// Remove keypoints very close to the borderKeyPointsFilter::runByImageBorder(keypoints, imagePyramid[level].size(), edgeThreshold);//去除邻近边界的点if( scoreType == ORB::HARRIS_SCORE ){// Keep more points than necessary as FAST does not give amazing cornersKeyPointsFilter::retainBest(keypoints, 2 * featuresNum);//按Fast强度排序,保留前2*featuresNum个特征点// Compute the Harris cornerness (better scoring than FAST)HarrisResponses(imagePyramid[level], keypoints, 7, HARRIS_K); //计算每个角点的Harris强度响应}//cull to the final desired level, using the new Harris scores or the original FAST scores.KeyPointsFilter::retainBest(keypoints, featuresNum);//按Harris强度排序,保留前featuresNum个float sf = getScale(level, firstLevel, scaleFactor);// Set the level of the coordinatesfor (vector<KeyPoint>::iterator keypoint = keypoints.begin(),keypointEnd = keypoints.end(); keypoint != keypointEnd; ++keypoint){keypoint->octave = level;  //层信息keypoint->size = patchSize*sf; //}computeOrientation(imagePyramid[level], keypoints, halfPatchSize, umax);  //计算角点的方向,(2)分析}
}

2.4.2 质心法计算角点主方向

static void computeOrientation(const Mat& image, vector<KeyPoint>& keypoints,int halfPatchSize, const vector<int>& umax)
{// Process each keypointfor (vector<KeyPoint>::iterator keypoint = keypoints.begin(),  //为每个角点计算主方向keypointEnd = keypoints.end(); keypoint != keypointEnd; ++keypoint){keypoint->angle = IC_Angle(image, halfPatchSize, keypoint->pt, umax);//计算质心方向}
}
static float IC_Angle(const Mat& image, const int half_k, Point2f pt,const vector<int> & u_max)
{int m_01 = 0, m_10 = 0;const uchar* center = &image.at<uchar> (cvRound(pt.y), cvRound(pt.x));// Treat the center line differently, v=0for (int u = -half_k; u <= half_k; ++u)m_10 += u * center[u];// Go line by line in the circular patchint step = (int)image.step1();for (int v = 1; v <= half_k; ++v)    //每次处理对称的两行v{// Proceed over the two linesint v_sum = 0;int d = u_max[v];for (int u = -d; u <= d; ++u){int val_plus = center[u + v*step], val_minus = center[u - v*step];v_sum += (val_plus - val_minus); //计算m_01时,位置上差一个符号m_10 += u * (val_plus + val_minus);}m_01 += v * v_sum;//计算上下两行的m_01}return fastAtan2((float)m_01, (float)m_10);//计算角度
}

2.4.3 计算特征点描述子

static void computeDescriptors(const Mat& image, vector<KeyPoint>& keypoints, Mat& descriptors,const vector<Point>& pattern, int dsize, int WTA_K)
{//convert to grayscale if more than one colorCV_Assert(image.type() == CV_8UC1);//create the descriptor mat, keypoints.size() rows, BYTES colsdescriptors = Mat::zeros((int)keypoints.size(), dsize, CV_8UC1);for (size_t i = 0; i < keypoints.size(); i++)computeOrbDescriptor(keypoints[i], image, &pattern[0], descriptors.ptr((int)i), dsize, WTA_K);
}
static void computeOrbDescriptor(const KeyPoint& kpt,const Mat& img, const Point* pattern,uchar* desc, int dsize, int WTA_K)
{float angle = kpt.angle; //angle = cvFloor(angle/12)*12.f;angle *= (float)(CV_PI/180.f);float a = (float)cos(angle), b = (float)sin(angle);const uchar* center = &img.at<uchar>(cvRound(kpt.pt.y), cvRound(kpt.pt.x));int step = (int)img.step;#if 1#define GET_VALUE(idx) \       //取旋转后一个像素点的值center[cvRound(pattern[idx].x*b + pattern[idx].y*a)*step + \cvRound(pattern[idx].x*a - pattern[idx].y*b)]
#elsefloat x, y;int ix, iy;#define GET_VALUE(idx) \ //取旋转后一个像素点,插值法(x = pattern[idx].x*a - pattern[idx].y*b, \y = pattern[idx].x*b + pattern[idx].y*a, \ix = cvFloor(x), iy = cvFloor(y), \x -= ix, y -= iy, \cvRound(center[iy*step + ix]*(1-x)*(1-y) + center[(iy+1)*step + ix]*(1-x)*y + \center[iy*step + ix+1]*x*(1-y) + center[(iy+1)*step + ix+1]*x*y))
#endifif( WTA_K == 2 ){for (int i = 0; i < dsize; ++i, pattern += 16)//每个特征描述子长度为32个字节{int t0, t1, val;t0 = GET_VALUE(0); t1 = GET_VALUE(1);val = t0 < t1;t0 = GET_VALUE(2); t1 = GET_VALUE(3);val |= (t0 < t1) << 1;t0 = GET_VALUE(4); t1 = GET_VALUE(5);val |= (t0 < t1) << 2;t0 = GET_VALUE(6); t1 = GET_VALUE(7);val |= (t0 < t1) << 3;t0 = GET_VALUE(8); t1 = GET_VALUE(9);val |= (t0 < t1) << 4;t0 = GET_VALUE(10); t1 = GET_VALUE(11);val |= (t0 < t1) << 5;t0 = GET_VALUE(12); t1 = GET_VALUE(13);val |= (t0 < t1) << 6;t0 = GET_VALUE(14); t1 = GET_VALUE(15);val |= (t0 < t1) << 7;desc[i] = (uchar)val;}}else if( WTA_K == 3 ){for (int i = 0; i < dsize; ++i, pattern += 12){int t0, t1, t2, val;t0 = GET_VALUE(0); t1 = GET_VALUE(1); t2 = GET_VALUE(2);val = t2 > t1 ? (t2 > t0 ? 2 : 0) : (t1 > t0);t0 = GET_VALUE(3); t1 = GET_VALUE(4); t2 = GET_VALUE(5);val |= (t2 > t1 ? (t2 > t0 ? 2 : 0) : (t1 > t0)) << 2;t0 = GET_VALUE(6); t1 = GET_VALUE(7); t2 = GET_VALUE(8);val |= (t2 > t1 ? (t2 > t0 ? 2 : 0) : (t1 > t0)) << 4;t0 = GET_VALUE(9); t1 = GET_VALUE(10); t2 = GET_VALUE(11);val |= (t2 > t1 ? (t2 > t0 ? 2 : 0) : (t1 > t0)) << 6;desc[i] = (uchar)val;}}else if( WTA_K == 4 ){for (int i = 0; i < dsize; ++i, pattern += 16){int t0, t1, t2, t3, u, v, k, val;t0 = GET_VALUE(0); t1 = GET_VALUE(1);t2 = GET_VALUE(2); t3 = GET_VALUE(3);u = 0, v = 2;if( t1 > t0 ) t0 = t1, u = 1;if( t3 > t2 ) t2 = t3, v = 3;k = t0 > t2 ? u : v;val = k;t0 = GET_VALUE(4); t1 = GET_VALUE(5);t2 = GET_VALUE(6); t3 = GET_VALUE(7);u = 0, v = 2;if( t1 > t0 ) t0 = t1, u = 1;if( t3 > t2 ) t2 = t3, v = 3;k = t0 > t2 ? u : v;val |= k << 2;t0 = GET_VALUE(8); t1 = GET_VALUE(9);t2 = GET_VALUE(10); t3 = GET_VALUE(11);u = 0, v = 2;if( t1 > t0 ) t0 = t1, u = 1;if( t3 > t2 ) t2 = t3, v = 3;k = t0 > t2 ? u : v;val |= k << 4;t0 = GET_VALUE(12); t1 = GET_VALUE(13);t2 = GET_VALUE(14); t3 = GET_VALUE(15);u = 0, v = 2;if( t1 > t0 ) t0 = t1, u = 1;if( t3 > t2 ) t2 = t3, v = 3;k = t0 > t2 ? u : v;val |= k << 6;desc[i] = (uchar)val;}}elseCV_Error( CV_StsBadSize, "Wrong WTA_K. It can be only 2, 3 or 4." );#undef GET_VALUE
}

3. ORB特征匹配

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>using namespace std;
using namespace cv;int main ( int argc, char** argv )
{if ( argc != 3 ){cout<<"usage: feature_extraction img1 img2"<<endl;return 1;}//-- 读取图像Mat img_1 = imread ( argv[1], CV_LOAD_IMAGE_COLOR );Mat img_2 = imread ( argv[2], CV_LOAD_IMAGE_COLOR );//-- 初始化std::vector<KeyPoint> keypoints_1, keypoints_2;Mat descriptors_1, descriptors_2;Ptr<ORB> orb = ORB::create ( 500, 1.2f, 8, 31, 0, 2, ORB::HARRIS_SCORE,31,20 );//-- 第一步: 检测 Oriented FAST 角点位置orb->detect ( img_1,keypoints_1 );orb->detect ( img_2,keypoints_2 );//-- 第二步: 根据角点位置计算 BRIEF 描述子orb->compute ( img_1, keypoints_1, descriptors_1 );orb->compute ( img_2, keypoints_2, descriptors_2 );Mat outimg1;drawKeypoints( img_1, keypoints_1, outimg1, Scalar::all(-1), DrawMatchesFlags::DEFAULT );imshow("ORB特征点",outimg1);//-- 第三步: 对两幅图像中的BRIEF描述子进行匹配,使用 Hamming 距离vector<DMatch> matches;BFMatcher matcher ( NORM_HAMMING );matcher.match ( descriptors_1, descriptors_2, matches );//-- 第四步:匹配点对筛选double min_dist=10000, max_dist=0;// 找出所有匹配之间的最小距离和最大距离,即是最相似的和最不相似的两组点之间的距离for ( int i = 0; i < descriptors_1.rows; i++ ){double dist = matches[i].distance;if ( dist < min_dist ) min_dist = dist;if ( dist > max_dist ) max_dist = dist;}printf ( "-- Max dist : %f \n", max_dist );printf ( "-- Min dist : %f \n", min_dist );// 当描述子之间的距离大于两倍的最小距离时,即认为匹配有误。// 但有时候最小距离会非常小,设置一个经验值作为下限。std::vector< DMatch > good_matches;for ( int i = 0; i < descriptors_1.rows; i++ ){if ( matches[i].distance <= max ( 2*min_dist, 30.0 ) ){good_matches.push_back ( matches[i] );}}//-- 第五步: 绘制匹配结果Mat img_match;Mat img_goodmatch;drawMatches ( img_1, keypoints_1, img_2, keypoints_2, matches, img_match );drawMatches ( img_1, keypoints_1, img_2, keypoints_2, good_matches, img_goodmatch );imshow ( "所有匹配点对", img_match );imshow ( "优化后匹配点对", img_goodmatch );waitKey(0);return 0;
}

在这里插入图片描述

  • 后续:
    在这里插入图片描述

参考:

1. ORB原理与Opencv源码解析
2. ORBSLAM2学习(一):ORB算法原理

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/88703.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

机器学习终极指南:特征工程(02/2) — 第 -2 部分

接上文&#xff1a;机器学习终极指南&#xff1a;特征工程&#xff08;01/2&#xff09; 五、处理不平衡数据 处理不平衡的数据是机器学习的一个重要方面。不平衡数据是指目标变量的分布不均匀&#xff0c;并且与另一个类相比&#xff0c;一个类的代表性不足。这可能导致模型…

[内网渗透]CFS三层靶机渗透

文章目录 [内网渗透]CFS三层靶机渗透网络拓扑图靶机搭建Target10x01.nmap主机探活0x02.端口扫描0x03.ThinkPHP5 RCE漏洞拿shell0x04.上传msf后门(reverse_tcp)反向连接拿主机权限 内网渗透Target2&#xff08;1&#xff09;路由信息探测&#xff08;2&#xff09;msf代理配置&a…

两个数组的交集-C语言/Java

描述 给定两个数组 nums1 和 nums2 &#xff0c;返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序。&#xff08;1 < nums1.length, nums2.length < 1000&#xff0c;0 < nums1[i], nums2[i] < 1000&#xff09; 示例1 输入…

yolov5代码解读之train.py【训练模型】

哇咔咔&#xff0c;登场 代码开头都是一些导包到模块的&#xff1a; 接下来来到入口函数&#xff1a; 我们直接来到main函数的内容&#xff1a;&#xff08;分四个部分&#xff09; 前两部分&#xff1a; 关于main函数的第二部分中的resume参数&#xff08;496行&#xff09;&…

概率图模型(Probabilistic Graphical Model,PGM)

概率图模型&#xff08;Probabilistic Graphical Model&#xff0c;PGM&#xff09;&#xff0c;是一种用图结构来描述多元随机变量之间条件独立性的概率模型。它可以用来表示复杂的概率分布&#xff0c;进行有效的推理和学习&#xff0c;以及解决各种实际问题&#xff0c;如图…

mysql延时问题排查

背景介绍 最近遇到一个奇怪的问题&#xff0c;有个业务&#xff0c;每天早上七点半产生主从延时&#xff0c;延时时间12.6K&#xff1b; 期间没有抽数/备份等任务&#xff1b;查看慢日志发现&#xff0c;期间有一个delete任务&#xff0c;在主库执行了161s delete from xxxx_…

人类:我觉得1+1=956446,你觉得呢?大模型:啊对对对

大模型太「听话」了怎么办&#xff1f; 大型语言模型&#xff08;LLM&#xff09;的自然语言理解与生成能力一直备受称赞&#xff0c;特别是 ChatGPT 等对话式语言模型能够与人类流畅、自然地进行多轮对话。然而&#xff0c;最近一篇 Google DeepMind 的论文研究发现 LLM 普遍存…

企业权限管理(八)-登陆使用数据库认证

Spring Security 使用数据库认证 在 Spring Security 中如果想要使用数据进行认证操作&#xff0c;有很多种操作方式&#xff0c;这里我们介绍使用 UserDetails 、 UserDetailsService来完成操作。 UserDetails public interface UserDetails extends Serializable { Collecti…

转义字符\

转移字符&#xff0c;就是通过字符&#xff0c;来转变原来字符的意思 常见的转义字符&#xff1a; 1、 2 注&#xff1a;" 的作用和他是类似的 3 4、 当打印\a时&#xff0c;电脑会出现一个警告&#xff0c;蜂鸣的声音 5、 阿斯克码表

机器学习---对数几率回归

1. 逻辑回归 逻辑回归&#xff08;Logistic Regression&#xff09;的模型是一个非线性模型&#xff0c; sigmoid函数&#xff0c;又称逻辑回归函数。但是它本质上又是一个线性回归模型&#xff0c;因为除去sigmoid映射函 数关系&#xff0c;其他的步骤&#xff0c;算法都是…

行业追踪,2023-08-09

自动复盘 2023-08-09 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…

Apache RocketMQ 命令注入

漏洞简介 RocketMQ 5.1.0及以下版本&#xff0c;在一定条件下&#xff0c;存在远程命令执行风险。RocketMQ的NameServer、Broker、Controller等多个组件外网泄露&#xff0c;缺乏权限验证&#xff0c;攻击者可以利用该漏洞利用更新配置功能以RocketMQ运行的系统用户身份执行命…

Java代理模式——静态代理与动态代理

代理模式 代理模式允许你为其他对象提供一个代理&#xff0c;以控制对这个对象的访问。代理模式在不改变实际对象的情况下&#xff0c;可以在访问对象时添加额外的功能。 可以理解为代理模式为被代理对象创造了一个替身&#xff0c;调用者可以通过这个替身去实现这个被代理对…

网络安全 Day30-容器架构上

容器架构上 1. 容器架构1.1 什么是容器1.2 容器 vs 虚拟机(化) :star::star:1.3 Docker极速上手指南1&#xff09;使用rpm包安装docker2) docker下载镜像加速的配置3) 载入镜像大礼包&#xff08;老师资料包中有&#xff09; 1.4 Docker使用案例1&#xff09; 案例01&#xff1…

【算法篇C++实现】常见查找算法

文章目录 &#x1f680;一、预备知识⛳&#xff08;一&#xff09;查找的定义⛳&#xff08;二&#xff09;数组和索引 &#x1f680;二、二分查找&#x1f680;三、穷举搜索&#x1f680;四、并行搜索⛳&#xff08;一&#xff09;并发的基本概念⛳&#xff08;二&#xff09;…

行业追踪,2023-08-10

自动复盘 2023-08-10 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…

关于MPU6050的VLOGIC引脚作用

关键字&#xff1a;MPU6X0X、 MPU6050、数字逻辑电平、VLOGIC 框图&#xff1a; 一、VLOGIC引脚作用? VLOGIC引脚主要用于设置为I2C供电引脚&#xff0c;以保证正确的I2C通信。 The bias and LDO section generates the internal supply and the reference voltages and cu…

轻松转换TS视频为MP4,实现优质视频剪辑体验

如果你是一个视频剪辑爱好者&#xff0c;你一定会遇到各种视频格式之间的转换问题&#xff0c;特别是将TS视频转换为MP4格式。别担心&#xff0c;我们的视频剪辑软件将为你提供最简单、高效的解决方案&#xff01; 首先第一步&#xff0c;我们要进入媒体梦工厂主页面&#xff…

Three.js 实现材质边缘通道发光效果

相关API的使用&#xff1a; 1. EffectComposer&#xff08;渲染后处理的通用框架&#xff0c;用于将多个渲染通道&#xff08;pass&#xff09;组合在一起创建特定的视觉效果&#xff09; 2. RenderPass(是用于渲染场景的通道。它将场景和相机作为输入&#xff0c;使用Three.…

【STM32】FreeRTOS消息队列和信号量学习

一、消息队列&#xff08;queue&#xff09; 队列是一种用于实现任务与任务之间&#xff0c;任务与中断之间消息交流的机制。 注意&#xff1a;1.数据的操作是FIFO模式。 2.队列需要明确数据的大小和队列的长度。 3.写和读都会出现堵塞。 实验&#xff1a;创建一个消息队列…