OTSU 算法可以說是自適應計算單閾值(用來轉換灰度圖像爲二值圖像)的簡單高效方法。下面的代碼最早由 Ryan Dibble提供,此後經過多人Joerg.Schulenburg, R.Z.Liu 等修改,補正。
轉自:http://forum.assuredigit.com/display_topic_threads.asp?ForumID=8&TopicID=3480
算法對輸入的灰度圖像的直方圖進行分析,將直方圖分成兩個部分,使得兩部分之間的距離最大。劃分點就是求得的閾值。
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 = &image[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);
}
關於倒數第十句,得從OTSU算法說起了,呵呵:
大津法由大津於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,根據方差的定義即得該式。因方差是灰度分佈均勻性的一種度量,方差值越大,說明構成圖像的兩部分差別越大, 當部分目標錯分爲背景或部分背景錯分爲目標都會導致兩部分差別變小,因此使類間方差最大的分割意味着錯分概率最小。
sb表示算出的sigma b(方差),從倒數第十二行註釋起吧,
if (sb > fmax) { /* 如果算出方差大於前一次算出方差,初始值fmax=-1.0*/
fmax = sb; /*那麼最大方差等於此時算出的方差 */
thresholdValue = k;/*最佳分割閾值就等於此時的灰度*/
}
另外注意,在If{}後還有一個},因爲If在FOR循環當中,for循環用於對每個灰度(從0到255)計算一次分割後的方差sb。這樣循環完畢最佳閾值就找到了!