一、Otsu算法原理
Otsu算法(大津法或最大類間方差法)使用的是聚類的思想,把圖像的灰度數按灰度級分成2個部分,使得兩個部分之間的灰度值差異最大,每個部分之間的灰度差異最小,通過方差的計算來尋找一個合適的灰度級別來划分。 所以可以在二值化的時候采用otsu算法來自動選取閾值進行二值化。otsu算法被認為是圖像分割中閾值選取的最佳算法,計算簡單,不受圖像亮度和對比度的影響。因此,使類間方差最大的分割意味着錯分概率最小。
設t為設定的閾值。
w0 | 分開后前景像素點數占圖像的比例 |
u0 | 分開后前景像素點的平均灰度 |
w1 | 分開后背景像素點數占圖像的比例 |
u1 | 分開后背景像素點的平均灰度 |
圖像總平均灰度為: u = w0∗u0 + w1∗u1
從L個灰度級遍歷 t,使得 t 為某個值的時候,前景和背景的方差最大,則 這個 t 值便是我們要求得的閾值。其中,方差的計算公式如下:
g = wo∗(u0−u)∗(u0−u) + w1∗(u1−u)∗(u1−u)
此公式計算量較大,可以采用:
g = w0∗w1∗(u0−u1)∗(u0−u1)
由於Otsu算法是對圖像的灰度級進行聚類,因此在執行Otsu算法之前,需要計算該圖像的灰度直方圖。
二、代碼實現算法
第一份代碼:來自Augusdi
1 #include<iostream> 2 #include<opencv\cv.h> 3 //#include<opencv2\highgui.hpp> 4 #include<opencv2\highgui\highgui.hpp> 5 6 int Otsu(IplImage* src); 7 8 int main() 9 { 10 IplImage* img = cvLoadImage("E:\\house.jpg", 0); 11 IplImage* dst = cvCreateImage(cvGetSize(img), 8, 1); 12 int threshold = Otsu(img); 13 14 cvThreshold(img, dst, threshold, 255, CV_THRESH_BINARY); 15 16 17 cvNamedWindow("img", 1); 18 cvShowImage("img", dst); 19 20 21 cvWaitKey(-1); 22 23 cvReleaseImage(&img); 24 cvReleaseImage(&dst); 25 26 cvDestroyWindow("dst"); 27 return 0; 28 } 29 30 int Otsu(IplImage* src) 31 { 32 int height = src->height; 33 int width = src->width; 34 long size = height * width; 35 36 //histogram 37 float histogram[256] = { 0 }; 38 for (int m = 0; m < height; m++) 39 { 40 unsigned char* p = (unsigned char*)src->imageData + src->widthStep * m; 41 for (int n = 0; n < width; n++) 42 { 43 histogram[int(*p++)]++; 44 } 45 } 46 47 int threshold; 48 long sum0 = 0, sum1 = 0; //存儲前景的灰度總和和背景灰度總和 49 long cnt0 = 0, cnt1 = 0; //前景的總個數和背景的總個數 50 double w0 = 0, w1 = 0; //前景和背景所占整幅圖像的比例 51 double u0 = 0, u1 = 0; //前景和背景的平均灰度 52 double variance = 0; //最大類間方差 53 int i, j; 54 double u = 0; 55 double maxVariance = 0; 56 for (i = 1; i < 256; i++) //一次遍歷每個像素 57 { 58 sum0 = 0; 59 sum1 = 0; 60 cnt0 = 0; 61 cnt1 = 0; 62 w0 = 0; 63 w1 = 0; 64 for (j = 0; j < i; j++) 65 { 66 cnt0 += histogram[j]; 67 sum0 += j * histogram[j]; 68 } 69 70 u0 = (double)sum0 / cnt0; 71 w0 = (double)cnt0 / size; 72 73 for (j = i; j <= 255; j++) 74 { 75 cnt1 += histogram[j]; 76 sum1 += j * histogram[j]; 77 } 78 79 u1 = (double)sum1 / cnt1; 80 w1 = 1 - w0; // (double)cnt1 / size; 81 82 u = u0 * w0 + u1 * w1; //圖像的平均灰度 83 printf("u = %f\n", u); 84 //variance = w0 * pow((u0 - u), 2) + w1 * pow((u1 - u), 2); 85 variance = w0 * w1 * (u0 - u1) * (u0 - u1); 86 if (variance > maxVariance) 87 { 88 maxVariance = variance; 89 threshold = i; 90 } 91 } 92 93 printf("threshold = %d\n", threshold); 94 return threshold; 95 }
第二份代碼:
1 #include <opencv\cv.h> 2 #include <opencv2\highgui\highgui.hpp> 3 4 int otsu(IplImage *image) 5 { 6 assert(NULL != image); 7 8 int width = image->width; 9 int height = image->height; 10 int x = 0, y = 0; 11 int pixelCount[256]; 12 float pixelPro[256]; 13 int i, j, pixelSum = width * height, threshold = 0; 14 15 uchar* data = (uchar*)image->imageData; 16 17 //初始化 18 for (i = 0; i < 256; i++) 19 { 20 pixelCount[i] = 0; 21 pixelPro[i] = 0; 22 } 23 24 //統計灰度級中每個像素在整幅圖像中的個數 25 for (i = y; i < height; i++) 26 { 27 for (j = x; j <width; j++) 28 { 29 pixelCount[data[i * image->widthStep + j]]++; 30 } 31 } 32 33 //計算每個像素在整幅圖像中的比例 34 for (i = 0; i < 256; i++) 35 { 36 pixelPro[i] = (float)(pixelCount[i]) / (float)(pixelSum); 37 } 38 39 //經典ostu算法,得到前景和背景的分割 40 //遍歷灰度級[0,255],計算出方差最大的灰度值,為最佳閾值 41 float w0, w1, u0tmp, u1tmp, u0, u1, u, deltaTmp, deltaMax = 0; 42 for (i = 0; i < 256; i++) 43 { 44 w0 = w1 = u0tmp = u1tmp = u0 = u1 = u = deltaTmp = 0; 45 46 for (j = 0; j < 256; j++) 47 { 48 if (j <= i) //背景部分 49 { 50 //以i為閾值分類,第一類總的概率 51 w0 += pixelPro[j]; 52 u0tmp += j * pixelPro[j]; 53 } 54 else //前景部分 55 { 56 //以i為閾值分類,第二類總的概率 57 w1 += pixelPro[j]; 58 u1tmp += j * pixelPro[j]; 59 } 60 } 61 62 u0 = u0tmp / w0; //第一類的平均灰度 63 u1 = u1tmp / w1; //第二類的平均灰度 64 u = u0tmp + u1tmp; //整幅圖像的平均灰度 65 //計算類間方差 66 deltaTmp = w0 * (u0 - u)*(u0 - u) + w1 * (u1 - u)*(u1 - u); 67 //找出最大類間方差以及對應的閾值 68 if (deltaTmp > deltaMax) 69 { 70 deltaMax = deltaTmp; 71 threshold = i; 72 } 73 } 74 //返回最佳閾值; 75 return threshold; 76 } 77 78 int main(int argc, char* argv[]) 79 { 80 IplImage* srcImage = cvLoadImage("house.jpg", 0); 81 assert(NULL != srcImage); 82 83 cvNamedWindow("src"); 84 cvShowImage("src", srcImage); 85 86 IplImage* biImage = cvCreateImage(cvGetSize(srcImage), 8, 1); 87 88 //計算最佳閾值 89 int threshold = otsu(srcImage); 90 printf("threshold = %d\n", threshold); 91 //對圖像二值化 92 cvThreshold(srcImage, biImage, threshold, 255, CV_THRESH_BINARY); 93 94 cvNamedWindow("binary"); 95 cvShowImage("binary", biImage); 96 97 cvWaitKey(0); 98 99 cvReleaseImage(&srcImage); 100 cvReleaseImage(&biImage); 101 cvDestroyWindow("src"); 102 cvDestroyWindow("binary"); 103 104 return 0; 105 }
三、代碼運行結果
代碼1運行結果:
代碼2運行結果: