大津法由大津於1979年提出,對圖像Image,記t爲前景與背景的分割閾值,前景點數佔圖像比例爲w0,平均灰度爲u0;背景點數佔圖像比例爲w1,平均灰度爲u1。圖像的總平均灰度爲:u=w0*u0+w1*u1。從最小灰度值到最大灰度值遍歷t,當t使得值g=w0*(u0-u)2+w1*(u1-u)2 最大時t即爲分割的最佳閾值。對大津法可作如下理解:該式實際上就是類間方差值,閾值t分割出的前景和背景兩部分構成了整幅圖像,而前景取值u0,概率爲 w0,背景取值u1,概率爲w1,總均值爲u,根據方差的定義即得該式。因方差是灰度分佈均勻性的一種度量,方差值越大,說明構成圖像的兩部分差別越大, 當部分目標錯分爲背景或部分背景錯分爲目標都會導致兩部分差別變小,因此使類間方差最大的分割意味着錯分概率最小。
直接應用大津法計算量較大,因此我們在實現時採用了等價的公式g=w0*w1*(u0-u1)2。部分計算過程如下:
- //遍歷所有灰度值求Max g。
- for intCurrentLevel:=0 to intArrLen do
- begin
- if intSclGrayLevel[intCurrentLevel]=0 then
- continue
- else
- begin
- // 計算當閾值爲intCurrentLevel時的g
- intCount:=0;
- intSumPels:=0;
- for intLoop:=0 to intCurrentLevel do
- begin
- intCount:=intCount+intSclGrayLevel[intLoop];
- intSumPels:=intSumPels+intSumPelsArr[intLoop];
- end;
- w0:=intCount/intSize;
- u0:=intSumPels/intCount;
- w1:=1-w0;
- if intSize-intCount<>0 then
- u1:=(intTotalPels-intSumPels)/(intSize-intCount)
- else
- u1:=0;
- RlTempO:=w0*w1*(u0-u1)*(u0-u1);
- if RlTempO>RlMaxO then
- begin
- RlMaxO:=RlTempO;
- Result:=intCurrentLevel;
- end;
- end;
我們在測試中發現:大津法選取出來的閾值非常理想,對各種情況的表現都較爲良好。雖然它在很多情況下都不是最佳的分割,但分割質量通常都有一定的保障,可以說是最穩定的分割。由上可知,大津算法是一種較爲通用的分割算法。在它的思想的啓迪下,人們進一步提出了多種類似的評估閾值的算法,具體可參加【5】、【6】等。
OTSU的算法,很好用,好不容易纔找到的。
- /*
- OTSU 算法可以說是自適應計算單閾值(用來轉換灰度圖像爲二值圖像)的簡單高效方法。
- 下面的代碼最早由 Ryan Dibble提供,此後經過多人Joerg.Schulenburg, R.Z.Liu 等修改,補正。
- 算法對輸入的灰度圖像的直方圖進行分析,將直方圖分成兩個部分,使得兩部分之間的距離最大。
- 劃分點就是求得的閾值。
- parameter: *image --- buffer for image
- rows, cols --- size of image
- x0, y0, dx, dy --- region of vector used for computing threshold
- vvv --- debug option, is 0, no debug information outputed
- */
- /*======================================================================*/
- /* OTSU global thresholding routine */
- /* takes a 2D unsigned char array pointer, number of rows, and */
- /* number of cols in the array. returns the value of the threshold */
- /*======================================================================*/
- int otsu (unsigned char *image, int rows, int cols, int x0, int y0, int dx, int dy, int vvv)
- {
- unsigned char *np; // 圖像指針
- int thresholdValue=1; // 閾值
- int ihist[256]; // 圖像直方圖,256個點
- int i, j, k; // various counters
- int n, n1, n2, gmin, gmax;
- double m1, m2, sum, csum, fmax, sb;
- // 對直方圖置零...
- memset(ihist, 0, sizeof(ihist));
- gmin=255; gmax=0;
- // 生成直方圖
- for (i = y0 + 1; i < y0 + dy - 1; i++) {
- np = [i*cols+x0+1];
- for (j = x0 + 1; j < x0 + dx - 1; j++) {
- ihist[*np]++;
- if(*np > gmax) gmax=*np;
- if(*np < gmin) gmin=*np;
- np++; /* next pixel */
- }
- }
- // set up everything
- sum = csum = 0.0;
- n = 0;
- for (k = 0; k <= 255; k++) {
- sum += (double) k * (double) ihist[k]; /* x*f(x) 質量矩*/
- n += ihist[k]; /* f(x) 質量 */
- }
- if (!n) {
- // if n has no value, there is problems...
- fprintf (stderr, "NOT NORMAL thresholdValue = 160/n");
- return (160);
- }
- // do the otsu global thresholding method
- fmax = -1.0;
- n1 = 0;
- for (k = 0; k < 255; k++) {
- n1 += ihist[k];
- if (!n1) { continue; }
- n2 = n - n1;
- if (n2 == 0) { break; }
- csum += (double) k *ihist[k];
- m1 = csum / n1;
- m2 = (sum - csum) / n2;
- sb = (double) n1 *(double) n2 *(m1 - m2) * (m1 - m2);
- /* bbg: note: can be optimized. */
- if (sb > fmax) {
- fmax = sb;
- thresholdValue = k;
- }
- }
- // at this point we have our thresholding value
- // debug code to display thresholding values
- if ( vvv & 1 )
- fprintf(stderr,"# OTSU: thresholdValue = %d gmin=%d gmax=%d/n",
- thresholdValue, gmin, gmax);
- return(thresholdValue);
- }