[转载]opencv MSER

最大稳定极值区域(MSER-Maximally Stable Extremal Regions)可以用于图像的斑点区域检测。该算法最早是由Matas等人于2002年提出,它是基于分水岭的概念。

MSER的基本原理是对一幅灰度图像(灰度值为0~255)取阈值进行二值化处理,阈值从0到255依次递增。阈值的递增类似于分水岭算法中的水面的上升,随着水面的上升,有一些较矮的丘陵会被淹没,如果从天空往下看,则大地分为陆地和水域两个部分,这类似于二值图像。在得到的所有二值图像中,图像中的某些连通区域变化很小,甚至没有变化,则该区域就被称为最大稳定极值区域。这类似于当水面持续上升的时候,有些被水淹没的地方的面积没有变化。它的数学定义为:

q(i)=|Qi+-Qi-|/|Qi|            (1)

其中,Qi表示阈值为i时的某一连通区域,△为灰度阈值的微小变化量,q(i)为阈值是i时的区域Qi的变化率。当q(i)为局部极小值时,则Qi为最大稳定极值区域。

需要说明的是,上述做法只能检测出灰度图像的黑色区域,不能检测出白色区域,因此还需要对原图进行反转,然后再进行阈值从0~255的二值化处理过程。这两种操作又分别称为MSER+和MSER-。

MSER具有以下特点:

1、对图像灰度具有仿射变换的不变性;

2、稳定性:具有相同阈值范围内所支持的区域才会被选择;

3、无需任何平滑处理就可以实现多尺度检测,即小的和大的结构都可以被检测到。

MSER的原理比较简单,但要更快更好的实现它,是需要一定的算法、数据结构和编程技巧的。David Nister等人于2008年提出了Linear Time Maximally Stable Extremal Regions算法,该算法要比原著提出的算法快,opencv就是利用该算法实现MSER的。但这里要说明一点的是,opencv不是利用公式1计算MSER的,而是利用更易于实现的改进方法:

q(i)=|Qi-Qi-|/|Qi-|            (2)

David Nister提出的算法是基于改进的分水岭算法,即当往一个固定的地方注水的时候,只有当该地方的沟壑被水填满以后,水才会向其四周溢出,随着注水量的不断增加,各个沟壑也会逐渐被水淹没,但各个沟壑的水面不是同时上升的,它是根据水漫过地方的先后顺序,一个沟壑一个沟壑地填满水,只有当相邻两个沟壑被水连通在一起以后,水面对于这两个沟壑来说才是同时上升的。该算法的具体步骤如下:

1、初始化栈和堆,栈用于存储组块(组块就是区域,就相当于水面,水漫过的地方就会出现水面,水面的高度就是图像的灰度值,因此用灰度值来表示组块的值),堆用于存储组块的边界像素,相当于水域的岸边,岸边要高于水面的,因此边界像素的灰度值一定不小于它所包围的区域(即组块)的灰度值。首先向栈内放入一个虚假的组块,当该组块被弹出时意味着程序的结束;

2、把图像中的任意一个像素(一般选取图像的左上角像素)作为源像素,标注该像素为已访问过,并且把该像素的灰度值作为当前值。这一步相当于往源像素这一地点注水;

3、向栈内放入一个空组块,该组块的值是当前值;

4、按照顺序搜索当前值的4-领域内剩余的边缘,对于每一个邻域,检查它是否已经被访问过,如果没有,则标注它为已访问过并检索它的灰度值,如果灰度值不小于当前值,则把它放入用于存放边界像素的堆中。另一方面,如果领域灰度值小于当前值,则把当前值放入堆中,而把领域值作为当前值,并回到步骤3;

5、累计栈顶组块的像素个数,即计算区域面积,这是通过循环累计得到的,这一步相当于水面的饱和;

6、弹出堆中的边界像素。如果堆是空的,则程序结束;如果弹出的边界像素的灰度值等于当前值,则回到步骤4;

7、从堆中得到的像素值会大于当前值,因此我们需要处理栈中所有的组块,直到栈中的组块的灰度值大于当前边界像素灰度值为止。然后回到步骤4。

至于如何处理组块,则需要进入处理栈子模块中,传入该子模块的值为步骤7中从堆中提取得到的边界像素灰度值。子模块的具体步骤为:

1)、处理栈顶的组块,即根据公式2计算最大稳定区域,判断其是否为极值区域;

2)、如果边界像素灰度值小于距栈顶第二个组块的灰度值,那么设栈顶组块的灰度值为边界像素灰度值,并退出该子模块。之所以会出现这种情况,是因为在栈顶组块和第二个组块之间还有组块没有被检测处理,因此我们需要改变栈顶组块的灰度值为边界像素灰度值(相当于这两层的组块进行了合并),并回到主程序,再次搜索组块;

3)、弹出栈顶组块,并与目前栈顶组块合并;

4)、如果边界像素灰度值大于栈顶组块的灰度值,则回到步骤1。

在opencv2.4.9中,MSER算法是用类的方法给出的:

  1. class MserFeatureDetector : public FeatureDetector  

  2. {  

  3. public:  

  4. MserFeatureDetector( CvMSERParams params=cvMSERParams() );  

  5. MserFeatureDetector( int delta, int minArea, int maxArea,  

  6. double maxVariation, double minDiversity,  

  7. int maxEvolution, double areaThreshold,  

  8. double minMargin, int edgeBlurSize );  

  9. virtual void read( const FileNode& fn );  

  10. virtual void write( FileStorage& fs ) const;  

  11. protected:  

  12. ...  

  13. };  

而具体的MSER类为:

  1. class MSER : public CvMSERParams  

  2. {  

  3. public:  

  4. // default constructor  

  5. //缺省的构造函数  

  6. MSER();  

  7. // constructor that initializes all the algorithm parameters  

  8. //带有所有算法参数的构造函数  

  9. MSER( int _delta, int _min_area, int _max_area,  

  10. float _max_variation, float _min_diversity,  

  11. int _max_evolution, double _area_threshold,  

  12. double _min_margin, int _edge_blur_size );  

  13. // runs the extractor on the specified image; returns the MSERs,  

  14. // each encoded as a contour (vector<Point>, see findContours)  

  15. // the optional mask marks the area where MSERs are searched for  

  16. void operator()( const Mat& image, vector<vector<Point> >& msers, const Mat& mask ) const;  

  17. };  

  1. MSER算法所需要的参数较多:  

  2. delta为灰度值的变化量,即公式1和2中的△;  

  3. _min_area和_max_area为检测到的组块面积的范围;  

  4. _max_variation为最大的变化率,即如果公式1和2中的q(i)小于该值,则被认为是最大稳定极值区域;  

  5. _min_diversity为稳定区域的最小变换量。  

  6. 其他的参数用于对彩色图像的MSER检测,这里不做介绍。  

  7. MSER类通过重载( )运算符,得到了最大稳定极值区域的点集msers,其中image为输入图像,mask为掩码矩阵。  

  8. 下面我们就对MSER类的源码进行分析:  

  1. void MSER::operator()( const Mat& image, vector<vector<Point> >& dstcontours, const Mat& mask ) const  

  2. {  

  3.    CvMat _image = image, _mask, *pmask = 0;  

  4.    if( mask.data )  

  5.        pmask = &(_mask = mask);  

  6.    MemStorage storage(cvCreateMemStorage(0));  

  7.    Seq<CvSeq*> contours;  

  8.    //调用extractMSER函数,得到MSER的区域点集序列contours.seq  

  9.    //MSERParams为MSER所需要的参数  

  10.    extractMSER( &_image, pmask, &contours.seq, storage,  

  11.                 MSERParams(delta, minArea, maxArea, maxVariation, minDiversity,  

  12.                            maxEvolution, areaThreshold, minMargin, edgeBlurSize));  

  13.    SeqIterator<CvSeq*> it = contours.begin();  

  14.    size_t i, ncontours = contours.size();  

  15.    dstcontours.resize(ncontours);  

  16.    //复制点集序列  

  17.    for( i = 0; i < ncontours; i++, ++it )  

  18.        Seq<Point>(*it).copyTo(dstcontours[i]);  

  19. }  

extractMSER函数首先定义了一些变量,并进行参数的判断。然后根据输入图像的类型分别调用不同的函数:

  1. static void  

  2. extractMSER( CvArr* _img,  

  3.           CvArr* _mask,  

  4.           CvSeq** _contours,  

  5.           CvMemStorage* storage,  

  6.           MSERParams params )  

  7. {  

  8.    CvMat srchdr, *src = cvGetMat( _img, &srchdr );  

  9.    CvMat maskhdr, *mask = _mask ? cvGetMat( _mask, &maskhdr ) : 0;  

  10.    CvSeq* contours = 0;  

  11.  

  12.    CV_Assert(src != 0);  

  13.    CV_Assert(CV_MAT_TYPE(src->type) == CV_8UC1 || CV_MAT_TYPE(src->type) == CV_8UC3);  

  14.    CV_Assert(mask == 0 || (CV_ARE_SIZES_EQ(src, mask) && CV_MAT_TYPE(mask->type) == CV_8UC1));  

  15.    CV_Assert(storage != 0);  

  16.  

  17.    contours = *_contours = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvSeq*), storage );  

  18.  

  19.    // choose different method for different image type  

  20.    // for grey image, it is: Linear Time Maximally Stable Extremal Regions  

  21.    // for color image, it is: Maximally Stable Colour Regions for Recognition and Matching  

  22.    switch ( CV_MAT_TYPE(src->type) )  

  23.    {  

  24.        case CV_8UC1:    //处理灰度图像  

  25.            extractMSER_8UC1( src, mask, contours, storage, params );  

  26.            break;  

  27.        case CV_8UC3:    //处理彩色图像  

  28.            extractMSER_8UC3( src, mask, contours, storage, params );  

  29.            break;  

  30.    }  

  31. }  

灰度图像的MSER处理方法就是应用本文介绍的方法:

  1. static void extractMSER_8UC1( CvMat* src,  

  2.             CvMat* mask,  

  3.             CvSeq* contours,  

  4.             CvMemStorage* storage,  

  5.             MSERParams params )  

  6. {  

  7.    //为了加快运算速度,把原图的宽扩展成高度复合数,即2^N的形式  

  8.    //step为扩展后的宽,初始值为8  

  9.    int step = 8;  

  10.    //stepgap为N,初始值为3,即8=2^3  

  11.    int stepgap = 3;  

  12.    //通过step向左移位的方式扩展原图的宽  

  13.    while ( step < src->step+2 )  

  14.    {  

  15.        step <<= 1;  

  16.        stepgap++;  

  17.    }  

  18.    int stepmask = step-1;  

  19.  

  20.    // to speedup the process, make the width to be 2^N  

  21.    //创建扩展后的图像矩阵,宽为2^N,高为原图高+2  

  22.    CvMat* img = cvCreateMat( src->rows+2, step, CV_32SC1 );  

  23.    //定义第二行地址  

  24.    int* ioptr = img->data.i+step+1;  

  25.    int* imgptr;  

  26.  

  27.    // pre-allocate boundary heap  

  28.    //步骤1,初始化堆和栈  

  29.    //定义堆,用于存储组块的边界像素  

  30.    int** heap = (int**)cvAlloc( (src->rows*src->cols+256)*sizeof(heap[0]) );  

  31.    int** heap_start[256];  

  32.    //heap_start为三指针变量,heap_start为边界像素的灰度值,因此它数量为256个;*heap_start表示边界像素中该灰度值的数量;**heap_start表示边界像素中该灰度值中第*heap_start个所对应的像素地址指针。  

  33.    heap_start[0] = heap;  

  34.  

  35.    // pre-allocate linked point and grow history  

  36.    //pts表示组块内像素链表,即像素间相互链接  

  37.    LinkedPoint* pts = (LinkedPoint*)cvAlloc( src->rows*src->cols*sizeof(pts[0]) );  

  38.    //history表示每个组块生长的状况,即随着阈值的增加,组块的大小是在不断扩大的  

  39.    MSERGrowHistory* history = (MSERGrowHistory*)cvAlloc( src->rows*src->cols*sizeof(history[0]) );  

  40.    //comp表示图像内连通的组块(即区域),原则上每一个灰度值就会有一个组块,但之所以组块的个数是257个,是因为有一个组块是虚假组块,用于表示程序的结束。用栈的形式来管理各个组块  

  41.    MSERConnectedComp comp[257];  

  42.  

  43.    // darker to brighter (MSER-)  

  44.    //MSER-  

  45.    //先对原图进行预处理  

  46.    imgptr = preprocessMSER_8UC1( img, heap_start, src, mask );  

  47.    //执行MSER操作  

  48.    extractMSER_8UC1_Pass( ioptr, imgptr, heap_start, pts, history, comp, step, stepmask, stepgap, params, -1, contours, storage );  

  49.    // brighter to darker (MSER+)  

  50.    //MSER+  

  51.    imgptr = preprocessMSER_8UC1( img, heap_start, src, mask );  

  52.    extractMSER_8UC1_Pass( ioptr, imgptr, heap_start, pts, history, comp, step, stepmask, stepgap, params, 1, contours, storage );  

  53.  

  54.    // clean up  

  55.    //清理内存  

  56.    cvFree( &history );  

  57.    cvFree( &heap );  

  58.    cvFree( &pts );  

  59.    cvReleaseMat( &img );  

  60. }  

preprocessMSER_8UC1函数为预处理的过程,主要就是对宽度扩展以后的图像矩阵进行赋值,每一位都赋予不同的含义:

// to preprocess src image to followingformat

// 32-bit image

// > 0 is available, < 0 is visited

// 17~19 bits is the direction

// 8~11 bits is the bucket it falls to (forBitScanForward)

// 0~8 bits is the color

/*定义图像矩阵的数据格式为有符号32位整型,最高一位表示是否被访问过,0表示没有被访问过,1表示被访问过(因为是有符号数,所以最高一位是1也就是负数);16~18位(这里源码注解有误)表示4邻域的方向;0~7位表示该像素的灰度值*/

//img代表图像矩阵,MSER处理的是该矩阵

//src为输入原图

//heap_cur为边界像素堆

//mask为掩码

  1. static int* preprocessMSER_8UC1( CvMat* img,  

  2.            int*** heap_cur,  

  3.            CvMat* src,  

  4.            CvMat* mask )  

  5. {  

  6.    int srccpt = src->step-src->cols;  

  7.    //由于图像矩阵的宽经过了2^N处理,所以它的宽比原图的宽要长,cpt_1代表两个宽度的差值  

  8.    int cpt_1 = img->cols-src->cols-1;  

  9.    //图像矩阵的地址指针  

  10.    int* imgptr = img->data.i;  

  11.    //定义真实的图像起始地址  

  12.    int* startptr;  

  13.  

  14.    //定义并初始化灰度值数组  

  15.    int level_size[256];  

  16.    for ( int i = 0; i < 256; i++ )  

  17.        level_size[i] = 0;  

  18.    //为图像矩阵的第一行赋值  

  19.    for ( int i = 0; i < src->cols+2; i++ )  

  20.    {  

  21.        *imgptr = -1;  

  22.        imgptr++;  

  23.    }  

  24.    //地址指针指向图像矩阵的第二行  

  25.    imgptr += cpt_1-1;  

  26.    //原图首地址指针  

  27.    uchar* srcptr = src->data.ptr;  

  28.    if ( mask )    //如果定义了掩码矩阵  

  29.    {  

  30.        startptr = 0;  

  31.        //掩码矩阵首地址指针  

  32.        uchar* maskptr = mask->data.ptr;  

  33.        //遍历整个原图  

  34.        for ( int i = 0; i < src->rows; i++ )  

  35.        {  

  36.            *imgptr = -1;  

  37.            imgptr++;  

  38.            for ( int j = 0; j < src->cols; j++ )  

  39.            {  

  40.                if ( *maskptr )  

  41.                {  

  42.                    if ( !startptr )  

  43.                        startptr = imgptr;    //赋值  

  44.                    *srcptr = 0xff-*srcptr;    //反转图像的灰度值  

  45.                    level_size[*srcptr]++;    //为灰度值计数  

  46.                    //为图像矩阵赋值,它的低8位是原图灰度值,8~10位是灰度值的高3位  

  47.                    *imgptr = ((*srcptr>>5)<<8)|(*srcptr);  

  48.                } else {    //掩码覆盖的像素值为-1  

  49.                    *imgptr = -1;  

  50.                }  

  51.                //地址加1  

  52.                imgptr++;  

  53.                srcptr++;  

  54.                maskptr++;  

  55.            }  

  56.            //跳过图像矩阵比原图多出的部分  

  57.            *imgptr = -1;  

  58.            imgptr += cpt_1;  

  59.            srcptr += srccpt;  

  60.            maskptr += srccpt;  

  61.        }  

  62.    } else {    //没有定义掩码  

  63.        startptr = imgptr+img->cols+1;    //赋值  

  64.        //遍历整幅图像,为图像矩阵赋值  

  65.        for ( int i = 0; i < src->rows; i++ )  

  66.        {  

  67.            *imgptr = -1;  

  68.            imgptr++;  

  69.            for ( int j = 0; j < src->cols; j++ )  

  70.            {  

  71.                *srcptr = 0xff-*srcptr;  

  72.                level_size[*srcptr]++;  

  73.                *imgptr = ((*srcptr>>5)<<8)|(*srcptr);  

  74.                imgptr++;  

  75.                srcptr++;  

  76.            }  

  77.            *imgptr = -1;  

  78.            imgptr += cpt_1;  

  79.            srcptr += srccpt;  

  80.        }  

  81.    }  

  82.    //为图像矩阵的最后一行赋值  

  83.    for ( int i = 0; i < src->cols+2; i++ )  

  84.    {  

  85.        *imgptr = -1;  

  86.        imgptr++;  

  87.    }  

  88.    //定义边界像素堆的大小  

  89.    //heap_cur[]对应灰度值,heap_cur[][]对应该灰度值的个数  

  90.    //根据灰度值的个数定义heap_cur的长度  

  91.    heap_cur[0][0] = 0;  

  92.    for ( int i = 1; i < 256; i++ )  

  93.    {  

  94.        heap_cur[i] = heap_cur[i-1]+level_size[i-1]+1;  

  95.        heap_cur[i][0] = 0;  

  96.    }  

  97.    //返回在图像矩阵中第一个真正的像素点的地址  

  98.    return startptr;  

  99. }  

extractMSER_8UC1_Pass函数执行MSER算法,MSER-和MSER+都执行该函数,但可以通过参数color来区分。

  1. static void extractMSER_8UC1_Pass( int* ioptr,  

  2.              int* imgptr,  

  3.              int*** heap_cur,  

  4.              LinkedPoint* ptsptr,  

  5.              MSERGrowHistory* histptr,  

  6.              MSERConnectedComp* comptr,  

  7.              int step,  

  8.              int stepmask,  

  9.              int stepgap,  

  10.              MSERParams params,  

  11.              int color,  

  12.              CvSeq* contours,  

  13.              CvMemStorage* storage )  

  14. {  

  15.    //设置第一个组块的灰度值为256,该灰度值是真实图像中不存在的灰度值,以区分真实图像的组块,从而判断程序是否结束  

  16.    comptr->grey_level = 256;  

  17.    //步骤2和步骤3  

  18.    //指向第二个组块  

  19.    comptr++;  

  20.    //设置第二个组块为输入图像第一个像素(左上角)的灰度值  

  21.    comptr->grey_level = (*imgptr)&0xff;  

  22.    //初始化该组块  

  23.    initMSERComp( comptr );  

  24.    //在最高位标注该像素为已被访问过,即该值小于0  

  25.    *imgptr |= 0x80000000;  

  26.    //得到该像素所对应的堆,即指向它所对应的灰度值  

  27.    heap_cur += (*imgptr)&0xff;  

  28.    //定义方向,即偏移量,因为是4邻域,所以该数组分别对应右、下、左、上  

  29.    int dir[] = { 1, step, -1, -step };  

  30. #ifdef __INTRIN_ENABLED__  

  31.    unsigned long heapbit[] = { 0, 0, 0, 0, 0, 0, 0, 0 };  

  32.    unsigned long* bit_cur = heapbit+(((*imgptr)&0x700)>>8);  

  33. #endif  

  34.    //死循环,退出该死循环的条件有两个:一是到达组块的栈底;二是边界像素堆中没有任何值。达到栈底也就意味着堆中没有值,在此函数中两者是一致的。  

  35.    for ( ; ; )  

  36.    {  

  37.        // take tour of all the 4 directions  

  38.        //步骤4  

  39.        //在4邻域内进行搜索  

  40.        while ( ((*imgptr)&0x70000) < 0x40000 )  

  41.        {  

  42.            // get the neighbor  

  43.            /* ((*imgptr)&0x70000)>>16得到第16位至第18位数据,该数据对应的4邻域的方向,再通过dir数组得到4邻域的偏移量,因此imgptr_nbr为当前像素4邻域中某一个方向上邻域的地址指针 */  

  44.            int* imgptr_nbr = imgptr+dir[((*imgptr)&0x70000)>>16];  

  45.            //检查邻域像素是否被访问过,如果被访问过,则会在第一位置1,因此该值会小于0,否则第一位为0,该值大于0  

  46.            if ( *imgptr_nbr >= 0 ) // if the neighbor is not visited yet  

  47.            {  

  48.                //标注该像素已被访问过,即把第一位置1  

  49.                *imgptr_nbr |= 0x80000000; // mark it as visited  

  50.                //比较当前像素与邻域像素灰度值  

  51.                if ( ((*imgptr_nbr)&0xff) < ((*imgptr)&0xff) )  

  52.                {  

  53.                    //如果邻域值小于当前值,把当前值放入堆中  

  54.                    // when the value of neighbor smaller than current  

  55.                    // push current to boundary heap and make the neighbor to be the current one  

  56.                    // create an empty comp  

  57.                    //堆中该像素灰度值的数量加1,即对该灰度值像素个数计数  

  58.                    (*heap_cur)++;  

  59.                    //把当前值的地址放入堆中  

  60.                    **heap_cur = imgptr;  

  61.                    //重新标注当前值的方向位,以备下一次访问该值时搜索下一个邻域  

  62.                    *imgptr += 0x10000;  

  63.                    //定位邻域值所对应的堆的位置  

  64.                    //当前heap_cur所指向的灰度值为while循环搜索中的最小灰度值,即水溢过的最低点  

  65.                    heap_cur += ((*imgptr_nbr)&0xff)-((*imgptr)&0xff);  

  66. #ifdef __INTRIN_ENABLED__  

  67.                    _bitset( bit_cur, (*imgptr)&0x1f );  

  68.                    bit_cur += (((*imgptr_nbr)&0x700)-((*imgptr)&0x700))>>8;  

  69. #endif  

  70.                    imgptr = imgptr_nbr;    //邻域值换为当前值  

  71.                    //步骤3  

  72.                    comptr++;    //创建一个组块  

  73.                    initMSERComp( comptr );    //初始化该组块  

  74.                    comptr->grey_level = (*imgptr)&0xff;    //为该组块的灰度值赋值  

  75.                    //当某个邻域值小于当前值,则不对当前值再做任何操作,继续下次循环,在下次循环中,处理的则是该邻域值,即再次执行步骤4  

  76.                    continue;  

  77.                } else {  

  78.                    //如果邻域值大于当前值,把邻域值放入堆中  

  79.                    // otherwise, push the neighbor to boundary heap  

  80.                    //找到该邻域值在堆中的灰度值位置,并对其计数,即对该灰度值像素个数计数  

  81.                    heap_cur[((*imgptr_nbr)&0xff)-((*imgptr)&0xff)]++;  

  82.                    //把该邻域像素地址放入堆中  

  83.                    *heap_cur[((*imgptr_nbr)&0xff)-((*imgptr)&0xff)] = imgptr_nbr;  

  84. #ifdef __INTRIN_ENABLED__  

  85.                    _bitset( bit_cur+((((*imgptr_nbr)&0x700)-((*imgptr)&0x700))>>8), (*imgptr_nbr)&0x1f );  

  86. #endif  

  87.                }  

  88.            }  

  89.            *imgptr += 0x10000;    //重新标注当前值的领域方向  

  90.        }  

  91.        //imsk表示结束while循环后所得到的最后像素地址与图像首地址的相对距离  

  92.        int imsk = (int)(imgptr-ioptr);  

  93.        //得到结束while循环后的最后像素的坐标位置  

  94.        //从这里可以看出图像的宽采样2^N的好处,即imsk>>stepgap  

  95.        ptsptr->pt = cvPoint( imsk&stepmask, imsk>>stepgap );  

  96.        // get the current location  

  97.        //步骤5  

  98.        //对栈顶的组块的像素个数累加,即计算组块的面积大小,并链接组块内的像素点  

  99.        //结束while循环后,栈顶组块的灰度值就是该次循环后得到的最小灰度值,也就是该组块为极低点,就相当于水已经流到了最低的位置  

  100.        accumulateMSERComp( comptr, ptsptr );  

  101.        //指向下一个像素点链表位置  

  102.        ptsptr++;  

  103.        // get the next pixel from boundary heap  

  104.        //步骤6  

  105.        /*结束while循环后,如果**heap_cur有值的话,heap_cur指向的应该是while循环中得到的灰度值最小值,也就是在组块的边界像素中,有与组块相同的灰度值,因此要把该值作为当前值继续while循环,也就是相当于组块面积的扩展*/  

  106.        if ( **heap_cur )    //有值  

  107.        {  

  108.            imgptr = **heap_cur;    //把该像素点作为当前值  

  109.            (*heap_cur)--;    //像素的个数要相应的减1  

  110. #ifdef __INTRIN_ENABLED__  

  111.            if ( !**heap_cur )  

  112.                _bitreset( bit_cur, (*imgptr)&0x1f );  

  113. #endif  

  114.        //步骤7  

  115.        //已经找到了最小灰度值的组块,并且边界像素堆中的灰度值都比组块的灰度值大,则这时需要组块,即计算最大稳定极值区域  

  116.        } else {  

  117. #ifdef __INTRIN_ENABLED__  

  118.            bool found_pixel = 0;  

  119.            unsigned long pixel_val;  

  120.            for ( int i = ((*imgptr)&0x700)>>8; i < 8; i++ )  

  121.            {  

  122.                if ( _BitScanForward( &pixel_val, *bit_cur ) )  

  123.                {  

  124.                    found_pixel = 1;  

  125.                    pixel_val += i<<5;  

  126.                    heap_cur += pixel_val-((*imgptr)&0xff);  

  127.                    break;  

  128.                }  

  129.                bit_cur++;  

  130.            }  

  131.            if ( found_pixel )  

  132. #else  

  133.            heap_cur++;    //指向高一级的灰度值  

  134.            unsigned long pixel_val = 0;  

  135.            //在边界像素堆中,找到边界像素中的最小灰度值  

  136.            for ( unsigned long i = ((*imgptr)&0xff)+1; i < 256; i++ )  

  137.            {  

  138.                if ( **heap_cur )  

  139.                {  

  140.                    pixel_val = i;    //灰度值  

  141.                    break;  

  142.                }  

  143.                //定位在堆中所对应的灰度值,与pixel_val是相等的  

  144.                heap_cur++;  

  145.            }  

  146.            if ( pixel_val )    //如果找到了像素值  

  147. #endif  

  148.            {  

  149.                imgptr = **heap_cur;    //从堆中提取出该像素  

  150.                (*heap_cur)--;    //对应的像素个数减1  

  151. #ifdef __INTRIN_ENABLED__  

  152.                if ( !**heap_cur )  

  153.                    _bitreset( bit_cur, pixel_val&0x1f );  

  154. #endif  

  155.                //进入处理栈子模块  

  156.                if ( pixel_val < comptr[-1].grey_level )  

  157.                //如果从堆中提取出的最小灰度值小于距栈顶第二个组块的灰度值,则说明栈顶组块和第二个组块之间仍然有没有处理过的组块,因此在计算完MSER值后还要继续返回步骤4搜索该组块  

  158.                {  

  159.                    // check the stablity and push a new history, increase the grey level  

  160.                    //利用公式2计算栈顶组块的q(i)值  

  161.                    if ( MSERStableCheck( comptr, params ) )    //是MSER  

  162.                    {  

  163.                        //得到组块内的像素点  

  164.                        CvContour* contour = MSERToContour( comptr, storage );  

  165.                        contour->color = color;    //标注是MSER-还是MSER+  

  166.                        //把组块像素点放入序列中  

  167.                        cvSeqPush( contours, &contour );  

  168.                    }  

  169.                    MSERNewHistory( comptr, histptr );  

  170.                    //改变栈顶组块的灰度值,这样就可以和上一层的组块进行合并  

  171.                    comptr[0].grey_level = pixel_val;  

  172.                    histptr++;  

  173.                } else {  

  174.                    //从堆中提取出的最小灰度值大于等于距栈顶第二个组块的灰度值  

  175.                    // keep merging top two comp in stack until the grey level >= pixel_val  

  176.                    //死循环,用于处理灰度值相同并且相连的组块之间的合并  

  177.                    for ( ; ; )  

  178.                    {  

  179.                        //指向距栈顶第二个组块  

  180.                        comptr--;  

  181.                        //合并前两个组块,并把合并后的组块作为栈顶组块  

  182.                        MSERMergeComp( comptr+1, comptr, comptr, histptr );  

  183.                        histptr++;  

  184.                        /*如果pixel_val = comptr[0].grey_level,说明在边界上还有属于该组块的像素;如果pixel_val &lt; comptr[0].grey_level,说明还有比栈顶组块灰度值更小的组块没有搜索到。这两种情况都需要回到步骤4中继续搜索组块*/  

  185.                        if ( pixel_val <= comptr[0].grey_level )  

  186.                            break;  

  187.                        //合并栈内前两个组块,直到pixel_val < comptr[-1].grey_level为止  

  188.                        if ( pixel_val < comptr[-1].grey_level )  

  189.                        {  

  190.                            // check the stablity here otherwise it wouldn't be an ER  

  191.                            if ( MSERStableCheck( comptr, params ) )  

  192.                            {  

  193.                                CvContour* contour = MSERToContour( comptr, storage );  

  194.                                contour->color = color;  

  195.                                cvSeqPush( contours, &contour );  

  196.                            }  

  197.                            MSERNewHistory( comptr, histptr );  

  198.                            comptr[0].grey_level = pixel_val;  

  199.                            histptr++;  

  200.                            break;  

  201.                        }  

  202.                    }  

  203.                }  

  204.            } else  

  205.                //边界像素堆中没有任何像素,则退出死循环,该函数返回。  

  206.                break;  

  207.        }  

  208.    }  

  209. }  

MSER就分析到这里,至于其中用到的一些子函数就不再分析。这里还要说明一点的是,MSER+和MSER-使用的都是同一个函数,两者的区别是一个组块面积增加,另一个减少。说实话,该部分的内容数据结构较为复杂,有一些内容我理解得也不透彻,比如结构MSERGrowHistory真正的含义还比较模糊。

下面就给出最大稳定极值区域检测的应用实例:

  1. #include "opencv2/core/core.hpp"  

  2. #include "opencv2/highgui/highgui.hpp"  

  3. #include "opencv2/imgproc/imgproc.hpp"  

  4. #include <opencv2/features2d/features2d.hpp>  

  5. #include <iostream>  

  6. using namespace cv;  

  7. using namespace std;  

  8.  

  9. int main(int argc, char *argv[])  

  10. {  

  11.    Mat src,gray;  

  12.    src = imread("lenna.bmp");  

  13.    cvtColor( src, gray, CV_BGR2GRAY );  

  14.    //创建MSER类  

  15.    MSER ms;  

  16.    //用于组块区域的像素点集  

  17.    vector<vector<Point>> regions;  

  18.    ms(gray, regions, Mat());  

  19.    //在灰度图像中用椭圆形绘制组块  

  20.    for (int i = 0; i < regions.size(); i++)  

  21.    {  

  22.        ellipse(gray, fitEllipse(regions[i]), Scalar(255));  

  23.    }  

  24.    imshow("mser", gray);  

  25.    waitKey(0);  

  26.    return 0;  

  27. }  

结果如下图所示。本文介绍的方法只适用于灰度图像,所以如果输入的是彩色图像,要把它转换为灰度图像。另外在创建MSER类的时候,我们使用的是缺省构造函数,因此MSER算法所需的参数是系统默认的。所以我们检测的结果与David Nister等人在原著中的检测结果略有不同。

 

 

 

 

http://blog.sciencenet.cn/blog-1327159-849648.html  此文来自科学网刘鹏博客,转载请注明出处。 

posted on 2015-04-02 15:32  cv_ml_张欣男  阅读(3206)  评论(0编辑  收藏  举报

导航