FAST特征点检测的matlab实现

1. 简介

Features From Accelerate Segment Test (FAST) 是一种常用的特征点检测方法。它具有速度快,特征点数量可控的优点。由FAST衍生出来的Oriented FAST and Rotated Brief (ORB)描述子也常常被应用于实时性较强的应用中。OPENCV中有关于FAST实现的源代码,在matlab中也可以直接使用detectFASTFeatures()函数提取FAST特征点。但是matlab中的我们无法看到FAST的实现方式,也无法对其进行优化改进。本文提供了一个关于FAST的matlab版本,帮助那些喜欢matlab的朋友们学习参考。为了让整个博文具有完整性,我会简单地介绍一下FAST的实现原理,便于与后面matlab的源码对应,方便读者的理解与使用,如果熟悉FAST原理的小伙伴可以直接跳过原理部分讲解,阅读源代码,也可以点击下面的链接下载我写的FAST的matlab源码。
https://download.csdn.net/download/qq_35721810/10867848

2. FAST的原理介绍

和大部分特征点提取方法一样,FAST特征点提取主要分为两个步骤:

  1. 特征点检测
  2. 极值点抑制

2.1 特征点检测

从原理的角度讲,FAST并不像如Harris,SIFT,SURF那样具有非常严格的数学背景,它更像是一种基于主观经验判断的方法。其核心思想就是找到一些比周围点的浅或者深的点作为特征点,如图1所示
FAST特征点提取示意图
图1. FAST特征点提取示意图

FAST特征点检测的方法,就是比较检测点的灰度值P0与检测点附近的点的灰度值P1,P2,…,P16之间的关系。这里P1,P2,…,P16是以P0为中心3为半径的Bresenham圆的圆周上点的灰度。读者可以根据自己的需要适当地改变Bresenham圆的半径。
刚才提到的FAST特征点提取是要找到相比周围点浅或者深的点,为了减小图像噪声的干扰,FAST作者提出的方法是将P1,P2,…,P16看成一个循环列表,如果他们中有连续n个点大于P0 + threshold或者连续n个点小于P0 - threshold,则认为该检测点是“可能”的特征点(后面要进行极值点抑制,被抑制的点会从特征点的候选名单里剔除,为了便于表达,下文中我就不严格区别特征点的candidate和真正的特征点)。这里的threshold主要还是为了防止由于图像噪声引起的检测误差,可以是一个相对于P0的比例,也可以是一个具体的灰度值,对于半径为3的Bresenham圆一般n可以取9或者12(大于圆周上一般的点数即可)。下面举几个特征点与非特征点的例子帮助大家理解。
连续n个点大于待检测点
图2. 连续9个及以上的点大于待检测点。该检测点为特征点

假设图2中,检测点的灰度值P0 = 100,用橙色表示,threshold = 10,n = 9,红色的点代表灰度值大于P0 + threshold (110)的点,黄色代表灰度值小于P0 - threshold(90)的点,灰色代表灰度值介于90到110之间的点。从图中可以看到由于P13,P14,P15,P16,P1,P2,P3,P4,P5,P6这10个连续的点都大于P0 + threshold (110),并且连续的点数大于等于n = 9,因此该检测点为特征点。同样的方法可以得到图3中有连续的9个点(P1到P9)小于P0 - threshold(90),因此该检测点也为特征点。
连续9个以上的点小于待检测点
图3. 连续9个及以上的点小于待检测点。该检测点为特征点

下面给出一个反例。如图4所示,虽然P0周围的点都满足|Pi-P0|>thershold,但是没有连续的n个点大于P0+threshold或者没有连续的n个点小于P0-threshold,因此该检测点不是特征点。需要指出的是作者的原文的表述中有一定的奇异性。“n” contiguous pixels out of the 16 need to be either above or below P0 by the threshold, if the pixel needs to be detected as an interest point .[1] 这里容易误解成有n个连续的点满足 |Pi-P0|>thershold,可以认为是特征点。但是我在仔细研读赵春江解读的opencv的FAST源码后[2],发现作者的本意是要求这n个连续的点必须都大于P0+threshold,或者都小于P0-threshold,才满足特征点判定条件。(当然特征点检测算法是人定义的,并没有严格的对与错之分,这里我只是尽可能地还原作者的本意。)
在这里插入图片描述
图4. 非特征点的案例

这里有一个问题:为什么要与圆周上的点的灰度值做比较?而不是直接与检测点相邻的每一个点进行比较呢?我认为可能是FAST的作者希望能予以特征点一些相对冗余的包容度(这一点恰好与基于一阶导数差分或者二阶导数差分判断的特征点方法相反)。图像特征点其实并不是一个非常精确的概念,随着光影,摄像头焦距的变化,特征点很可能会在原本位置附近“浮动”,因此跳开检测点相邻区域,只比较圆周上的点,可以忽略特征点这种浮动带来的影响。如果相邻区域出现多个特征点可以通过极值点抑制的方法来剔除。
上述过程是用于判定待定点是否为特征点的方法,根据FAST特征点的定义判定一个检测点是否为特征点,需要依次查看周围16个点的连续9个点是否大于P0 + threshold以及小于P0 - threshold的情况,每一个检测点最糟情况是进行2169次判断。如果这样的操作需要遍历全图每一个点,显然计算量也不小,FAST的作者提出了一个“偷懒”的方法。即优先判断上下左右四个点P1,P5,P9,P13。并且作者还引入了一个阈值列表来加速该过程的判断,具体细节可以参考赵春江解读的opencv的FAST源码[2]。不过需要指出的是作者的这种优化思想主要是针对CPU逐点计算的优化思想(大部分点不是特征点),对于FPGA,GPU等并行加速设计时,if语句的判断操作并不适用。下面的matlab实现中,为了让代码看起来更容易理解,我只注重原理的实现,并不追求时效性。

2.2 极值点抑制

极值点抑制是指把上述过程中提取出来可能的特征点,通过一定的逻辑进行筛选。留下那些更具有代表性的点。Harris,SIFT,和SURF的极值点抑制是利用Hessian矩阵的行列式计算特征点边界特性,边界特性强的点其特征相似度较大,会被剔除。
FAST方法的特征提取相对更加显获一些。FAST的作者把“更具代表性的点”理解为在一定区域内相比与周围环境更浅或者更深的点。
如何让算法找到这些灰度更浅或者更深的点?首先就必须给这些特征点进行打分,其次在一定的区域内进行筛选(分数最高的留下,分数低的淘汰)。
第一,FAST的打分方法是改变threshold,找到最大的一个threshold使该点恰好满足特征点判定的条件,即满足n个以上连续的点大于P0 + threshold,或者P0-threshold*。这个threshold即为该点的得分值V = threshold。如果图像的像素是UINT类型,V一定为圆周上某个与特征点差值的绝对值减一,即V=|Pk-P0|-1,k为1到16中的某一个点。算法实现过程中只需要遍历这16个点找到满足条件的最大的thershold*即可。
特征点打分案例
图5. 特征点打分

假设特征点的灰度值P0 = 100,threshold = 10,当threhsold* = 11时,灰度值为111的点不再满足大于P0 + threshold* 的条件。但是还是有9个连续的点满足大于P0 + threshold*。当threshold* = 14时,该检测点恰好不满足特征点的判定条件,因此threshold*的最大值为V = 14 -1 = 13。我们可以遍历整张图片如果该点不是特征点V(i,j) = 0,如果该点为特征点V(i,j) >= threshold。
第二,极值点抑制方法。刚才的方法可以得到一个特征点打分V(i,j)的map,遍历每一个可能的特征点,如果该特征点的V不是在以它为中心3x3的区域内最大值,则删除该点,如果是则保留。

扫描二维码关注公众号,回复: 4634246 查看本文章

3. matlab源码实现

matlab的源代码可以通过以下的链接下载:
https://download.csdn.net/download/qq_35721810/10867848
文件中有一张png的测试图片和三个.m文件。如下图所示
文件说明
图5 文件说明

matlabFast.m是使用matlab中自带的detectFASTFeatures()函数提取的FAST特征点,可以用于对比实验。myFAST.m是FAST特征点检测的实现。testMyFAST.m是FAST检测的一个用例。读者可以直接运行testMyFAST.m看到FAST源码的实现结果。

下面我主要针对myFAST.m进行解读。
myFAST是一个实现FAST特征点检测的类,这个类中有四个properties

    properties
        numInChain = 12;
        threshold = 0.3;
        img;
        featureMap;
    end

numInChain代表连续点的数量,即上文中提到的n。threshold代表判断特征点超出的阈值,这里我使用的是比例。img是图像,下面初始化的时候,我会把原始图像转成灰度图并保存在img中。featureMap就是上文提到的V,它是一个与原图一样大小的map,如果该点不是特征点featureMap(i, j) = 0,如果该点是特征点featureMap(i, j) 即为该点的打分值。
methods中的第一个函数为myFAST的构造函数。

        function obj = myFAST(img_)
            if ndims(img_) == 3
                obj.img = rgb2gray(img_);
            else
                obj.img = img_;
            end
        end

img_为原始图像,构造函数把原始图像转为灰度图,并存在类的img中。
函数operate()是FAST实现的主要函数。

        % FAST的实现
        function [corners] = operate(obj)
            corners = []; % 特征点
            tempCorners = []; % 特征点的candidate
            [m, n] = size(obj.img);
            obj.featureMap = zeros(m, n); %特征点的打分值,如果不是特征点打分为0
            for i = 4:m-4
                for j = 4:n-4
                    % p0为检测点
                    p0 = obj.img(i,j);
                    % 依次提取当前点半径为3的Bresenham圆圆周上的点,检测点正上方为p(1)
                    p = [];
                    p(end + 1) = obj.img(i - 3 ,j    );  %p1
                    p(end + 1) = obj.img(i - 3 ,j + 1);  %p2
                    p(end + 1) = obj.img(i - 2 ,j + 2);  %p3
                    p(end + 1) = obj.img(i - 1 ,j + 3);  %p4
                    p(end + 1) = obj.img(i     ,j + 3);  %p5
                    p(end + 1) = obj.img(i + 1 ,j + 3);  %p6
                    p(end + 1) = obj.img(i + 2 ,j + 2);  %p7
                    p(end + 1) = obj.img(i + 3 ,j + 1);  %p8
                    p(end + 1) = obj.img(i + 3 ,j    );  %p9
                    p(end + 1) = obj.img(i + 3 ,j - 1);  %p10
                    p(end + 1) = obj.img(i + 2 ,j - 2);  %p11
                    p(end + 1) = obj.img(i + 1 ,j - 3);  %p12
                    p(end + 1) = obj.img(i     ,j - 3);  %p13
                    p(end + 1) = obj.img(i - 1 ,j - 3);  %p14
                    p(end + 1) = obj.img(i - 2 ,j - 2);  %p15
                    p(end + 1) = obj.img(i - 3 ,j - 1);  %p16
                    thr = obj.threshold*p0;
                    
                    %判断该点是否为特征点的candidate
                    %featureFlag = 1 代表周围有n个以上的点大于p0 + thr
                    %featureFlag = 2 代表周围有n个以上的点小于p0 - thr
                    featureFlag = FastFeaturePointDetect(p0,p,thr,obj.numInChain);
                    if featureFlag == 1 || featureFlag == 2  
                        %如果是特征点,计算特征点的打分值
                        val = ExtremeSuppression(p0,p,thr, obj.numInChain,featureFlag);
                        tempCorners(end + 1).x = j;
                        tempCorners(end).y = i;
                        tempCorners(end).val = val;
                        obj.featureMap(i,j) = val;
                    end
                end
            end
            
            %极值点抑制
            for k = 1:length(tempCorners)
                j = tempCorners(k).x;
                i = tempCorners(k).y;
                tempList(1) = obj.featureMap(i - 1,j - 1);
                tempList(2) = obj.featureMap(i - 1,j    );
                tempList(3) = obj.featureMap(i - 1,j + 1);
                tempList(4) = obj.featureMap(i    ,j - 1);
                tempList(5) = obj.featureMap(i    ,j + 1);
                tempList(6) = obj.featureMap(i - 1,j - 1);
                tempList(7) = obj.featureMap(i - 1,j    );
                tempList(8) = obj.featureMap(i - 1,j + 1);
                %如果特征点的打分值是附近3*3区域里最大的则将tempCorners(k)付给corners
                if tempCorners(k).val > max(tempList)
                    corners(end+1).x = tempCorners(k).x;
                    corners(end).y = tempCorners(k).y;
                    corners(end).val = tempCorners(k).val;
                end
            end
        end
    end
end

函数operate()中有两个大循环,第一个循环会遍历全图(由于Bresenham圆半径为3,因此中心点从(4,4)开始),依次判断图中每一个像素是否为特征点的candidate,如果是,则将该点的坐标保存下来,并且为该特征点打分,并且记录在featureMap中。第二个循环遍历每一个特征点的candidate,判断是否是3x3区域中打分值最大的。如果是,则保留存入corners里,如果不是,则删除。
第一个循环中有两个主要的函数: FastFeaturePointDetect()和ExtremeSuppression()。FastFeaturePointDetect()是用来判断该点是否为特征点;ExtremeSuppression()是用来给特征点打分的(这里名命为ExtremeSuppression极值点抑制,可能写代码时有欠考虑,但其实这个函数的作用只是为特征点打分,抑制是operate()的第二个大循环实现的)

%判断该点是否为特征点的candidate
function flag = FastFeaturePointDetect(p0,p,thr, numInChain)
flag = 0;
% 遍历16个点,看是否有numInChain个点大于p0 + thr
% 循环列表的index = mod(k-1,length(p))+1
for i = 1:16
    if p(i) - p0 > thr
        counter = 1;
        for j = 1:numInChain - 1
            k = i + j;
            if p(mod(k-1,length(p))+1) - p0 > thr
                counter = counter + 1;
            else
                break;
            end
        end
        if counter == numInChain
            flag = 1;
            return
        end
    else
        continue;
    end
end

% 遍历16个点,看是否有numInChain个点小于p0 - thr
for i = 1:16
    if p0 - p(i) > thr
        counter = 1;
        for j = 1:numInChain - 1
            k = i + j;
            if  p0 - p(mod(k-1,length(p))+1) > thr
                counter = counter + 1;
            else
                break;
            end
        end
        if counter == numInChain
            flag = 2;
            return
        end
    else
        continue;
    end
end

end

下面一次给大家讲解上述的两个函数。首先FastFeaturePointDetect(p0,p,thr, numInChain)的输入p0代表待检测点,即上文中的P0,p代表p0周围的16个点,thr代表threshold,numInChain代表n个连续的点数。这个函数中一共有两个二个循环,第一个循环用来判断是否有numInChain个点大于p0 + thr;第二个循环用来判断是否有numInChain个点小于p0 - thr。这里为了让实现更加简单易懂,我并没有严格按照FAST作者推荐的“偷懒”方法去实现(我猜用matlab的朋友们并不关心算法的运行速度)。这里有一个使用循环列表的小技巧,在matlab中循环列表的index可以根据index = mod(k-1,length( p ))+1来实现,用一个例子说明,假设length( p ) = 16,k为17,k此时已经超出了p的长度,可以计算出mod(17 - 1,16)+1 = 1,因此index正好指向p的第一元素。这里的featureFlag为该函数的输出结果,如果结果为1代表有numInChain个点大于p0 + thr,如果结果为2代表有numInChain个点小于p0 - thr,如果结果为0代表该点不是特征点。

%给特征点打分
function [val] = ExtremeSuppression(p0, p,thr, numInChain,featureFlag)
N = length(p);
if featureFlag == 1
    d = p - double(p0);
else
    d = double(p0) - p;
end
    a0 = thr;
    
    %打分的结果一定是d(1)到d(16)中的一个,遍历16次找出最大的满足条件的d(k)
    for i = 1:16
        a = min([d(mod(i,N)+1),d(mod(i+1,N)+1),d(mod(i+2,N)+1)]);        
        if a < a0
            continue;        
        end        
        for j = 4:numInChain-1
            a = min(a,d(mod(i+j-1,N)+1));
        end
        
        a0 = max(a0,min(a, d(mod(i-1,N)+1) ));
        a0 = max(a0, min(a, d(mod(i-1,N)+1) ));
    end

    
val = a0 -1;
end

特征点的打分用ExtremeSuppression(p0, p,thr, numInChain,featureFlag)函数实现。它的基本思想就是打分值一定是这16个点中其中一个,因此遍历这16个点,如果这个点之后的连续9个点中最小的点,大于thr,则将其记录下来。满足上面条件最大的点的灰度值减一,即为所求结果。

4. 结果展示

最后,是我的算法和matlab自带函数的效果对比图,总体效果上看还是差不多的,但是个人感觉matlab里detectFASTFeatures()的特征点提取还是比我自己实现的效果要好一点吧,可能里面的极值点抑制与我的方法有一些差异。
myFAST
图6. myFAST的实现结果

matlabFAST
图7. matlab函数的实现结果

[1]: Features from Accelerated Segment Test, Deepak Geetha Viswanathan
[2]: https://blog.csdn.net/zhaocj/article/details/40301561

猜你喜欢

转载自blog.csdn.net/qq_35721810/article/details/85158020