现在的位置: 首页 > 综合 > 正文

模糊阈值分割(2)

2013年03月26日 ⁄ 综合 ⁄ 共 4238字 ⁄ 字号 评论关闭

根据模糊阈值分割的原理,本文在“模糊阈值分割代码(1)"的基础上进行了拓展,主要是利用图像直方图,搜索最佳波谷阈值,实现多阈值分割

其过程主要如下:

1. 获取图像直方图

2. 进行直方图平滑,以去毛刺

3. 进行波峰搜索。这部分比较复杂,需要考虑的问题也多。包括波峰的判定准则、伪峰值的去除、半峰值的判断等等。

一般来说,一个峰值包括起始位置(上坡段)、终止位置(下坡段)和峰值位置。本文上、下坡段判断是通过连续增长(下降)的数量来判断的,比如连续超过10个增长的就认为是上坡段,而连续10个下降的就是下坡段。当然了,有些峰值非常小,这个就需要缩小判定阈值。峰值的位置是在上下坡段的区间内取重心的方式得到的(见函数GetPeaks)。

4. 在波峰之间采用S函数的模糊化(MemshipFunc),获得模糊曲线。我们知道,模糊化的过程窗口的宽度很重要,一般窗宽取峰值距离的0.3~0.8。由于上一步把峰值取到了,所以这一步就可以根据峰值间的距离设定窗宽,达到自适应的目的。

5. 获得模糊率曲线后,搜索不同峰值间的最小值就可以获得分割阈值。

struct Peack
{
	int nStat;
	int nEnd;
	int nValue;
	Peack():nStat(0),nEnd(0),nValue(0)
	{}
};


/*
名称:BlurCure
参数:lpBmp - 图像数据指针
      lSrcWidth - 图像宽度
      lSrcHeight - 图像高度
      dLineBites - 图像单行数据量
说明:获取模糊阈值分割的模糊曲线dBlurData
*/
void BlurCurve(LPSTR lpBmp, long lSrcWidth, long lSrcHeight, DWORD dLineBites)
{
	if(lpBmp == NULL)
		return;
	int i,j;
	LPSTR lpSrcPtr = NULL;
	BYTE btTempValue = 0;
	int nTistogram[256] = {0};
	for (i = 0;i < lSrcHeight; ++i)
	{
		lpSrcPtr = lpBmp + i * dLineBites;
		for (j = 0;j <lSrcWidth; ++j)
		{
			btTempValue = (BYTE)*(lpSrcPtr + j);
			nTistogram[btTempValue]++;
		}
	}
	       //-----------------直方图平均-----------
	int nSum = 0, nMaxTtg = 0;
	const int c_nScop = 5;
	int arrValue[c_nScop] = {0};
	for (j = 0;j <256; ++j)
	{
		nSum += nTistogram[j];
		if(j < c_nScop)
		{
			arrValue[j] = nTistogram[j];
			nTistogram[j] = nSum/(j+1);
		}
		else
		{
			i = j%c_nScop;
			nSum -= arrValue[i];
			arrValue[i] = nTistogram[j];
			nTistogram[j] = nSum/c_nScop;
		}
		if(nMaxTtg < nTistogram[j])
			nMaxTtg = nTistogram[j];
		outFile<<nTistogram[j]<<endl;
	}//end
	//---------------------------------------        vector <Peack> vctPeak;
	GetPeaks(nTistogram,nMaxTtg/500,vctPeak);
	int nNum = vctPeak.size();
	if(nNum > 1)
	{
		int arrThesh[255] = {0};
		int nSetScop = 255/(nNum -1);
		for (i=0; i<nNum-1; i++)
		{
			int nWinWidth = int(0.3*(vctPeak[i+1].nValue-vctPeak[i].nValue));
			double dGetMemship = 0,dMinData = 0xFFFFFFFF;
			for (int nGrayPos = vctPeak[i].nValue;nGrayPos <= vctPeak[i+1].nValue; ++nGrayPos)
			{
				double dOutData = 0;
				for (j = 0;j <256; ++j)
				{
					dGetMemship = MemshipFunc(j,nGrayPos,nWinWidth);
					if(dGetMemship > 0.5)
						dGetMemship = 1 - dGetMemship;
					if(dGetMemship < 0)
						dGetMemship = 0;//-dGetMemship;
					dOutData += nTistogram[j]*dGetMemship;
				}
				//dOutData *= 2.0/(lSrcHeight*lSrcWidth);
				if(dMinData > dOutData)
				{
					dMinData = dOutData;
					arrThesh[i] = nGrayPos;
				}
			}
		}
		//---------
		for (i = 0;i < lSrcHeight; ++i)
		{
			lpSrcPtr = lpSrcData + i * dLineBites;
			lpDstPtr = lpDstData + i * dLineBites;
			for (j = 0;j <lSrcWidth; ++j)
			{
				btTempValue = (BYTE)*(lpSrcPtr + j);
				int k;
				for ( k=0; k<nNum-1; k++)
				{
					if(btTempValue < arrThesh[k])
					{
						*(lpDstPtr + j)=  (BYTE)(k*nSetScop);
						break;
					}
				}
				if(k == nNum-1)
					*(lpDstPtr + j) = 255;
			}
		}//END FOR
	}
	else if(nNum == 1)//单峰,用峰值左侧点为分割点
	{
		for (i = 0;i < lSrcHeight; ++i)
		{
			lpSrcPtr = lpSrcData + i * dLineBites;
			lpDstPtr = lpDstData + i * dLineBites;
			for (j = 0;j <lSrcWidth; ++j)
			{
				btTempValue = (BYTE)*(lpSrcPtr + j);
				if(btTempValue < vctPeak[0].nEnd)
					*(lpDstPtr + j)=  0;
				else
					*(lpDstPtr + j) = 255;
			}
		}
	}
	else
	{
		MessageBox(_T("分割失败"),_T("提示"),MB_ICONWARNING);
	}
}
/*
函数:GetPeaks
参数: pTistogram - 图像直方图
      nLowValue - 谷底最小值
      vctPeak - 返回的峰值列表

说明:函数主要对处理过的直方图查找谷峰
*/
void GetPeaks(const int pTistogram[256], const int nLowValue,vector<Peack> &vctPeak)
{
	int arrPos[2] = {0};
	int nSratMin = 0xFFFFFF,nContinueNum=0;
	int nPreValue = pTistogram[0];
	int nSetThres = 8;
	for (int i=1; i<256; i++)
	{
		if(nSratMin > pTistogram[i])
		{
			nSratMin = pTistogram[i];
			if(nSratMin <= nLowValue)
				nSetThres = 5;
		}
		//-------------------------------
		if(arrPos[0] != 0)
		{
			if(pTistogram[i] < nPreValue)
			{
				nContinueNum ++;
				if(nContinueNum >= nSetThres)
					arrPos[1] = i;
			}
			else// if(nTistogram[i] > nPreValue)
			{
				if(arrPos[1] != 0)//data out
				{
					Peack pk;
					pk.nStat = arrPos[0];
					pk.nEnd = arrPos[1];
					vctPeak.push_back(pk);
					//--------
					arrPos[0] = arrPos[1] =0;
					nSratMin = 0xFFFFFF;
					nSetThres = 10;
				}
				nContinueNum = 0;
			}
		}
		else
		{
			if(pTistogram[i] > nPreValue)
			{
				nContinueNum ++;
				if(nContinueNum >= nSetThres)
					arrPos[0] = i-nSetThres;
			}
			else //if(nTistogram[i] < nPreValue)
			{
				static int s_nLowNum = 0;
				if(pTistogram[i] < nPreValue)
				{
					s_nLowNum ++;
					if(s_nLowNum == i && s_nLowNum > 10)
						arrPos[0] = 1;
				}
				else if(nContinueNum > 2)
					nContinueNum ++;
				else
					nContinueNum = 0;
			}
		}
		/////////////////////////////////
		nPreValue = pTistogram[i];
	}
	//============峰值计算==============
	int nSum = 0,nTistoSum = 0;
	vector<Peack>::iterator it=vctPeak.begin();
	while (it != vctPeak.end())
	{
		nSum = nTistoSum = 0;
		for (int i=(*it).nStat; i<=(*it).nEnd; i++)
		{
			nSum += i*pTistogram[i];
			nTistoSum += pTistogram[i];
		}
		(*it).nValue = nSum/nTistoSum;
		it++;
	}
}

 

下面是“模糊阈值分割代码(1)”中的瓶底图像获得的峰值信息:

 

三个阈值信息:

 

 

最终的分割结果:

 

 

 

从图上可以看出,该种方法还是比较准确的得到了分割的阈值,结果还是比较理想的。

当然,这是针对的多峰值的情况,对于单峰值的情况也是适用的。

其实这种算法在上世纪80年代就已经推出了,之所以没有推广开,问题是在求解的过程中有很多不确定性。比如峰值的确定,本身峰值怎么求的准确就是一个困难的问题,特别是在复杂的情况下,比如弱灰度、有噪音的情况,峰值往往并不明显。而要获得自私应的模糊阈值的,其窗宽又依赖于峰值选取的结果,结果可想而知,通用性就大大降低了!要解决这类算法的通用性问题,首先要减小不确定性,这也是现在依旧在探讨的问题。

抱歉!评论已关闭.