一、 二值化
二值化就是让图像的像素点矩阵中的每个像素点的灰度值为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