原文:

http://blog.csdn.net/zhaocj/article/details/43191829

前面我们介绍了MSER方法,但该方法不适用于对彩色图像的区域检测。为此,Forssen于2007年提出了针对彩色图像的最大稳定极值区域的检测方法——MSCR(Maximally Stable Colour Regions)。

MSCR的检测方法是基于凝聚聚类(AgglomerativeClustering)算法,它把图像中的每个像素作为对象,通过某种相似度准则,依次逐层的进行合并形成簇,即先合并相似度大的对象,再合并相似度小的对象,直到满足某种终止条件为止。这一过程在MSCR中被称为进化过程,即逐步合并图像中的像素,从而形成斑点区域。

MSCR中所使用的相似度准则是卡方距离(Chi-squared distance):

      (1)

其中,xy分别为彩色图像中的两个不同像素,下标k表示不同的通道,例如红、绿、蓝三个颜色通道。因此公式1是一种颜色相似度的度量。

MSCR通过邻域像素之间的颜色相似度来进行聚类合并,邻域关系可以是水平垂直间邻域,也可以是还包括对角线间邻域。Opencv使用的是水平垂直间邻域,即当前像素与其右侧像素通过公式1得到一个相似度值,再与其下面像素通过公式1得到另一个相似度值。所以一般来说,每个像素都有两个相似度值,但图像的最右侧一列和最下面一行只有一个相似度值。因此对于一个大小为L×M的彩色图像来说,一共有2×L×M-L-M个相似度值。我们把这些相似度值放入一个列表中,由于该相似度是邻域之间的相似度,类似于求图像的边缘,所以该列表也称为边缘列表。

在凝聚聚类算法中,是需要逐层进行合并的。在MSCR中合并的层次也称为进化步长,用t来表示,t∈[0…T],根据经验值,T一般为200,即一共进行200步的进化过程。在每一层,都对应一个不同的颜色相似度阈值dthr,在该层只选取那些颜色相似度小于该阈值的像素进行合并。每一层的阈值是不同,并且随着t的增加,阈值也增加,因此达到了合并的区域面积逐步增加的目的。阈值的选取是关键,我们知道,图像像素邻域间的相关性是很大的,也就是通过公式1计算得到的值存在着大量的很小的值,而很大的值少之又少。因此如果我们仍然采用类似于MSER那样,随着t的增加,线性增加dthr的方法,会带来一个严重的后果,就是在进化的开始(t较小的时候),形成斑点区域的速率很快,而在进化的后期(t接近T时),形成斑点区域的速率很慢。为了解决这个问题,即在不同的进化步长下有相同的速率,对于阈值的选取,MSCR采用的是改进型的累积分布函数(CDF)的逆函数的形式。在实际应用中,事先把该函数值存储在表中,使用时通过查表的形式根据不同的t得到不同的dthr。

在每一个进化步长内,MSCR会合并一些颜色相似的像素,相邻像素之间就会组成斑点区域,对这些区域我们就需要判断其是否为最大稳定极值区域。对于所形成的斑点区域,我们需要给定该区域的面积a*和相似度阈值d*这两个参数。虽然随着进化步长t的增加,阈值dt(也就是dthr)也在增加,该区域的面积at也在增加,但只有满足两个步长间面积之比大于一定值的时候,才会重新初始化该区域的a*和d*,即:

     (2)

一般athr=1.01。下面给出MSCR判断稳定区域的公式:

    (3)

自从上一次初始化(即更新a*和d*)以来,如果s达到了最小值,则该区域为稳定区域。在判断稳定区域的过程中,还应该满足另外两个条件:1是公式3中的t不能是更新a*和d*之后的第一个进化步长;2是公式3中的分母部分要大于一定的阈值,即

dt - d* > mmin        (4)

一般mmin设置为0.003。稳定区域通过公式3找到后,那么极值区域的判断与MSER的方法一样,是通过稳定区域的面积变量率来判断的,即上一篇文章里的公式1。

下面给出彩色图像MSCR的步骤:

1、应用公式1计算颜色相似度,得到彩色图像的边缘列表;

2、对边缘列表进行平滑处理;

3、进化处理,由各个进化步长的距离阈值得到稳定极值区域。

在opencv2.4.9中,MSCR和MSER共用一个类:

[cpp] view plain copy print?

  1. class MSER : public CvMSERParams
  2. {
  3. public:
  4. // default constructor
  5. MSER();
  6. // constructor that initializes all the algorithm parameters
  7. MSER( int _delta, int _min_area, int _max_area,
  8. float _max_variation, float _min_diversity,
  9. int _max_evolution, double _area_threshold,
  10. double _min_margin, int _edge_blur_size );
  11. // runs the extractor on the specified image; returns the MSERs,
  12. // each encoded as a contour (vector<Point>, see findContours)
  13. // the optional mask marks the area where MSERs are searched for
  14. void operator()( const Mat& image, vector<vector<Point> >& msers, const Mat& mask ) const;
  15. };

class MSER : public CvMSERParams
{
public:
// default constructor
MSER();
// constructor that initializes all the algorithm parameters
MSER( int _delta, int _min_area, int _max_area,
float _max_variation, float _min_diversity,
int _max_evolution, double _area_threshold,
double _min_margin, int _edge_blur_size );
// runs the extractor on the specified image; returns the MSERs,
// each encoded as a contour (vector<Point>, see findContours)
// the optional mask marks the area where MSERs are searched for
void operator()( const Mat& image, vector<vector<Point> >& msers, const Mat& mask ) const;
};

但MSCR比MSER多用了几个参数:

_max_evolution为进化总步长,就是参数T,一般T = 200;

_area_threshold为重新初始化的面积阈值,就是公式2中的参数athr,一般athr = 1.01;

_min_margin为最小步长距离,就是公式4中mmin,一般mmin = 0.003;

_edge_blur_size为对边缘列表进行平滑处理的孔径大小

上一篇文件已经介绍过,在MSER类中的重载( )运算符中,调用了extractMSER函数,在该函数内通过判断输入图像的类型确定是灰度图像还是彩色图像,如果是彩色图像则调用extractMSER_8UC3函数:

[cpp] view plain copy print?

  1. static void
  2. extractMSER_8UC3( CvMat* src,
  3. CvMat* mask,
  4. CvSeq* contours,
  5. CvMemStorage* storage,
  6. MSERParams params )
  7. {
  8. //在应用凝聚聚类算法时,把图像中的每个像素作为一个对象,即一个节点,因此该语句是定义并分配图像节点空间
  9. MSCRNode* map = (MSCRNode*)cvAlloc( src->cols*src->rows*sizeof(map[0]) );
  10. //定义边缘列表的个数,即2 × L × M – L - M
  11. int Ne = src->cols*src->rows*2-src->cols-src->rows;
  12. //定义并分配边缘列表空间
  13. MSCREdge* edge = (MSCREdge*)cvAlloc( Ne*sizeof(edge[0]) );
  14. TempMSCR* mscr = (TempMSCR*)cvAlloc( src->cols*src->rows*sizeof(mscr[0]) );
  15. //定义变量,用于由公式1计算图像每个像素颜色相似度的距离均值
  16. double emean = 0;
  17. //创建水平梯度矩阵,即当前像素与其右侧像素之间的差值
  18. CvMat* dx = cvCreateMat( src->rows, src->cols-1, CV_64FC1 );
  19. //创建垂直梯度矩阵,即当前像素与其下面像素之间的差值
  20. CvMat* dy = cvCreateMat( src->rows-1, src->cols, CV_64FC1 );
  21. //MSCR的预处理过程,主要完成步骤1和步骤2,后面会详细讲解
  22. Ne = preprocessMSER_8UC3( map, edge, &emean, src, mask, dx, dy, Ne, params.edgeBlurSize );
  23. //得到颜色相似度的距离均值
  24. emean = emean / (double)Ne;
  25. //对边缘列表进行升序排列,便于后面的距离阈值比较
  26. QuickSortMSCREdge( edge, Ne, 0 );
  27. //定义边缘列表的空间的上限
  28. MSCREdge* edge_ub = edge+Ne;
  29. //定义边缘列表的地址指针
  30. MSCREdge* edgeptr = edge;
  31. TempMSCR* mscrptr = mscr;
  32. // the evolution process
  33. //步骤3,进化处理,在t∈[ 0…T ]中循环,这里的i就是前面文章介绍的进化步长t
  34. for ( int i = 0; i < params.maxEvolution; i++ )
  35. {
  36. //下面的4条语句用于计算当前t下的dthr值,thres为dthr
  37. //数组chitab为事先计算好的查询表
  38. double k = (double)i/(double)params.maxEvolution*(TABLE_SIZE-1);
  39. int ti = cvFloor(k);
  40. double reminder = k-ti;
  41. double thres = emean*(chitab3[ti]*(1-reminder)+chitab3[ti+1]*reminder);
  42. // to process all the edges in the list that chi < thres
  43. //处理所有颜色相似度小于阈值的像素
  44. //edgeptr < edge_ub的作用是判断边缘列表指针是否超过了列表的上限,即所指向的不是边缘
  45. while ( edgeptr < edge_ub && edgeptr->chi < thres )
  46. {
  47. //由当前像素的左侧像素找到该像素所在的簇的根节点,也就是找到代表该像素所在区域的像素
  48. MSCRNode* lr = findMSCR( edgeptr->left );
  49. //由当前像素的右侧像素找到该像素所在的簇的根节点,也就是找到代表该像素所在区域的像素
  50. //需要注意的是,这里的左侧和右侧并不是真正意义的左侧和右侧,它们是由preprocessMSER_8UC3函数确定的
  51. MSCRNode* rr = findMSCR( edgeptr->right );
  52. // get the region root (who is responsible)
  53. //如果上面得到的两个根节点是一个节点,则不需要进行任何处理
  54. //如果这两个根节点不是一个,则需要把它们所代表的两个区域进行合并
  55. if ( lr != rr )
  56. {
  57. // rank idea take from: N-tree Disjoint-Set Forests for Maximally Stable Extremal Regions
  58. //下面的if语句用于判断是用rr还是用lr来代表合并后的区域,并且最终通过交换来实现lr代表合并后的区域
  59. //rank值大的根节点代表合并后的区域
  60. if ( rr->rank > lr->rank )
  61. {
  62. MSCRNode* tmp;
  63. CV_SWAP( lr, rr, tmp );
  64. } else if ( lr->rank == rr->rank ) {
  65. // at the same rank, we will compare the size
  66. //如果两个根节点的rank值相同,则区域面积大的代表合并后的区域
  67. if ( lr->size > rr->size )
  68. {
  69. MSCRNode* tmp;
  70. CV_SWAP( lr, rr, tmp );
  71. }
  72. lr->rank++;
  73. }
  74. //定义rr所表示的区域的根节点为lr
  75. rr->shortcut = lr;
  76. //合并两个区域,合并后区域面积为两个区域面积之和
  77. lr->size += rr->size;
  78. // join rr to the end of list lr (lr is a endless double-linked list)
  79. //把rr加入lr列表中,组成一个循环双链接列表
  80. lr->prev->next = rr;
  81. lr->prev = rr->prev;
  82. rr->prev->next = lr;
  83. rr->prev = lr;
  84. // area threshold force to reinitialize
  85. //利用公式2计算是否需要区域的重新初始化
  86. //if语句成立,则表示需要重新初始化
  87. if ( lr->size > (lr->size-rr->size)*params.areaThreshold )
  88. {
  89. //更新面积,即a*值
  90. lr->sizei = lr->size;
  91. //更新当前的进化步长,即t值,以区分各个层
  92. lr->reinit = i;
  93. //tmsr保存着上一次计算得到的稳定区域信息
  94. if ( lr->tmsr != NULL )
  95. {
  96. //公式4
  97. lr->tmsr->m = lr->dt-lr->di;
  98. /*tmsr赋值为NULL,表示该区域已经进行了重新初始化,因此在下次进化步长并计算到该节点的时候,需要保存该区域的最大稳定极值区域;还有一个目的是避免重复计算公式4*/
  99. lr->tmsr = NULL;
  100. }
  101. //更新颜色相似度值,即d*值
  102. lr->di = edgeptr->chi;
  103. //为公式3中的s赋予一个极小的值
  104. lr->s = 1e10;
  105. }
  106. //为该区域的颜色相似度赋值
  107. lr->dt = edgeptr->chi;
  108. //在重新初始化以后的进化步长中,当计算到该节点时,需要进入if语句内,以判断最大稳定极值区域
  109. if ( i > lr->reinit )
  110. {
  111. //公式3
  112. double s = (double)(lr->size-lr->sizei)/(lr->dt-lr->di);
  113. //当公式3中的s是最小值时
  114. if ( s < lr->s )
  115. {
  116. // skip the first one and check stablity
  117. // i > lr->reinit+1的目的是避免计算重新初始化后的第一个进化步长
  118. // MSCRStableCheck函数为计算最大稳定机制区域,即计算区域面积的变化率
  119. if ( i > lr->reinit+1 && MSCRStableCheck( lr, params ) )
  120. {
  121. //tmsr为NULL,表示至从上次重新初始化以来,还没有为tmsr赋值,因此这次得到的稳定区域要作为最终输出保存下来
  122. if ( lr->tmsr == NULL )
  123. {
  124. //gmsr为全局稳定区域,tmsr为暂存稳定区域,mscrptr为mscr的指针变量,它是最终输出的稳定区域
  125. lr->gmsr = lr->tmsr = mscrptr;
  126. mscrptr++;    //指向下一个地址
  127. }
  128. //为tmsr赋值
  129. lr->tmsr->size = lr->size;
  130. lr->tmsr->head = lr;
  131. lr->tmsr->tail = lr->prev;
  132. lr->tmsr->m = 0;
  133. }
  134. //保证s为最小值
  135. lr->s = s;
  136. }
  137. }
  138. }
  139. //指向下一个边缘
  140. edgeptr++;
  141. }
  142. //如果超出了边缘列表的范围,则退出for循环
  143. if ( edgeptr >= edge_ub )
  144. break;
  145. }
  146. //对最终得到的稳定区域进行裁剪,并输出
  147. for ( TempMSCR* ptr = mscr; ptr < mscrptr; ptr++ )
  148. // to prune area with margin less than minMargin
  149. //公式4,判断是否满足条件
  150. if ( ptr->m > params.minMargin )
  151. {
  152. //创建序列
  153. CvSeq* _contour = cvCreateSeq( CV_SEQ_KIND_GENERIC|CV_32SC2, sizeof(CvContour), sizeof(CvPoint), storage );
  154. //初始化该序列
  155. cvSeqPushMulti( _contour, 0, ptr->size );
  156. MSCRNode* lpt = ptr->head;
  157. for ( int i = 0; i < ptr->size; i++ )
  158. {
  159. CvPoint* pt = CV_GET_SEQ_ELEM( CvPoint, _contour, i );
  160. //得到稳定区域的坐标值
  161. pt->x = (lpt->index)&0xffff;
  162. pt->y = (lpt->index)>>16;
  163. lpt = lpt->next;
  164. }
  165. CvContour* contour = (CvContour*)_contour;
  166. cvBoundingRect( contour );
  167. contour->color = 0;
  168. //把坐标值压入序列中
  169. cvSeqPush( contours, &contour );
  170. }
  171. //清内存
  172. cvReleaseMat( &dx );
  173. cvReleaseMat( &dy );
  174. cvFree( &mscr );
  175. cvFree( &edge );
  176. cvFree( &map );
  177. }

static void
extractMSER_8UC3( CvMat* src,CvMat* mask,CvSeq* contours,CvMemStorage* storage,MSERParams params )
{//在应用凝聚聚类算法时,把图像中的每个像素作为一个对象,即一个节点,因此该语句是定义并分配图像节点空间MSCRNode* map = (MSCRNode*)cvAlloc( src->cols*src->rows*sizeof(map[0]) );//定义边缘列表的个数,即2 × L × M – L - Mint Ne = src->cols*src->rows*2-src->cols-src->rows;//定义并分配边缘列表空间MSCREdge* edge = (MSCREdge*)cvAlloc( Ne*sizeof(edge[0]) );TempMSCR* mscr = (TempMSCR*)cvAlloc( src->cols*src->rows*sizeof(mscr[0]) );//定义变量,用于由公式1计算图像每个像素颜色相似度的距离均值double emean = 0;//创建水平梯度矩阵,即当前像素与其右侧像素之间的差值CvMat* dx = cvCreateMat( src->rows, src->cols-1, CV_64FC1 );//创建垂直梯度矩阵,即当前像素与其下面像素之间的差值CvMat* dy = cvCreateMat( src->rows-1, src->cols, CV_64FC1 );//MSCR的预处理过程,主要完成步骤1和步骤2,后面会详细讲解Ne = preprocessMSER_8UC3( map, edge, &emean, src, mask, dx, dy, Ne, params.edgeBlurSize );//得到颜色相似度的距离均值emean = emean / (double)Ne;//对边缘列表进行升序排列,便于后面的距离阈值比较QuickSortMSCREdge( edge, Ne, 0 );//定义边缘列表的空间的上限MSCREdge* edge_ub = edge+Ne;//定义边缘列表的地址指针MSCREdge* edgeptr = edge;TempMSCR* mscrptr = mscr;// the evolution process//步骤3,进化处理,在t∈[ 0…T ]中循环,这里的i就是前面文章介绍的进化步长tfor ( int i = 0; i < params.maxEvolution; i++ ){//下面的4条语句用于计算当前t下的dthr值,thres为dthr//数组chitab为事先计算好的查询表double k = (double)i/(double)params.maxEvolution*(TABLE_SIZE-1);int ti = cvFloor(k);double reminder = k-ti;double thres = emean*(chitab3[ti]*(1-reminder)+chitab3[ti+1]*reminder);// to process all the edges in the list that chi < thres//处理所有颜色相似度小于阈值的像素//edgeptr < edge_ub的作用是判断边缘列表指针是否超过了列表的上限,即所指向的不是边缘while ( edgeptr < edge_ub && edgeptr->chi < thres ){//由当前像素的左侧像素找到该像素所在的簇的根节点,也就是找到代表该像素所在区域的像素MSCRNode* lr = findMSCR( edgeptr->left );//由当前像素的右侧像素找到该像素所在的簇的根节点,也就是找到代表该像素所在区域的像素//需要注意的是,这里的左侧和右侧并不是真正意义的左侧和右侧,它们是由preprocessMSER_8UC3函数确定的MSCRNode* rr = findMSCR( edgeptr->right );// get the region root (who is responsible)//如果上面得到的两个根节点是一个节点,则不需要进行任何处理//如果这两个根节点不是一个,则需要把它们所代表的两个区域进行合并if ( lr != rr ){// rank idea take from: N-tree Disjoint-Set Forests for Maximally Stable Extremal Regions//下面的if语句用于判断是用rr还是用lr来代表合并后的区域,并且最终通过交换来实现lr代表合并后的区域//rank值大的根节点代表合并后的区域if ( rr->rank > lr->rank ){MSCRNode* tmp;CV_SWAP( lr, rr, tmp );} else if ( lr->rank == rr->rank ) {// at the same rank, we will compare the size//如果两个根节点的rank值相同,则区域面积大的代表合并后的区域if ( lr->size > rr->size ){MSCRNode* tmp;CV_SWAP( lr, rr, tmp );}lr->rank++;}//定义rr所表示的区域的根节点为lrrr->shortcut = lr;//合并两个区域,合并后区域面积为两个区域面积之和lr->size += rr->size;// join rr to the end of list lr (lr is a endless double-linked list)//把rr加入lr列表中,组成一个循环双链接列表lr->prev->next = rr;lr->prev = rr->prev;rr->prev->next = lr;rr->prev = lr;// area threshold force to reinitialize//利用公式2计算是否需要区域的重新初始化//if语句成立,则表示需要重新初始化if ( lr->size > (lr->size-rr->size)*params.areaThreshold ){//更新面积,即a*值lr->sizei = lr->size;//更新当前的进化步长,即t值,以区分各个层lr->reinit = i;//tmsr保存着上一次计算得到的稳定区域信息if ( lr->tmsr != NULL ){//公式4lr->tmsr->m = lr->dt-lr->di;/*tmsr赋值为NULL,表示该区域已经进行了重新初始化,因此在下次进化步长并计算到该节点的时候,需要保存该区域的最大稳定极值区域;还有一个目的是避免重复计算公式4*/lr->tmsr = NULL;}//更新颜色相似度值,即d*值lr->di = edgeptr->chi;//为公式3中的s赋予一个极小的值lr->s = 1e10;}//为该区域的颜色相似度赋值lr->dt = edgeptr->chi;//在重新初始化以后的进化步长中,当计算到该节点时,需要进入if语句内,以判断最大稳定极值区域if ( i > lr->reinit ){//公式3double s = (double)(lr->size-lr->sizei)/(lr->dt-lr->di);//当公式3中的s是最小值时if ( s < lr->s ){// skip the first one and check stablity// i > lr->reinit+1的目的是避免计算重新初始化后的第一个进化步长// MSCRStableCheck函数为计算最大稳定机制区域,即计算区域面积的变化率if ( i > lr->reinit+1 && MSCRStableCheck( lr, params ) ){//tmsr为NULL,表示至从上次重新初始化以来,还没有为tmsr赋值,因此这次得到的稳定区域要作为最终输出保存下来if ( lr->tmsr == NULL ){//gmsr为全局稳定区域,tmsr为暂存稳定区域,mscrptr为mscr的指针变量,它是最终输出的稳定区域lr->gmsr = lr->tmsr = mscrptr;mscrptr++;    //指向下一个地址}//为tmsr赋值lr->tmsr->size = lr->size;lr->tmsr->head = lr;lr->tmsr->tail = lr->prev;lr->tmsr->m = 0;}//保证s为最小值lr->s = s;}}}//指向下一个边缘edgeptr++;}//如果超出了边缘列表的范围,则退出for循环if ( edgeptr >= edge_ub )break;}//对最终得到的稳定区域进行裁剪,并输出for ( TempMSCR* ptr = mscr; ptr < mscrptr; ptr++ )// to prune area with margin less than minMargin//公式4,判断是否满足条件if ( ptr->m > params.minMargin ){//创建序列CvSeq* _contour = cvCreateSeq( CV_SEQ_KIND_GENERIC|CV_32SC2, sizeof(CvContour), sizeof(CvPoint), storage );//初始化该序列cvSeqPushMulti( _contour, 0, ptr->size );MSCRNode* lpt = ptr->head;for ( int i = 0; i < ptr->size; i++ ){CvPoint* pt = CV_GET_SEQ_ELEM( CvPoint, _contour, i );//得到稳定区域的坐标值pt->x = (lpt->index)&0xffff;pt->y = (lpt->index)>>16;lpt = lpt->next;}CvContour* contour = (CvContour*)_contour;cvBoundingRect( contour );contour->color = 0;//把坐标值压入序列中cvSeqPush( contours, &contour );}//清内存cvReleaseMat( &dx );cvReleaseMat( &dy );cvFree( &mscr );cvFree( &edge );cvFree( &map );
}

下面我们来介绍一下preprocessMSER_8UC3函数:

[cpp] view plain copy print?

  1. // the preprocess to get the edge list with proper gaussian blur
  2. static int preprocessMSER_8UC3( MSCRNode* node,    //图像像素节点
  3. MSCREdge* edge,    //边缘列表
  4. double* total,    //求相似度均值时使用,这里是所有像素相似度之和
  5. CvMat* src,    //原始图像
  6. CvMat* mask,    //掩码矩阵
  7. CvMat* dx,    //水平梯度矩阵
  8. CvMat* dy,    //垂直梯度矩阵
  9. int Ne,    //边缘列表元素的个数
  10. int edgeBlurSize )    //平滑处理的孔径尺寸大小
  11. {
  12. int srccpt = src->step-src->cols*3;
  13. uchar* srcptr = src->data.ptr;    //图像当前像素指针
  14. uchar* lastptr = src->data.ptr+3;    //右侧像素指针
  15. double* dxptr = dx->data.db;    //水平梯度数据指针
  16. //计算当前像素与其右侧像素之间的颜色相似度
  17. for ( int i = 0; i < src->rows; i++ )
  18. {
  19. //图像最右侧一列没有该相似度,因此j < src->cols-1
  20. for ( int j = 0; j < src->cols-1; j++ )
  21. {
  22. //公式1,计算卡方距离,保存到dx内
  23. *dxptr = ChiSquaredDistance( srcptr, lastptr );
  24. //地址递增
  25. dxptr++;
  26. srcptr += 3;
  27. lastptr += 3;
  28. }
  29. //指向下一行
  30. srcptr += srccpt+3;
  31. lastptr += srccpt+3;
  32. }
  33. srcptr = src->data.ptr;    //图像当前像素指针
  34. lastptr = src->data.ptr+src->step;    //下一行像素指针
  35. double* dyptr = dy->data.db;    //垂直梯度数据指针
  36. //计算当前像素与其下面一行像素之间的颜色相似度
  37. //图像最下面一行没有该相似度,因此i < src->rows-1
  38. for ( int i = 0; i < src->rows-1; i++ )
  39. {
  40. for ( int j = 0; j < src->cols; j++ )
  41. {
  42. //保存到dy内
  43. *dyptr = ChiSquaredDistance( srcptr, lastptr );
  44. dyptr++;
  45. srcptr += 3;
  46. lastptr += 3;
  47. }
  48. srcptr += srccpt;
  49. lastptr += srccpt;
  50. }
  51. // get dx and dy and blur it
  52. //对颜色相似度值进行高斯平滑处理
  53. if ( edgeBlurSize >= 1 )
  54. {
  55. cvSmooth( dx, dx, CV_GAUSSIAN, edgeBlurSize, edgeBlurSize );
  56. cvSmooth( dy, dy, CV_GAUSSIAN, edgeBlurSize, edgeBlurSize );
  57. }
  58. dxptr = dx->data.db;
  59. dyptr = dy->data.db;
  60. // assian dx, dy to proper edge list and initialize mscr node
  61. // the nasty code here intended to avoid extra loops
  62. /*下面的if语句是为边缘列表赋值,如果定义了掩码矩阵,则边缘列表不保存被掩码掉的像素的边缘信息,因此边缘列表的个数Ne需要重新计算并输出。在这里我们以没有定义掩码矩阵为例进行讲解,两者的本质是一样的*/
  63. if ( mask )
  64. {
  65. Ne = 0;
  66. int maskcpt = mask->step-mask->cols+1;
  67. uchar* maskptr = mask->data.ptr;
  68. MSCRNode* nodeptr = node;
  69. initMSCRNode( nodeptr );
  70. nodeptr->index = 0;
  71. *total += edge->chi = *dxptr;
  72. if ( maskptr[0] && maskptr[1] )
  73. {
  74. edge->left = nodeptr;
  75. edge->right = nodeptr+1;
  76. edge++;
  77. Ne++;
  78. }
  79. dxptr++;
  80. nodeptr++;
  81. maskptr++;
  82. for ( int i = 1; i < src->cols-1; i++ )
  83. {
  84. initMSCRNode( nodeptr );
  85. nodeptr->index = i;
  86. if ( maskptr[0] && maskptr[1] )
  87. {
  88. *total += edge->chi = *dxptr;
  89. edge->left = nodeptr;
  90. edge->right = nodeptr+1;
  91. edge++;
  92. Ne++;
  93. }
  94. dxptr++;
  95. nodeptr++;
  96. maskptr++;
  97. }
  98. initMSCRNode( nodeptr );
  99. nodeptr->index = src->cols-1;
  100. nodeptr++;
  101. maskptr += maskcpt;
  102. for ( int i = 1; i < src->rows-1; i++ )
  103. {
  104. initMSCRNode( nodeptr );
  105. nodeptr->index = i<<16;
  106. if ( maskptr[0] )
  107. {
  108. if ( maskptr[-mask->step] )
  109. {
  110. *total += edge->chi = *dyptr;
  111. edge->left = nodeptr-src->cols;
  112. edge->right = nodeptr;
  113. edge++;
  114. Ne++;
  115. }
  116. if ( maskptr[1] )
  117. {
  118. *total += edge->chi = *dxptr;
  119. edge->left = nodeptr;
  120. edge->right = nodeptr+1;
  121. edge++;
  122. Ne++;
  123. }
  124. }
  125. dyptr++;
  126. dxptr++;
  127. nodeptr++;
  128. maskptr++;
  129. for ( int j = 1; j < src->cols-1; j++ )
  130. {
  131. initMSCRNode( nodeptr );
  132. nodeptr->index = (i<<16)|j;
  133. if ( maskptr[0] )
  134. {
  135. if ( maskptr[-mask->step] )
  136. {
  137. *total += edge->chi = *dyptr;
  138. edge->left = nodeptr-src->cols;
  139. edge->right = nodeptr;
  140. edge++;
  141. Ne++;
  142. }
  143. if ( maskptr[1] )
  144. {
  145. *total += edge->chi = *dxptr;
  146. edge->left = nodeptr;
  147. edge->right = nodeptr+1;
  148. edge++;
  149. Ne++;
  150. }
  151. }
  152. dyptr++;
  153. dxptr++;
  154. nodeptr++;
  155. maskptr++;
  156. }
  157. initMSCRNode( nodeptr );
  158. nodeptr->index = (i<<16)|(src->cols-1);
  159. if ( maskptr[0] && maskptr[-mask->step] )
  160. {
  161. *total += edge->chi = *dyptr;
  162. edge->left = nodeptr-src->cols;
  163. edge->right = nodeptr;
  164. edge++;
  165. Ne++;
  166. }
  167. dyptr++;
  168. nodeptr++;
  169. maskptr += maskcpt;
  170. }
  171. initMSCRNode( nodeptr );
  172. nodeptr->index = (src->rows-1)<<16;
  173. if ( maskptr[0] )
  174. {
  175. if ( maskptr[1] )
  176. {
  177. *total += edge->chi = *dxptr;
  178. edge->left = nodeptr;
  179. edge->right = nodeptr+1;
  180. edge++;
  181. Ne++;
  182. }
  183. if ( maskptr[-mask->step] )
  184. {
  185. *total += edge->chi = *dyptr;
  186. edge->left = nodeptr-src->cols;
  187. edge->right = nodeptr;
  188. edge++;
  189. Ne++;
  190. }
  191. }
  192. dxptr++;
  193. dyptr++;
  194. nodeptr++;
  195. maskptr++;
  196. for ( int i = 1; i < src->cols-1; i++ )
  197. {
  198. initMSCRNode( nodeptr );
  199. nodeptr->index = ((src->rows-1)<<16)|i;
  200. if ( maskptr[0] )
  201. {
  202. if ( maskptr[1] )
  203. {
  204. *total += edge->chi = *dxptr;
  205. edge->left = nodeptr;
  206. edge->right = nodeptr+1;
  207. edge++;
  208. Ne++;
  209. }
  210. if ( maskptr[-mask->step] )
  211. {
  212. *total += edge->chi = *dyptr;
  213. edge->left = nodeptr-src->cols;
  214. edge->right = nodeptr;
  215. edge++;
  216. Ne++;
  217. }
  218. }
  219. dxptr++;
  220. dyptr++;
  221. nodeptr++;
  222. maskptr++;
  223. }
  224. initMSCRNode( nodeptr );
  225. nodeptr->index = ((src->rows-1)<<16)|(src->cols-1);
  226. if ( maskptr[0] && maskptr[-mask->step] )
  227. {
  228. *total += edge->chi = *dyptr;
  229. edge->left = nodeptr-src->cols;
  230. edge->right = nodeptr;
  231. Ne++;
  232. }
  233. } else {
  234. //定义节点指针
  235. MSCRNode* nodeptr = node;
  236. //下面是计算图像的左上角第一个像素节点
  237. initMSCRNode( nodeptr );    //初始化节点
  238. //index为对应的序列值,也就是图像的坐标,纵坐标保存在高16位内,横坐标保存在低16位内
  239. nodeptr->index = 0;
  240. //为边缘列表的卡方距离赋值,并累加该距离值
  241. *total += edge->chi = *dxptr;
  242. dxptr++;    //递增
  243. edge->left = nodeptr;    //边缘列表的左侧指向当前像素节点
  244. edge->right = nodeptr+1;    //右侧指向下一个像素节点
  245. edge++;    //递增
  246. nodeptr++;    //递增
  247. //下面的for循环是计算图像的第一行像素,对应的边缘列表的卡方距离保存的是水平梯度
  248. for ( int i = 1; i < src->cols-1; i++ )
  249. {
  250. initMSCRNode( nodeptr );
  251. nodeptr->index = i;
  252. *total += edge->chi = *dxptr;
  253. dxptr++;
  254. edge->left = nodeptr;
  255. edge->right = nodeptr+1;
  256. edge++;
  257. nodeptr++;
  258. }
  259. initMSCRNode( nodeptr );
  260. nodeptr->index = src->cols-1;    //图像第一行最后一个像素
  261. nodeptr++;    //指向图像的第二行
  262. //下面的双重for循环计算的是除了第一行和最后一行以外的像素
  263. for ( int i = 1; i < src->rows-1; i++ )
  264. {
  265. initMSCRNode( nodeptr );
  266. nodeptr->index = i<<16;    //图像的第一列
  267. *total += edge->chi = *dyptr;    //垂直梯度
  268. dyptr++;
  269. edge->left = nodeptr-src->cols;    //左侧为上面一行像素节点
  270. edge->right = nodeptr;    //右侧为当前像素节点
  271. edge++;
  272. *total += edge->chi = *dxptr;    //水平梯度
  273. dxptr++;
  274. edge->left = nodeptr;
  275. edge->right = nodeptr+1;
  276. edge++;
  277. nodeptr++;
  278. for ( int j = 1; j < src->cols-1; j++ )
  279. {
  280. initMSCRNode( nodeptr );
  281. nodeptr->index = (i<<16)|j;
  282. *total += edge->chi = *dyptr;
  283. dyptr++;
  284. edge->left = nodeptr-src->cols;
  285. edge->right = nodeptr;
  286. edge++;
  287. *total += edge->chi = *dxptr;
  288. dxptr++;
  289. edge->left = nodeptr;
  290. edge->right = nodeptr+1;
  291. edge++;
  292. nodeptr++;
  293. }
  294. //图像最后一列像素
  295. initMSCRNode( nodeptr );
  296. nodeptr->index = (i<<16)|(src->cols-1);
  297. *total += edge->chi = *dyptr;
  298. dyptr++;
  299. edge->left = nodeptr-src->cols;
  300. edge->right = nodeptr;
  301. edge++;
  302. nodeptr++;
  303. }
  304. //图像的最后一行像素
  305. initMSCRNode( nodeptr );
  306. nodeptr->index = (src->rows-1)<<16;
  307. *total += edge->chi = *dxptr;
  308. dxptr++;
  309. edge->left = nodeptr;
  310. edge->right = nodeptr+1;
  311. edge++;
  312. *total += edge->chi = *dyptr;
  313. dyptr++;
  314. edge->left = nodeptr-src->cols;
  315. edge->right = nodeptr;
  316. edge++;
  317. nodeptr++;
  318. for ( int i = 1; i < src->cols-1; i++ )
  319. {
  320. initMSCRNode( nodeptr );
  321. nodeptr->index = ((src->rows-1)<<16)|i;
  322. *total += edge->chi = *dxptr;
  323. dxptr++;
  324. edge->left = nodeptr;
  325. edge->right = nodeptr+1;
  326. edge++;
  327. *total += edge->chi = *dyptr;
  328. dyptr++;
  329. edge->left = nodeptr-src->cols;
  330. edge->right = nodeptr;
  331. edge++;
  332. nodeptr++;
  333. }
  334. initMSCRNode( nodeptr );
  335. nodeptr->index = ((src->rows-1)<<16)|(src->cols-1);
  336. *total += edge->chi = *dyptr;
  337. edge->left = nodeptr-src->cols;
  338. edge->right = nodeptr;
  339. }
  340. return Ne;
  341. }

// the preprocess to get the edge list with proper gaussian blur
static int preprocessMSER_8UC3( MSCRNode* node,    //图像像素节点MSCREdge* edge,    //边缘列表double* total,    //求相似度均值时使用,这里是所有像素相似度之和CvMat* src,    //原始图像CvMat* mask,    //掩码矩阵CvMat* dx,    //水平梯度矩阵CvMat* dy,    //垂直梯度矩阵int Ne,    //边缘列表元素的个数int edgeBlurSize )    //平滑处理的孔径尺寸大小
{int srccpt = src->step-src->cols*3;uchar* srcptr = src->data.ptr;    //图像当前像素指针uchar* lastptr = src->data.ptr+3;    //右侧像素指针double* dxptr = dx->data.db;    //水平梯度数据指针//计算当前像素与其右侧像素之间的颜色相似度for ( int i = 0; i < src->rows; i++ ){//图像最右侧一列没有该相似度,因此j < src->cols-1for ( int j = 0; j < src->cols-1; j++ ){//公式1,计算卡方距离,保存到dx内*dxptr = ChiSquaredDistance( srcptr, lastptr );//地址递增dxptr++;srcptr += 3;lastptr += 3;}//指向下一行srcptr += srccpt+3;lastptr += srccpt+3;}srcptr = src->data.ptr;    //图像当前像素指针lastptr = src->data.ptr+src->step;    //下一行像素指针double* dyptr = dy->data.db;    //垂直梯度数据指针//计算当前像素与其下面一行像素之间的颜色相似度//图像最下面一行没有该相似度,因此i < src->rows-1for ( int i = 0; i < src->rows-1; i++ ){for ( int j = 0; j < src->cols; j++ ){//保存到dy内*dyptr = ChiSquaredDistance( srcptr, lastptr );dyptr++;srcptr += 3;lastptr += 3;}srcptr += srccpt;lastptr += srccpt;}// get dx and dy and blur it//对颜色相似度值进行高斯平滑处理if ( edgeBlurSize >= 1 ){cvSmooth( dx, dx, CV_GAUSSIAN, edgeBlurSize, edgeBlurSize );cvSmooth( dy, dy, CV_GAUSSIAN, edgeBlurSize, edgeBlurSize );}dxptr = dx->data.db;dyptr = dy->data.db;// assian dx, dy to proper edge list and initialize mscr node// the nasty code here intended to avoid extra loops/*下面的if语句是为边缘列表赋值,如果定义了掩码矩阵,则边缘列表不保存被掩码掉的像素的边缘信息,因此边缘列表的个数Ne需要重新计算并输出。在这里我们以没有定义掩码矩阵为例进行讲解,两者的本质是一样的*/if ( mask ){Ne = 0;int maskcpt = mask->step-mask->cols+1;uchar* maskptr = mask->data.ptr;MSCRNode* nodeptr = node;initMSCRNode( nodeptr );nodeptr->index = 0;*total += edge->chi = *dxptr;if ( maskptr[0] && maskptr[1] ){edge->left = nodeptr;edge->right = nodeptr+1;edge++;Ne++;}dxptr++;nodeptr++;maskptr++;for ( int i = 1; i < src->cols-1; i++ ){initMSCRNode( nodeptr );nodeptr->index = i;if ( maskptr[0] && maskptr[1] ){*total += edge->chi = *dxptr;edge->left = nodeptr;edge->right = nodeptr+1;edge++;Ne++;}dxptr++;nodeptr++;maskptr++;}initMSCRNode( nodeptr );nodeptr->index = src->cols-1;nodeptr++;maskptr += maskcpt;for ( int i = 1; i < src->rows-1; i++ ){initMSCRNode( nodeptr );nodeptr->index = i<<16;if ( maskptr[0] ){if ( maskptr[-mask->step] ){*total += edge->chi = *dyptr;edge->left = nodeptr-src->cols;edge->right = nodeptr;edge++;Ne++;}if ( maskptr[1] ){*total += edge->chi = *dxptr;edge->left = nodeptr;edge->right = nodeptr+1;edge++;Ne++;}}dyptr++;dxptr++;nodeptr++;maskptr++;for ( int j = 1; j < src->cols-1; j++ ){initMSCRNode( nodeptr );nodeptr->index = (i<<16)|j;if ( maskptr[0] ){if ( maskptr[-mask->step] ){*total += edge->chi = *dyptr;edge->left = nodeptr-src->cols;edge->right = nodeptr;edge++;Ne++;}if ( maskptr[1] ){*total += edge->chi = *dxptr;edge->left = nodeptr;edge->right = nodeptr+1;edge++;Ne++;}}dyptr++;dxptr++;nodeptr++;maskptr++;}initMSCRNode( nodeptr );nodeptr->index = (i<<16)|(src->cols-1);if ( maskptr[0] && maskptr[-mask->step] ){*total += edge->chi = *dyptr;edge->left = nodeptr-src->cols;edge->right = nodeptr;edge++;Ne++;}dyptr++;nodeptr++;maskptr += maskcpt;}initMSCRNode( nodeptr );nodeptr->index = (src->rows-1)<<16;if ( maskptr[0] ){if ( maskptr[1] ){*total += edge->chi = *dxptr;edge->left = nodeptr;edge->right = nodeptr+1;edge++;Ne++;}if ( maskptr[-mask->step] ){*total += edge->chi = *dyptr;edge->left = nodeptr-src->cols;edge->right = nodeptr;edge++;Ne++;}}dxptr++;dyptr++;nodeptr++;maskptr++;for ( int i = 1; i < src->cols-1; i++ ){initMSCRNode( nodeptr );nodeptr->index = ((src->rows-1)<<16)|i;if ( maskptr[0] ){if ( maskptr[1] ){*total += edge->chi = *dxptr;edge->left = nodeptr;edge->right = nodeptr+1;edge++;Ne++;}if ( maskptr[-mask->step] ){*total += edge->chi = *dyptr;edge->left = nodeptr-src->cols;edge->right = nodeptr;edge++;Ne++;}}dxptr++;dyptr++;nodeptr++;maskptr++;}initMSCRNode( nodeptr );nodeptr->index = ((src->rows-1)<<16)|(src->cols-1);if ( maskptr[0] && maskptr[-mask->step] ){*total += edge->chi = *dyptr;edge->left = nodeptr-src->cols;edge->right = nodeptr;Ne++;}} else {//定义节点指针MSCRNode* nodeptr = node;//下面是计算图像的左上角第一个像素节点initMSCRNode( nodeptr );    //初始化节点//index为对应的序列值,也就是图像的坐标,纵坐标保存在高16位内,横坐标保存在低16位内nodeptr->index = 0; //为边缘列表的卡方距离赋值,并累加该距离值*total += edge->chi = *dxptr;dxptr++;    //递增edge->left = nodeptr;    //边缘列表的左侧指向当前像素节点edge->right = nodeptr+1;    //右侧指向下一个像素节点edge++;    //递增nodeptr++;    //递增//下面的for循环是计算图像的第一行像素,对应的边缘列表的卡方距离保存的是水平梯度for ( int i = 1; i < src->cols-1; i++ ){initMSCRNode( nodeptr );nodeptr->index = i;    *total += edge->chi = *dxptr;dxptr++;edge->left = nodeptr;edge->right = nodeptr+1;edge++;nodeptr++;}initMSCRNode( nodeptr );nodeptr->index = src->cols-1;    //图像第一行最后一个像素nodeptr++;    //指向图像的第二行//下面的双重for循环计算的是除了第一行和最后一行以外的像素for ( int i = 1; i < src->rows-1; i++ ){initMSCRNode( nodeptr );nodeptr->index = i<<16;    //图像的第一列*total += edge->chi = *dyptr;    //垂直梯度dyptr++;edge->left = nodeptr-src->cols;    //左侧为上面一行像素节点edge->right = nodeptr;    //右侧为当前像素节点edge++;*total += edge->chi = *dxptr;    //水平梯度dxptr++;edge->left = nodeptr;edge->right = nodeptr+1;edge++;nodeptr++;for ( int j = 1; j < src->cols-1; j++ ){initMSCRNode( nodeptr );nodeptr->index = (i<<16)|j;*total += edge->chi = *dyptr;dyptr++;edge->left = nodeptr-src->cols;edge->right = nodeptr;edge++;*total += edge->chi = *dxptr;dxptr++;edge->left = nodeptr;edge->right = nodeptr+1;edge++;nodeptr++;}//图像最后一列像素initMSCRNode( nodeptr );nodeptr->index = (i<<16)|(src->cols-1);*total += edge->chi = *dyptr;dyptr++;edge->left = nodeptr-src->cols;edge->right = nodeptr;edge++;nodeptr++;}//图像的最后一行像素initMSCRNode( nodeptr );nodeptr->index = (src->rows-1)<<16;*total += edge->chi = *dxptr;dxptr++;edge->left = nodeptr;edge->right = nodeptr+1;edge++;*total += edge->chi = *dyptr;dyptr++;edge->left = nodeptr-src->cols;edge->right = nodeptr;edge++;nodeptr++;for ( int i = 1; i < src->cols-1; i++ ){initMSCRNode( nodeptr );nodeptr->index = ((src->rows-1)<<16)|i;*total += edge->chi = *dxptr;dxptr++;edge->left = nodeptr;edge->right = nodeptr+1;edge++;*total += edge->chi = *dyptr;dyptr++;edge->left = nodeptr-src->cols;edge->right = nodeptr;edge++;nodeptr++;}initMSCRNode( nodeptr );nodeptr->index = ((src->rows-1)<<16)|(src->cols-1);*total += edge->chi = *dyptr;edge->left = nodeptr-src->cols;edge->right = nodeptr;}return Ne;
}

下面我们再总结一下preprocessMSER_8UC3函数,首先根据公式1计算卡方距离,当前像素与其右侧像素之间的距离放在dx中,当前像素与其下面像素之间的距离放在dy中,存放的顺序都是从图像的左上角至图像的右下角。另外图像的最右一列没有dx,图像的最下一行没有dy。然后对dx和dy进行高斯平滑处理。最后创建边缘列表。边缘列表的顺序也是从图像的左上角至图像的右下角,与dx和dy的顺序完全一致,并且个数是dx与dy数量之和。如果边缘列表元素的卡方距离(edge->chi)为dx,则它的左侧(edge->left)和右侧(edge->right)分别指向的是图像的当前像素节点和它的右侧像素节点,因此对于图像的最右侧像素节点,没有dx,只有dy;如果边缘列表元素的卡方距离(edge->chi)为dy,则它的左侧(edge->left)和右侧(edge->right)分别指向的是图像的当前像素上一行像素节点和当前像素节点,因此对于图像的第一行没有dy,只有dx;图像的右上角的一个像素既没有dx,也没有dy;而其余像素节点既有dx,又有dy。

下面给出应用MSCR的应用实例:

[cpp] view plain copy print?

  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. int main(int argc, char *argv[])
  9. {
  10. Mat src,yuv;
  11. src = imread("puzzle.png");
  12. cvtColor(src, yuv, COLOR_BGR2YCrCb);
  13. MSER ms;
  14. vector<vector<Point>> regions;
  15. ms(yuv, regions, Mat());
  16. for (int i = 0; i < regions.size(); i++)
  17. {
  18. ellipse(src, fitEllipse(regions[i]), Scalar(255,0,0));
  19. }
  20. imshow("mscr", src);
  21. waitKey(0);
  22. return 0;
  23. }

#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <opencv2/features2d/features2d.hpp>
#include <iostream>
using namespace cv;
using namespace std;int main(int argc, char *argv[])
{Mat src,yuv;src = imread("puzzle.png");cvtColor(src, yuv, COLOR_BGR2YCrCb);MSER ms;vector<vector<Point>> regions;ms(yuv, regions, Mat());for (int i = 0; i < regions.size(); i++){ellipse(src, fitEllipse(regions[i]), Scalar(255,0,0));}imshow("mscr", src);waitKey(0);return 0;
}

从程序中可以看出,在进行MSCR之前,需要把RGB彩色图像转换为YCrCb形式,如果直接应用RGB彩色空间,则会检测到一些不正确的区域。

图像局部特征(十五)--MSCR相关推荐

  1. 图像局部特征(五)--斑点检测之SIFT算法原理总结

     尺度不变特征变换匹配算法详解 Scale Invariant Feature Transform(SIFT) Just For Fun zdd  zddmail@gmail.com 对于初学者, ...

  2. OpenCV系列之图像阈值 | 十五

    目标 在本教程中,您将学习简单阈值,自适应阈值和Otsu阈值. 你将学习函数cv.threshold和cv.adaptiveThreshold. 简单阈值 在这里,问题直截了当.对于每个像素,应用相同 ...

  3. OpenCV学习笔记(十五):图像仿射变换:warpAffine(),getRotationMatrix2D()

    OpenCV学习笔记(十五):图像仿射变换:warpAffine(),getRotationMatrix2D() 一个任意的仿射变换都能表示为乘以一个矩阵(线性变换)接着再加上一个向量(平移)的形式. ...

  4. [Python从零到壹] 四十五.图像增强及运算篇之图像灰度非线性变换详解

    欢迎大家来到"Python从零到壹",在这里我将分享约200篇Python系列文章,带大家一起去学习和玩耍,看看Python这个有趣的世界.所有文章都将结合案例.代码和作者的经验讲 ...

  5. python灰度图片格式_[Python图像处理] 十五.图像的灰度线性变换

    [Python图像处理] 十五.图像的灰度线性变换 发布时间:2019-03-28 00:08, 浏览次数:619 , 标签: Python 该系列文章是讲解Python OpenCV图像处理知识,前 ...

  6. 数字图像处理:第十五章 图象分割

    第十五章 图象分割 目录 1.    引言 2.    阈值与图象分割 3.    梯度与图象分割 4.    边界提取与轮廓跟踪 5.    Hough变换 6.    区域增长 作业 1.  引言 ...

  7. NeHe OpenGL第三十五课:播放AVI

    NeHe OpenGL第三十五课:播放AVI 在OpenGL中播放AVI: 在OpenGL中如何播放AVI呢?利用Windows的API把每一帧作为纹理绑定到OpenGL中,虽然很慢,但它的效果不错. ...

  8. 《算法竞赛中的初等数论》(二)正文 0x20同余(ACM / OI / MO)(十五万字符数论书)

    整理的算法模板合集: ACM模板 点我看算法全家桶系列!!! 实际上是一个全新的精炼模板整合计划 写在最前面:本文部分内容来自网上各大博客或是各类图书,由我个人整理,增加些许见解,仅做学习交流使用,无 ...

  9. 第十五届智能车竞赛技术报告-成电金秋-AI电磁

    01引言 1.1 大赛介绍 全国大学生"恩智浦"杯智能汽车竞赛是以"立足培养.重在参与.鼓励 探索.追求卓越"为宗旨,鼓励创新的一项科技竞赛活动.今年首次新增了 ...

  10. 第十五届智能车竞赛东北赛区普通四轮组冠军车-哈尔滨工业大学-紫丁香

    学 校:哈尔滨工业大学 队伍名称:紫丁香一队 参赛队员:李洋赵宁张磊带队教师: 张依 摘要   本文详细介绍了哈尔滨工业大学"紫丁香一队"在第十五届全国大学生智能汽车竞赛基础四轮组 ...

最新文章

  1. S/4HANA Product edit button - draft node
  2. springMVC分析-2
  3. Mybatis动态sql的使用
  4. 马云:未来10年,人类将面临AI、IoT和区块链3大挑战!(视频+全文)
  5. 前端:JS/32/form对象(表单)(form对象的属性,方法和事件),受返回值影响的两个事件(onclick事件,onsubmit事件),获取表单的元素对象的三种方式,表单的提交和验证方法总结
  6. 简述什么是图灵机_什么是图灵机
  7. oracle 卸载asm,卸载oracleasm实验模拟
  8. R语言实战应用精讲50篇(三十一)-R语言实现决策树(附R语言代码)
  9. suse 11添加阿里源
  10. php企业微信获取userid,企业微信端项目登陆、获取用户信息流程等
  11. 2022春招——芯动科技FPGA岗技术面(一面心得)
  12. 论二级域名收集的各种姿势
  13. 2021年web前端基础面试题
  14. 十进制转换为二、十六进制的方法
  15. 知乎里怎么看个人简介_如何做一份优秀的简历?
  16. 必读论文|信息检索与推荐必读论文10篇
  17. python3.7-初学篇-07
  18. 互联网及其应用——第一章 互联网概述
  19. php字符值函数,php从指定ASCII值返回字符函数chr()
  20. tableau 如何选择tableau计算类型?基本计算 / LOD计算 / 表计算

热门文章

  1. Win10下Linux子系统使用串口(不是USB转串口)
  2. Android写log到文件模版
  3. linux如何写一个daemon程序
  4. 基于Surface的视频编解码与OpenGL ES渲染
  5. vscode之npm不是内部活外部命令
  6. 基于yolo的口罩识别(开源代码和数据集)
  7. 流式上传文件到服务器磁盘,在ASP.NET中流式传输大文件上传
  8. Android 谷歌巨头,国际巨头强势助攻,谷歌新系统开始提速,华为的对手不止一个...
  9. python怎么打印图片_Python打印图片
  10. 漂流瓶html5,微信又搞事情?漂流瓶下线,居然还有这些新功能...