FAST角点检测实现代码源自于Opencv2.4.11版本sources\modules\features2d\src\fast.cpp文件第54~250行
代码的详细解释以注释的形式给出:
/*
* 函数:FAST_t
* 功能:对图像_img进行FAST角点检测,检测结果存放在keypoints中
* 参数:_img 输入 待检测角点的图像
* keypoints 输出 检测到的角点
* threshold 输入 灰度差阈值(一般情况下该阈值越高,找到的角点越少)
* nonmax_suppression 输入 是否进行非最大值抑制
* 返回值:无
*/
template<int patternSize> // patternSize有8(5-8)、12(7-12)、16(9-16)三个选项,分别对应FAST算法的模板形式,默认为16
void FAST_t(InputArray _img, std::vector<KeyPoint>& keypoints, int threshold, bool nonmax_suppression)
{
Mat img = _img.getMat();
const int K = patternSize/2, N = patternSize + K + 1; // patternSize=16, K=8, N=25
#if CV_SSE2
const int quarterPatternSize = patternSize/4; // quarterPatternSize=4
(void)quarterPatternSize;
#endif
int i, j, k, pixel[25];
makeOffsets(pixel, (int)img.step, patternSize); // 按照模板大小,提前计算偏移量
keypoints.clear();
threshold = std::min(std::max(threshold, 0), 255); // 规范阈值在0~255范围内
#if CV_SSE2
// 设置16个8位整数的值(16个整数均为相同值),例如:delta的值由16个值为-128的8位长整数组成
__m128i delta = _mm_set1_epi8(-128), t = _mm_set1_epi8((char)threshold), K16 = _mm_set1_epi8((char)K);
(void)K16;
(void)delta;
(void)t;
#endif
uchar threshold_tab[512];
for( i = -255; i <= 255; i++ )
threshold_tab[i+255] = (uchar)(i < -threshold ? 1 : i > threshold ? 2 : 0);
AutoBuffer<uchar> _buf((img.cols+16)*3*(sizeof(int) + sizeof(uchar)) + 128);
uchar* buf[3];
buf[0] = _buf; buf[1] = buf[0] + img.cols; buf[2] = buf[1] + img.cols;
int* cpbuf[3];
cpbuf[0] = (int*)alignPtr(buf[2] + img.cols, sizeof(int)) + 1;
cpbuf[1] = cpbuf[0] + img.cols + 1;
cpbuf[2] = cpbuf[1] + img.cols + 1;
memset(buf[0], 0, img.cols*3);
// 从第3行开始计算,遍历至倒数第3行为止(因为FAST算法要求计算某像素周围16个像素格与中心像素的灰度差)
for(i = 3; i < img.rows-2; i++)
{
const uchar* ptr = img.ptr<uchar>(i) + 3;
uchar* curr = buf[(i - 3)%3];
int* cornerpos = cpbuf[(i - 3)%3];
memset(curr, 0, img.cols);
int ncorners = 0;
if( i < img.rows - 3 )
{
j = 3;
#if CV_SSE2
if( patternSize == 16 )
{
for(; j < img.cols - 16 - 3; j += 16, ptr += 16)
{
__m128i m0, m1;
// 加载128bits值(16个8位的值,即16个图像灰度值),返回可存放在代表寄存器的变量中的值
__m128i v0 = _mm_loadu_si128((const __m128i*)ptr);
// 中心像素灰度与阈值t(50)相减,差值再与delta(-128=0x80)进行异或(取绝对值),得到v1,即v1=|中心像素灰度-50|
__m128i v1 = _mm_xor_si128(_mm_subs_epu8(v0, t), delta);
// 中心像素灰度与阈值t(50)相加,和值再与delta(-128=0x80)进行异或(取绝对值),得到v0,即v0=|中心像素灰度+50|
v0 = _mm_xor_si128(_mm_adds_epu8(v0, t), delta);
// 加载中心像素正下方第三行的图像灰度值(以下简称“正下方灰度值”),并减去delta(-128=0x80),相当于与0x80异或,
// 与中心像素灰度值的处理方式保持一致,便于后面的比较操作
__m128i x0 = _mm_sub_epi8(_mm_loadu_si128((const __m128i*)(ptr + pixel[0])), 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[2*quarterPatternSize])), delta);
// 左
__m128i x3 = _mm_sub_epi8(_mm_loadu_si128((const __m128i*)(ptr + pixel[3*quarterPatternSize])), delta);
// 若正下方灰度值>v0,并且正右方灰度值>v0,则m0=0xFF,否则m0=0x0(比较都是8位8位比,m0的值也是8位8位赋,下同)
m0 = _mm_and_si128(_mm_cmpgt_epi8(x0, v0), _mm_cmpgt_epi8(x1, v0));
// 若v1>正下方灰度值,并且正右方灰度值>v1,则m1=0xFF,否则m1=0x0
m1 = _mm_and_si128(_mm_cmpgt_epi8(v1, x0), _mm_cmpgt_epi8(v1, x1));
// 若m0不为0x0,或者正右方灰度值>v0同时正上方灰度值>v0,则m0=0xff,否则m0=0x0
m0 = _mm_or_si128(m0, _mm_and_si128(_mm_cmpgt_epi8(x1, v0), _mm_cmpgt_epi8(x2, v0)));
// 若m1不为0x0,或者v1>正右方灰度值同时v1>正上方灰度值,则m1=0xff,否则m1=0x0
m1 = _mm_or_si128(m1, _mm_and_si128(_mm_cmpgt_epi8(v1, x1), _mm_cmpgt_epi8(v1, x2)));
// 若m0不为0x0,或者正上方灰度值>v0同时正左方灰度值>v0,则m0=0xff,否则m0=0x0
m0 = _mm_or_si128(m0, _mm_and_si128(_mm_cmpgt_epi8(x2, v0), _mm_cmpgt_epi8(x3, v0)));
// 若m1不为0x0,或者v1>正上方灰度值同时v1>正左方灰度值,则m1=0xff,否则m1=0x0
m1 = _mm_or_si128(m1, _mm_and_si128(_mm_cmpgt_epi8(v1, x2), _mm_cmpgt_epi8(v1, x3)));
// 若m0不为0x0,或者正左方灰度值>v0同时正下方灰度值>v0,则m0=0xff,否则m0=0x0
m0 = _mm_or_si128(m0, _mm_and_si128(_mm_cmpgt_epi8(x3, v0), _mm_cmpgt_epi8(x0, v0)));
// 若m1不为0x0,或者v1>正左方灰度值同时v1>正下方灰度值,则m1=0xff,否则m1=0x0
m1 = _mm_or_si128(m1, _mm_and_si128(_mm_cmpgt_epi8(v1, x3), _mm_cmpgt_epi8(v1, x0)));
// 若m0不为0x0,或者m1不为0x0,则m0=0xFF,否则m0=0x0
m0 = _mm_or_si128(m0, m1);
// 总的来说,以上这段代码的含义就是:批量(16个图像数据一次性)比较中心像素与其“上下左右”四个像素格的灰度
// 值(注意:这里的“上下左右”不是相邻的“上下左右”,而是隔了3格的“上下左右”!!!),只要有一个像素格的灰度值
// 与中心像素的灰度值只差大于设定阈值(50),就初步确定该中心像素是角点的候选者
// _mm_movemask_epi8()的作用是取所有8bit 操作数最高bit,然后把它们存储到返回值里
// 即,对包含16个8bit数的128bit输入,取得高位16个bit,存入32位的返回值里,并且将返回值的高位置0
int mask = _mm_movemask_epi8(m0);
// 若批量处理的16个像素中没有一个像素的四周像素值与中心像素值之差大于阈值的,则跳过后面的处理步骤
if( mask == 0 )
continue;
// 若批量处理的16个像素中后面8个像素的四周像素值与中心像素值之差都小于阈值,则回退8格,继续处理
if( (mask & 255) == 0 )
{
j -= 8;
ptr -= 8;
continue;
}
// 初始化变量,c0 = c1 = max0 = max1 = 0
__m128i c0 = _mm_setzero_si128(), c1 = c0, max0 = c0, max1 = c0;
// 从中心像素正下方第三行的像素格开始,围绕中心像素逆时针转一圈半
for( k = 0; k < N; k++ )
{
// 加载128bits值(16个8位的值,即16个图像灰度值),与delta(-128=0x80)异或,与v0和v1的操作保持一致
__m128i x = _mm_xor_si128(_mm_loadu_si128((const __m128i*)(ptr + pixel[k])), delta);
// 比较x和v0中每一个对应的8位的数值的大小,若x>v0,则m0对应的8位数值=0xFF,否则m0对应的8位数值=0x0
m0 = _mm_cmpgt_epi8(x, v0);
// 同上,若v1>x,则m1对应的8位数值=0xFF,否则m1对应的8位数值=0x0
m1 = _mm_cmpgt_epi8(v1, x);
// 若m0=0xff,则c0自加1,否则c0=0(每8位)
c0 = _mm_and_si128(_mm_sub_epi8(c0, m0), m0);
// 若m1=0xff,则c1自加1,否则c1=0(每8位)
c1 = _mm_and_si128(_mm_sub_epi8(c1, m1), m1);
// max0记录c0的最大值(每8位)
max0 = _mm_max_epu8(max0, c0);
// max1记录c1的最大值(每8位)
max1 = _mm_max_epu8(max1, c1);
}
// 统计在围绕中心像素逆时针遍历一圈半的过程中,像素差连续超过阈值的最大像素格个数
max0 = _mm_max_epu8(max0, max1);
// 判断连续超过阈值的像素格个数是否大于了模板大小的一半,若模板大小为16,则连续超过阈值的像素格个数必须大于8
int m = _mm_movemask_epi8(_mm_cmpgt_epi8(max0, K16));
for( k = 0; m > 0 && k < 16; k++, m >>= 1 )
if(m & 1)
{
// 记录角点位置,角点位置为什么要这么记暂时还不清楚
cornerpos[ncorners++] = j+k;
// 是否进行非最大值抑制,若进行非最大值抑制,则对角点进行打分,并将打分结果存入
if(nonmax_suppression)
curr[j+k] = (uchar)cornerScore<patternSize>(ptr+k, pixel, threshold);
}
}
}
#endif
// 注意:以下代码在不使用SSE2指令集的情况下才会被调用,功能与上面#if CV_SSE2...#endif中的代码功能相同
for( ; j < img.cols - 3; j++, ptr++ )
{
int v = ptr[0];
// 下面这行代码的目的是:当像素值与v(中心像素像素值)的差值小于负阈值时,取到1;大于阈值时,取到2;否则,取到0
const uchar* tab = &threshold_tab[0] - v + 255;
// 判断9号格和1号格的像素值与中心像素的像素值之差是否大于阈值,只要有一个差值的绝对值大于阈值,d就不等于0
int d = tab[ptr[pixel[0]]] | tab[ptr[pixel[8]]];
// 若9号格和1号格的像素值与中心像素值之差都没有超过阈值,则不认为该点为角点
if( d == 0 )
continue;
// 判断7号格和15号格的像素值与中心像素的像素值之差是否大于阈值,只要有一个差值的绝对值大于阈值,d就不等于0
d &= tab[ptr[pixel[2]]] | tab[ptr[pixel[10]]];
// 判断5号格和13号格的像素值与中心像素的像素值之差是否大于阈值,只要有一个差值的绝对值大于阈值,d就不等于0
d &= tab[ptr[pixel[4]]] | tab[ptr[pixel[12]]];
// 判断3号格和11号格的像素值与中心像素的像素值之差是否大于阈值,只要有一个差值的绝对值大于阈值,d就不等于0
d &= tab[ptr[pixel[6]]] | tab[ptr[pixel[14]]];
// 7和15号格/5和13号格/3和11号格每一对中必须至少有一个差值超过阈值,否则不认为该点为角点
if( d == 0 )
continue;
// 判断8号格和16号格的像素值与中心像素的像素值之差是否大于阈值,只要有一个差值的绝对值大于阈值,d就不等于0
d &= tab[ptr[pixel[1]]] | tab[ptr[pixel[9]]];
// 判断6号格和14号格的像素值与中心像素的像素值之差是否大于阈值,只要有一个差值的绝对值大于阈值,d就不等于0
d &= tab[ptr[pixel[3]]] | tab[ptr[pixel[11]]];
// 判断4号格和12号格的像素值与中心像素的像素值之差是否大于阈值,只要有一个差值的绝对值大于阈值,d就不等于0
d &= tab[ptr[pixel[5]]] | tab[ptr[pixel[13]]];
// 判断2号格和10号格的像素值与中心像素的像素值之差是否大于阈值,只要有一个差值的绝对值大于阈值,d就不等于0
d &= tab[ptr[pixel[7]]] | tab[ptr[pixel[15]]];
// 若存在周围像素格的像素值与中心像素的像素值差值小于负阈值的情况
if( d & 1 )
{
int vt = v - threshold, count = 0;
for( k = 0; k < N; k++ )
{
int x = ptr[pixel[k]];
if(x < vt)
{
// count用来统计16个周围像素格里面像素值与中心像素像素值之差连续小于负阈值的格子的个数
// 若count大于模板大小的一半(如果模板大小为16,那么count就必须大于8),则将该像素记为候选角点
if( ++count > K )
{
// 记录角点位置,角点位置为中心像素点在图像中的列数
cornerpos[ncorners++] = j;
// 是否进行非最大值抑制,若进行非最大值抑制,则对角点进行打分,并将打分结果存入
if(nonmax_suppression)
curr[j] = (uchar)cornerScore<patternSize>(ptr, pixel, threshold);
break;
}
}
else
count = 0;
}
}
// 若存在周围像素格的像素值与中心像素的像素值差值大于阈值的情况
if( d & 2 )
{
int vt = v + threshold, count = 0;
for( k = 0; k < N; k++ )
{
int x = ptr[pixel[k]];
if(x > vt)
{
if( ++count > K )
{
cornerpos[ncorners++] = j;
if(nonmax_suppression)
curr[j] = (uchar)cornerScore<patternSize>(ptr, pixel, threshold);
break;
}
}
else
count = 0;
}
}
}
}
// 遍历完一行后,记录找到的角点的个数
cornerpos[-1] = ncorners;
// 若处理刚好是第3行,则跳过后面的步骤
if( i == 3 )
continue;
const uchar* prev = buf[(i - 4 + 3)%3];
const uchar* pprev = buf[(i - 5 + 3)%3];
cornerpos = cpbuf[(i - 4 + 3)%3];
ncorners = cornerpos[-1];
for( k = 0; k < ncorners; k++ )
{
j = cornerpos[k];
int score = prev[j];
// 进行非最大值抑制,若该候选角点对应的分数在其邻域8个像素格内最大,则将该候选角点放入输出角点的集合中
if( !nonmax_suppression ||
(score > prev[j+1] && score > prev[j-1] &&
score > pprev[j-1] && score > pprev[j] && score > pprev[j+1] &&
score > curr[j-1] && score > curr[j] && score > curr[j+1]) )
{
keypoints.push_back(KeyPoint((float)j, (float)(i-1), 7.f, -1, (float)score));
}
}
}
}