一、 二值化
二值化就是让图像的像素点矩阵中的每个像素点的灰度值为0(黑色)或者255(白色),也就是让整个图像呈现只有黑和白的效果。在灰度化的图像中灰度值的范围为0~255,在二值化后的图像中的灰度值范围是0或者255。
黑色:
二值化后的R = 0
二值化后的G = 0
二值化后的B = 0
白色:
二值化后的R = 255
二值化后的G = 255
二值化后的B = 255
那么一个像素点在灰度化之后的灰度值怎么转化为0或者255呢?比如灰度值为100,那么在二值化后到底是0还是255?这就涉及到取一个阀值的问题。
三、阈值求取算法
1. 傻瓜法
2. 大律二值算法
将图像理解成255个图层,每一层分布了不同的像素,这些像素垂直叠加合成了一张完整的灰度图。就是找到一个合适的灰度值,大于这个值的我们将它称之为背景(灰度值越大像素越黑),小于这个值的我们
将它称之为前景(灰度值越小像素越白)。
h:图像的宽度
w:图像的高度(h*w 得到图像的像素数量)
t :灰度阈值(我们要求的值,大于这个值的像素我们将它的灰度设置为255,小于的设置为0)
n0:小于阈值的像素,前景
n1:大于等于阈值的像素,背景
n0 + n1 == h * w
w0:前景像素数量占总像素数量的比例
w0 = n0 / (h * w)
w1:背景像素数量占总像素数量的比例
w1 = n1 / (h * w)
w0 + w1 == 1
u0:前景平均灰度
u0 = n0灰度累加和 / n0
u1:背景平均灰度
u1 = n1灰度累加和 / n1
u:平均灰度
u = (n0灰度累加和 + n1灰度累加和) / (h * w) 根据上面的关系
u = w0 * u0 + w1 * u1
g:类间方差(那个灰度的g最大,哪个灰度就是需要的阈值t)
g = w0 * (u0 - u)^2 + w1 * (u1 - u)^2
根据上面的关系,可以推出:(这个一步一步推导就可以得到)
g = w0 * w1 * (u0 - u1) ^ 2
然后,遍历每一个灰度值,找到这个灰度值对应的 g
找到最大的 g 对应的 t;
算法实现
Mat Binary_OSTU(Mat img)
{
// get height and width
int width = img.cols;
int height = img.rows;
Mat out = Mat::zeros(height, width, CV_8UC1);
double p0 = 0;
double u0 = 0;
double p1 = 0;
double u1 = 0;
double n0 = 0;
double n1 = 0;
int val;
double max_sb = 0, sb = 0;
int threshold = 0;
for (int k = 0; k < 255; k++)
{
p0 = 0;
u0 = 0;
p1 = 0;
u1 = 0;
n0 = 0;
n1 = 0;
//分为两类
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
val = (int)(img.at<uchar>(i, j));
if (val < k)
{
n0++;
u0 += val;
}
else
{
n1++;
u1 += val;
}
}
}
//求出概率,均值
u0 = u0 / n0; //第一类均值
u1 = u1 / n1;
p0 = n0 / (width* height);
p1 = n1 / (width* height);
sb = p0 * p1*pow((u0 - u1), 2);
if (sb > max_sb)
{
max_sb = sb;
threshold = k;
}
}
cout << "threshold: " << threshold << endl;
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
if ((int)img.at<uchar>(i, j) > threshold)
out.at<uchar>(i, j) = 255;
else
out.at<uchar>(i, j) = 0;
}
}
return out;
}
参考文献:
Otsu N. A threshold selection method from gray-level histogram. IEEE Trans,1979;SMC-9;62-66