Opencv249源码分析MSCR.docx
《Opencv249源码分析MSCR.docx》由会员分享,可在线阅读,更多相关《Opencv249源码分析MSCR.docx(22页珍藏版)》请在冰豆网上搜索。
![Opencv249源码分析MSCR.docx](https://file1.bdocx.com/fileroot1/2023-1/22/80fb90de-0273-48ac-a809-58c5f940f4a8/80fb90de-0273-48ac-a809-58c5f940f4a81.gif)
Opencv249源码分析MSCR
Opencv2.4.9源码分析——MSCR
前面我们介绍了MSER方法,但该方法不适用于对彩色图像的区域检测。
为此,Forssen于2007年提出了针对彩色图像的最大稳定极值区域的检测方法——MSCR(MaximallyStableColourRegions)。
MSCR的检测方法是基于凝聚聚类(AgglomerativeClustering)算法,它把图像中的每个像素作为对象,通过某种相似度准则,依次逐层的进行合并形成簇,即先合并相似度大的对象,再合并相似度小的对象,直到满足某种终止条件为止。
这一过程在MSCR中被称为进化过程,即逐步合并图像中的像素,从而形成斑点区域。
MSCR中所使用的相似度准则是卡方距离(Chi-squareddistance):
其中,x和y分别为彩色图像中的两个不同像素,下标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*,即:
3、进化处理,由各个进化步长的距离阈值得到稳定极值区域。
在opencv2.4.9中,MSCR和MSER共用一个类:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
classMSER:
publicCvMSERParams
{
public:
//defaultconstructor
MSER();
//constructorthatinitializesallthealgorithmparameters
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);
//runstheextractoronthespecifiedimage;returnstheMSERs,
//eachencodedasacontour(vector,seefindContours)
//theoptionalmaskmarkstheareawhereMSERsaresearchedfor
voidoperator()(constMat&image,vector>&msers,constMat&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]viewplaincopy在CODE上查看代码片派生到我的代码片
staticvoid
extractMSER_8UC3(CvMat*src,
CvMat*mask,
CvSeq*contours,
CvMemStorage*storage,
MSERParamsparams)
{
//在应用凝聚聚类算法时,把图像中的每个像素作为一个对象,即一个节点,因此该语句是定义并分配图像节点空间
MSCRNode*map=(MSCRNode*)cvAlloc(src->cols*src->rows*sizeof(map[0]));
//定义边缘列表的个数,即2×L×M–L-M
intNe=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计算图像每个像素颜色相似度的距离均值
doubleemean=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;
//theevolutionprocess
//步骤3,进化处理,在t∈[0…T]中循环,这里的i就是前面文章介绍的进化步长t
for(inti=0;i{
//下面的4条语句用于计算当前t下的dthr值,thres为dthr
//数组chitab为事先计算好的查询表
doublek=(double)i/(double)params.maxEvolution*(TABLE_SIZE-1);
intti=cvFloor(k);
doublereminder=k-ti;
doublethres=emean*(chitab3[ti]*(1-reminder)+chitab3[ti+1]*reminder);
//toprocessalltheedgesinthelistthatchi//处理所有颜色相似度小于阈值的像素
//edgeptrwhile(edgeptrchi{
//由当前像素的左侧像素找到该像素所在的簇的根节点,也就是找到代表该像素所在区域的像素
MSCRNode*lr=findMSCR(edgeptr->left);
//由当前像素的右侧像素找到该像素所在的簇的根节点,也就是找到代表该像素所在区域的像素
//需要注意的是,这里的左侧和右侧并不是真正意义的左侧和右侧,它们是由preprocessMSER_8UC3函数确定的
MSCRNode*rr=findMSCR(edgeptr->right);
//gettheregionroot(whoisresponsible)
//如果上面得到的两个根节点是一个节点,则不需要进行任何处理
//如果这两个根节点不是一个,则需要把它们所代表的两个区域进行合并
if(lr!
=rr)
{
//rankideatakefrom:
N-treeDisjoint-SetForestsforMaximallyStableExtremalRegions
//下面的if语句用于判断是用rr还是用lr来代表合并后的区域,并且最终通过交换来实现lr代表合并后的区域
//rank值大的根节点代表合并后的区域
if(rr->rank>lr->rank)
{
MSCRNode*tmp;
CV_SWAP(lr,rr,tmp);
}elseif(lr->rank==rr->rank){
//atthesamerank,wewillcomparethesize
//如果两个根节点的rank值相同,则区域面积大的代表合并后的区域
if(lr->size>rr->size)
{
MSCRNode*tmp;
CV_SWAP(lr,rr,tmp);
}
lr->rank++;
}
//定义rr所表示的区域的根节点为lr
rr->shortcut=lr;
//合并两个区域,合并后区域面积为两个区域面积之和
lr->size+=rr->size;
//joinrrtotheendoflistlr(lrisaendlessdouble-linkedlist)
//把rr加入lr列表中,组成一个循环双链接列表
lr->prev->next=rr;
lr->prev=rr->prev;
rr->prev->next=lr;
rr->prev=lr;
//areathresholdforcetoreinitialize
//利用公式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)
{
//公式4
lr->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)
{
//公式3
doubles=(double)(lr->size-lr->sizei)/(lr->dt-lr->di);
//当公式3中的s是最小值时
if(ss)
{
//skipthefirstoneandcheckstablity
//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//topruneareawithmarginlessthanminMargin
//公式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(inti=0;isize;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]viewplaincopy在CODE上查看代码片派生到我的代码片
//thepreprocesstogettheedgelistwithpropergaussianblur
staticintpreprocessMSER_8UC3(MSCRNode*node,//图像像素节点
MSCREdge*edge,//边缘列表
double*total,//求相似度均值时使用,这里是所有像素相似度之和
CvMat*src,//原始图像
CvMat*mask,//掩码矩阵
CvMat*dx,//水平梯度矩阵
CvMat*dy,//垂直梯度矩阵
intNe,//边缘列表元素的个数
intedgeBlurSize)//平滑处理的孔径尺寸大小
{
intsrccpt=src->step-src->cols*3;
uchar*srcptr=src->data.ptr;//图像当前像素指针
uchar*lastptr=src->data.ptr+3;//右侧像素指针
double*dxptr=dx->data.db;//水平梯度数据指针
//计算当前像素与其右侧像素之间的颜色相似度
for(inti=0;irows;i++)
{
//图像最右侧一列没有该相似度,因此jcols-1
for(intj=0;jcols-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;//垂直梯度数据指针
//计算当前像素与其下面一行像素之间的颜色相似度
//图像最下面一行没有该相似度,因此irows-1
for(inti=0;irows-1;i++)
{
for(intj=0;jcols;j++)
{
//保存到dy内
*dyptr=ChiSquaredDistance(srcptr,lastptr);
dyptr++;
srcptr+=3;
lastptr+=3;
}
srcptr+=srccpt;
lastptr+=srccpt;
}
//getdxanddyandblurit
//对颜色相似度值进行高斯平滑处理
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;
//assiandx,dytoproperedgelistandinitializemscrnode
//thenastycodereintendedtoavoidextraloops
/*下面的if语句是为边缘列表赋值,如果定义了掩码矩阵,则边缘列表不保存被掩码掉的像素的边缘信息,因此边缘列表的个数Ne需要重新计算并输出。
在这里我们以没有定义掩码矩阵为例进行讲解,两者的本质是一样的*/
if(mask)
{
Ne=0;
intmaskcpt=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(inti=1;icols-1;i++)
{
initMSCRN