OpenCV代码精妙之二 FAST特征点检测 巧妙避免大量if

FAST源码分析

FAST 是被广泛使用的一种特征点,由 英国学者 提出,
在SLAM 这样的性能要求高的场合中被大量使用,

顾名思义, FAST的最大特点就是 快速,好像是说能达到 SIFT 速度的100倍左右
具体多少不重要,能快个数量级是肯定的

FAST 具体方法为比较图像中某个点的像素值和周围像素点的差异,
如果 具有一定数量的邻域点 和 中心点(即待判断是否为特征点的像素点) 像素差距超过 既定阈值,
那么 该中心点被判断为 特征点

既然是 比较 像素值, 那么实现时 最直观的方法就是 使用 if , 进行各种比较,
如下是 FAST 作者 在github上给出的代码
这里写图片描述

作者在其原始论文《Machine learning for high-speed corner detection》中给出了 这种代码是怎么来的,即,使用决策树在数据集上进行训练,最后把决策树转换为C语言代码。

这样的代码执行效率非常低,因为 大量的逻辑判断,导致 无法发挥CPU 的流水线 优势,
《深入理解计算机系统》上面阐述过 相关原理, CPU 会 预执行指令,碰到逻辑判断也会如此,所谓的“冒险”,如果预测失败,那么就再返回到 正确的分支上继续执行。

好了, OpenCV 的代码是怎么实现的呢? 看不到大量的if, 当然 OpenCV 顺手把 SSE 的版本也实现了下
抠出来的关键代码如下

    int patternSize = 16;
    Mat img = _img.getMat();
    const int K = patternSize/2, N = patternSize + K + 1;

    int i, j, k, pixel[25];
    makeOffsets2(pixel, (int)img.step, patternSize);

    keypoints.clear();

    threshold = std::min(std::max(threshold, 0), 255);


    // 中心点和周围点的比较情况: 分类成为darker、similar、brighter三种
    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];

    // //保存对应角点强度值,否则为0, 强度值一个 uchar 类型就够了
    buf[0] = _buf; buf[1] = buf[0] + img.cols; buf[2] = buf[1] + img.cols;

    //保存角点位置,+1为了存储这一行的角点总数
    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);

    // 逐行进行检测
    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;

            for( ; j < img.cols - 3; j++, ptr++ )
            {
                int v = ptr[0];
                const uchar* tab = &threshold_tab[0] - v + 255;
                int d = tab[ptr[pixel[0]]] | tab[ptr[pixel[8]]];

                if( d == 0 )
                    continue;

                d &= tab[ptr[pixel[2]]] | tab[ptr[pixel[10]]];
                d &= tab[ptr[pixel[4]]] | tab[ptr[pixel[12]]];
                d &= tab[ptr[pixel[6]]] | tab[ptr[pixel[14]]];

                if( d == 0 )
                    continue;

                d &= tab[ptr[pixel[1]]] | tab[ptr[pixel[9]]];
                d &= tab[ptr[pixel[3]]] | tab[ptr[pixel[11]]];
                d &= tab[ptr[pixel[5]]] | tab[ptr[pixel[13]]];
                d &= tab[ptr[pixel[7]]] | tab[ptr[pixel[15]]];

                if( d & 1 ) ////     中心值大,周围小的情况
                {
                    int vt = v - threshold, count = 0;

                    for( k = 0; k < N; k++ ) //且连续一半的像素点灰度差值( v-x > threshold )大于阈值  
                    {
                        int x = ptr[pixel[k]];
                        if(x < vt)
                        {
                            if( ++count > K )
                            {
                                cornerpos[ncorners++] = j;
                                if(nonmax_suppression)
                                    curr[j] = (uchar)cornerScore2(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)cornerScore2(ptr, pixel, threshold);
                                break;
                            }
                        }
                        else
                            count = 0;
                    }
                }
            } // end of for 列
        }

        cornerpos[-1] = ncorners; ////存储第i行上的角点总数量

        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]; ////存储 第 m-1 行 角点的列位置
        ncorners = cornerpos[-1]; // 其实是第 m-1 行 角点个数

        // 假设当前把第 m 行的点都检测了,如下代码实际上是在处理 第 m-1 行, prev是第m-1行的信息
        // pprev是第 m-2 行的信息, curr是第m行的信息,所以插入的特征点的纵坐标为 i-1
        for( k = 0; k < ncorners; k++ )
        {
            j = cornerpos[k];
            int score = prev[j]; 
            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));
            }
        }
    } // end of for 行

避免大量 if 的核心原理就在于, 利用了 实际需要比较的都是像素数据, 即整数, 范围为0-255,
通过使用 算术运算,判断 结果的正负 ,间接实现了 像素的 大小比较!
看到这样的代码,不得不 佩服 作者功力之深厚!!!

要比较出两个数的大小是否超过某个阈值,那么 需要将这两个数字相减,
然后 将 差 和阈值进行比较

OpenCV 的取巧之处在于 差 和 阈值的比较方法很独特,
先是利用 先验信息 形成了一个 临时数组threshold_tab, 该数组的计算方法如下

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

计算结果如下图所示
这里写图片描述
将某两个像素 进行 比较的 方法如下
先获取 第一个像素像素值,假定为 v1
另一个像素值大小为 v2
const uchar* tab = &threshold_tab[0] - v + 255;
int d = tab[v2]

d的值 就能表明 v1 和 v2 的差异是否超过 特定阈值!
更妙的地方是 , 如果 v1 和 v2 的差异如果 超过阈值, 那么d 要么是1, 要么是2
分别 对应 v1 > v2+ th 和 v2 > v1 + th
如果 在 阈值范围内, 那么 d的值 位0

而 1 和 2 的共同点时 二进制 表示中 都有 一位 为1
因此, FAST 中 大量的像素差异比较 可以 转换为 位运算!
所以代码中有大量这样的代码
d &= tab[ptr[pixel[1]]] | tab[ptr[pixel[9]]];

即在获取 比较结果
比如

                int d = tab[ptr[pixel[0]]] | tab[ptr[pixel[8]]];

                if( d == 0 )
                    continue;

即表明 ptr[pixel[0]] 和 ptr[pixel[8]] 都和 当前像素值比较接近!
因为 此时 或运算 两边都是0

这样, 避免了大量 if
厉害了, OpenCV !

猜你喜欢

转载自blog.csdn.net/haithink/article/details/77937253