一、Fast算法

1、基本原理

Fast特征点检测feature2D原理是在圆周上按顺时针方向从1到16的顺序对圆周像素点进行编号。如果在圆周上有N个连续的像素的亮度都比圆心像素的亮度Ip加上阈值t还要亮,或者比圆心像素的亮度减去阈值还要暗,则圆心像素被称为角点。

算法核心:利用周围像素比较的信息可以得到特征点,简单、高效。

FAST特征检测算法来源于corner的定义,基于特征点周围的像素灰度值。检测候选特征点周围一圈的像素值,如果候选区域内像素点足够多且与候选点灰度值差值足够大,则认为一个特征点。所以思路是:构建差值窗口,阈值选择(点足够多)

其中I(x)为圆周上任意一点的灰度,I(p)为圆心的灰度,Ed为灰度值差的阈值,如果N大于给定阈值,一般为周围圆圈点的四分之三,则认为p是一个特征点。

在原理基础上,为了提高运算速度,在计算时采用额外加速方法。

在点周围每隔90度的四个点,如果有3个和候选点的灰度值值足够大才认为此候选点为特征点候选点。如果不满足此条件直接丢弃。程序中采用半径为3,共有16(N)个周围像素需要比较。FAST_9,FAST_10就是表示周围像素个数。

2、算法流程

根据算法原理在设计程序时大体流程为:

1、设置阈值:用于比较是否周围像素点和候选点的差值是否足够大,阈值选择很重要,也是一个缺陷

2、构建移动窗口:程序中设计为半径为3,大约16个像素组成的区域,与中心点像素比较

3、候选像素与构建的周围区域比较:算法采用先与图中位置法检查在位置1,9,5和13四个位置的像素,首先检测位置1和位置9,如果它们都比阈值暗或比阈值亮,再检测位置5和位置13。如果$P$是一个角点,那么上述四个像素点中至少有3个应该必须都大于$I_p+t$或者小于$I_p-t$,因为若是一个角点,超过四分之三圆的部分应该满足判断条件。如果满足,则检测圆内所有点。如果不满足直接舍弃

4、对角点进行非极大值抑制,得到角点输出。

以上方法还是有不够鲁棒的地方,但可以通过机器学习和非极大值抑制的方法来增强鲁棒性。

1、计算得分函数,它的值V是特征点与其圆周上16个像素点的绝对差值中所有连续10个像素中的最小值的最大值,而且该值还要大于阈值t;

2、在3×3的特征点邻域内(而不是图像邻域),比较V;

3、剔除掉非极大值的特征点

3、算法性质

  1. 通过周围区域判断四个角上点不能拒绝许多的候选点;
  2. 检测出来的角点不是最优的,这是因为它的效率取决于问题的排序与角点的分布;
  3. 对于角点分析的结果被丢弃了;
  4. 多个特征点容易挤在一起
  5. 阈值选择对结果有很大影响

二、算法源码

注:此源码来自于opencv,在此基础进行分析和理解。

template<int patternSize>
void FAST_t(InputArray _img, std::vector<KeyPoint>& keypoints, int threshold, bool nonmax_suppression)
{
Mat img = _img.getMat(); //提取出输入图像矩阵
//K为圆周连续像素的个数
//N用于循环圆周的像素点,因为要首尾连接,所以N要比实际圆周像素数量多K+1个
const int K = patternSize/, N = patternSize + K + ;
#if CV_SSE2
const int quarterPatternSize = patternSize/;
(void)quarterPatternSize;
#endif
int i, j, k, pixel[];
//找到圆周像素点相对于圆心的偏移量
makeOffsets(pixel, (int)img.step, patternSize);
//特征点向量清零
keypoints.clear();
//保证阈值不大于255,不小于0
threshold = std::min(std::max(threshold, ), ); #if CV_SSE2
__m128i delta = _mm_set1_epi8(-), t = _mm_set1_epi8((char)threshold), K16 = _mm_set1_epi8((char)K);
(void)K16;
(void)delta;
(void)t;
#endif
// threshold_tab为阈值列表,在进行阈值比较的时候,只需查该表即可
uchar threshold_tab[];
/*为阈值列表赋值,该表分为三段:第一段从threshold_tab[0]至threshold_tab[255 - threshold],值为1,落在该区域的值表示满足角点判断条件2;第二段从threshold_tab[255 – threshold]至threshold_tab[255 + threshold],值为0,落在该区域的值表示不是角点;第三段从threshold_tab[255 + threshold]至threshold_tab[511],值为2,落在该区域的值表示满足角点判断条件1*/
for( i = -; i <= ; i++ )
threshold_tab[i+] = (uchar)(i < -threshold ? : i > threshold ? : );
//开辟一段内存空间
AutoBuffer<uchar> _buf((img.cols+)**(sizeof(int) + sizeof(uchar)) + );
uchar* buf[];
/*buf[0、buf[1]和buf[2]分别表示图像的前一行、当前行和后一行。因为在非极大值抑制的步骤2中,是要在3×3的角点邻域内进行比较,因此需要三行的图像数据。因为只有得到了当前行的数据,所以对于上一行来说,才凑够了连续三行的数据,因此输出的非极大值抑制的结果是上一行数据的处理结果*/
buf[] = _buf; buf[] = buf[] + img.cols; buf[] = buf[] + img.cols;
//cpbuf存储角点的坐标位置,也是需要连续三行的数据
int* cpbuf[];
cpbuf[] = (int*)alignPtr(buf[] + img.cols, sizeof(int)) + ;
cpbuf[] = cpbuf[] + img.cols + ;
cpbuf[] = cpbuf[] + img.cols + ;
memset(buf[], , img.cols*); //buf数组内存清零
//遍历整幅图像像素,寻找角点
//由于圆的半径为3个像素,因此图像的四周边界都留出3个像素的宽度
for(i = ; i < img.rows-; i++)
{
//得到图像行的首地址指针
const uchar* ptr = img.ptr<uchar>(i) + ;
//得到buf的某个数组,用于存储当前行的得分函数的值V
uchar* curr = buf[(i - )%];
//得到cpbuf的某个数组,用于存储当前行的角点坐标位置
int* cornerpos = cpbuf[(i - )%];
memset(curr, , img.cols); //清零
int ncorners = ; //检测到的角点数量 if( i < img.rows - )
{
//每一行都留出3个像素的宽度
j = ;
#if CV_SSE2
if( patternSize == )
{
for(; j < img.cols - - ; j += , ptr += )
{
__m128i m0, m1;
__m128i v0 = _mm_loadu_si128((const __m128i*)ptr);
__m128i v1 = _mm_xor_si128(_mm_subs_epu8(v0, t), delta);
v0 = _mm_xor_si128(_mm_adds_epu8(v0, t), delta); __m128i x0 = _mm_sub_epi8(_mm_loadu_si128((const __m128i*)(ptr + pixel[])), delta);
__m128i x1 = _mm_sub_epi8(_mm_loadu_si128((const __m128i*)(ptr + pixel[quarterPatternSize])), delta);
__m128i x2 = _mm_sub_epi8(_mm_loadu_si128((const __m128i*)(ptr + pixel[*quarterPatternSize])), delta);
__m128i x3 = _mm_sub_epi8(_mm_loadu_si128((const __m128i*)(ptr + pixel[*quarterPatternSize])), delta);
m0 = _mm_and_si128(_mm_cmpgt_epi8(x0, v0), _mm_cmpgt_epi8(x1, v0));
m1 = _mm_and_si128(_mm_cmpgt_epi8(v1, x0), _mm_cmpgt_epi8(v1, x1));
m0 = _mm_or_si128(m0, _mm_and_si128(_mm_cmpgt_epi8(x1, v0), _mm_cmpgt_epi8(x2, v0)));
m1 = _mm_or_si128(m1, _mm_and_si128(_mm_cmpgt_epi8(v1, x1), _mm_cmpgt_epi8(v1, x2)));
m0 = _mm_or_si128(m0, _mm_and_si128(_mm_cmpgt_epi8(x2, v0), _mm_cmpgt_epi8(x3, v0)));
m1 = _mm_or_si128(m1, _mm_and_si128(_mm_cmpgt_epi8(v1, x2), _mm_cmpgt_epi8(v1, x3)));
m0 = _mm_or_si128(m0, _mm_and_si128(_mm_cmpgt_epi8(x3, v0), _mm_cmpgt_epi8(x0, v0)));
m1 = _mm_or_si128(m1, _mm_and_si128(_mm_cmpgt_epi8(v1, x3), _mm_cmpgt_epi8(v1, x0)));
m0 = _mm_or_si128(m0, m1);
int mask = _mm_movemask_epi8(m0);
if( mask == )
continue;
if( (mask & ) == )
{
j -= ;
ptr -= ;
continue;
} __m128i c0 = _mm_setzero_si128(), c1 = c0, max0 = c0, max1 = c0;
for( k = ; k < N; k++ )
{
__m128i x = _mm_xor_si128(_mm_loadu_si128((const __m128i*)(ptr + pixel[k])), delta);
m0 = _mm_cmpgt_epi8(x, v0);
m1 = _mm_cmpgt_epi8(v1, x); c0 = _mm_and_si128(_mm_sub_epi8(c0, m0), m0);
c1 = _mm_and_si128(_mm_sub_epi8(c1, m1), m1); max0 = _mm_max_epu8(max0, c0);
max1 = _mm_max_epu8(max1, c1);
} max0 = _mm_max_epu8(max0, max1);
int m = _mm_movemask_epi8(_mm_cmpgt_epi8(max0, K16)); for( k = ; m > && k < ; k++, m >>= )
if(m & )
{
cornerpos[ncorners++] = j+k;
if(nonmax_suppression)
curr[j+k] = (uchar)cornerScore<patternSize>(ptr+k, pixel, threshold);
}
}
}
#endif
for( ; j < img.cols - ; j++, ptr++ )
{
//当前像素的灰度值
int v = ptr[];
//由当前像素的灰度值,确定其在阈值列表中的位置
const uchar* tab = &threshold_tab[] - v + ;
//pixel[0]表示圆周上编号为0的像素相对于圆心坐标的偏移量
//ptr[pixel[0]表示圆周上编号为0的像素值
//tab[ptr[pixel[0]]]表示相对于当前像素(即圆心)圆周上编号为0的像素值在阈值列表threshold_tab中所查询得到的值,如果为1,说明I0 < Ip - t,如果为2,说明I0 > Ip + t,如果为0,说明 Ip – t < I0 < Ip + t。因此通过tab,就可以得到当前像素是否满足角点条件。
//编号为0和8(即直径在圆周上的两个像素点)在列表中的值相或后得到d。d=0说明编号为0和8的值都是0;d=1说明编号为0和8的值至少有一个为1,而另一个不能为2;d=2说明编号为0和8的值至少有一个为2,而另一个不能为1;d=3说明编号为0和8的值有一个为1,另一个为2。只可能有这四种情况。
int d = tab[ptr[pixel[]]] | tab[ptr[pixel[]]];
//d=0说明圆周上不可能有连续12个像素满足角点条件,因此当前值一定不是角点,所以退出此次循环,进入下一次循环
if( d == )
continue;
//继续进行其他直径上两个像素点的判断
d &= tab[ptr[pixel[]]] | tab[ptr[pixel[]]];
d &= tab[ptr[pixel[]]] | tab[ptr[pixel[]]];
d &= tab[ptr[pixel[]]] | tab[ptr[pixel[]]];
//d=0说明上述d中至少有一个d为0,所以肯定不是角点;另一种情况是一个d为2,而另一个d为1,相与后也为0,这说明一个是满足角点条件1,而另一个满足角点条件2,所以肯定也不会有连续12个像素满足同一个角点条件的,因此也一定不是角点。
if( d == )
continue;
//继续判断圆周上剩余的像素点
d &= tab[ptr[pixel[]]] | tab[ptr[pixel[]]];
d &= tab[ptr[pixel[]]] | tab[ptr[pixel[]]];
d &= tab[ptr[pixel[]]] | tab[ptr[pixel[]]];
d &= tab[ptr[pixel[]]] | tab[ptr[pixel[]]];
//如果满足if条件,则说明有可能满足角点条件2
if( d & )
{
//vt为真正的角点条件,即Ip – t,count为连续像素的计数值
int vt = v - threshold, count = ;
//遍历整个圆周
for( k = ; k < N; k++ )
{
int x = ptr[pixel[k]]; //提取出圆周上的像素值
if(x < vt) //如果满足条件2
{
//连续计数,并判断是否大于K(K为圆周像素的一半)
if( ++count > K )
{
//进入该if语句,说明已经得到一个角点
//保存该点的位置,并把当前行的角点数加1
cornerpos[ncorners++] = j;
//进行非极大值抑制的第一步,计算得分函数
if(nonmax_suppression)
curr[j] = (uchar)cornerScore<patternSize>(ptr, pixel, threshold);
break; //退出循环
}
}
else
count = ; //连续像素的计数值清零
}
}
//如果满足if条件,则说明有可能满足角点条件1
if( d & )
{
//vt为真正的角点条件,即Ip + t,count为连续像素的计数值
int vt = v + threshold, count = ;
//遍历整个圆周
for( k = ; k < N; k++ )
{
int x = ptr[pixel[k]]; //提取出圆周上的像素值
if(x > vt) //如果满足条件1
{
//连续计数,并判断是否大于K(K为圆周像素的一半)
if( ++count > K )
{
//进入该if语句,说明已经得到一个角点
//保存该点的位置,并把当前行的角点数加1
cornerpos[ncorners++] = j;
//进行非极大值抑制的第一步,计算得分函数
if(nonmax_suppression)
curr[j] = (uchar)cornerScore<patternSize>(ptr, pixel, threshold);
break; //退出循环
}
}
else
count = ; //连续像素的计数值清零
}
}
}
}
//保存当前行所检测到的角点数
cornerpos[-] = ncorners;
//i=3说明只仅仅计算了一行的数据,还不能进行非极大值抑制的第二步,所以不进行下面代码的操作,直接进入下一次循环
if( i == )
continue;
//以下代码是进行非极大值抑制的第二步,即在3×3的角点邻域内对得分函数的值进行非极大值抑制。因为经过上面代码的计算,已经得到了当前行的数据,所以可以进行上一行的非极大值抑制。因此下面的代码进行的是上一行的非极大值抑制。
//提取出上一行和上两行的图像像素
const uchar* prev = buf[(i - + )%];
const uchar* pprev = buf[(i - + )%];
//提取出上一行所检测到的角点位置
cornerpos = cpbuf[(i - + )%];
//提取出上一行的角点数
ncorners = cornerpos[-];
//在上一行内遍历整个检测到的角点
for( k = ; k < ncorners; k++ )
{
j = cornerpos[k]; //得到角点的位置
int score = prev[j]; //得到该角点的得分函数值
//在3×3的角点邻域内,计算当前角点是否为最大值,如果是则压入特性值向量中
if( !nonmax_suppression ||
(score > prev[j+] && score > prev[j-] &&
score > pprev[j-] && score > pprev[j] && score > pprev[j+] &&
score > curr[j-] && score > curr[j] && score > curr[j+]) )
{
keypoints.push_back(KeyPoint((float)j, (float)(i-), .f, -, (float)score));
}
}
}
}

在该函数内,对阈值列表理解起来可能有一定的难度,下面我们举一个具体的例子来进行讲解。设我们选取的阈值threshold为30,则根据

for( i = -255; i <= 255; i++ )

threshold_tab[i+255] = (uchar)(i < -threshold ? 1 : i > threshold? 2 : 0);

我们可以从-255到255一共分为3段:-255~-30,-30~30,30~255。由于数组的序号不能小于0,因此在给threshold_tab数组赋值上,序号要加上255,这样区间就变为:0~225,225~285,285~510,而这三个区间对应的值分别为1,0和2。设我们当前像素值为40,则根据

const uchar* tab = &threshold_tab[0] -v + 255;

tab的指针指向threshold_tab[215]处,因为255-40=215。这样在圆周像素与当前像素进行比较时,使用的是threshold_tab[215]以后的值。例如圆周上编号为0的像素值为5,则该值在阈值列表中的位置是threshold_tab[215 + 5],是threshold_tab[220]。它在阈值列表中的第一段,即threshold_tab[220] = 1,说明编号为0的像素满足角点条件2。我们来验证一下:5 < 40 – 30,确实满足条件2;如果圆周上编号为1的像素值为80,则该值在阈值列表中的位置是threshold_tab[295](即215 + 80 = 295),而它在阈值列表中的第三段,即threshold_tab[295] = 2,因此它满足角点条件1,即80 > 40 + 30;而如果圆周上编号为2的像素值为45,则threshold_tab[260] = 0,它不满足角点条件,即40 – 30 < 45 < 40 + 30。

三、opencv函数解析

1、测试函数

void main()   {       Mat src;       src = imread("D:/Demo.jpg");       // vector of keyPoints       std::vector<KeyPoint> keyPoints;       // construction of the fast feature detector object       FastFeatureDetector fast();   // 检测的阈值为40       // feature point detection 
    fast.detect(src,keyPoints);       drawKeypoints(src, keyPoints, src, Scalar::all(-), DrawMatchesFlags::DRAW_OVER_OUTIMG);       imshow("FAST feature", src);       cvWaitKey();   }

2、函数解释

在OpenCV中,当patternSize为16时,用以下数组表示这16个点相对于圆心的坐标:

static const int offsets16[][2] =

{

{0,  3}, { 1,  3}, { 2,  2}, { 3,  1}, { 3, 0}, { 3, -1}, { 2, -2}, { 1, -3},

{0, -3}, {-1, -3}, {-2, -2}, {-3, -1}, {-3, 0}, {-3,  1}, {-2,  2}, {-1,  3}

};

OpenCV用函数来计算圆周上的点相对于圆心坐标在原图像中的位置:

void makeOffsets(int pixel[], int rowStride, int patternSize)
{
//分别定义三个数组,用于表示patternSize为16,12和8时,圆周像素对于圆心的相对坐标位置
static const int offsets16[][] =
{
{, }, { , }, { , }, { , }, { , }, { , -}, { , -}, { , -},
{, -}, {-, -}, {-, -}, {-, -}, {-, }, {-, }, {-, }, {-, }
}; static const int offsets12[][] =
{
{, }, { , }, { , }, { , }, { , -}, { , -},
{, -}, {-, -}, {-, -}, {-, }, {-, }, {-, }
}; static const int offsets8[][] =
{
{, }, { , }, { , }, { , -},
{, -}, {-, -}, {-, }, {-, }
};
//根据patternSize值,得到具体应用上面定义的哪个数组
const int (*offsets)[] = patternSize == ? offsets16 :
patternSize == ? offsets12 :
patternSize == ? offsets8 : ; CV_Assert(pixel && offsets); int k = ;
//代入输入图像每行的像素个数,得到圆周像素的绝对坐标位置
for( ; k < patternSize; k++ )
pixel[k] = offsets[k][] + offsets[k][] * rowStride;
//由于要计算连续的像素,因此要循环的多列出一些值
for( ; k < ; k++ )
pixel[k] = pixel[k - patternSize];
}
template<>
int cornerScore<>(const uchar* ptr, const int pixel[], int threshold)
{
const int K = , N = K* + ;
//v为当前像素值
int k, v = ptr[];
short d[N];
//计算当前像素值与其圆周像素值之间的差值
for( k = ; k < N; k++ )
d[k] = (short)(v - ptr[pixel[k]]); #if CV_SSE2
__m128i q0 = _mm_set1_epi16(-), q1 = _mm_set1_epi16();
for( k = ; k < ; k += )
{
__m128i v0 = _mm_loadu_si128((__m128i*)(d+k+));
__m128i v1 = _mm_loadu_si128((__m128i*)(d+k+));
__m128i a = _mm_min_epi16(v0, v1);
__m128i b = _mm_max_epi16(v0, v1);
v0 = _mm_loadu_si128((__m128i*)(d+k+));
a = _mm_min_epi16(a, v0);
b = _mm_max_epi16(b, v0);
v0 = _mm_loadu_si128((__m128i*)(d+k+));
a = _mm_min_epi16(a, v0);
b = _mm_max_epi16(b, v0);
v0 = _mm_loadu_si128((__m128i*)(d+k+));
a = _mm_min_epi16(a, v0);
b = _mm_max_epi16(b, v0);
v0 = _mm_loadu_si128((__m128i*)(d+k+));
a = _mm_min_epi16(a, v0);
b = _mm_max_epi16(b, v0);
v0 = _mm_loadu_si128((__m128i*)(d+k+));
a = _mm_min_epi16(a, v0);
b = _mm_max_epi16(b, v0);
v0 = _mm_loadu_si128((__m128i*)(d+k+));
a = _mm_min_epi16(a, v0);
b = _mm_max_epi16(b, v0);
v0 = _mm_loadu_si128((__m128i*)(d+k));
q0 = _mm_max_epi16(q0, _mm_min_epi16(a, v0));
q1 = _mm_min_epi16(q1, _mm_max_epi16(b, v0));
v0 = _mm_loadu_si128((__m128i*)(d+k+));
q0 = _mm_max_epi16(q0, _mm_min_epi16(a, v0));
q1 = _mm_min_epi16(q1, _mm_max_epi16(b, v0));
}
q0 = _mm_max_epi16(q0, _mm_sub_epi16(_mm_setzero_si128(), q1));
q0 = _mm_max_epi16(q0, _mm_unpackhi_epi64(q0, q0));
q0 = _mm_max_epi16(q0, _mm_srli_si128(q0, ));
q0 = _mm_max_epi16(q0, _mm_srli_si128(q0, ));
threshold = (short)_mm_cvtsi128_si32(q0) - ;
#else
//a0为阈值
int a0 = threshold;
//满足角点条件2时,更新阈值
for( k = ; k < ; k += )
{
//a为d[k+1],d[k+2]和d[k+3]中的最小值
int a = std::min((int)d[k+], (int)d[k+]);
a = std::min(a, (int)d[k+]);
//如果a小于阈值,则进行下一次循环
if( a <= a0 )
continue;
//更新阈值
//a为从d[k+1]到d[k+8]中的最小值
a = std::min(a, (int)d[k+]);
a = std::min(a, (int)d[k+]);
a = std::min(a, (int)d[k+]);
a = std::min(a, (int)d[k+]);
a = std::min(a, (int)d[k+]);
//从d[k]到d[k+9]中的最小值与a0比较,哪个大,哪个作为新的阈值
a0 = std::max(a0, std::min(a, (int)d[k]));
a0 = std::max(a0, std::min(a, (int)d[k+]));
}
//满足角点条件1时,更新阈值
int b0 = -a0;
for( k = ; k < ; k += )
{
int b = std::max((int)d[k+], (int)d[k+]);
b = std::max(b, (int)d[k+]);
b = std::max(b, (int)d[k+]);
b = std::max(b, (int)d[k+]);
if( b >= b0 )
continue;
b = std::max(b, (int)d[k+]);
b = std::max(b, (int)d[k+]);
b = std::max(b, (int)d[k+]); b0 = std::min(b0, std::max(b, (int)d[k]));
b0 = std::min(b0, std::max(b, (int)d[k+]));
} threshold = -b0-;
#endif #if VERIFY_CORNERS
testCorner(ptr, pixel, K, N, threshold);
#endif
//更新后的阈值作为输出
return threshold;
}

四、参考文献

1、FAST特征

2、fast特征源码分析

Fast特征检测的更多相关文章

  1. FAST特征点检测

    Features From Accelerated Segment Test 1. FAST算法原理 博客中已经介绍了很多图像特征检测算子,我们可以用LoG或者DoG检测图像中的Blobs(斑点检测) ...

  2. 【OpenCV文档】用于角点检测的Fast算法

    原文地址:http://docs.opencv.org/trunk/doc/py_tutorials/py_feature2d/py_fast/py_fast.html#fast-algorithm- ...

  3. OpenCV特征点提取----Fast特征

    1.FAST(featuresfrom accelerated segment test)算法 http://blog.csdn.net/yang_xian521/article/details/74 ...

  4. 第十四节、FAST角点检测(附源码)

    在前面我们已经陆续介绍了许多特征检测算子,我们可以根据图像局部的自相关函数求得Harris角点,后面又提到了两种十分优秀的特征点以及他们的描述方法SIFT特征和SURF特征.SURF特征是为了提高运算 ...

  5. FAST特征点检测算法

    一 原始方法 简介 在局部特征点检测快速发展的时候,人们对于特征的认识也越来越深入,近几年来许多学者提出了许许多多的特征检测算法及其改进算法,在众多的特征提取算法中,不乏涌现出佼佼者. 从最早期的Mo ...

  6. SLAM: 图像角点检测的Fast算法(OpenCV文档)

    官方链接:http://docs.opencv.org/trunk/doc/py_tutorials/py_feature2d/py_fast/py_fast.html#fast-algorithm- ...

  7. FAST特征点检测&&KeyPoint类

    FAST特征点检测算法由E.Rosten和T.Drummond在2006年在其论文"Machine Learning for High-speed Corner Detection" ...

  8. [转]ORB特征提取-----FAST角点检测

    转载地址:https://blog.csdn.net/maweifei/article/details/62887831 (一)ORB特征点提取算法的简介 Oriented FAST and Rota ...

  9. ORB

    http://wenku.baidu.com/link?url=R4Ev8aJNxwmjV0egSUqVBjmnt1KT_llzp8Oy2NbHnwa7Me9UAIHkiMG2Vwucu3RSDKwy ...

随机推荐

  1. 异常: http://www.ly.com/news/visa.html: java.io.IOException: unzipBestEffort returned null

    nutch 运行时异常: http://www.ly.com/news/visa.html: java.io.IOException: unzipBestEffort returned null 参考 ...

  2. Oracle的登录操作

    在完美的启动Oracle数据库之后就可以登录数据库了: 1. 首先登录时使用的用户名默认是“SYSTEM”密码是你安装的时候自行设置的. 登录使用的命令是“sqlplus / as sysdba”之后 ...

  3. BZOJ 3713: [PA2014]Iloczyn

    Description 斐波那契数列的定义为:k=0或1时,F[k]=k:k>1时,F[k]=F[k-1]+F[k-2].数列的开头几项为0,1,1,2,3,5,8,13,21,34,55,-你 ...

  4. UVA 1160 X-Plosives

    题意是一次装入物品,物品由两种元素组成,当遇到即将装入的物品与已经装入的物品形成k个物品,k种元素,跳过该物品的装入.可以将每种元素看成顶点,物品看成一条边.这样问题就转化为利用并查集求环的情况. 算 ...

  5. UVA 515 King

    差分约束系统的第一个题目,看了落花大神的博客后,对差分约束有了一定了解,关键在于建图,然后就是判断是否存在负权回路. 关于差分约束系统的解释详见维基百科:http://zh.wikipedia.org ...

  6. JavaScript实现命令行交互

    原文地址: http://www.cnblogs.com/liaoyu/p/js-terminal.html 周末闲着想试试用 JavaScript 模拟命令行交互的功能,希望达到的几个功能点如下: ...

  7. Android 三档自定义滑动开关,禁止点击功能的实现,用默认的seekbar组件实现

    android三档自定义滑动开关,禁止点击功能的实现,普通开关网上有很多例子,三档滑动开关的则找了整天都没有相关例子,开始用普通开关的源码修改了自己实现了一个类,但效果不如人意,各种边界情况的算法很难 ...

  8. chrome插件 postman插件 接口测试、API & HTTP 请求调试工具

    Postman 是一个非常棒的Chrome扩展,提供功能强大的API & HTTP 请求调试. 它能够发送任何类型的HTTP requests (GET, HEAD, POST, PUT..) ...

  9. ActionBar官方教程(4)给ActionBar添加操作项及它们的事件处理

    Adding Action Items The action bar provides users access to the most important action items relating ...

  10. IQ Test

    IQ Test Description: Bob is preparing to pass IQ test. The most frequent task in this test is to fin ...