大津法由大津于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);
- }