MSER算法

本文转载于:https://blog.csdn.net/datase/article/details/72331031
注:转载处的文章也是转载的,但是该作者没有给出原创地址,所以我只能先给出该作者的地址,侵删。
代码部分转载出问题,请大家转到连接处查看。

最稳定极值区域介绍

如把灰度图看成高低起伏的地形图,其中灰度值看成海平面高度的话,MSER的作用就是在灰度图中找到符合条件的坑洼。条件为坑的最小高度,坑的大小,坑的倾斜程度,坑中如果已有小坑时大坑与小坑的变化率。

上图展示了几种不同的坑洼,根据最小高度,大小,倾斜程度这些条件的不同,选择的坑也就不同。

 上图展示了最后一个条件,大坑套小坑的情况。根据条件的不同,选择也不同。

 以上便是对坑的举例,MSER主要流程就三部分组成:

    1.预处理数据

    2.遍历灰度图

    3.判断一个区域(坑洼)是否满足条件

简单来说,就如将水注入这个地形中。水遇到低处就往低处流,如果没有低处了,水位就会一点点增长,直至淹没整个地形。在之前预处理下数据,在水位提高时判断下是否满足条件。

预处理数据

 先说下流程中的主要部件,如下:

  1.img,由原8位单通道灰度图转化的更容易遍历和记录数据的32位单通道图。预处理内容为:

    32位值记录从这点是否探索过,探索过的方向,灰度值;图大小也扩大了,最外添加了一个像素的完整一圈,值为-1可看作墙,宽度也改变为2的整数次方,用于加快运算。

    如果由掩码图,如下:

  2.heap,记录坑洼边界的堆栈,每个灰度值都有自己的堆栈。预处理内容为:

    计算所有灰度值的个数,这样提前就可以分配堆栈大小。例如知道了灰度2的像素由4个,就可以将灰度2的堆栈大小分配为5(多一个位标志位空)。

  3.comp,记录水坑数据的堆栈,有水位值(灰度值),面积(像素个数和像素位置)等。预处理内容为:

    仅仅是分配内存,分配257个(0-255外多一个用作结束)

  4.history,记录水位抬高的历史,就是一个小坑抬高水位后一点点变成大坑的历史。预处理内容为:

    仅仅是分配内存,大小为像素点个数(就是宽*高)。可以想成所有点都不同都可以形成历史的最大个数。

遍历灰度图

在重复下整个简单的过程:就如将水注入这个地形中。水遇到低处就往低处流,如果没有低处了,水位就会一点点增长,直至淹没整个地形。先说下主要部件:

  1.img,由原8位单通道灰度图转化的更容易遍历和记录数据的32位单通道图。遍历时:

    当前像素位置中有3位记录方向(除了东南西北还有一个用来代表结束),逐个改变方向遍历。还有最高1位记录是否发现过了。根据方向遍历相邻像素,如果4个方向都探索过了,就从heap边界中找到一个最小灰度的边界,出栈来用作当前像素。最终将所有像素的4个方向都走完,也是所有像素都被发现了,遍历就结束。

  2.heap,记录坑洼边界的堆栈,每个灰度值都有自己的堆栈。遍历时:

    当水遇到低处时入栈当前位置为低处的边界,当水遇到相等高度或高处时入栈那个边界;当抬高水位时出栈被抬高到的边界。

  3.comp,记录水坑数据的堆栈,有水位值(灰度值),面积(像素个数和像素位置)等。遍历时:

    当水位下降时新入栈,水位提高时出栈并可能与之前的合并。

  4.history,记录水位抬高的历史,就是一个小坑抬高水位后一点点变成大坑的历史。遍历时:

    history主要是记录用来判断最大稳定极值区域的数据,没有遍历的作用。主要记录时刻有两种:提高水位到边界heap中的最小高度,提高水位到comp中上一项的高度。要记录灰度值,像素数,快捷指针,孩子指针,稳定时的像素数。

下面举例子,走下遍历的流程(并不是依次就是一步,一些步骤合并了)(红色为有变动位置,时间匆忙没有仔细校准每个位置):

 

中上,要遍历的灰度图。为了方便观看,上文提到周围一圈的-1被去掉了。

左下,history是抬高水位的历史。

中下,comp是水位数据。预先入栈一个256的灰度作为顶,用来抬高水位时判断边界值小还是上一个水位数据的灰度值小。

右下,heap是边界堆栈,heap_start是每个灰度指向heap堆栈的指针。特殊说明下,heap是一个个堆栈连接在一起的一个数组,由于上面说的预处理过了,已经知道每个灰度的像素个数,所以提前指定了heap_start中每个灰度指向heap中的位置,指向0代表所在堆栈没有数据。例如灰度2有4个像素,所以灰度3的指针从灰度2指针后5个后开始,4个是像素数,1个是代表空的0。

从A1位置开始,comp中入栈个灰度2的数据项,并将heap_cur当前指针设置为2灰度的指针。

探索A1右边B1,标识为已发现。B1的值2没有小于当前水位值2,作为边界入栈。

探索A1下面的A2。值1小于当前水位2,将2入栈边界栈,入栈水位数据1,调整边界指针heap_cur为指向1的指针,当前像素为A2。

探索A2右边B3与下边A3,都没有比当前水位1小,分别入栈所属灰度的边界栈。

A2所有方向都探索完,将A2加入当前水位数据comp中。

在边界栈中找到最小灰度的一个值出栈(图5里边界里有灰度2的和灰度3的,从当前灰度1开始一点点加大所以找到了灰度2),出栈了A3。A3的灰度2,所以抬高水位。记录历史histroy,修改当前水位数据灰度为2,边界指针heap_cur指向2灰度的堆栈。

探索A3周边,发现B3,灰度3比当前大作为边界入栈。

A3所有方向也都探索完,将A3加入当前水位数据comp中。

边界中找到A1。由于A1灰度还是2,没有提升水位。将A1作为当前像素。

刚刚的A1周围也早就探索完了,将A1加入当前水位数据comp中。

又在边界中找到了B1,并出栈作为当前像素。

B1右边探索到了C1,加入灰度3的边界栈。

这时,B1周围已经探索完毕,将B1加入当前水位数据comp中。

B1被加入在边界栈中从灰度2开始查找,找到灰度3中C1作为当前像素。然后记录历史history,提高当前水位数据comp的灰度值,设置heap_cur指针到灰度3的边界栈。

从当前像素C1向下找到C2,C2灰度比当前低。将当前像素C1入栈边界栈,新建灰度2的水位数据comp,边界指针heap_cur指向灰度2,设置C2为当前指针。

探索C2下面最后一个像素C3,将C3加入边界栈。

将C2加入水位数据comp中。

需要抬高水位了,从灰度3的边界栈中出栈C3,发现灰度和上一个水位数据comp的灰度一样,需要合并这两个comp数据。添加历史history,合并两个comp数,改变边界栈heap_cur到灰度3,设置C3为当前像素。

最后的C3,C1,B3,B2周围都没有可以探索的像素了,依次出栈加入水位数据。

至此所有9个像素都探索完毕了。

判断一个区域(坑洼)是否满足条件

先看下参数:

  int delta;      // 两个区域间的灰度差
  int minArea;     // 区域最小像素数
  int maxArea;     // 区域最大像素数
  double maxVariation;  // 两个区域的偏差
  double minDiversity;  // 当前区域与稳定区域的变化率

一个水坑的变化如下图A,随着水位的提高,面积由Ra变为Rb在到Rc,Ra为Rb的父区域;判断极值区域的方法如图B,在delta水位差间两个区域面积是否满足一定条件;还有一个判断条件如图C,如果已经有一个候选区域Rstable了,Rcandidate是否可以作为一个极值区域,也就是大坑套小坑的情况。

maxVariation是上图B的情况,值为下面的公式A;minDiversity是上图C的情况,值为下面公式B:

下面是在条件判断时两个有用的部件(其他没有任何作用):

  3.comp,记录水坑数据的堆栈,有水位值(灰度值),面积(像素个数和像素位置)等。条件判断时:

    有个history指向当前区域的历史的指针,用来查找当前区域之前的变化历史;var用来记录上次计算的variation;div用来记录上次计算的diversity。(var与div用来确保坑越来越稳定,如果与上次的值比较发散了则不满足条件)

  4.history,记录水位抬高的历史,就是一个小坑抬高水位后一点点变成大坑的历史。条件判断时:

    每一个历史项都有指向孩子历史的指针child,与指向相差delta灰度历史的快捷指针shortcut,还有上次稳定时的像素数stable,最后就是那个历史时刻的灰度值val与像素数size。(快捷指针是用来加速计算的,在历史里一个一个向前找也能找到,但总没有直接在上次找到的位置前后找更快吧:))

源码

 基本结构:

复制代码
typedef struct LinkedPoint
    {
  struct LinkedPoint* prev;
        struct LinkedPoint* next;
        Point pt;
    }
    LinkedPoint;
</span><span style="color:#008000;">//</span><span style="color:#008000;"> the history of region grown</span>
typedef <span style="color:#0000ff;">struct</span><span style="color:#000000;"> MSERGrowHistory
{
    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 快捷路径,是指向以前历史的指针。因为不是一个一个连接的,所以不是parent。算法中是记录灰度差为delta的历史的指针。
    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 例如:当前是灰度是10,delta=3,这个指针就指向灰度为7时候的历史</span>
    <span style="color:#0000ff;">struct</span> MSERGrowHistory*<span style="color:#000000;"> shortcut;
    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 指向更新历史的指针,就是从这个历史繁衍的新历史,所以叫孩子</span>
    <span style="color:#0000ff;">struct</span> MSERGrowHistory*<span style="color:#000000;"> child;
    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 大于零代表稳定,值是稳定是的像素数。这个值在不停的继承</span>
    <span style="color:#0000ff;">int</span> stable; <span style="color:#008000;">//</span><span style="color:#008000;"> when it ever stabled before, record the size
    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 灰度值</span>
    <span style="color:#0000ff;">int</span><span style="color:#000000;"> val;
    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 像素数</span>
    <span style="color:#0000ff;">int</span><span style="color:#000000;"> size;
}
MSERGrowHistory;

typedef </span><span style="color:#0000ff;">struct</span><span style="color:#000000;"> MSERConnectedComp
{
    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 像素点链的头</span>
    LinkedPoint*<span style="color:#000000;"> head;
    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 像素点链的尾</span>
    LinkedPoint*<span style="color:#000000;"> tail;
    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 区域上次的增长历史,可以通过找个历史找到之前的记录</span>
    MSERGrowHistory*<span style="color:#000000;"> history;
    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 灰度值</span>
    unsigned <span style="color:#0000ff;">long</span><span style="color:#000000;"> grey_level;
    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 像素数</span>
    <span style="color:#0000ff;">int</span><span style="color:#000000;"> size;
    </span><span style="color:#0000ff;">int</span> dvar; <span style="color:#008000;">//</span><span style="color:#008000;"> the derivative of last var</span>
    <span style="color:#0000ff;">float</span> <span style="color:#0000ff;">var</span>; <span style="color:#008000;">//</span><span style="color:#008000;"> the current variation (most time is the variation of one-step back)</span>

}
MSERConnectedComp;

struct MSERParams
{
MSERParams(
int _delta, int _minArea, int _maxArea, double _maxVariation,
double _minDiversity, int _maxEvolution, double _areaThreshold,
double _minMargin, int _edgeBlurSize)
: delta(_delta), minArea(_minArea), maxArea(_maxArea), maxVariation(_maxVariation),
minDiversity(_minDiversity), maxEvolution(_maxEvolution), areaThreshold(_areaThreshold),
minMargin(_minMargin), edgeBlurSize(_edgeBlurSize)
{}

    </span><span style="color:#008000;">//</span><span style="color:#008000;"> MSER使用</span>
    <span style="color:#0000ff;">int</span> delta;                        <span style="color:#008000;">//</span><span style="color:#008000;"> 两个区域间的灰度差</span>
    <span style="color:#0000ff;">int</span> minArea;                    <span style="color:#008000;">//</span><span style="color:#008000;"> 区域最小像素数</span>
    <span style="color:#0000ff;">int</span> maxArea;                    <span style="color:#008000;">//</span><span style="color:#008000;"> 区域最大像素数</span>
    <span style="color:#0000ff;">double</span> maxVariation;        <span style="color:#008000;">//</span><span style="color:#008000;"> 两个区域的偏差</span>
    <span style="color:#0000ff;">double</span> minDiversity;        <span style="color:#008000;">//</span><span style="color:#008000;"> 当前区域与稳定区域的变化率
    </span><span style="color:#008000;">//</span><span style="color:#008000;"> MSCR使用</span>
    <span style="color:#0000ff;">int</span><span style="color:#000000;"> maxEvolution;
    </span><span style="color:#0000ff;">double</span><span style="color:#000000;"> areaThreshold;
    </span><span style="color:#0000ff;">double</span><span style="color:#000000;"> minMargin;
    </span><span style="color:#0000ff;">int</span><span style="color:#000000;"> edgeBlurSize;
};</span></pre>
复制代码

 预处理:

复制代码
// to preprocess src image to following format
    // 32-bit image
    // > 0 is available, < 0 is visited
    // 17~19 bits is the direction
    // 8~11 bits is the bucket it falls to (for BitScanForward)
    // 0~8 bits is the color
    /** @brief 将所给原单通道灰度图和掩码图 预处理为一张方便遍历与记录数据的32位单通道图像;并且根据像素灰度值分配边缘栈。
    * 32位格式如下:
    * > 0 可用,< 0 已经被访问
    * 17~19位用于记录下一个要探索的方向,5个值
    * 8~11位 用于优化的二值搜索
    * 0~8位用于记录灰度值
    *@param heap_cur 边缘栈
    *@param src 原单通道灰度图
    *@param mask 掩码图
    */
    static int* preprocessMSER_8UC1(CvMat* img,
        int*** heap_cur,
        CvMat* src,
        CvMat* mask)
    {
        // 数据有效内容是在img中,由一圈-1包围着,靠左的区域。也就是被一圈-1的墙包围着。
    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 原始数据跳转到下一行的偏移量。</span>
    <span style="color:#0000ff;">int</span> srccpt = src-&gt;step - src-&gt;<span style="color:#000000;">cols;

    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 跳转到下一行的偏移量,最后减一是因为,例如:xoooxxx,o是有效数据,x是扩充出来的。偏移量应该是3,就是ooo最
    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 右边的xxx个数。为了计算,就需要减去ooo最左面的一个x。</span>
    <span style="color:#0000ff;">int</span> cpt_1 = img-&gt;cols - src-&gt;cols - <span style="color:#800080;">1</span><span style="color:#000000;">;
    </span><span style="color:#0000ff;">int</span>* imgptr = img-&gt;<span style="color:#000000;">data.i;
    </span><span style="color:#0000ff;">int</span>*<span style="color:#000000;"> startptr;

    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 用于记录每个灰度有多少像素</span>
    <span style="color:#0000ff;">int</span> level_size[<span style="color:#800080;">256</span><span style="color:#000000;">];
    </span><span style="color:#0000ff;">for</span> (<span style="color:#0000ff;">int</span> i = <span style="color:#800080;">0</span>; i &lt; <span style="color:#800080;">256</span>; i++<span style="color:#000000;">)
        level_size[i] </span>= <span style="color:#800080;">0</span><span style="color:#000000;">;

    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 设置第一行为-1</span>
    <span style="color:#0000ff;">for</span> (<span style="color:#0000ff;">int</span> i = <span style="color:#800080;">0</span>; i &lt; src-&gt;cols + <span style="color:#800080;">2</span>; i++<span style="color:#000000;">)
    {
        </span>*imgptr = -<span style="color:#800080;">1</span><span style="color:#000000;">;
        imgptr</span>++<span style="color:#000000;">;
    }

    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 偏移到第一个有效数据所在行的开头</span>
    imgptr += cpt_1 - <span style="color:#800080;">1</span><span style="color:#000000;">;
    uchar</span>* srcptr = src-&gt;<span style="color:#000000;">data.ptr;
    </span><span style="color:#0000ff;">if</span><span style="color:#000000;"> (mask)
    {
        </span><span style="color:#008000;">//</span><span style="color:#008000;"> 有掩码</span>
        startptr = <span style="color:#800080;">0</span>;            <span style="color:#008000;">//</span><span style="color:#008000;"> 数据处理的开始位置,为最左上的位置。</span>
        uchar* maskptr = mask-&gt;<span style="color:#000000;">data.ptr;
        </span><span style="color:#0000ff;">for</span> (<span style="color:#0000ff;">int</span> i = <span style="color:#800080;">0</span>; i &lt; src-&gt;rows; i++<span style="color:#000000;">)
        {
            </span><span style="color:#008000;">//</span><span style="color:#008000;"> 最左面设置为-1</span>
            *imgptr = -<span style="color:#800080;">1</span><span style="color:#000000;">;
            imgptr</span>++<span style="color:#000000;">;
            </span><span style="color:#0000ff;">for</span> (<span style="color:#0000ff;">int</span> j = <span style="color:#800080;">0</span>; j &lt; src-&gt;cols; j++<span style="color:#000000;">)
            {
                </span><span style="color:#0000ff;">if</span> (*<span style="color:#000000;">maskptr)
                {
                    </span><span style="color:#0000ff;">if</span> (!<span style="color:#000000;">startptr)
                        startptr </span>=<span style="color:#000000;"> imgptr;

                    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 灰度值取反!!!!! !!!!! !!!!! !!!!!</span>
                    *srcptr = <span style="color:#800080;">0xff</span> - *<span style="color:#000000;">srcptr;

                    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 所在灰度值个数自增</span>
                    level_size[*srcptr]++<span style="color:#000000;">;

                    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 写入0~8位,8~13位用作BitScanForward</span>
                    *imgptr = ((*srcptr &gt;&gt; <span style="color:#800080;">5</span>) &lt;&lt; <span style="color:#800080;">8</span>) | (*<span style="color:#000000;">srcptr);
                }
                </span><span style="color:#0000ff;">else</span><span style="color:#000000;"> {
                    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 标为-1,就是当作一个已经被发现的位置,和外围-1墙的原理一样</span>
                    *imgptr = -<span style="color:#800080;">1</span><span style="color:#000000;">;
                }
                imgptr</span>++<span style="color:#000000;">;
                srcptr</span>++<span style="color:#000000;">;
                maskptr</span>++<span style="color:#000000;">;
            }

            </span><span style="color:#008000;">//</span><span style="color:#008000;"> 最右面设置为-1</span>
            *imgptr = -<span style="color:#800080;">1</span><span style="color:#000000;">;

            </span><span style="color:#008000;">//</span><span style="color:#008000;"> 都跳到下一行开始</span>
            imgptr +=<span style="color:#000000;"> cpt_1;
            srcptr </span>+=<span style="color:#000000;"> srccpt;
            maskptr </span>+=<span style="color:#000000;"> srccpt;
        }
    }
    </span><span style="color:#0000ff;">else</span><span style="color:#000000;"> {
        </span><span style="color:#008000;">//</span><span style="color:#008000;"> 就是没有掩码的情况</span>
        startptr = imgptr + img-&gt;cols + <span style="color:#800080;">1</span><span style="color:#000000;">;
        </span><span style="color:#0000ff;">for</span> (<span style="color:#0000ff;">int</span> i = <span style="color:#800080;">0</span>; i &lt; src-&gt;rows; i++<span style="color:#000000;">)
        {
            </span>*imgptr = -<span style="color:#800080;">1</span><span style="color:#000000;">;
            imgptr</span>++<span style="color:#000000;">;
            </span><span style="color:#0000ff;">for</span> (<span style="color:#0000ff;">int</span> j = <span style="color:#800080;">0</span>; j &lt; src-&gt;cols; j++<span style="color:#000000;">)
            {
                </span>*srcptr = <span style="color:#800080;">0xff</span> - *<span style="color:#000000;">srcptr;
                level_size[</span>*srcptr]++<span style="color:#000000;">;
                </span>*imgptr = ((*srcptr &gt;&gt; <span style="color:#800080;">5</span>) &lt;&lt; <span style="color:#800080;">8</span>) | (*<span style="color:#000000;">srcptr);
                imgptr</span>++<span style="color:#000000;">;
                srcptr</span>++<span style="color:#000000;">;
            }
            </span>*imgptr = -<span style="color:#800080;">1</span><span style="color:#000000;">;
            imgptr </span>+=<span style="color:#000000;"> cpt_1;
            srcptr </span>+=<span style="color:#000000;"> srccpt;
        }
    }

    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 设置最后一行为-1</span>
    <span style="color:#0000ff;">for</span> (<span style="color:#0000ff;">int</span> i = <span style="color:#800080;">0</span>; i &lt; src-&gt;cols + <span style="color:#800080;">2</span>; i++<span style="color:#000000;">)
    {
        </span>*imgptr = -<span style="color:#800080;">1</span><span style="color:#000000;">;
        imgptr</span>++<span style="color:#000000;">;
    }

    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 确定每个灰度在边界堆中的指针位置。0代表没有值。</span>
    heap_cur[<span style="color:#800080;">0</span>][<span style="color:#800080;">0</span>] = <span style="color:#800080;">0</span><span style="color:#000000;">;
    </span><span style="color:#0000ff;">for</span> (<span style="color:#0000ff;">int</span> i = <span style="color:#800080;">1</span>; i &lt; <span style="color:#800080;">256</span>; i++<span style="color:#000000;">)
    {
        heap_cur[i] </span>= heap_cur[i - <span style="color:#800080;">1</span>] + level_size[i - <span style="color:#800080;">1</span>] + <span style="color:#800080;">1</span><span style="color:#000000;">;
        heap_cur[i][</span><span style="color:#800080;">0</span>] = <span style="color:#800080;">0</span><span style="color:#000000;">;
    }
    </span><span style="color:#0000ff;">return</span><span style="color:#000000;"> startptr;
}</span></pre>
复制代码

 主流程及遍历方法:

复制代码
static void extractMSER_8UC1_Pass(int* ioptr,
        int* imgptr,
        int*** heap_cur,                            // 边界栈的堆,里面是每一个灰度的栈
        LinkedPoint* ptsptr,
        MSERGrowHistory* histptr,
        MSERConnectedComp* comptr,
        int step,
        int stepmask,
        int stepgap,
        MSERParams params,
        int color,
        CvSeq* contours,
        CvMemStorage* storage)
    {
        // ER栈第一项为结束的标识项,值为大于255的256
        comptr->grey_level = 256;
    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 将当前位置值入栈,并初始化</span>
    comptr++<span style="color:#000000;">;
    comptr</span>-&gt;grey_level = (*imgptr) &amp; <span style="color:#800080;">0xff</span><span style="color:#000000;">;
    initMSERComp(comptr);

    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 设置为已经发现</span>
    *imgptr |= <span style="color:#800080;">0x80000000</span><span style="color:#000000;">;

    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 加上灰度偏移就将指针定位到了相应灰度的边界栈上</span>
    heap_cur += (*imgptr) &amp; <span style="color:#800080;">0xff</span><span style="color:#000000;">;

    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 四个方向的偏移量,上下的偏移是隔行的步长</span>
    <span style="color:#0000ff;">int</span> dir[] = { <span style="color:#800080;">1</span>, step, -<span style="color:#800080;">1</span>, -<span style="color:#000000;">step };

#ifdef INTRIN_ENABLED
unsigned long heapbit[] = { 0, 0, 0, 0, 0, 0, 0, 0 };
unsigned
long* bit_cur = heapbit + (((*imgptr) & 0x700) >> 8);
#endif

    <span style="color:#008000;">//</span><span style="color:#008000;"> 循环</span>
    <span style="color:#0000ff;">for</span><span style="color:#000000;"> (;;)
    {
        </span><span style="color:#008000;">//</span><span style="color:#008000;"> take tour of all the 4 directions
        </span><span style="color:#008000;">//</span><span style="color:#008000;"> 提取当前像素的方向值,判断是否还有方向没有走过</span>
        <span style="color:#0000ff;">while</span> (((*imgptr) &amp; <span style="color:#800080;">0x70000</span>) &lt; <span style="color:#800080;">0x40000</span><span style="color:#000000;">)
        {
            </span><span style="color:#008000;">//</span><span style="color:#008000;"> get the neighbor
            </span><span style="color:#008000;">//</span><span style="color:#008000;"> 通过方向对应的偏移获得相邻像素指针</span>
            <span style="color:#0000ff;">int</span>* imgptr_nbr = imgptr + dir[((*imgptr) &amp; <span style="color:#800080;">0x70000</span>) &gt;&gt; <span style="color:#800080;">16</span><span style="color:#000000;">];

            </span><span style="color:#008000;">//</span><span style="color:#008000;"> 判断是否访问过</span>
            <span style="color:#0000ff;">if</span> (*imgptr_nbr &gt;= <span style="color:#800080;">0</span>) <span style="color:#008000;">//</span><span style="color:#008000;"> if the neighbor is not visited yet</span>

{
// 没有访问过,标记为访问过
*imgptr_nbr |= 0x80000000; // mark it as visited
if (((*imgptr_nbr) & 0xff) < ((*imgptr) & 0xff))
{
// when the value of neighbor smaller than current
// push current to boundary heap and make the neighbor to be the current one
// create an empty comp
// 如果相邻像素的灰度小于当前像素,将当前像素加入边界栈堆,并把相邻像素设置为当前像素,并新建ER栈项
// 将当前加入边界栈堆
(*heap_cur)++;
**heap_cur = imgptr;

                    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 转换方向</span>
                    *imgptr += <span style="color:#800080;">0x10000</span><span style="color:#000000;">;

                    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 将边界栈堆的指针调整为相邻的像素灰度所对应的位置</span>
                    heap_cur += ((*imgptr_nbr) &amp; <span style="color:#800080;">0xff</span>) - ((*imgptr) &amp; <span style="color:#800080;">0xff</span><span style="color:#000000;">);

#ifdef INTRIN_ENABLED
_bitset(bit_cur, (*imgptr) & 0x1f);
bit_cur
+= (((*imgptr_nbr) & 0x700) - ((*imgptr) & 0x700)) >> 8;
#endif
// 将相邻像素设置为当前像素
imgptr = imgptr_nbr;

                    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 新建ER栈项,并设置灰度为当前像素灰度</span>
                    comptr++<span style="color:#000000;">;
                    initMSERComp(comptr);
                    comptr</span>-&gt;grey_level = (*imgptr) &amp; <span style="color:#800080;">0xff</span><span style="color:#000000;">;
                    </span><span style="color:#0000ff;">continue</span><span style="color:#000000;">;
                }
                </span><span style="color:#0000ff;">else</span><span style="color:#000000;"> {
                    </span><span style="color:#008000;">//</span><span style="color:#008000;"> otherwise, push the neighbor to boundary heap
                    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 否则,将相邻像素添加到对应的边界帧堆中</span>
                    heap_cur[((*imgptr_nbr) &amp; <span style="color:#800080;">0xff</span>) - ((*imgptr) &amp; <span style="color:#800080;">0xff</span>)]++<span style="color:#000000;">;
                    </span>*heap_cur[((*imgptr_nbr) &amp; <span style="color:#800080;">0xff</span>) - ((*imgptr) &amp; <span style="color:#800080;">0xff</span>)] =<span style="color:#000000;"> imgptr_nbr;

#ifdef INTRIN_ENABLED
_bitset(bit_cur + ((((*imgptr_nbr) & 0x700) - ((*imgptr) & 0x700)) >> 8), (*imgptr_nbr) & 0x1f);
#endif
}
}

            </span><span style="color:#008000;">//</span><span style="color:#008000;"> 将当前像素的方向转换到下一个方向</span>
            *imgptr += <span style="color:#800080;">0x10000</span><span style="color:#000000;">;
        }

        </span><span style="color:#0000ff;">int</span> imsk = (<span style="color:#0000ff;">int</span>)(imgptr -<span style="color:#000000;"> ioptr);

        </span><span style="color:#008000;">//</span><span style="color:#008000;"> 记录x&amp;y,</span>
        ptsptr-&gt;pt = cvPoint(imsk&amp;stepmask, imsk &gt;&gt;<span style="color:#000000;"> stepgap);
        </span><span style="color:#008000;">//</span><span style="color:#008000;"> get the current location</span>

accumulateMSERComp(comptr, ptsptr);
ptsptr
++;
// get the next pixel from boundary heap
// 从边界栈堆中获取一个像素用作当前像素
if (**heap_cur)
{
// 当前灰度的边界栈堆有值可以用,将当前边界栈堆值设置为当前像素,因为当前边界栈堆的灰度就是当前像素的灰度,所以可以直接拿出来用
imgptr = **heap_cur;

            </span><span style="color:#008000;">//</span><span style="color:#008000;"> 出栈</span>
            (*heap_cur)--<span style="color:#000000;">;

#ifdef INTRIN_ENABLED
if (!heap_cur)
_bitreset(bit_cur, (
*imgptr) & 0x1f);
#endif
}
else {
// 当前灰度边界栈堆中没有值可以用
#ifdef INTRIN_ENABLED
bool found_pixel = 0;
unsigned
long pixel_val;
for (int i = ((*imgptr) & 0x700) >> 8; i < 8; i++)
{
if (_BitScanForward(&pixel_val, *bit_cur))
{
found_pixel
= 1;
pixel_val
+= i << 5;
heap_cur
+= pixel_val - ((*imgptr) & 0xff);
break;
}
bit_cur
++;
}
if (found_pixel)
#else
// 从当前灰度后逐步提高灰度值,在边界堆中找到一个边界像素
heap_cur++;
unsigned
long pixel_val = 0;
for (unsigned long i = ((*imgptr) & 0xff) + 1; i < 256; i++)
{
if (
heap_cur)
{
// 不为零,指针指向了一个像素,这个灰度值还有边界
pixel_val = i;
break;
}

                </span><span style="color:#008000;">//</span><span style="color:#008000;"> 提高灰度值</span>
                heap_cur++<span style="color:#000000;">;
            }

            </span><span style="color:#008000;">//</span><span style="color:#008000;"> 判断边界中是否还有像素</span>
            <span style="color:#0000ff;">if</span><span style="color:#000000;"> (pixel_val)

#endif
{
// 将边界中的像素作为当前像素,并从边界中去除
imgptr = heap_cur;
(
*heap_cur)–;
#ifdef INTRIN_ENABLED
if (!
heap_cur)
_bitreset(bit_cur, pixel_val
& 0x1f);
#endif
if (pixel_val < comptr[-1].grey_level)
{
// 刚从边界获得灰度如果小于上一个MSER组件灰度值,需要提高当前水位到边界的灰度值
// check the stablity and push a new history, increase the grey level
if (MSERStableCheck(comptr, params))
{
CvContour
* contour = MSERToContour(comptr, storage);
contour
->color = color;
cvSeqPush(contours,
&contour);
}

                    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 由于水位要有变化了,添加一个历史</span>

MSERNewHistory(comptr, histptr);

                    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 提高水位到边界的水位</span>
                    comptr[<span style="color:#800080;">0</span>].grey_level =<span style="color:#000000;"> pixel_val;

                    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 指向下一个未使用历史空间</span>
                    histptr++<span style="color:#000000;">;
                }
                </span><span style="color:#0000ff;">else</span><span style="color:#000000;"> {
                    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 刚从边界获得灰度如果不小于上一个MSER组件灰度值,其实就是和上一个灰度值一样。
                    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 例如:当前水位2,上一个水位3,从边界出栈的水位为3.

                    </span><span style="color:#008000;">//</span><span style="color:#008000;"> keep merging top two comp in stack until the grey level &gt;= pixel_val</span>
                    <span style="color:#0000ff;">for</span><span style="color:#000000;"> (;;)
                    {
                        </span><span style="color:#008000;">//</span><span style="color:#008000;"> 合并MSER组件,里面也随带完成了一个历史</span>
                        comptr--<span style="color:#000000;">;
                        MSERMergeComp(comptr </span>+ <span style="color:#800080;">1</span><span style="color:#000000;">, comptr, comptr, histptr);
                        histptr</span>++<span style="color:#000000;">;

                        </span><span style="color:#0000ff;">if</span> (pixel_val &lt;= comptr[<span style="color:#800080;">0</span><span style="color:#000000;">].grey_level)
                            </span><span style="color:#0000ff;">break</span><span style="color:#000000;">;

                        </span><span style="color:#008000;">//</span><span style="color:#008000;"> 到这里,等于comptr[0].grey_level &lt; pixel_val,也是当前像素的灰度与MSER组件的不一致,要提高MSER组件灰度</span>
                        <span style="color:#0000ff;">if</span> (pixel_val &lt; comptr[-<span style="color:#800080;">1</span><span style="color:#000000;">].grey_level)
                        {
                            </span><span style="color:#008000;">//</span><span style="color:#008000;"> 其实就是comptr[0].grey_level &lt; pixel_val &lt; comptr[-1].grey_level
                            </span><span style="color:#008000;">//</span><span style="color:#008000;"> 当前灰度大于当前MSER灰度小于上一个MSER组件灰度。同上面的代码情况一样。
                            </span><span style="color:#008000;">//</span><span style="color:#008000;"> check the stablity here otherwise it wouldn't be an ER</span>
                            <span style="color:#0000ff;">if</span> (MSERStableCheck(comptr, <span style="color:#0000ff;">params</span><span style="color:#000000;">))
                            {
                                CvContour</span>* contour =<span style="color:#000000;"> MSERToContour(comptr, storage);
                                contour</span>-&gt;color =<span style="color:#000000;"> color;
                                cvSeqPush(contours, </span>&amp;<span style="color:#000000;">contour);
                            }
                            MSERNewHistory(comptr, histptr);
                            comptr[</span><span style="color:#800080;">0</span>].grey_level =<span style="color:#000000;"> pixel_val;
                            histptr</span>++<span style="color:#000000;">;
                            </span><span style="color:#0000ff;">break</span><span style="color:#000000;">;
                        }
                    }
                }
            }
            </span><span style="color:#0000ff;">else</span>
                <span style="color:#0000ff;">break</span><span style="color:#000000;">;
        }
    }
}

</span><span style="color:#008000;">/*</span><span style="color:#008000;">* @brief 通过8UC1类型的图像提取MSER
*@param mask 掩码
*@param contours 轮廓结果
*@param storage 轮廓内存空间
*@param params 参数
</span><span style="color:#008000;">*/</span>
<span style="color:#0000ff;">static</span> <span style="color:#0000ff;">void</span> extractMSER_8UC1(CvMat*<span style="color:#000000;"> src,
    CvMat</span>*<span style="color:#000000;"> mask,
    CvSeq</span>*<span style="color:#000000;"> contours,
    CvMemStorage</span>*<span style="color:#000000;"> storage,
    MSERParams </span><span style="color:#0000ff;">params</span><span style="color:#000000;">)
{
    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 为了加速计算,将每行数据大小扩展为大于原大小的第一个2的整指数。
    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 这样在后面计算y时,只要右移stepgap就算除以2^stepgap了</span>
    <span style="color:#0000ff;">int</span> step = <span style="color:#800080;">8</span><span style="color:#000000;">;
    </span><span style="color:#0000ff;">int</span> stepgap = <span style="color:#800080;">3</span><span style="color:#000000;">;
    </span><span style="color:#0000ff;">while</span> (step &lt; src-&gt;step + <span style="color:#800080;">2</span><span style="color:#000000;">)
    {
        step </span>&lt;&lt;= <span style="color:#800080;">1</span><span style="color:#000000;">;
        stepgap</span>++<span style="color:#000000;">;
    }
    </span><span style="color:#0000ff;">int</span> stepmask = step - <span style="color:#800080;">1</span><span style="color:#000000;">;

    </span><span style="color:#008000;">//</span><span style="color:#008000;"> to speedup the process, make the width to be 2^N</span>
    CvMat* img = cvCreateMat(src-&gt;rows + <span style="color:#800080;">2</span><span style="color:#000000;">, step, CV_32SC1);
    </span><span style="color:#0000ff;">int</span>* ioptr = img-&gt;data.i + step + <span style="color:#800080;">1</span>;        <span style="color:#008000;">//</span><span style="color:#008000;"> 数据在扩展后的最开始位置</span>
    <span style="color:#0000ff;">int</span>* imgptr;                                        <span style="color:#008000;">//</span><span style="color:#008000;"> 用于指向mser遍历的当前像素(所有数据)

    </span><span style="color:#008000;">//</span><span style="color:#008000;"> pre-allocate boundary heap
    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 预分配边界堆和每个灰度指向堆的指针数组
    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 堆大小就是像素数+所有灰度值(一个标志数据,用来表明这个灰度没有数据了)</span>
    <span style="color:#0000ff;">int</span>** heap = (<span style="color:#0000ff;">int</span>**)cvAlloc((src-&gt;rows*src-&gt;cols + <span style="color:#800080;">256</span>) * <span style="color:#0000ff;">sizeof</span>(heap[<span style="color:#800080;">0</span><span style="color:#000000;">]));
    </span><span style="color:#0000ff;">int</span>** heap_start[<span style="color:#800080;">256</span><span style="color:#000000;">];
    heap_start[</span><span style="color:#800080;">0</span>] =<span style="color:#000000;"> heap;

    </span><span style="color:#008000;">//</span><span style="color:#008000;"> pre-allocate linked point and grow history
    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 预分配连接像素点,用于将区域中的像素连接起来,大小就为所有像素个数</span>
    LinkedPoint* pts = (LinkedPoint*)cvAlloc(src-&gt;rows*src-&gt;cols * <span style="color:#0000ff;">sizeof</span>(pts[<span style="color:#800080;">0</span><span style="color:#000000;">]));
    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 预分配增长历史,用于记录区域在太高水位后的父子关系,最大个数为所有像素个数。</span>
    MSERGrowHistory* history = (MSERGrowHistory*)cvAlloc(src-&gt;rows*src-&gt;cols * <span style="color:#0000ff;">sizeof</span>(history[<span style="color:#800080;">0</span><span style="color:#000000;">]));
    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 预分配区域,用于记录每个区域的数据,大小为所有灰度值+1个超大灰度值代表顶</span>
    MSERConnectedComp comp[<span style="color:#800080;">257</span><span style="color:#000000;">];

    </span><span style="color:#008000;">//</span><span style="color:#008000;"> darker to brighter (MSER-)
    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 提取mser亮区域(preprocessMSER_8UC1中将灰度值取反)</span>
    imgptr =<span style="color:#000000;"> preprocessMSER_8UC1(img, heap_start, src, mask);
    extractMSER_8UC1_Pass(ioptr, imgptr, heap_start, pts, history, comp, step, stepmask, stepgap, </span><span style="color:#0000ff;">params</span>, -<span style="color:#800080;">1</span><span style="color:#000000;">, contours, storage);
    </span><span style="color:#008000;">//</span><span style="color:#008000;"> brighter to darker (MSER+)
    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 提取mser暗区域</span>
    imgptr =<span style="color:#000000;"> preprocessMSER_8UC1(img, heap_start, src, mask);
    extractMSER_8UC1_Pass(ioptr, imgptr, heap_start, pts, history, comp, step, stepmask, stepgap, </span><span style="color:#0000ff;">params</span>, <span style="color:#800080;">1</span><span style="color:#000000;">, contours, storage);

    </span><span style="color:#008000;">//</span><span style="color:#008000;"> clean up</span>
    cvFree(&amp;<span style="color:#000000;">history);
    cvFree(</span>&amp;<span style="color:#000000;">heap);
    cvFree(</span>&amp;<span style="color:#000000;">pts);
    cvReleaseMat(</span>&amp;<span style="color:#000000;">img);
}</span></pre>
复制代码

条件判断和生成结果:

复制代码
// clear the connected component in stack
    static void
        initMSERComp(MSERConnectedComp* comp)
    {
        comp->size = 0;
        comp->var = 0;
        comp->dvar = 1;
        comp->history = NULL;
    }
</span><span style="color:#008000;">//</span><span style="color:#008000;"> add history of size to a connected component</span>
<span style="color:#0000ff;">static</span> <span style="color:#0000ff;">void</span>
    <span style="color:#008000;">/*</span><span style="color:#008000;">* @brief 通过当前ER项构建一个对应的历史,也就是说找个ER项要准备改变了
    </span><span style="color:#008000;">*/</span><span style="color:#000000;">
    MSERNewHistory(MSERConnectedComp</span>* comp, MSERGrowHistory*<span style="color:#000000;"> history)
{
    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 初始时将下一条历史设置为自己</span>
    history-&gt;child =<span style="color:#000000;"> history;
    </span><span style="color:#0000ff;">if</span> (NULL == comp-&gt;<span style="color:#000000;">history)
    {
        </span><span style="color:#008000;">//</span><span style="color:#008000;"> 从来没有历史过,将快捷路径也设置为自己,稳定的像素数为0</span>
        history-&gt;shortcut =<span style="color:#000000;"> history;
        history</span>-&gt;stable = <span style="color:#800080;">0</span><span style="color:#000000;">;
    }
    </span><span style="color:#0000ff;">else</span><span style="color:#000000;"> {
        </span><span style="color:#008000;">//</span><span style="color:#008000;"> 有历史,将当前历史设置为上一个历史的下个历史</span>
        comp-&gt;history-&gt;child =<span style="color:#000000;"> history;

        </span><span style="color:#008000;">//</span><span style="color:#008000;"> 快捷路径与稳定值继承至上一个历史</span>
        history-&gt;shortcut = comp-&gt;history-&gt;<span style="color:#000000;">shortcut;
        history</span>-&gt;stable = comp-&gt;history-&gt;<span style="color:#000000;">stable;
    }

    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 记录这时的ER项的灰度值与像素数</span>
    history-&gt;val = comp-&gt;<span style="color:#000000;">grey_level;
    history</span>-&gt;size = comp-&gt;<span style="color:#000000;">size;

    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 设置ER项的历史为找个最新的历史</span>
    comp-&gt;history =<span style="color:#000000;"> history;
}

</span><span style="color:#008000;">//</span><span style="color:#008000;"> merging two connected component</span>
<span style="color:#0000ff;">static</span> <span style="color:#0000ff;">void</span><span style="color:#000000;">
    MSERMergeComp(MSERConnectedComp</span>*<span style="color:#000000;"> comp1,
        MSERConnectedComp</span>*<span style="color:#000000;"> comp2,
        MSERConnectedComp</span>*<span style="color:#000000;"> comp,
        MSERGrowHistory</span>*<span style="color:#000000;"> history)
{
    LinkedPoint</span>*<span style="color:#000000;"> head;
    LinkedPoint</span>*<span style="color:#000000;"> tail;
    comp</span>-&gt;grey_level = comp2-&gt;<span style="color:#000000;">grey_level;
    history</span>-&gt;child =<span style="color:#000000;"> history;
    </span><span style="color:#008000;">//</span><span style="color:#008000;"> select the winner by size</span>
    <span style="color:#0000ff;">if</span> (comp1-&gt;size &gt;= comp2-&gt;<span style="color:#000000;">size)
    {
        </span><span style="color:#0000ff;">if</span> (NULL == comp1-&gt;<span style="color:#000000;">history)
        {
            history</span>-&gt;shortcut =<span style="color:#000000;"> history;
            history</span>-&gt;stable = <span style="color:#800080;">0</span><span style="color:#000000;">;
        }
        </span><span style="color:#0000ff;">else</span><span style="color:#000000;"> {
            comp1</span>-&gt;history-&gt;child =<span style="color:#000000;"> history;
            history</span>-&gt;shortcut = comp1-&gt;history-&gt;<span style="color:#000000;">shortcut;
            history</span>-&gt;stable = comp1-&gt;history-&gt;<span style="color:#000000;">stable;
        }

        </span><span style="color:#008000;">//</span><span style="color:#008000;"> 如果组件2有stable,并且大于1的,则stable使用2的值</span>
        <span style="color:#0000ff;">if</span> (NULL != comp2-&gt;history &amp;&amp; comp2-&gt;history-&gt;stable &gt; history-&gt;<span style="color:#000000;">stable)
            history</span>-&gt;stable = comp2-&gt;history-&gt;<span style="color:#000000;">stable;

        </span><span style="color:#008000;">//</span><span style="color:#008000;"> 使用数量多的</span>
        history-&gt;val = comp1-&gt;<span style="color:#000000;">grey_level;
        history</span>-&gt;size = comp1-&gt;<span style="color:#000000;">size;
        </span><span style="color:#008000;">//</span><span style="color:#008000;"> put comp1 to history</span>
        comp-&gt;<span style="color:#0000ff;">var</span> = comp1-&gt;<span style="color:#0000ff;">var</span><span style="color:#000000;">;
        comp</span>-&gt;dvar = comp1-&gt;<span style="color:#000000;">dvar;

        </span><span style="color:#008000;">//</span><span style="color:#008000;"> 如果组件1和2都有像素点,将两个链按照1-&gt;2连接在一起</span>
        <span style="color:#0000ff;">if</span> (comp1-&gt;size &gt; <span style="color:#800080;">0</span> &amp;&amp; comp2-&gt;size &gt; <span style="color:#800080;">0</span><span style="color:#000000;">)
        {
            comp1</span>-&gt;tail-&gt;next = comp2-&gt;<span style="color:#000000;">head;
            comp2</span>-&gt;head-&gt;prev = comp1-&gt;<span style="color:#000000;">tail;
        }

        </span><span style="color:#008000;">//</span><span style="color:#008000;"> 确定头尾</span>
        head = (comp1-&gt;size &gt; <span style="color:#800080;">0</span>) ? comp1-&gt;head : comp2-&gt;<span style="color:#000000;">head;
        tail </span>= (comp2-&gt;size &gt; <span style="color:#800080;">0</span>) ? comp2-&gt;tail : comp1-&gt;<span style="color:#000000;">tail;
        </span><span style="color:#008000;">//</span><span style="color:#008000;"> always made the newly added in the last of the pixel list (comp1 ... comp2)</span>

}
else {
// 与上面的正好相反
if (NULL == comp2->history)
{
history
->shortcut = history;
history
->stable = 0;
}
else {
comp2
->history->child = history;
history
->shortcut = comp2->history->shortcut;
history
->stable = comp2->history->stable;
}
if (NULL != comp1->history && comp1->history->stable > history->stable)
history
->stable = comp1->history->stable;
history
->val = comp2->grey_level;
history
->size = comp2->size;
// put comp2 to history
comp->var = comp2->var;
comp
->dvar = comp2->dvar;
if (comp1->size > 0 && comp2->size > 0)
{
comp2
->tail->next = comp1->head;
comp1
->head->prev = comp2->tail;
}

        head </span>= (comp2-&gt;size &gt; <span style="color:#800080;">0</span>) ? comp2-&gt;head : comp1-&gt;<span style="color:#000000;">head;
        tail </span>= (comp1-&gt;size &gt; <span style="color:#800080;">0</span>) ? comp1-&gt;tail : comp2-&gt;<span style="color:#000000;">tail;
        </span><span style="color:#008000;">//</span><span style="color:#008000;"> always made the newly added in the last of the pixel list (comp2 ... comp1)</span>

}
comp
->head = head;
comp
->tail = tail;
comp
->history = history;

    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 新ER的像素数量是两个ER项的和</span>
    comp-&gt;size = comp1-&gt;size + comp2-&gt;<span style="color:#000000;">size;
}

</span><span style="color:#008000;">/*</span><span style="color:#008000;">* @brief 通过delta计算给定ER项的偏差
</span><span style="color:#008000;">*/</span>
<span style="color:#0000ff;">static</span> <span style="color:#0000ff;">float</span> MSERVariationCalc(MSERConnectedComp* comp, <span style="color:#0000ff;">int</span><span style="color:#000000;"> delta)
{
    MSERGrowHistory</span>* history = comp-&gt;<span style="color:#000000;">history;
    </span><span style="color:#0000ff;">int</span> val = comp-&gt;<span style="color:#000000;">grey_level;
    </span><span style="color:#0000ff;">if</span> (NULL !=<span style="color:#000000;"> history)
    {
        </span><span style="color:#008000;">//</span><span style="color:#008000;"> 从快捷路径开始往回找历史,找到灰度差大于delta的历史</span>
        MSERGrowHistory* shortcut = history-&gt;<span style="color:#000000;">shortcut;
        </span><span style="color:#0000ff;">while</span> (shortcut != shortcut-&gt;shortcut &amp;&amp; shortcut-&gt;val + delta &gt;<span style="color:#000000;"> val)
            shortcut </span>= shortcut-&gt;<span style="color:#000000;">shortcut;

        </span><span style="color:#008000;">//</span><span style="color:#008000;"> 由于快捷路径是直接跳过一些历史的,要找到最准确的历史还要从以前历史往当前找</span>
        MSERGrowHistory* child = shortcut-&gt;<span style="color:#000000;">child;
        </span><span style="color:#0000ff;">while</span> (child != child-&gt;child &amp;&amp; child-&gt;val + delta &lt;=<span style="color:#000000;"> val)
        {
            shortcut </span>=<span style="color:#000000;"> child;
            child </span>= child-&gt;<span style="color:#000000;">child;
        }
        </span><span style="color:#008000;">//</span><span style="color:#008000;"> get the position of history where the shortcut-&gt;val &lt;= delta+val and shortcut-&gt;child-&gt;val &gt;= delta+val
        </span><span style="color:#008000;">//</span><span style="color:#008000;"> 更新快捷路径</span>
        history-&gt;shortcut =<span style="color:#000000;"> shortcut;

        </span><span style="color:#008000;">//</span><span style="color:#008000;"> 返回(R-R(-delta)) / (R-delta)</span>
        <span style="color:#0000ff;">return</span> (<span style="color:#0000ff;">float</span>)(comp-&gt;size - shortcut-&gt;size) / (<span style="color:#0000ff;">float</span>)shortcut-&gt;<span style="color:#000000;">size;
        </span><span style="color:#008000;">//</span><span style="color:#008000;"> here is a small modification of MSER where cal ||R_{i}-R_{i-delta}||/||R_{i-delta}||
        </span><span style="color:#008000;">//</span><span style="color:#008000;"> in standard MSER, cal ||R_{i+delta}-R_{i-delta}||/||R_{i}||
        </span><span style="color:#008000;">//</span><span style="color:#008000;"> my calculation is simpler and much easier to implement</span>

}

    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 没有历史,结果为1。也就是没有-delta对应的值。
    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 如果按照(R-R(-delta)) / R(-delta) = 1公式推导:
    </span><span style="color:#008000;">//</span><span style="color:#008000;"> R = 2R(-delta)
    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 就面积来说,怎么两倍这种关系都比较奇怪,因为是xy两个维度的,每个维度提高sqrt(2)倍</span>
    <span style="color:#0000ff;">return</span> <span style="color:#800080;">1</span><span style="color:#000000;">.;
}

</span><span style="color:#008000;">/*</span><span style="color:#008000;">* @brief 检查是否为最稳定极值区域
</span><span style="color:#008000;">*/</span>
<span style="color:#0000ff;">static</span> <span style="color:#0000ff;">bool</span> MSERStableCheck(MSERConnectedComp* comp, MSERParams <span style="color:#0000ff;">params</span><span style="color:#000000;">)
{
    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 检查就是要确定水位的底是否是稳定的
    </span><span style="color:#008000;">//</span><span style="color:#008000;"> tricky part: it actually check the stablity of one-step back
    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 稳定区域都是由比较而来的,不能没有上一个历史。</span>
    <span style="color:#0000ff;">if</span> (comp-&gt;history == NULL || comp-&gt;history-&gt;size &lt;= <span style="color:#0000ff;">params</span>.minArea || comp-&gt;history-&gt;size &gt;= <span style="color:#0000ff;">params</span><span style="color:#000000;">.maxArea)
        </span><span style="color:#0000ff;">return</span> <span style="color:#800080;">0</span><span style="color:#000000;">;

    </span><span style="color:#008000;">//</span><span style="color:#008000;"> diversity : (R(-1) - R(stable)) / R(-1)
    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 使用水位的底与稳定时大小做比较</span>
    <span style="color:#0000ff;">float</span> div = (<span style="color:#0000ff;">float</span>)(comp-&gt;history-&gt;size - comp-&gt;history-&gt;stable) / (<span style="color:#0000ff;">float</span>)comp-&gt;history-&gt;<span style="color:#000000;">size;

    </span><span style="color:#008000;">//</span><span style="color:#008000;"> variation</span>
    <span style="color:#0000ff;">float</span> <span style="color:#0000ff;">var</span> = MSERVariationCalc(comp, <span style="color:#0000ff;">params</span><span style="color:#000000;">.delta);

    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 现在的variation要大于以前的variation,就是以前的更稳定
    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 灰度值差是否大于1</span>
    <span style="color:#0000ff;">int</span> dvar = (comp-&gt;<span style="color:#0000ff;">var</span> &lt; <span style="color:#0000ff;">var</span> || (unsigned <span style="color:#0000ff;">long</span>)(comp-&gt;history-&gt;val + <span style="color:#800080;">1</span>) &lt; comp-&gt;<span style="color:#000000;">grey_level);
    </span><span style="color:#0000ff;">int</span> stable = (dvar &amp;&amp; !comp-&gt;dvar &amp;&amp; comp-&gt;<span style="color:#0000ff;">var</span> &lt; <span style="color:#0000ff;">params</span>.maxVariation &amp;&amp; div &gt; <span style="color:#0000ff;">params</span><span style="color:#000000;">.minDiversity);
    comp</span>-&gt;<span style="color:#0000ff;">var</span> = <span style="color:#0000ff;">var</span><span style="color:#000000;">;
    comp</span>-&gt;dvar =<span style="color:#000000;"> dvar;
    </span><span style="color:#0000ff;">if</span><span style="color:#000000;"> (stable)
        </span><span style="color:#008000;">//</span><span style="color:#008000;"> 如果稳定的话,稳定值就是像素数</span>
        comp-&gt;history-&gt;stable = comp-&gt;history-&gt;<span style="color:#000000;">size;
    </span><span style="color:#0000ff;">return</span> stable != <span style="color:#800080;">0</span><span style="color:#000000;">;
}

</span><span style="color:#008000;">//</span><span style="color:#008000;"> add a pixel to the pixel list</span>
<span style="color:#008000;">/*</span><span style="color:#008000;">* @brief 添加像素到给定的MSER项中
</span><span style="color:#008000;">*/</span>
<span style="color:#0000ff;">static</span> <span style="color:#0000ff;">void</span> accumulateMSERComp(MSERConnectedComp* comp, LinkedPoint*<span style="color:#000000;"> point)
{
    </span><span style="color:#0000ff;">if</span> (comp-&gt;size &gt; <span style="color:#800080;">0</span><span style="color:#000000;">)
    {
        </span><span style="color:#008000;">//</span><span style="color:#008000;"> 之前有像素,连接到原来像素的链上</span>
        point-&gt;prev = comp-&gt;<span style="color:#000000;">tail;
        comp</span>-&gt;tail-&gt;next =<span style="color:#000000;"> point;
        point</span>-&gt;next =<span style="color:#000000;"> NULL;
    }
    </span><span style="color:#0000ff;">else</span><span style="color:#000000;"> {
        </span><span style="color:#008000;">//</span><span style="color:#008000;"> 第一个像素</span>
        point-&gt;prev =<span style="color:#000000;"> NULL;
        point</span>-&gt;next =<span style="color:#000000;"> NULL;
        comp</span>-&gt;head =<span style="color:#000000;"> point;
    }

    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 新加入的点作为尾巴</span>
    comp-&gt;tail =<span style="color:#000000;"> point;

    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 像素数自增</span>
    comp-&gt;size++<span style="color:#000000;">;
}

</span><span style="color:#008000;">//</span><span style="color:#008000;"> convert the point set to CvSeq</span>
<span style="color:#0000ff;">static</span> CvContour* MSERToContour(MSERConnectedComp* comp, CvMemStorage*<span style="color:#000000;"> storage)
{
    CvSeq</span>* _contour = cvCreateSeq(CV_SEQ_KIND_GENERIC | CV_32SC2, <span style="color:#0000ff;">sizeof</span>(CvContour), <span style="color:#0000ff;">sizeof</span><span style="color:#000000;">(CvPoint), storage);
    CvContour</span>* contour = (CvContour*<span style="color:#000000;">)_contour;

    </span><span style="color:#008000;">//</span><span style="color:#008000;"> 上次历史就是水位的底,将水位的底都添加到轮廓中</span>
    cvSeqPushMulti(_contour, <span style="color:#800080;">0</span>, comp-&gt;history-&gt;<span style="color:#000000;">size);
    LinkedPoint</span>* lpt = comp-&gt;<span style="color:#000000;">head;
    </span><span style="color:#0000ff;">for</span> (<span style="color:#0000ff;">int</span> i = <span style="color:#800080;">0</span>; i &lt; comp-&gt;history-&gt;size; i++<span style="color:#000000;">)
    {
        CvPoint</span>* pt =<span style="color:#000000;"> CV_GET_SEQ_ELEM(CvPoint, _contour, i);
        pt</span>-&gt;x = lpt-&gt;<span style="color:#000000;">pt.x;
        pt</span>-&gt;y = lpt-&gt;<span style="color:#000000;">pt.y;
        lpt </span>= lpt-&gt;<span style="color:#000000;">next;
    }
    cvBoundingRect(contour);
    </span><span style="color:#0000ff;">return</span><span style="color:#000000;"> contour;
}</span></pre>
复制代码

p.s. 以上代码有点长了:(

应用 

下面对一些图片做实验,测试下mser的检出能力。

复制代码
    // 加载图像
    Mat srcColor = imread("");
</span><span style="color:#008000;">//</span><span style="color:#008000;">创建MSER类&nbsp;&nbsp;</span>
MSER ms(<span style="color:#800080;">4</span>                    <span style="color:#008000;">//</span><span style="color:#008000;"> delta</span>
    , <span style="color:#800080;">60</span>                        <span style="color:#008000;">//</span><span style="color:#008000;"> min area</span>
    , <span style="color:#800080;">1600</span>                    <span style="color:#008000;">//</span><span style="color:#008000;"> max area</span>
    , <span style="color:#800080;">0.05f</span>                    <span style="color:#008000;">//</span><span style="color:#008000;"> max variation </span>
    , <span style="color:#800080;">0.4f</span>                        <span style="color:#008000;">//</span><span style="color:#008000;"> min diversity </span>
    );                            <span style="color:#008000;">//</span><span style="color:#008000;"> edge blur size 

</span><span style="color:#008000;">//</span><span style="color:#008000;"> 转换为灰度图</span>

Mat srcGray;
cvtColor(srcColor, srcGray, CV_BGR2GRAY);

</span><span style="color:#008000;">//</span><span style="color:#008000;">用于组块区域的像素点集&nbsp;&nbsp;</span>
vector&lt;vector&lt;Point&gt;&gt;<span style="color:#000000;"> regions;
ms(srcGray, regions, Mat());

</span><span style="color:#0000ff;">for</span> (<span style="color:#0000ff;">int</span> i = <span style="color:#800080;">0</span>; i &lt; regions.size(); i++<span style="color:#000000;">)
{
    </span><span style="color:#008000;">//</span><span style="color:#008000;">用连线绘制&nbsp;&nbsp;</span>
    <span style="color:#808080;">///</span><span style="color:#008000;">/polylines(srcGray, regions[i], true, Scalar(0, 0, 255));</span>
    <span style="color:#008000;">//</span><span style="color:#008000;">用椭圆形绘制&nbsp;&nbsp;</span>
    ellipse(srcColor, fitEllipse(regions[i]), Scalar(<span style="color:#800080;">0</span>, <span style="color:#800080;">0</span>, <span style="color:#800080;">255</span>), <span style="color:#800080;">1</span><span style="color:#000000;">);
}</span></pre>
复制代码

可以看出mser对旋转和不同大小的字符都有一定的检出能力,但如果想对不同灰度变化也有能力,应该修改源码来适应了。

下次介绍mscr,用于在彩色图种查找稳定区域。

猜你喜欢

转载自blog.csdn.net/Du_Shuang/article/details/82837191