------------------------------------------------------------------------------------------------------------------------
【图像算法】彩色图像分割专题七:基于分水岭的彩色分割
SkySeraph July 7th 2011 HQU
Email:zgzhaobo@gmail.com QQ:452728574
Latest Modified Date:July 7th 2011 HQU
------------------------------------------------------------------------------------------------------------------------
》原理
分水岭算法有好好几种实现算法,拓扑学,形态学,浸水模拟和降水模拟等,最常用的就是Soille和Vincent首先提出的模拟浸没的算法,该算法分割结果效果好,速度快,具有较强的实用性,其简要概括如下。其它更多细节见后文给出的参考资料,不再细述。
Vincent和Soille算法包括2个部分:第一个部分是排序;第二部分为泛洪。算法描述如下:
(1)将根据图像中每个像素的RGB值根据公式Gray=0.299R+0.587G+0.114B得到与之对应的灰度值。
(2)根据Sobel算子得到图像的梯度。(边缘像素的梯度为其邻域像素的梯度,梯度范围为0-255)
(3)对梯度进行从小到大的排序,相同的梯度为同一个梯度层级。
(4)处理第一个梯度层级所有的像素,如果其邻域已经被标识属于某一个区域,则将这个像素加入一个先进先出的队列。
(5)先进先出队列非空时,弹出第一个元素。扫描该像素的邻域像素,如果其邻域像素的梯度属于同一层(梯度相等),则根据邻域像素的标识来刷新该像素的标识。一直循环到队列为空。
(6)再次扫描当前梯度层级的像素,如果还有像素未被标识,说明它是一个新的极小区域,则当前区域的值(当前区域的值从0开始计数)加1后赋值给该为标识的像素。然后从该像素出发继续执行步骤5的泛洪直至没有新的极小区域。
(7)返回步骤4,处理下一个梯度层级的像素,直至所有梯度层级的像素都被处理。流程如下:
------------------------------------------------------------------------------------------------------------------------
》源码
核心代码(部分源码来源网络,修改并做了很详细的注释)
void MyWatershed::WatershedSegmentVincent(IplImage* img)
//分水岭分割:img为彩色
{
int x,y,i; //循环
IplImage* pSrc = NULL;
pSrc = cvCreateImage(cvGetSize(img),img->depth,img->nChannels);
cvCopyImage(img,pSrc);
int width = pSrc->width; //图像宽度
int height = pSrc->height; //图像高度
int depth = pSrc->depth; //图像位深(IPL_DEPTH_8U...)
int channels = pSrc->nChannels; //图像通道数(1、2、3、4)
int imgSize = pSrc->imageSize; //图像大小 imageSize = height*widthStep
int step = pSrc->widthStep/sizeof(uchar);
uchar* data = (uchar *)pSrc->imageData;
int imageLen = width*height;
/*
cvNamedWindow("src");
cvShowImage("src",pSrc);
*/
/*
//===================通过Sobel求取梯度图像====================//
// 彩色转灰度
IplImage* gray = cvCreateImage(cvGetSize(pSrc),pSrc->depth,1);
cvCvtColor(pSrc,gray,CV_BGR2GRAY);
cvNamedWindow("gray");
cvShowImage("gray",gray);
// Sobel
IplImage* sobel = cvCreateImage(cvGetSize(pSrc),IPL_DEPTH_16S,1); //IPL_DEPTH_16S: 以Sobel方式求完导数后会有负值,还有会大于255的值
cvSobel(gray,sobel,1,1,3);
IplImage *sobel8u=cvCreateImage(cvGetSize(sobel),IPL_DEPTH_8U,1);
cvConvertScaleAbs(sobel,sobel8u,1,0); //转为8位无符号
// 临时显示sobel
cvNamedWindow("sobel8u");
cvShowImage("sobel8u",sobel8u); */
//===================① 获取原彩色图像的梯度值(先转灰度)===================//
int* deltar = new INT[imageLen]; //梯度模数组
//FLOAT* deltasita = new FLOAT[imgSize];//梯度角度数组;
// 彩色转灰度
IplImage* gray = cvCreateImage(cvGetSize(pSrc),pSrc->depth,1);
cvCvtColor(pSrc,gray,CV_BGR2GRAY);
// 求梯度
GetGra(gray,deltar);
//GetGra2(sobel8u,deltar);
//===================② 梯度值排序===================//
//以下统计各梯度频率
MyImageGraPtWatershed* graposarr = new MyImageGraPtWatershed[imageLen];
INT* gradientfre = new INT[256];//图像中各点梯度值频率;
INT* gradientadd = new INT[257];//各梯度起终位置;
memset( gradientfre, 0, 256*sizeof(INT));
memset( gradientadd, 0, 257*sizeof(INT));
LONG xstart, imagepos, deltapos;
xstart = imagepos = deltapos = 0;
for (y=0; y<height; y++)
{
for ( x=0; x<width; x++)
{
xstart = y*width;
deltapos = xstart + x;
if (deltar[deltapos]>255)
{
deltar[deltapos] = 255;
}
INT tempi = (INT)(deltar[deltapos]);
gradientfre[tempi] ++;//灰度值频率;
}
}
//统计各梯度的累加概率;
INT added = 0;
gradientadd[0] = 0;//第一个起始位置为0;
for (INT ii=1; ii<256; ii++)
{
added += gradientfre[ii-1];
gradientadd[ii] = added;
}
gradientadd[256] = imageLen;//最后位置;
memset( gradientfre, 0, 256*sizeof(INT)); //清零,下面用作某梯度内的指针;
//自左上至右下sorting....
for (y=0; y<height; y++)
{
xstart = y*width;
for ( x=0; x<width; x++)
{
deltapos = xstart + x;
INT tempi = (INT)(deltar[deltapos]);//当前点的梯度值,由于前面的步骤,最大只能为255;
//根据梯度值决定在排序数组中的位置;
INT tempos = gradientadd[tempi] + gradientfre[tempi];
gradientfre[tempi] ++;//梯度内指针后移;
graposarr[tempos].gradient = tempi; //根据当前点的梯度将该点信息放后排序后数组中的合适位置中去;
graposarr[tempos].x = x;
graposarr[tempos].y = y;
}
}
//===================③ 泛洪得每个像素所属区域标记flag=================//
INT rgnumber = 0; //分割后的区域数
INT* flag = new INT[imageLen]; //各点标识数组
Flood(graposarr,gradientadd,0,255,flag,rgnumber,width,height);
//===================④ 由flag标记求各个区域的LUV均值=================//
//以下准备计算各个区域的LUV均值;
//分割后各个区的一些统计信息,第一个元素不用,图像中各点所属区域的信息存放在flag数组中
MyRgnInfoWatershed* rginfoarr = new MyRgnInfoWatershed[rgnumber+1];
//清空该数组
for (i=0; i<rgnumber+1; i++) //LUV
{
rginfoarr[i].isflag = FALSE;
rginfoarr[i].ptcount = 0;
rginfoarr[i].l = 0;
rginfoarr[i].u = 0;
rginfoarr[i].v = 0;
}
//以下保存LUV数据;
if ( luvData != NULL )
{
delete [] luvData;
luvData = NULL;
}
luvData = new MyLUV[width*height];
pMyColorSpace.RgbtoLuvPcm(data, width, height, luvData);
for (y=0; y<height; y++)
{
xstart = y*width;
for ( x=0; x<width; x++)
{
INT pos = xstart + x;
INT rgid = flag[pos];//当前位置点所属区域在区统计信息数组中的位置
//以下将该点的信息加到其所属区信息中去
rginfoarr[rgid].ptcount ++;
rginfoarr[rgid].l += luvData[pos].l;
rginfoarr[rgid].u += luvData[pos].u;
rginfoarr[rgid].v += luvData[pos].v;
}
}
//求出各个区的LUV均值;
for (i=0; i<=rgnumber; i++)
{
rginfoarr[i].l = (FLOAT) ( rginfoarr[i].l / rginfoarr[i].ptcount );
rginfoarr[i].u = (FLOAT) ( rginfoarr[i].u / rginfoarr[i].ptcount );
rginfoarr[i].v = (FLOAT) ( rginfoarr[i].v / rginfoarr[i].ptcount );
}
//===================⑤ 区域融合 ===================//
//根据各区luv均值(rginfoarr)和各区之间邻接关系(用flag计算)进行区域融合
INT* mergearr = new INT[rgnumber+1];
memset( mergearr, -1, sizeof(INT)*(rgnumber+1) );
INT mergednum = 0;
MergeRgs(rginfoarr, rgnumber, flag, width, height, mergearr, mergednum);
//MergeRgs(rginfoarr, rgnumber, flag, width, height);
//===================⑥ 确定融合后各个像素所属区域===================//
//确定合并后各像素点所属区域;
for (y=0; y<(height); y++)
{
xstart = y*width;
for ( x=0; x<(width); x++)
{
INT pos = xstart + x;
INT rgid = flag[pos];//该点所属区域;
flag[pos] = FindMergedRgn(rgid, mergearr);
}
}
delete [] mergearr;
mergearr = NULL;
//===================⑧ 绘制区域轮廓,保存结果===================//
//=================== ===================//
//以下根据标识数组查找边界点(不管四边点以减小复杂度)
IplImage* dstContour = cvCreateImage(cvGetSize(pSrc),IPL_DEPTH_8U,3);
for (y=1; y<(height-1); y++)
{
xstart = y*width;
for ( x=1; x<(width-1); x++)
{
INT pos = xstart + x;
INT imagepos = pos * 3;
INT left = pos - 1;
INT up = pos - width;
INT right = pos +1;
INT down = pos + width;
if ( ( flag[right]!=flag[pos] ) || ( flag[down]!=flag[pos] ) )
//if ( flag[pos]==0 )
{
((uchar *)(dstContour->imageData+y*dstContour->widthStep))[x*dstContour->nChannels+0] = 0;
((uchar *)(dstContour->imageData+y*dstContour->widthStep))[x*dstContour->nChannels+1] = 0;
((uchar *)(dstContour->imageData+y*dstContour->widthStep))[x*dstContour->nChannels+2] = 250;
//imageData[imagepos] = 0;
//imageData[imagepos+1] = 0;
//imageData[imagepos+2] = 250;
}
}
}
cvNamedWindow("dstContour",1);
cvShowImage("dstContour",dstContour);
//=================== ===================//
// 在源图像上绘制轮廓
CvMemStorage* storage= cvCreateMemStorage(0);
CvSeq* contour= 0;//可动态增长元素序列
IplImage* dstContourGray= cvCreateImage(cvGetSize(dstContour),8,1);
cvCvtColor(dstContour,dstContourGray,CV_BGR2GRAY);
cvNamedWindow("dstContourGray",1);
cvShowImage("dstContourGray",dstContourGray);
cvFindContours( //函数cvFindContours从二值图像中检索轮廓,并返回检测到的轮廓的个数
dstContourGray,//二值化图像
storage, //轮廓存储容器
&contour, //指向第一个轮廓输出的指针
sizeof(CvContour), //序列头大小
CV_RETR_CCOMP, //内外轮廓都检测; CV_RETR_EXTERNAL:只检索最外面的轮廓
CV_CHAIN_APPROX_SIMPLE//压缩水平垂直和对角分割,即函数只保留末端的象素点 CV_CHAIN_CODE//CV_CHAIN_APPROX_NONE
);
IplImage* dst = cvCreateImage(cvGetSize(pSrc),IPL_DEPTH_8U,3);
cvCopyImage(pSrc,dst);
for (;contour!= 0;contour= contour->h_next)
{
CvScalar color= CV_RGB(rand()&255,rand()&255,rand()&255);
cvDrawContours( //在图像中绘制轮廓
dst,
contour,//指向初始轮廓的指针
color, //外层轮廓的颜色
color, //内层轮廓的颜色
-1, //绘制轮廓的最大等级(
//0:当前;
//1:相同级别下的轮廓;
//2:绘制同级与下一级轮廓;
//负数:不会绘制当前轮廓之后的轮廓,但会绘制子轮廓,到(abs(该参数)-1)为止)
1, //轮廓线条粗细
8 //线条的类型
);
}
cvNamedWindow("Contours",1);
cvShowImage("Contours",dst);
cvSaveImage(".\\result.jpg",dst);
cvReleaseImage(&dstContour);
cvReleaseImage(&dst);
cvReleaseImage(&dstContourGray);
cvReleaseMemStorage(&storage);
cvWaitKey(0);
cvDestroyAllWindows();
// 释放资源
delete [] gradientadd; gradientadd = NULL;//大小256
delete [] gradientfre; gradientfre = NULL;//大小256
delete [] deltar; deltar = NULL;//大小imageLen
delete [] graposarr; graposarr = NULL;//大小imageLen
cvReleaseImage(&pSrc);
cvReleaseImage(&gray);
}
函数模块
void MyWatershed::GetGra(IplImage* image, INT* deltar)
//通过sobel获取灰度图像image的梯度值存数组deltar
{
// 定义
IplImage* pSrc = NULL;
pSrc = cvCreateImage(cvGetSize(image),image->depth,image->nChannels);
cvCopyImage(image,pSrc);
int width = pSrc->width;
int height = pSrc->height;
/* 测试参数
CString mPath;
//m_Path.GetWindowText(mPath);
mPath.Format(_T("width:%d, height: %d"),width,height);
AfxMessageBox(mPath);
*/
static int nWeight[2][3][3];
nWeight[0][0][0]=-1;
nWeight[0][0][1]=0;
nWeight[0][0][2]=1;
nWeight[0][1][0]=-2;
nWeight[0][1][1]=0;
nWeight[0][1][2]=2;
nWeight[0][2][0]=-1;
nWeight[0][2][1]=0;
nWeight[0][2][2]=1;
nWeight[1][0][0]=1;
nWeight[1][0][1]=2;
nWeight[1][0][2]=1;
nWeight[1][1][0]=0;
nWeight[1][1][1]=0;
nWeight[1][1][2]=0;
nWeight[1][2][0]=-1;
nWeight[1][2][1]=-2;
nWeight[1][2][2]=-1;
int nTmp[3][3];
FLOAT dGray;
FLOAT dGradOne;
FLOAT dGradTwo;
int x,y;
int* gra=new int[height*width];
int gr;
for (y=0;y<height;y++)
{
for (x=0;x<width;x++)
{
int image_pos=width*y+x;
/*
int memory_pos1=3*width*y+3*x;
int memory_pos2=3*width*y+3*x+1;
int memory_pos3=3*width*y+3*x+2;
FLOAT b=image[memory_pos1];
FLOAT g=image[memory_pos2];
FLOAT r=image[memory_pos3];
int gr=(int)(0.299*r+0.587*g+0.114*b);*/
gr = ((uchar *)(pSrc->imageData))[y*pSrc->widthStep+x];
gra[image_pos]=gr;
}
}
//暂不计算边缘点
for (y=1;y<height-1;y++)
{
for (x=1;x<width-1;x++)
{
dGray=0;
dGradOne=0;
dGradTwo=0;
nTmp[0][0]=gra[(y-1)*width+x-1];
nTmp[0][1]=gra[(y-1)*width+x];
nTmp[0][2]=gra[(y-1)*width+x+1];
nTmp[1][0]=gra[y*width+x-1];
nTmp[1][1]=gra[y*width+x];
nTmp[1][2]=gra[y*width+x+1];
nTmp[2][0]=gra[(y+1)*width+x-1];
nTmp[2][1]=gra[(y+1)*width+x];
nTmp[2][2]=gra[(y+1)*width+x+1];
for (int yy=0;yy<3;yy++)
{
for (int xx=0;xx<3;xx++)
{
dGradOne+=nTmp[yy][xx]*nWeight[0][yy][xx];
dGradTwo+=nTmp[yy][xx]*nWeight[1][yy][xx];
}
}
dGray=dGradOne*dGradOne+dGradTwo*dGradTwo;
dGray=sqrtf(dGray);
deltar[y*width+x]=(int)dGray;
}
}
//边缘赋为其内侧点的值
for (y=0; y<height; y++)
{
INT x1 = 0;
INT pos1 = y*width + x1;
deltar[pos1] = deltar[pos1+1];
INT x2 = width-1;
INT pos2 = y*width + x2;
deltar[pos2] = deltar[pos2-1];
}
for ( x=0; x<width; x++)
{
INT y1 = 0;
INT pos1 = x;
INT inner = x + width;//下一行;
deltar[pos1] = deltar[inner];
INT y2 = height-1;
INT pos2 = y2*width + x;
inner = pos2 - width;//上一行;
deltar[pos2] = deltar[inner];
}
delete [] gra;
gra=NULL;
}
//////////////////////////////////////////////////////////////////////////
// Luc Vincent and Pierre Soille的分水岭分割flood步骤的实现代码,
// 修改自相应伪代码, 伪代码来自作者论文《Watersheds in Digital Spaces:
// An Efficient Algorithm Based on Immersion Simulations》
// IEEE TRANSACTIONS ON PATTERN ANALYSIS AND MACHINE INTELLIGENCE.
// VOL.13, NO.6, JUNE 1991;
// by dzj, 2004.06.28
// MyImageGraPt* imiarr - 输入的排序后数组
// INT* graddarr -------- 输入的各梯度数组,由此直接存取各H像素点
// INT minh,INT maxh --- 最小最大梯度
// INT* flagarr --------- 输出标记数组
// 注意:目前不设分水岭标记,只设每个像素所属区域;
//////////////////////////////////////////////////////////////////////////
void MyWatershed::Flood(MyImageGraPtWatershed* imiarr, INT* graddarr,
INT minh, INT maxh,
INT* flagarr, INT& outrgnumber,
INT width,INT height)
{
int imageWidth=width;
int imageHeight=height;
const INT INIT = -2;//initial value of lable image
const INT MASK = -1;//initial value at each level
const INT WATERSHED = 0; //label of the watershed pixels
INT h = 0;
INT imagelen = imageWidth * imageHeight;//图像中像素的个数
for (INT i=0; i<imagelen; i++)
{
flagarr[i] = INIT;
}
//memset(flagarr, INIT, sizeof(INT)*imagelen);
INT* imd = new INT[imagelen]; //距离数组,直接存取;
for (i=0; i<imagelen; i++)
{
imd[i] = 0;
}
//memset(imd, 0, sizeof(INT)*imagelen);
std::queue <int> myqueue;
INT curlabel = 0;//各盆地标记;
for (h=minh; h<=maxh; h++)
{
INT stpos = graddarr[h];
INT edpos = graddarr[h+1];
for (INT ini=stpos; ini<edpos; ini++)
{
INT x = imiarr[ini].x;
INT y = imiarr[ini].y;
INT ipos = y*imageWidth + x;
flagarr[ipos] = MASK;
//以下检查该点邻域是否已标记属于某区或分水岭,若是,则将该点加入fifo;
INT left = ipos - 1;
if (x-1>=0)
{
if (flagarr[left]>=0)
{
imd[ipos] = 1;
myqueue.push(ipos);//点位置压入fifo;
continue;
}
}
INT right = ipos + 1;
if (x+1<imageWidth)
{
if (flagarr[right]>=0)
{
imd[ipos] = 1;
myqueue.push(ipos);//点位置压入fifo;
continue;
}
}
INT up = ipos - imageWidth;
if (y-1>=0)
{
if (flagarr[up]>=0)
{
imd[ipos] = 1;
myqueue.push(ipos);//点位置压入fifo;
continue;
}
}
INT down = ipos + imageWidth;
if (y+1<imageHeight)
{
if (flagarr[down]>=0)
{
imd[ipos] = 1;
myqueue.push(ipos);//点位置压入fifo;
continue;
}
}
}
//以下根据先进先出队列扩展现有盆地;
INT curdist = 1; myqueue.push(-99);//特殊标记;
while (TRUE)
{
INT p = myqueue.front();
myqueue.pop();
if (p == -99)
{
if ( myqueue.empty() )
{
break;
}else
{
myqueue.push(-99);
curdist = curdist + 1;
p = myqueue.front();
myqueue.pop();
}
}
//以下找p的邻域;
INT y = (INT) (p/imageWidth);
INT x = p - y*imageWidth;
INT left = p - 1;
if (x-1>=0)
{
if ( ( (imd[left]<curdist) && flagarr[left]>0)
|| (flagarr[left]==0) )
{
if ( flagarr[left]>0 )
{
//ppei属于某区域(不是分水岭);
if ( (flagarr[p]==MASK)
|| (flagarr[p]==WATERSHED) )
{
//将其设为邻点所属区域;
flagarr[p] = flagarr[left];
}else if (flagarr[p]!=flagarr[left])
{
//原来赋的区与现在赋的区不同,设为分水岭;
//flagarr[p] = WATERSHED;
}
}else if (flagarr[p]==MASK)//ppei为分岭;
{
flagarr[p] = WATERSHED;
}
}else if ( (flagarr[left]==MASK) && (imd[left]==0) )
//ppei中已MASK的点,但尚未标记(即不属某区也不是分水岭);
{
imd[left] = curdist + 1; myqueue.push(left);
}
}
INT right = p + 1;
if (x+1<imageWidth)
{
if ( ( (imd[right]<curdist) && flagarr[right]>0)
|| (flagarr[right]==0) )
{
if ( flagarr[right]>0 )
{
//ppei属于某区域(不是分水岭);
if ( (flagarr[p]==MASK)
|| (flagarr[p]==WATERSHED) )
{
//将其设为邻点所属区域;
flagarr[p] = flagarr[right];
}else if (flagarr[p]!=flagarr[right])
{
//原来赋的区与现在赋的区不同,设为分水岭;
//flagarr[p] = WATERSHED;
}
}else if (flagarr[p]==MASK)//ppei为分岭;
{
flagarr[p] = WATERSHED;
}
}else if ( (flagarr[right]==MASK) && (imd[right]==0) )
//ppei中已MASK的点,但尚未标记(即不属某区也不是分水岭);
{
imd[right] = curdist + 1; myqueue.push(right);
}
}
INT up = p - imageWidth;
if (y-1>=0)
{
if ( ( (imd[up]<curdist) && flagarr[up]>0)
|| (flagarr[up]==0) )
{
if ( flagarr[up]>0 )
{
//ppei属于某区域(不是分水岭);
if ( (flagarr[p]==MASK)
|| (flagarr[p]==WATERSHED) )
{
//将其设为邻点所属区域;
flagarr[p] = flagarr[up];
}else if (flagarr[p]!=flagarr[up])
{
//原来赋的区与现在赋的区不同,设为分水岭;
//flagarr[p] = WATERSHED;
}
}else if (flagarr[p]==MASK)//ppei为分岭;
{
flagarr[p] = WATERSHED;
}
}else if ( (flagarr[up]==MASK) && (imd[up]==0) )
//ppei中已MASK的点,但尚未标记(即不属某区也不是分水岭);
{
imd[up] = curdist + 1; myqueue.push(up);
}
}
INT down = p + imageWidth;
if (y+1<imageHeight)
{
if ( ( (imd[down]<curdist) && flagarr[down]>0)
|| (flagarr[down]==0) )
{
if ( flagarr[down]>0 )
{
//ppei属于某区域(不是分水岭);
if ( (flagarr[p]==MASK)
|| (flagarr[p]==WATERSHED) )
{
//将其设为邻点所属区域;
flagarr[p] = flagarr[down];
}else if (flagarr[p]!=flagarr[down])
{
//原来赋的区与现在赋的区不同,设为分水岭;
//flagarr[p] = WATERSHED;
}
}else if (flagarr[p]==MASK)//ppei为分岭;
{
flagarr[p] = WATERSHED;
}
}else if ( (flagarr[down]==MASK) && (imd[down]==0) )
//ppei中已MASK的点,但尚未标记(既不属某区也不是分水岭);
{
imd[down] = curdist + 1; myqueue.push(down);
}
}
}//以上现有盆地的扩展;
//以下处理新发现的盆地;
for (ini=stpos; ini<edpos; ini++)
{
INT x = imiarr[ini].x;
INT y = imiarr[ini].y;
INT ipos = y*imageWidth + x;
imd[ipos] = 0;//重置所有距离
if (flagarr[ipos]==MASK)
{
//经过前述扩展后该点仍为MASK,则该点必为新盆地的一个起始点;
curlabel = curlabel + 1;
myqueue.push(ipos);
flagarr[ipos] = curlabel;
while ( myqueue.empty()==FALSE )
{
INT ppei = myqueue.front();
myqueue.pop();
INT ppeiy = (INT) (ppei/imageWidth);
INT ppeix = ppei - ppeiy*imageWidth;
INT ppeileft = ppei - 1;
if ( (ppeix-1>=0) && (flagarr[ppeileft]==MASK) )
{
myqueue.push(ppeileft);//点位置压入fifo;
flagarr[ppeileft] = curlabel;
}
INT ppeiright = ppei + 1;
if ( (ppeix+1<imageWidth) && (flagarr[ppeiright]==MASK) )
{
myqueue.push(ppeiright);//点位置压入fifo;
flagarr[ppeiright] = curlabel;
}
INT ppeiup = ppei - imageWidth;
if ( (ppeiy-1>=0) && (flagarr[ppeiup]==MASK) )
{
myqueue.push(ppeiup);//点位置压入fifo;
flagarr[ppeiup] = curlabel;
}
INT ppeidown = ppei + imageWidth;
if ( (ppeiy+1<imageHeight) && (flagarr[ppeidown]==MASK) )
{
myqueue.push(ppeidown);//点位置压入fifo;
flagarr[ppeidown] = curlabel;
}
}
}
}//以上处理新发现的盆地;
}
outrgnumber = curlabel;
delete [] imd;
imd = NULL;
/*
const INT INIT = -2;
const INT MASK = -1;
const INT WATERSHED = 0;
INT imagelen=width*height;
INT *imd=new INT[imagelen];
memset(imd, 0, sizeof(INT)*imagelen);
std::queue <int> myqueue;
INT curlabe=0;
for (int i=minh;i<=maxh;i++)
{
INT stpos=graddarr[i];
INT edpos=graddarr[i+1];
INT x,y;
static int disX[]={1,0,-1,0};
static int disY[]={0,1,0,-1};
for (int j=stpos;j<edpos;j++)
{
x=imiarr[j].x;
y=imiarr[j].y;
int pos=y*width+x;
flagarr[pos]=MASK;
for (int ii=0;ii<4;ii++)
{
int nepos=(y+disY[ii])*width+x+disX[ii];
if (((x+disX[ii])>=0)&&((x+disX[ii])<width)&&
((y+disY[ii])>=0)&&((y+disY[ii])<height)&&(flagarr[nepos]>0||flagarr[nepos]==0))
{
imd[pos]=1;
myqueue.push(pos);
break;
}
}
}
int curdist=1;//距离标志,个人感觉标志着是否被处理了
myqueue.push(-99);//特殊标记
while (TRUE)
{
int siz=myqueue.size();
int p=myqueue.front();
myqueue.pop();
if (p==-99)
{
if (myqueue.empty()==TRUE)
{
break;
}
else
{
myqueue.push(-99);
curdist+=1;
p=myqueue.front();
myqueue.pop();
}
}
//搜索p的临点
for(int ii=0;ii<4;ii++)
{
y=(INT)(p/width);
x=imagelen-y*width;
if (((x+disX[ii])>0||((x+disX[ii])==0))&&((x+disX[ii])<width)&&
((y+disY[ii])>0||((y+disY[ii])==0))&&((y+disY[ii])<height))
{
int npos=(y+disY[ii])*width+x+disX[ii];
if (imd[npos]<curdist&&(flagarr[npos]>0||flagarr[npos]==0))
{
if (flagarr[npos]>0)
{
if (flagarr[p]==MASK||flagarr[p]==WATERSHED)
{
flagarr[p]=flagarr[npos];
}
else if (flagarr[p]!=flagarr[npos])
{
flagarr[p]=WATERSHED;
}
}
else if (flagarr[p]==MASK)
{
flagarr[p]=WATERSHED;
}
}
else if (flagarr[npos]==MASK&&imd[npos]==0)
{
imd[npos]=curdist+1;
myqueue.push(npos);
}
}
}
}//while
//搜索临点的for
for (int jj=stpos;jj<edpos;jj++)
{
x=imiarr[jj].x;
y=imiarr[jj].y;
INT ipos = y*width + x;
imd[ipos] = 0;//重置所有距离
if (flagarr[ipos]==MASK)
{
//经过前述扩展后该点仍为MASK,则该点必为新盆地的一个起始点;
curlabe = curlabe + 1;
myqueue.push(ipos);
flagarr[ipos] = curlabe;
while ( myqueue.empty()==FALSE )
{
INT ppei = myqueue.front();
myqueue.pop();
int s=myqueue.size();
INT ppeiy = (INT) (ppei/width);
INT ppeix = ppei - ppeiy*width;
INT ppeileft = ppei - 1;
if ( (ppeix-1>=0) && (flagarr[ppeileft]==MASK) )
{
myqueue.push(ppeileft);//点位置压入fifo;
flagarr[ppeileft] = curlabe;
}
INT ppeiright = ppei + 1;
if ( (ppeix+1<width) && (flagarr[ppeiright]==MASK) )
{
myqueue.push(ppeiright);//点位置压入fifo;
flagarr[ppeiright] = curlabe;
}
INT ppeiup = ppei - width;
if ( (ppeiy-1>=0) && (flagarr[ppeiup]==MASK) )
{
myqueue.push(ppeiup);//点位置压入fifo;
flagarr[ppeiup] = curlabe;
}
INT ppeidown = ppei + width;
if ( (ppeiy+1<height) && (flagarr[ppeidown]==MASK) )
{
myqueue.push(ppeidown);//点位置压入fifo;
flagarr[ppeidown] = curlabe;
}
}
}
}//搜索临点的for
}//最后的一个for
outrgnumber=curlabe;
delete [] imd;
imd=NULL;
*/
}
#define NearMeasureBias 200.0//判定区域颜色相似的阈值;
void MyWatershed::MergeRgs(MyRgnInfoWatershed* rginfoarr, INT rgnumber, INT* flag, INT width, INT height, INT* outmerge, INT& rgnum)
//合并相似区域;
{
//////////////////////////////////////////////////////////////////////////
//1、建立各区的邻域数组;
//2、依次扫描各区域,寻找极小区域;
//3、对每个极小区(A),在相邻区中找到最相似者;
//4、与相似区(B)合并(各种信息刷新),在极小区(A)的邻域中
// 删除相似区(B),在邻域数组中删除相似区(B)对应的项,将
// 相似区(B)的相邻区s加到极小区(A)的邻域中去;
//5、记录合并信息,设一数组专门存放该信息,该数组的第A个元素值设为B;
//6、判断是否仍为极小区,若是则返回3;
//7、是否所有区域都已处理完毕,若非则返回2;
//
// 由于各区的相邻区不会太多,因此采用邻接数组作为存储结构;
//////////////////////////////////////////////////////////////////////////
CString* neiarr = new CString[rgnumber+1];//第一个不用;
INT* mergearr = outmerge;//记录合并情况数组;
//建立邻域数组;
for (INT y=0; y<height; y++)
{
INT lstart = y * width;
for (INT x=0; x<width; x++)
{
INT pos = lstart + x;
INT left=-1, right=-1, up=-1, down=-1;
pMyMath.GetNeiInt(x, y, pos, width, height, left, right, up, down);//找pos的四个邻域;
//确定并刷新邻域区信息;
INT curid = flag[pos];
AddNeiOfCur(curid, left, right, up, down, flag, neiarr);
}
}//建立邻域数组;
//区域信息数组中的有效信息从1开始,第i个位置存放第i个区域的信息;
for (INT rgi=1; rgi<=rgnumber; rgi++)
{
//扫描所有区域,找极小区;
LONG allpoints = width * height;
LONG nmin = (LONG) (allpoints / 400);
INT curid = rgi;
//rginfoarr[rgi].isflag初始为FALSE,在被合并到其它区后改为TRUE;
while ( ( (rginfoarr[rgi].ptcount)<nmin )
&& !rginfoarr[rgi].isflag )
{
//该区为极小区,遍历所有相邻区,找最接近者;
CString neistr = neiarr[curid];
INT nearid = FindNearestNei(curid, neistr, rginfoarr, mergearr);
//合并curid与nearid;
MergeTwoRgn(curid, nearid, neiarr, rginfoarr, mergearr);
}
}
//以下再合并相似区域,(无论大小),如果不需要,直接将整个循环注释掉就行了;
INT countjjj = 0;
//区域信息数组中的有效信息从1开始,第i个位置存放第i个区域的信息;
for (INT ii=1; ii<=rgnumber; ii++)
{
if (!rginfoarr[ii].isflag)
{
INT curid = ii;
MergeNearest(curid, rginfoarr, neiarr, mergearr);
}
}
INT counttemp = 0;
for (INT i=0; i<rgnumber; i++)
{
if (!rginfoarr[i].isflag)
{
counttemp ++;
}
}
rgnum = counttemp;
delete [] neiarr;
neiarr = NULL;
}
void MyWatershed::MergeNearest(INT curid, MyRgnInfoWatershed* rginfoarr, CString* neiarr, INT* mergearr)
//合并相似区域;
{
//依次处理各个邻域,若相似,则合并;
//CString neistr = neiarr[curid];
FLOAT cl, cu, cv;
cl = rginfoarr[curid].l;//当前区的LUV值;
cu = rginfoarr[curid].u;
cv = rginfoarr[curid].v;
BOOL loopmerged = TRUE;//一次循环中是否有合并操作发生,若无,则退出循环;
while (loopmerged)
{
loopmerged = FALSE;
CString tempstr = neiarr[curid];//用于本函数内部处理;
while (tempstr.GetLength()>0)
{
INT pos = tempstr.Find(" ");
ASSERT(pos>=0);
CString idstr = tempstr.Left(pos);
tempstr.Delete(0, pos+1);
INT idint = (INT) strtol(idstr, NULL, 10);
//判断该区是否已被合并,若是,则一直找到该区当前的区号;
idint = FindMergedRgn(idint, mergearr);
if (idint==curid)
{
continue;//这个邻区已被合并到当前区,跳过;
}
FLOAT tl, tu, tv;
tl = rginfoarr[idint].l;//当前处理的邻区的LUV值;
tu = rginfoarr[idint].u;
tv = rginfoarr[idint].v;
DOUBLE tempdis = pow(tl-cl, 2)
+ pow(tu-cu, 2) + pow(tv-cv, 2);
if (tempdis<NearMeasureBias)
{
MergeTwoRgn(curid, idint, neiarr, rginfoarr, mergearr);
cl = rginfoarr[curid].l;//当前区的LUV值刷新;
cu = rginfoarr[curid].u;
cv = rginfoarr[curid].v;
loopmerged = TRUE;
}
}
}
}
void MyWatershed::MergeTwoRgn(INT curid, INT nearid
, CString* neiarr, MyRgnInfoWatershed* rginfoarr, INT* mergearr)
//将nearid合并到curid中去,更新合并后的区信息,并记录该合并;
{
//将区信息中nearid对应项的标记设为已被合并;
rginfoarr[nearid].isflag = TRUE;
//更新合并后的LUV信息;
LONG ptincur = rginfoarr[curid].ptcount;
LONG ptinnear = rginfoarr[nearid].ptcount;
DOUBLE curpercent = (FLOAT)ptincur / (FLOAT)(ptincur+ptinnear);
rginfoarr[curid].ptcount = ptincur + ptinnear;
rginfoarr[curid].l = (FLOAT) ( curpercent * rginfoarr[curid].l
+ (1-curpercent) * rginfoarr[nearid].l );
rginfoarr[curid].u = (FLOAT) ( curpercent * rginfoarr[curid].u
+ (1-curpercent) * rginfoarr[nearid].u );
rginfoarr[curid].v = (FLOAT) ( curpercent * rginfoarr[curid].v
+ (1-curpercent) * rginfoarr[nearid].v );
//将nearid的邻域加到curid的邻域中去;
AddBNeiToANei(curid, nearid, neiarr, mergearr);
//记录该合并;
mergearr[nearid] = curid;
}
void MyWatershed::AddBNeiToANei(INT curid, INT nearid, CString* neiarr, INT* mergearr)
//将nearid的邻域加到curid的邻域中去;
{
//先从curid的邻区中把nearid删去;
/*
CString tempstr;
tempstr.Format("%d ", nearid);
INT temppos = neiarr[curid].Find(tempstr, 0);
while (temppos>0 && neiarr[curid].GetAt(temppos-1)!=' ')
{
temppos = neiarr[curid].Find(tempstr, temppos+1);
}
if (temppos>=0)
{
//否则邻近区为合并过来的区,忽略;
neiarr[curid].Delete(temppos, tempstr.GetLength());
}
*/
//将nearid的邻区依次加到curid的邻区中去;
CString neistr = neiarr[nearid];
CString curstr = neiarr[curid];
//一般说来,极小区的邻域应该较少,因此,为着提高合并速度,将
//curstr加到neistr中去,然后将结果赋给neiarr[curid];
while ( curstr.GetLength()>0 )
{
INT pos = curstr.Find(" ");
ASSERT(pos>=0);
CString idstr = curstr.Left(pos);
curstr.Delete(0, pos+1);
INT idint = (INT) strtol(idstr, NULL, 10);
idint = FindMergedRgn(idint, mergearr);
idstr += " ";
if ( (idint == curid) || (idint == nearid) )
{
continue;//本区不与本区相邻;
}else
{
if ( neistr.Find(idstr, 0) >= 0 )
{
continue;
}else
{
neistr += idstr;//加到邻区中去;
}
}
}
neiarr[curid] = neistr;
/*
CString toaddneis = neiarr[nearid];
while (toaddneis.GetLength()>0)
{
INT pos = toaddneis.Find(" ");
ASSERT(pos>=0);
CString idstr = toaddneis.Left(pos);
toaddneis.Delete(0, pos+1);
INT idint = (INT) strtol(idstr, NULL, 10);
idint = FindMergedRgn(idint, mergearr);
idstr += " ";
if ( (idint == curid) || (idint == nearid) )
{
continue;//本区不与本区相邻;
}else
{
if ( neiarr[curid].Find(idstr, 0) >= 0 )
{
continue;
}else
{
neiarr[curid] += idstr;//加到邻区中去;
}
}
}
*/
}
INT MyWatershed::FindNearestNei(INT curid, CString neistr, MyRgnInfoWatershed* rginfoarr, INT* mergearr)
//寻找neistr中与curid最接近的区,返回该区id号;
{
INT outid = -1;
DOUBLE mindis = 999999;
FLOAT cl, cu, cv;
cl = rginfoarr[curid].l;//当前区的LUV值;
cu = rginfoarr[curid].u;
cv = rginfoarr[curid].v;
CString tempstr = neistr;//用于本函数内部处理;
while (tempstr.GetLength()>0)
{
INT pos = tempstr.Find(" ");
ASSERT(pos>=0);
CString idstr = tempstr.Left(pos);
tempstr.Delete(0, pos+1);
INT idint = (INT) strtol(idstr, NULL, 10);
//判断该区是否已被合并,若是,则一直找到该区当前的区号;
idint = FindMergedRgn(idint, mergearr);
if (idint==curid)
{
continue;//这个邻区已被合并到当前区,跳过;
}
FLOAT tl, tu, tv;
tl = rginfoarr[idint].l;//当前处理的邻区的LUV值;
tu = rginfoarr[idint].u;
tv = rginfoarr[idint].v;
DOUBLE tempdis = pow(tl-cl, 2)
+ pow(tu-cu, 2) + pow(tv-cv, 2);
if (tempdis<mindis)
{
mindis = tempdis;//最大距离和对应的相邻区ID;
outid = idint;
}
}
return outid;
}
INT MyWatershed::FindMergedRgnMaxbias(INT idint, INT* mergearr, INT bias)
//大阈值终止查找合并区,用于coarse watershed,
//调用者必须保证idint有效,即:mergearr[idint]>0;
//以及mergearr有效,即:mergearr[idint]<idint;
{
INT outid = idint;
while ( mergearr[outid]<bias )
{
outid = mergearr[outid];
}
return mergearr[outid];
}
INT MyWatershed::FindMergedRgn(INT idint, INT* mergearr)
//找到idint最终所合并到的区号;
{
INT outid = idint;
while ( mergearr[outid] > 0 )
{
outid = mergearr[outid];
}
return outid;
}
void MyWatershed::AddNeiRgn(INT curid, INT neiid, CString* neiarr)
//增加neiid为curid的相邻区
{
CString tempneis = neiarr[curid];//当前的相邻区;
CString toaddstr;
toaddstr.Format("%d ", neiid);
INT temppos = tempneis.Find(toaddstr, 0);
while (temppos>0 && neiarr[curid].GetAt(temppos-1)!=' ')
{
temppos = neiarr[curid].Find(toaddstr, temppos+1);
}
if ( temppos<0 )
{
//当前相邻区中没有tempneis,则加入
neiarr[curid] += toaddstr;
}
}
void MyWatershed::AddNeiOfCur(INT curid, INT left, INT right, INT up, INT down, INT* flag, CString* neiarr)
//刷新当前点的所有相邻区;
{
INT leftid, rightid, upid, downid;
leftid = rightid = upid = downid = curid;
if (left>=0)
{
leftid = flag[left];
if (leftid!=curid)
{
//邻点属于另一区, 加邻域点信息;
AddNeiRgn(curid, leftid, neiarr);
}
}
if (right>0)
{
rightid = flag[right];
if (rightid!=curid)
{
//邻点属于另一区, 加邻域点信息;
AddNeiRgn(curid, rightid, neiarr);
}
}
if (up>=0)
{
upid = flag[up];
if (upid!=curid)
{
//邻点属于另一区, 加邻域点信息;
AddNeiRgn(curid, upid, neiarr);
}
}
if (down>0)
{
downid = flag[down];
if (downid!=curid)
{
//邻点属于另一区, 加邻域点信息;
AddNeiRgn(curid, downid, neiarr);
}
}
}
-----------------------------------------------------------------------------------------------------------------------
》效果
找了一辆车做测试,效果勉强,运行速度很慢。
原图:
轮廓:
轮廓图:
------------------------------------------------------------------------------------------------------------------------
》参考资料:
Watershed (image processing): http://en.wikipedia.org/wiki/Watershed_(image_processing)
IMAGE SEGMENTATION AND MATHEMATICAL MORPHOLOGY http://cmm.ensmp.fr/~beucher/wtshed.html
分水岭算法(Watershed Algorithm):http://hi.baidu.com/changfeng01200/blog/item/0ed6920b51f82e1794ca6bc5.html
分水岭分割方法:http://hi.baidu.com/huangwenzhixin/blog/item/039d5713c81bf62add5401e2.html
分水岭算法简单实现:http://blog.csdn.net/tt2com/article/details/6321610
分水岭分割算法:http://hi.baidu.com/%CA%D8%CD%FB%CC%EC%B1%DF%B5%C4%D4%C2/blog/item/57926db72b319ee531add1d2.html
------------------------------------------------------------------------------------------------------------------------
Author: SKySeraph
Email/GTalk: zgzhaobo@gmail.com QQ:452728574
From: http://www.cnblogs.com/skyseraph/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
------------------------------------------------------------------------------------------------------------------------