FAST角点检测算法之Opencv代码详解

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));
            }
        }
    }
}
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
fast-lio-localization 是一个基于激光雷达的定位算法,它的核心思想是将激光雷达数据转化为一些特征,并根据这些特征进行定位。下面是 fast-lio-localization 的代码详解: 1. 读取激光雷达数据 首先,在 main 函数中我们需要读取激光雷达数据。fast-lio-localization 支持多种不同类型的激光雷达,包括 Velodyne HDL-32E、Velodyne VLP-16、Hokuyo UTM-30LX 和 Sick LMS200。 2. 预处理激光雷达数据 预处理激光雷达数据是 fast-lio-localization 的一个重要步骤。在这个步骤中,我们需要将激光雷达数据转化为一些特征。这些特征包括: - 点云:将激光雷达数据转化为点云,便于后续处理。 - 点云滤波:对点云进行滤波,去除一些噪声点。 - 特征提取:从点云中提取一些特征,如角点和平面点。 3. 初始化粒子滤波器 fast-lio-localization 使用粒子滤波器来进行定位。在初始化粒子滤波器时,我们需要设置一些参数,如粒子数量、初始位置和方向等。 4. 运行粒子滤波器 在运行粒子滤波器时,我们需要执行以下步骤: - 根据当前机器人的运动模型,对粒子进行预测。 - 使用激光雷达数据和地图,计算每个粒子的权重。 - 根据粒子的权重,重新采样粒子。 5. 输出定位结果 在粒子滤波器运行完毕后,我们可以得到一些最终的粒子。通过对这些粒子的统计分析,我们可以得到机器人的位置和方向,并输出定位结果。 以上就是 fast-lio-localization 的代码详解。需要注意的是,fast-lio-localization 是一个比较复杂的算法,需要对激光雷达数据和粒子滤波器等知识有一定的了解才能深入理解它的实现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值