1. 程式人生 > >最大類間方差法(大津法OTSU)

最大類間方差法(大津法OTSU)

演算法介紹

最大類間方差法是1979年由日本學者大津提出的,是一種自適應閾值確定的方法,又叫大津法,簡稱OTSU,是一種基於全域性的二值化演算法,它是根據影象的灰度特性,將影象分為前景和背景兩個部分。當取最佳閾值時,兩部分之間的差別應該是最大的,在OTSU演算法中所採用的衡量差別的標準就是較為常見的最大類間方差。前景和背景之間的類間方差如果越大,就說明構成影象的兩個部分之間的差別越大,當部分目標被錯分為背景或部分背景被錯分為目標,都會導致兩部分差別變小,當所取閾值的分割使類間方差最大時就意味著錯分概率最小[1]。

記T為前景與背景的分割閾值,前景點數佔影象比例為w 0  ,平均灰度為u 0  ;背景點數佔影象比例為w

 1  ,平均灰度為u 1  ,影象的總平均灰度為u ,前景和背景圖象的方差,則有:

u=w 0 ×u 0 +w 1 ×u 1  
g=w 0 ×(u 0 u) 2 +w 1 ×(u 1 u) 2  
聯立上面兩式可得:
g=w 0 ×w 1 ×(u 0 u 1 ) 2  

g=w 0 1w 0 ×(u 0 u) 2  
當方差g 最大時,可以認為此時前景和背景差異最大,此時的灰度T是最佳閾值。類間方差法對噪聲以及目標大小十分敏感,它僅對類間方差為單峰的影象產生較好的分割效果。當目標與背景的大小比例懸殊時(例如受光照不均、反光或背景複雜等因素影響),類間方差準則函式可能呈現雙峰或多峰,此時效果不好。直接用OTSU演算法處理自然場景銘牌圖片的部分結果例項如下:

1


1. 

程式碼實現

(C語言版,VS2012+opencv249)

#include "stdio.h"
#include "cv.h"
#include "highgui.h"
#include "Math.h"

int Otsu(IplImage* src);

int main()
{
    IplImage* img = cvLoadImage("lena.jpg",0); //獲取灰度影象img
    IplImage* dst = cvCreateImage(cvGetSize(img), 8, 1);
    int threshold = Otsu(img); //呼叫大津法求出最佳閾值
printf("otsu threshold = %d\n", threshold); cvThreshold(img, dst, threshold, 255, CV_THRESH_BINARY); //用otsu的閾值二值化 cvNamedWindow( "img", 1 ); cvNamedWindow( "dst", 1 ); cvShowImage("img", img); cvShowImage("dst", dst); cvWaitKey(-1); cvReleaseImage(&img); cvReleaseImage(&dst); cvDestroyWindow( "img" ); cvDestroyWindow( "dst" ); return 0; } int Otsu(IplImage* src) { int height=src->height; int width=src->width; //histogram float histogram[256] = {0}; for(int i=0; i < height; i++) { unsigned char* p=(unsigned char*)src->imageData + src->widthStep * i; for(int j = 0; j < width; j++) { histogram[*p++]++; } } //normalize histogram & average pixel value int size = height * width; float u =0; for(int i = 0; i < 256; i++) { histogram[i] = histogram[i] / size; u += i * histogram[i]; //整幅影象的平均灰度 } int threshold; float maxVariance=0; float w0 = 0, avgValue = 0; for(int i = 0; i < 256; i++) { w0 += histogram[i]; //假設當前灰度i為閾值, 0~i 灰度畫素所佔整幅影象的比例即前景比例 avgValue += i * histogram[i]; //avgValue/w0 = u0 float t = avgValue/w0 - u; //t=u0-u float variance = t * t * w0 /(1 - w0); if(variance > maxVariance) { maxVariance = variance; threshold = i; } } return threshold; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76

程式碼執行結果:
2

2.otsu 

參考資料:
[1]Otsu N. A threshold selection method from gray-level histograms[J]. Automatica, 1975, 11(285-296): 23-27. 

OTSU演算法:就是計算出灰度圖最佳閾值的演算法

1.先對灰度圖進行直方圖計算並歸一化處理,得到0-255之間每個畫素在灰度圖中出現的概率,即表示為某個畫素在灰度圖中出現了n個,灰度圖總的畫素點為N個,則這個畫素的出現概率為Pi=n/N

2.每個灰度圖可以由閾值k將灰度圖分為A,B兩大類,很容易得到A,B類在灰度圖中的出現概率以及灰度均值

3.計算灰度圖A,B類得類間方差,在最佳閾值K處,求得的類間方差最大,也就是類間方差最大的那個時刻的閾值就為灰度圖的最佳閾值

程式碼實現:

以下三種演算法時間複雜度依次降低,前兩種演算法自己所寫,最後一種演算法從網上看到大神寫的,膜拜!當然如果你想偷懶,可是更直接點用cvThreshold一步實現cvThreshold(srcCopy,thresholdImage,0,255,CV_THRESH_OTSU),花費時間與演算法三相當。

演算法一:

處理一張640*480的圖片花費時間大概在8000毫秒左右,非常低效,但是是一種非常直接的,顯而易見的實現方法。

[cpp] view plain copy print?
  1. int otsu(IplImage* src)  
  2. {  
  3.     CvScalar cs = cvSum(src);                             //影象總灰度值  
  4.     int Width = src->width;  
  5.     int Height = src->height;  
  6.     double U_t = 1.0*cs.val[0]/(Width*Height);                  //影象的平均灰度  
  7.     int threshold = 0;  
  8.     double delta = 0;  
  9.     for(int k=0; k<256; k++)  
  10.     {  
  11.         double sumOfGray = 0;  
  12.         int num = 0;  
  13.         for(int i=0; i < Width; i++)  
  14.         {  
  15.             for(int j=0; j < Height;j++)  
  16.             {  
  17.                 int t = cvGet2D(src, j,i).val[0];  
  18.                 if(t < k)  
  19.                 {  
  20.                     num++;             //統計灰度小於k的畫素點的個數  
  21.                     sumOfGray += t; //灰度值小於k的畫素點的灰度和  
  22.                 }  
  23.             }  
  24.         }  
  25.         double w0 = 1.0*num/(Width*Height);                     //灰度值小於閾值k的畫素的概率  
  26.         double w1 = 1.0 - w0;                               //灰度值大於k畫素的概率  
  27.         double u0 = 1.0*sumOfGray/num;                     //灰度值小於閾值k的畫素的平均灰度值  
  28.         double u1 = 1.0*(cs.val[0]-sumOfGray)/(Width*Height-num); //灰度值大於閾值k的畫素的平均灰度值  
  29.         double delta_tmp = w0*pow(u0-U_t,2) + w1*pow(u1-U_t,2);    //類間方差  
  30.         if(delta_tmp > delta)  
  31.         {  
  32.             delta = delta_tmp;  
  33.             threshold = k;  
  34.         }  
  35.     }  
  36.     cout<<cs.val[0]<<endl;  
  37.     cout<<U_t<<endl;  
  38.     return threshold;  
  39. }  
int otsu(IplImage* src)
{
	CvScalar cs = cvSum(src);                             //影象總灰度值
	int Width = src->width;
	int Height = src->height;
	double U_t = 1.0*cs.val[0]/(Width*Height);                  //影象的平均灰度
	int threshold = 0;
	double delta = 0;
	for(int k=0; k<256; k++)
	{
		double sumOfGray = 0;
		int num = 0;
		for(int i=0; i < Width; i++)
		{
			for(int j=0; j < Height;j++)
			{
				int t = cvGet2D(src, j,i).val[0];
				if(t < k)
				{
					num++;             //統計灰度小於k的畫素點的個數
					sumOfGray += t; //灰度值小於k的畫素點的灰度和
				}
			}
		}
		double w0 = 1.0*num/(Width*Height);                     //灰度值小於閾值k的畫素的概率
		double w1 = 1.0 - w0;                               //灰度值大於k畫素的概率
		double u0 = 1.0*sumOfGray/num;                     //灰度值小於閾值k的畫素的平均灰度值
		double u1 = 1.0*(cs.val[0]-sumOfGray)/(Width*Height-num); //灰度值大於閾值k的畫素的平均灰度值

		double delta_tmp = w0*pow(u0-U_t,2) + w1*pow(u1-U_t,2);    //類間方差
		
		if(delta_tmp > delta)
		{
			delta = delta_tmp;
			threshold = k;
		}
	}
	cout<<cs.val[0]<<endl;
	cout<<U_t<<endl;
	return threshold;
}

演算法二:

借用opencv中的直方圖函式,大大減少迴圈數,同一張圖片花費時間大概在16毫秒左右。

[cpp] view plain copy print?
  1. int otsu(IplImage* src)    
  2. {     
  3.     int hist_size = 256;    //直方圖尺寸      
  4.     int hist_height = 256;      
  5.     float range[] = {0,255};  //灰度級的範圍      
  6.     float* ranges[]={range};      
  7.     //建立一維直方圖,統計影象在[0 255]畫素的均勻分佈      
  8.     CvHistogram* gray_hist = cvCreateHist(1,&hist_size,CV_HIST_ARRAY,ranges,1);      
  9.     //計算灰度影象的一維直方圖      
  10.     cvCalcHist(&src,gray_hist,0,0);      
  11.     //歸一化直方圖      
  12.     cvNormalizeHist(gray_hist,1.0);      
  13.     int Width = src->width;    
  14.     int Height = src->height;     
  15.     int threshold = 0;    
  16.     double delta = 0;  
  17.     CvScalar sumOfGray = cvSum(src);  
  18.     double U_t = sumOfGray.val[0]/(Width*Height);  
  19.     for(int k=0; k<256; k++)    
  20.     {    
  21.         double u0 = 0,  u1 = 0, w0 = 0, w1 = 0, delta_tmp = 0, u00 = 0, u11 = 0;    
  22.         for(int i=0; i<k; i++)    
  23.         {    
  24.             u00 += cvQueryHistValue_1D(gray_hist,i)*i;   //灰度小於閾值k的畫素的平均灰度值    
  25.             w0 += cvQueryHistValue_1D(gray_hist,i);     //灰度小於閾值k的畫素的概率    
  26.         }  
  27.         u0 = u00/w0;  
  28.         for(int j=k; j<256; j++)    
  29.         {    
  30.             u11 += cvQueryHistValue_1D(gray_hist,j)*j;   //灰度大於閾值k的畫素的平均灰度值    
  31.             w1 += cvQueryHistValue_1D(gray_hist,j);     //灰度小於閾值k的畫素的概率    
  32.         }  
  33.         u1 = u11/w1;  
  34.         delta_tmp = w0*pow(u0-U_t,2) + w1*pow(u1-U_t,2);    //類間方差    
  35.         if(delta_tmp > delta)    
  36.         {    
  37.             delta = delta_tmp;    
  38.             threshold = k;    
  39.         }    
  40.     }   
  41.     cvReleaseHist(&gray_hist);  
  42.     return threshold;     
  43. }  
int otsu(IplImage* src)  
{   
    int hist_size = 256;    //直方圖尺寸    
    int hist_height = 256;    
    float range[] = {0,255};  //灰度級的範圍    
    float* ranges[]={range};    
    //建立一維直方圖,統計影象在[0 255]畫素的均勻分佈    
    CvHistogram* gray_hist = cvCreateHist(1,&hist_size,CV_HIST_ARRAY,ranges,1);    
    //計算灰度影象的一維直方圖    
    cvCalcHist(&src,gray_hist,0,0);    
    //歸一化直方圖    
    cvNormalizeHist(gray_hist,1.0);    
      
    int Width = src->width;  
    int Height = src->height;   
    int threshold = 0;  
    double delta = 0;
	CvScalar sumOfGray = cvSum(src);
	double U_t = sumOfGray.val[0]/(Width*Height);
	
    for(int k=0; k<256; k++)  
    {  
        double u0 = 0,  u1 = 0, w0 = 0, w1 = 0, delta_tmp = 0, u00 = 0, u11 = 0;  
         
        for(int i=0; i<k; i++)  
        {  
            u00 += cvQueryHistValue_1D(gray_hist,i)*i;   //灰度小於閾值k的畫素的平均灰度值  
            w0 += cvQueryHistValue_1D(gray_hist,i);     //灰度小於閾值k的畫素的概率  
        }
		
		u0 = u00/w0;

        for(int j=k; j<256; j++)  
        {  
            u11 += cvQueryHistValue_1D(gray_hist,j)*j;   //灰度大於閾值k的畫素的平均灰度值  
            w1 += cvQueryHistValue_1D(gray_hist,j);     //灰度小於閾值k的畫素的概率  
        }

		u1 = u11/w1;

        delta_tmp = w0*pow(u0-U_t,2) + w1*pow(u1-U_t,2);    //類間方差  
        if(delta_tmp > delta)  
        {  
            delta = delta_tmp;  
            threshold = k;  
        }  
    } 

	cvReleaseHist(&gray_hist);
    return threshold;   
}

演算法三:

一次迴圈實現,其中的公式需要自己用筆推導一下,非常精妙!完成一次運算的時間毫秒已經不能計量了,顯示為0!

[cpp] view plain copy print?
  1. int otsu(IplImage* src)    
  2. {     
  3.     int hist_size = 256;    //直方圖尺寸      
  4.     int hist_height = 256;      
  5.     float range[] = {0,255};  //灰度級的範圍      
  6.     float* ranges[]={range};      
  7.     //建立一維直方圖,統計影象在[0 255]畫素的均勻分佈      
  8.     CvHistogram* gray_hist = cvCreateHist(1,&hist_size,CV_HIST_ARRAY,ranges,1);      
  9.     //計算灰度影象的一維直方圖      
  10.     cvCalcHist(&src,gray_hist,0,0);      
  11.     //歸一化直方圖      
  12.     cvNormalizeHist(gray_hist,1.0);      
  13.     int Width = src->width;    
  14.     int Height = src->height;     
  15.     int threshold = 0;    
  16.     double delta = 0;  
  17.     double U_t = 0;  
  18.     for(int m=0; m<256; m++)  
  19.     {  
  20.         U_t += cvQueryHistValue_1D(gray_hist,m)*m;  
  21.     }  
  22.     double u = 0, w = 0;   
  23.     for(int k=0; k<256; k++)    
  24.     {    
  25.         u += cvQueryHistValue_1D(gray_hist,k)*k;   //    
  26.         w += cvQueryHistValue_1D(gray_hist,k);      //灰度大於閾值k的畫素的概率    
  27.         double t = U_t * w - u;  
  28.         double delta_tmp = t * t / (w * (1 - w) );   
  29.         if(delta_tmp > delta)    
  30.         {    
  31.             delta = delta_tmp;    
  32.             threshold = k;    
  33.         }    
  34.     }   
  35.     cvReleaseHist(&gray_hist);  
  36.     return threshold;     
  37. }