Fork me on GitHub

【图像算法】彩色图像分割专题七:基于分水岭的彩色分割

 ------------------------------------------------------------------------------------------------------------------------

【图像算法】彩色图像分割专题七:基于分水岭的彩色分割

      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/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.

 ------------------------------------------------------------------------------------------------------------------------

posted @ 2011-07-07 20:15  SkySeraph  阅读(9924)  评论(3编辑  收藏  举报