圖像自適應閾值(最大類間方差法、迭代法、矩形區域塊自適應閾值(使用積分處理的自適應處理))

一、最大類間方差法 或者 大津法 或者 OTSU

1、簡介

最大類間方差法是由日本學者大津(Nobuyuki Otsu)於1979年提出的,是一種自適應的閾值確定的方法,又叫大津法,簡稱OTSU。它是按圖像的灰度特性,將圖像分成背景和目標兩部分,或者說,是尋找一個閾值爲K,將圖像的顏色分爲1,2.....K和K+1.....256兩部分。

如何確定這個閾值K?算法分類的原理是讓背景和目標之間的類間方差最大,因爲背景和目標之間的類間方差越大,說明構成圖像的2部分的差別越大,錯分的可能性越小。下面進行公式推導:

2、最大類間方差法推到公式:

M*N 爲一張圖像所有的像素點。N1爲小於等於當前像素的所有點數目,w1爲 小於等於當前像素的所有點數佔所有像素點的比。

N2爲大於當前像素點的所有點的數目,w2爲 大於當前像素點的所有點的數目的佔比。u1 爲所有小於等於當前像素點值的和 除上N1,也就是小於等於當前像素值所有數的平均值,u2爲大於當前像素所有數的平均值,u爲以當前像素爲分割點時的期望(平均值),g爲方差。


代碼:

int ThresholdByOtsu( stImage* pImage)           // 最大類間方差法求閾值。
{
	int iWidth = pImage->cols;
	int iHeight = pImage->rows;
	unsigned char* pGrayImg = pImage->buffer;

	// verify the input parameters
	if ((pGrayImg == 0) || (iWidth <= 0) || (iHeight <= 0))
	{
		return -1;
	}
	int thresholdValue = 0; // Threshold
	int n, n1, n2;
	double m1, m2, sum, csum, fmax, sb;
	memset(ihist, 0, sizeof(ihist)); // set ihist[] to zero 
	n = iHeight*iWidth;
	sum = csum = 0.0;
	fmax = -1.0;
	n1 = 0;
	// hist generation
	for (int i = 0; i < iHeight; i++)
	{
		for (int j = 0; j < iWidth; j++)
		{
			ihist[*pGrayImg]++;         //統計每個像素值的個數。
			pGrayImg++;
		}
	}
	pGrayImg -= n;                           // 等於 pGrayImg = pImage->buffer;
	for (int k = 0; k <= 255; k++)
	{
		sum += (double)k * (double)ihist[k];         // 像素值乘以當前個數。
	}
	// Otsu Algorithm
	for (int k = 0; k <= 255; k++)
	{
		n1 += ihist[k];                                          //n1爲小於等於當前像素的個數
		if (n1 == 0)continue;
		n2 = n - n1;                                             // n2 爲當前當前像素的個數。
		if (n2 == 0)break;

		csum += (double)k *ihist[k];
		m1 = csum / n1;         // average level of class 1       // m1 遍歷到當前像素值得,平均值水平。

		m2 = (sum - csum) / n2; // average level of class 2        //m2 剩下的(大於當前)像素值的平均值水平。

		sb = (double)n1 *(double)n2 *(m1 - m2) * (m1 - m2);        // 這裏雖然是用的n1和n2不是公式中的w1、w2(比重).其實最終相求的,等同於w1、w2都擴大了iHeight*iWidth這麼多倍。
		if (sb > fmax)
		{
			fmax = sb;
			thresholdValue = k;
		}
	}
	return(thresholdValue);
}

二、迭代法求圖像閾值

迭代法閾值選擇算法是對雙峯法的改進,他首先選擇一個近似的閾值T,將圖像分割成兩個部分,R1和R2,計算出區域R1和R2的均值u1和u2,再選擇新的

閾值T=(u1+u2)/2;

重複上面的過程,知道u1和u2不在變化爲止。

由於迭代法比較易實現,在這就不寫代碼了。

三,矩陣區域塊自適應閾值(使用積分閾值自適應處理)

 摘要:  圖像閾值處理是許多計算機視覺和圖形應用中的常見任務。 對圖像進行閾值處理的目的是將像素分類爲“暗”或“亮”。 自適應閾值處理是一種閾值處理,它考慮了照明的空間變化。 我們提出了一種使用輸入的積分圖像進行實時自適應閾值處理的技術。 我們的技術是以前方法的擴展。 但是,我們的解決方案對圖像中的照明變化更加穩健。 此外,我們的方法簡單易行。我們的技術適用於以實時幀速處理實時視頻流,使其成爲增強現實等交互式應用的有用工具。

1、簡介(introduction)
圖像閾值化基於像素的特定特徵(例如強度值)來分割數字圖像。目標是創建二值圖像,將每個像素分類爲兩個類別之一,例如“暗”或“亮”。這是許多圖像處理應用程序和一些計算機圖形應用程序中的常見任務。例如,它通常是基於標記的增強現實系統的第一步[Billinghurstet al。 2001;布拉德利和羅斯2004; Fiala 2005],它已被用於高動態範圍攝影[Ward2003]。最基本的閾值處理方法是選擇固定的閾值並將每個像素與該值進行比較。這些技術已在許多調查論文中進行了廣泛的描述和評估[Weszka andRosenfeld 1978; Palumbo等人。 1986; Sahoo等。 1988;李等人。 1990; Glasbey 1993;特里爾和耆那教1995; Sezginand Sankur 2004]。然而,如果照明在圖像中在空間上變化或在視頻流中超時,則固定閾值經常失敗。

爲了解釋照明的變化,常見的解決方案是自適應閾值處理。主要區別在於爲圖像中的每個像素計算不同的閾值。該技術爲照明變化提供了更強大的功能。存在許多自適應閾值方法[White和Rohrer 1983; Bernsen1986;帕克1991; Wellner 1993;楊等人。 1994; Shen和Ip 1997;陳等人。 1998; Savakis 1998; Sauvolaand Pietikainen 2000;楊和燕2000]。進一步的例子和比較可以在[Venkateswarlu andBoyle 1995; Sezgin和Sankur 2004]。我們使用積分圖像提出了一種非常簡單明瞭的技術。我們的方法易於實現實時視頻流的實時性能。雖然我們的技術是以前方法的擴展[Wellner 1993],但我們增強了強光照變化的穩健性。此外,我們提供清晰整潔的解決方案,而不會增加實施的複雜性。我們的技術也類似於White和Rohrer用於光學字符識別的閾值處理方法[White and Rohrer 1983],但我們提出了一種針對實時視頻設計的實現方法。這項工作的動機是找到真正的現實應用程序。 Pintaric還提出了一種適用於增強現實標記的自適應閾值算法[Pintaric 2003],但是他的方法要求在前一幀中定位一個元素,以便技術正確地進行閾值處理。我們的算法不做任何假設,更通用,適合在任何應用中使用。源代碼可在本文末尾列出的地址在線獲取。

2、背景 

2.1實時自適應閾值處理
在本文中,我們專注於從實時視頻流中自適應地閾值化圖像。 爲了保持實時性能,閾值算法必須限制通過每個圖像的迭代次數。 閾值處理通常是一個子任務,它構成了一個更大的過程的一部分。 例如,在增強現實中,必須對輸入圖像進行分段以定位場景中用於動態建立相機設置的已知標記。 因此,簡單快速的自適應閾值技術是一個重要的工具

2.2積分圖像
積分圖像(也稱爲summed-area table)是一種工具,當我們有一個從像素到數值(比如灰度值)的映射函數f(x, y),並且希望計算一個矩形區域內的函數值的和時,積分圖是一個非常高效的工具。已應用積分圖像的示例包括紋理映射[Crow 1984],圖像中的人臉檢測[Viola and Jones 2004]和立體對應[Veksler 2003]。在沒有積分圖像的情況下,我們計算每個矩形窗口中像素點的映射函數的和,需要通過分別計算每個像素的函數值最後疊加實現。但是,如果我們需要計算多個重疊矩形窗口的總和,爲了降低時間複雜度和操作次數,我們可以使用積分圖像。

爲了計算積分圖像,我們在每個位置I(x,y)存儲左邊和上面像素(x,y)的所有f(x,y)項的總和。 對於每個像素,使用以下等式在線性時間內完成(考慮邊界情況),

圖2(左和中)說明了積分圖像的計算。 一旦我們得到積分圖像,任何具有左上角(x1,y1)和右下角(x2,y2)的矩形的函數的總和可以使用以下等式計算,計算時間不穩定。

圖2(右)說明使用等式2計算矩形D上的f(x,y)之和相當於計算矩形上的和(A + B + C + D) - (A + B) - (A+ C)+ A。

圖2:積分圖像。 左:圖像值的簡單輸入。 中心:計算的積分圖像。 右:使用積分圖像計算矩形D的總和。

3、技術                         ----> 這個是最重要的。

    我們的自適應閾值技術是Wellner方法的簡單擴展[WELNER 1993 ]。Welnne算法的主要思想是將每個像素與周圍像素的平均值進行比較。特別地,在遍歷圖像時計算了最後一個像素的近似移動平均值。如果當前像素的值比平均值低百分之t,那麼它被設置爲黑色,否則它被設置爲白色。這種方法的工作是將像素與附近像素的平均值進行比較,從而保持硬對比線,忽略軟梯度變化。這種方法的優點是隻需要遍歷一次圖像。WELNER使用1/8倍的圖像寬度來表示s的值,t=15。然而,這種方法的一個問題是它依賴於像素的掃描順序。此外,由於鄰域樣本在各個方向上不均勻分佈,所以在使用平均步長並不是一個很好的表現形式。通過使用積分圖像(和骶一個額外的迭代通過圖像),我們提出了一個解決方案,不遭受這些問題。我們的技術是乾淨的,直截了當的,易於編碼,併產針對不同的處理方式產生生相同的輸出。我們不是計算最後看到的s個像素的平均值,而是計算以每個像素爲中心的s x s像素窗口的平均值。 這是比較好的平均值,因爲它考慮了所有邊上的相鄰像素。利用積分圖像進行平均計算的時間是線性的。我們在第一次遍歷圖像的時候計算積分圖像在第二遍中,我們利用積分圖像計算每個像素的s x s的平均值,使用時間爲常量,然後進行比較。如果當前像素的值小於這個平均值,則將其設置爲黑色,否則它被設置爲白色

代碼:

void AdaptiveThresholdPartOptUsingSram(unsigned char * org_img, unsigned char * new_img, int width, int height, int block, int ratio)
{
	//org_img 原圖   new_img 二值化後的圖   block塊略等於1/8寬度 ratio?
	int S = (block - 1) / 2;//101
	int T = 20; //15
	T = (ratio >= 100 || ratio <= 0) ? 20 : ratio;

	int i, j;
	long sum = 0;
	int count = 0;
	int index = 0;
	int x1, y1, x2, y2;
	// 計算積分圖像    別說積分好不好,挺嚇人的,不就是求dp[i][j]嗎,dp[i][j] 爲從(0,0)爲起點到(i,j)爲終點的矩形內,所有點之和。
	Lx_2Row[0][0] = Lx_1Col[0] = org_img[0];
	// 原來這就是圖像積分。
	for (i = 1; i<width; i++)
	{
		Lx_2Row[0][i] = Lx_2Row[0][i - 1] + org_img[i];      // 橫向累加  第一行
	}

	for (j = 1; j<height; j++)
	{
		Lx_1Col[j] = Lx_1Col[j - 1] + org_img[j*width];      //  第一列 橫向累加。        
	}
	
	memcpy(&integralImg[0][0], &Lx_2Row[0][0], width * sizeof(unsigned int));

	for (j = 1; j<height; j++)
	{
		int row0 = (j) & 0x01, row1 = (j + 1) & 0x01;
		Lx_2Row[row0][0] = Lx_1Col[j];
		index = j*width + 1;
		for (i = 1; i<width; i++)
		{
			Lx_2Row[row0][i] = Lx_2Row[row0][i - 1] + Lx_2Row[row1][i] - Lx_2Row[row1][i - 1] + org_img[index++];
			// 其實這句話的推到公式可以簡化爲 dp[i][j] = dp[i][j-1] + dp[i-1][j] - dp[i-1][j] + map[i][j];
			// dp[i][j] 爲 以0,0爲點,以i、j終點的矩形中所有點的和。
			// 在第一行和一列已經累加出來的前提下。
		}
		memcpy(&integralImg[j][0], &Lx_2Row[row0][0], width * sizeof(unsigned int));
	}
	// integralImg 積分後的圖像。

	memset(new_img, 255, (height*width));    //放這輸入輸出圖可以用同一幀。全是255爲了中值後圖片與固定閾值分割的圖邏輯與操作使用

	count = block*block * 100;
	for (i = (S + 1); i< (height - S); i++)
	{
		for (j = (S + 1); j< (width - S); j++)
		{
			index = i*width + j;

			// set the SxS region
			x1 = j - S;
			x2 = j + S;
			y1 = i - S;
			y2 = i + S;

			sum = integralImg[y2][x2] - integralImg[y1][x2] - integralImg[y2][x1] + integralImg[y1][x1];
			// 求以(y1,x1)爲起點,(y2,x2)爲終點的 矩陣所有點之和。

			//bin[index] = (((unsigned int)( input[index] * count- sum*(100 - T) ))&&(1)) * 255;

			if ((org_img[index] * count) < (sum*(100 - T)))  //不懂這句話的,請看上文技術,如果當前像素的值比平均值低百分之t,那麼它被設置爲黑色,否則它被設置爲白色       
				new_img[index] = 0;
			//else
			//	new_img[index] = 255;

		}
	}
}

四、就是給圖像加邊框後,再進行塊狀區域積分求閾值

static int SurroundImage(unsigned char* src, unsigned char* dst, int width, int height, int exWidth, int exHeight)
{
	//原圖像是data;擴展後是DataDst width、height爲原圖的寬高,exwidth、exheight爲塊狀區域的寬高
	int i, j;
	unsigned char* pSrc, *pDst;
	int dstWidth = width + exWidth;
	int dstHeight = height + exHeight;

	if ((0 != width % 2) || (0 != height % 2))
	{
		memset(src, 0, (height*width));
		return -1;
	}

	//center
	pSrc = src;
	pDst = dst + (exHeight / 2)*(width + exWidth) + exWidth / 2;
	for (j = exHeight / 2; j < (height + exHeight / 2); j++)   // 把原圖複製過來。
	{
		memcpy(pDst, pSrc, (width));             //
		pSrc += (width);
		pDst += (width + exWidth);
	}

	//  9月17號 打卡
	//  9月18號 繼續? 繼續
	//4 corners /*加4個邊角*/  
	pSrc = src;
	pDst = dst;
	/*左上角的方框,全部賦值爲原圖的左上角點*/
	for (i = 0; i < (exWidth / 2); i++)
		for (j = 0; j < (exHeight / 2); j++)
			*(pDst + i + j*(dstWidth)) = *(pSrc + 0 + 0 * width);//pSrc[0][0];   // dstWidth  = width + exWidth
	/*右上角的方框,全部賦值爲原圖的右上角點*/
	for (i = (width + exWidth / 2); i < (width + exWidth); i++)
		for (j = 0; j < (exHeight / 2); j++)
			*(pDst + i + j*(dstWidth)) = *(pSrc + width - 1 + 0 * width); //pSrc[width-1][0];

	/*右下角的方框,全部賦值爲原圖的右下角點*/
	for (i = (width + exWidth / 2); i < (width + exWidth); i++)
		for (j = (height + exHeight / 2); j < (height + exHeight); j++)    // 行數
			*(pDst + i + j*(dstWidth)) = *(pSrc + width - 1 + (height - 1) * width); ;// pSrc[width - 1][height - 1];

	/*左下角的方框,全部賦值爲原圖的左下角點*/
	for (i = 0; i < (exWidth / 2); i++)
		for (j = (height + exHeight / 2); j < (height + exHeight); j++)
			*(pDst + i + j*(dstWidth)) = *(pSrc + 0 + (height - 1) * width); //pSrc[0][height-1];

																			 //4line
	pSrc = src;
	pDst = dst;
	//up
	/* 上方的線,都用原圖中的第一行賦值*/
	for (i = 0; i < (width); i++)
		for (j = 0; j < (exHeight / 2); j++)
		{
			*(pDst + i + exWidth / 2 + j*(dstWidth)) = *(pSrc + i + 0 * width); //pSrc[i][0];
		}
	//right
	/*右邊的線exWidth/2 * height 這麼多點,有height行,每行exWidth/2個點,全部用原圖最右邊的線上對應行的那個點賦值*/
	for (j = 0; j < (height); j++)
	{
		memset((pDst + width + exWidth / 2 + (j + exHeight / 2)*(dstWidth)), *(pSrc + (j + 1)*width - 1), (exWidth / 2)); //pSrc[][];
	}
	//down

	for (i = 0; i < (width); i++)
		for (j = (exHeight / 2 + height); j < (exHeight + height); j++)
		{
			*(pDst + i + exWidth / 2 + j*(dstWidth)) = *(pSrc + width*(height - 1) + i); //pSrc[i][0];
		}
	//left
	for (j = 0; j < (height); j++)
	{
		memset((pDst + 0 + (j + exHeight / 2)*(dstWidth)), *(pSrc + j*width), (exWidth / 2)); //pSrc[][];
	}
	/*關於爲什麼這麼賦值,我想應該經過驗證的。*/
	return 0;
}
void BinAdaptiveThresholdFull(unsigned char* input, int height, int width, unsigned char* bin, int block, int ratio)
{
	//input 原圖,width、height原圖的寬高,bin要輸出的圖像,block 塊狀區域大小,ratio根據這個值,調整T的。
	//ratio can be modified : 20 means 20%
	int T = 20; //15
	int halfBlock = (block - 1) / 2;
 
	long sum = 0;
	int count = 0;
	int index = 0;
	int x1, y1, x2, y2;

	if (0 == block % 2)//block must be odd
		return;

	T = (ratio >= 100 || ratio <= 0) ? 20: ratio;
	count = block*block * 100;

	SurroundImage(input, (unsigned char*)pSurroundImage, width, height, block - 1, block - 1);
#if (!DSP_HARDWARE_) &&( !UNIT_TEST_VERSION)
//IplSurroundImage = cvCreateImage(cvSize(width + block - 1 , height + block - 1), IPL_DEPTH_8U, 1);//debug
//IplSurroundImage->imageData = pSurroundImage;
#endif
	// 計算積分圖像	
	integralFullImg[0] = pSurroundImage[0];
	//Y=0
	for (int i = 1; i<(width + block - 1); i++)
	{
		int j = 0;
		integralFullImg[i + j*(width + block - 1)] = integralFullImg[i - 1 + j*(width + block - 1)] + pSurroundImage[i + j*(width + block - 1)];
	}
	//X=0
	for (int j = 1; j<(height + block - 1); j++)
	{
		int i = 0;
		integralFullImg[i + j*(width + block - 1)] = integralFullImg[i + (j - 1)*(width + block - 1)] + pSurroundImage[i + j*(width + block - 1)];
	}

	for (int j = 1; j<(height + block - 1); j++)
	{
		for (int i = 1; i<(width + block - 1); i++)
		{
			integralFullImg[i + j*(width + block - 1)] = integralFullImg[i - 1 + j*(width + block - 1)] + integralFullImg[i + (j - 1)*(width + block - 1)] - integralFullImg[i - 1 + (j - 1)*(width + block - 1)] + pSurroundImage[i + j*(width + block - 1)];
		}
	}

	for (int i = 0; i< (width); i++)
	{
		for (int j = 0; j< (height); j++)
		{
			// set the (block-1)*(block-1) region
			x1 = i;
			x2 = i + block - 1;
			y1 = j;
			y2 = j + block - 1;

			sum = integralFullImg[x2 + y2*(width + block - 1)] - integralFullImg[x2 + y1*(width + block - 1)] - integralFullImg[x1 + y2*(width + block - 1)] + integralFullImg[x1 + y1*(width + block - 1)];

			//bin[index] = (((unsigned int)( input[index] * count- sum*(100 - T) ))&&(1)) * 255;
			if ((input[i + width*j] * count) < (sum*(100 - T)))
				bin[i + width*j] = 0;
			else
				bin[i + width*j] = 255;
		}
	}
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章