类间方差最大化阈值分割算法 (Otsu)可以自动计算图像二值化时的阈值,otsu的中心思想是阈值T应使目标与背景两类的类间方差最大。具体原理如下:otsu是按图像的灰度特性,将图像分成背景和目标两部分。背景和目标之间的类间方差越大,说明构成图像的两部分的差别越大,当部分目标错分为背景或部分背景错分为目标都会导致两部分差别变小。因此,使类间方差最大的分割意味着错分概率最小。对于图像I(x,y),前景(即目标)和背景的分割阈值记作T,属于前景的像素点数占整幅图像的比例记为ω0,其平均灰度μ0;背景像素点数占整幅图像的比例为ω1,其平均灰度为μ1。图像的总平均灰度记为μ,类间方差记为g。假设图像的背景较暗,并且图像的大小为M×N,图像中像素的灰度值小于阈值T的像素个数记作N0,像素灰度大于阈值T的像素个数记作N1,则有:
ω0=N0/M×N (1)
ω1=N1/M×N (2)
N0+N1=M×N (3)
ω0+ω1=1 (4)
μ=ω0*μ0+ω1*μ1 (5)
g=ω0(μ0-μ)^2+ω1(μ1-μ)^2 (6)
将式(5)代入式(6),得到等价公式:
g=ω0ω1(μ0-μ1)^2 (7)
采用遍历的方法得到使类间方差最大的阈值T,即为所求。
Otsu算法的C++实现代码如下,传入参数为图像数据,返回值为二值化阈值。以下代码是我经历了一个星期才出来的结果,不过肯定是对的^_^,这个代码包含了一开始的图片读取,像素值计算以及最后图片写回,一套完整的代码,写出来给大家分享一下子。
#include <malloc.h>#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "bmpfile.h"
int main ()
{
int x,y;
char *tempbuf;
char *tempbuf1;
int check_results = 0;
tempbuf = (char *)malloc(2000 * sizeof(char));
tempbuf1 = (char *)malloc(2000 * sizeof(char));
unsigned int *in_pix, *out_pix;
in_pix = (unsigned int *) malloc(MAX_HEIGHT * MAX_WIDTH * sizeof(unsigned int));
out_pix = (unsigned int *) malloc(MAX_HEIGHT * MAX_WIDTH * sizeof(unsigned int));
// Arrays to store image data
unsigned char *R;
unsigned char *G;
unsigned char *B;
R = (unsigned char *)malloc(MAX_HEIGHT * MAX_WIDTH * sizeof(int));
G = (unsigned char *)malloc(MAX_HEIGHT * MAX_WIDTH * sizeof(int));
B = (unsigned char *)malloc(MAX_HEIGHT * MAX_WIDTH * sizeof(int));
//Get image data from disk
sprintf(tempbuf, "%s.bmp", INPUT_IMAGE_BASE);
// Fill a frame with data
int read_tmp = BMP_Read(tempbuf, MAX_HEIGHT, MAX_WIDTH, R, G, B);
if(read_tmp != 0)
{
printf("%s Loading image failed\n", tempbuf);
exit (1);
}
FILE *fp;
fp=fopen("pixval.txt","w");
if(fp==NULL)
{
printf("cannot open");
}
else
{
for(int i=0; i < MAX_HEIGHT; i++)
{
for(int j=0; j < MAX_WIDTH; j++)
{
fprintf(fp, "%d", R[(MAX_HEIGHT-1-i)*MAX_WIDTH+j]);
fprintf(fp, "\n");
//printf("R %d G %d B %d\n",R[(MAX_HEIGHT-1-i)*MAX_WIDTH+j],G[(MAX_HEIGHT-1-i)*MAX_WIDTH+j],B[(MAX_HEIGHT-1-i)*MAX_WIDTH+j]);
}
}
}
fclose(fp);
int histogram[256]={0};
for(int i=0; i < MAX_HEIGHT; i++)
{
for(int j=0; j < MAX_WIDTH; j++)
{
histogram[R[(MAX_HEIGHT-1-i)*MAX_WIDTH+j]]++;
}
}
FILE *fp1;
fp1=fopen("histogram.txt","w");
if(fp1==NULL)
{
printf("cannot open");
}
else
{
for(int i=0; i < 256; i++)
{
fprintf(fp1, "%d", histogram[i]);
fprintf(fp1, "\n");
}
}
fclose(fp1);
long size = MAX_HEIGHT * MAX_WIDTH;
int threshold;
long sum0 = 0, sum1 = 0; //存储前景的灰度总和和背景灰度总和
long cnt0 = 0, cnt1 = 0; //前景的总个数和背景的总个数
double w0 = 0, w1 = 0; //前景和背景所占整幅图像的比例
double u0 = 0, u1 = 0; //前景和背景的平均灰度
double variance = 0; //最大类间方差
int i, j;
// double u = 0;
double maxVariance = 0;
for(i = 0; i < 256; i++) //一次遍历每个像素
{
sum0 = 0;
sum1 = 0;
cnt0 = 0;
cnt1 = 0;
w0 = 0;
w1 = 0;
for(j = 0; j < i; j++)
{
cnt0 += histogram[j];
sum0 += j * histogram[j];
}
if(cnt0!=0)
{
u0 = (double)sum0 / cnt0;
w0 = (double)cnt0 / size;
// printf("sum0=%d\n",sum0);
}
for(j = i ; j <= 255; j++)
{
cnt1 += histogram[j];
sum1 += j * histogram[j];
}
if(cnt1!=0)
{
u1 = (double)sum1 / cnt1;
w1 = 1 - w0; // (double)cnt1 / size;
// printf("sum0+sum1=%d\n",sum0+sum1);
}
// u = u0 * w0 + u1 * w1; //图像的平均灰度
//printf("u = %f\n", u);
//variance = w0 * pow((u0 - u), 2) + w1 * pow((u1 - u), 2);
variance = w0 * w1 * (u0 - u1) * (u0 - u1);
// printf("variance = %f\n", variance);
if(variance > maxVariance)
{
maxVariance = variance;
threshold = i;
// printf("threshold=%d\n",threshold);
}
}
printf("threshold = %d\n", threshold);
for(int i = 0; i < MAX_HEIGHT; i++)
{
for(int j = 0; j < MAX_WIDTH; j++)
{
if(R[(MAX_HEIGHT-1-i)*MAX_WIDTH+j] <= threshold)
{
R[(MAX_HEIGHT-1-i)*MAX_WIDTH+j]=255;
G[(MAX_HEIGHT-1-i)*MAX_WIDTH+j]=255;
B[(MAX_HEIGHT-1-i)*MAX_WIDTH+j]=255;
}
else
{
R[(MAX_HEIGHT-1-i)*MAX_WIDTH+j]=0;
G[(MAX_HEIGHT-1-i)*MAX_WIDTH+j]=0;
B[(MAX_HEIGHT-1-i)*MAX_WIDTH+j]=0;
}
}
}
//Write the image back to disk
sprintf(tempbuf1, "%s.bmp", OUTPUT_IMAGE_BASE);
int write_tmp = BMP_Write(tempbuf1, MAX_HEIGHT, MAX_WIDTH, R, G, B);
if(write_tmp != 0)
{
printf("WriteBMP %s failed\n", tempbuf);
exit(1);
}
free(R);
free(G);
free(B);
free(tempbuf);
free(tempbuf1);
return 0;
}