图像二值化就就是把灰度图像分割为只有白色(前景)与黑色(背景)两种颜色的图像,通常用
0-表示黑色
1-表示白色(255)
一个典型的二值图像表示如下:
图像二值化,根据阈值使用的方法不同可以分为如下三类:
1.全局阈值法,意思是通过一个阈值实现对所有像素的二值化分割
2.局部阈值法,通过多个局部阈值实现二值化分割
3.自适应阈值法,通过与自身周围像素比较,直接二值化
Riddler-Calvard算法
Riddler-Calvard阈值法是基于直方图的二值化算法,是经典的全局阈值法,可惜OpenCV的全局阈值只支持OTSU大律法与Triangle两种,不支持Riddler-Calvard阈值法,其实Riddler-Calvard跟OTSU与Triangle一样都是基于直方图计算得到阈值的二值化分割算法,唯一个不同的是Riddler-Calvard是基于迭代查找实现,它的算法步骤描述如下:
首先假设初始阈值T比如T=127,然后得到分割后的两个像素cluster。计算它们的均值分别如下:
其中g表示图像像素灰度值,灰度值范围g={0,1,2,3,,,,,L-1},其中L=256表示256个灰度级别。P(g)表示图像直方图统计概率百分比。下标:f表示前景,b表示背景。计算得到新的阈值:
计算两个阈值之间的差值
如果差值大于指定错误,则用新阈值来计算(1)、(2)、然后计算(3),不断更新阈值,直到错误小于指定错误或者迭代次数超过指定次数为止。根据最终得到阈值T实现图像二值分割。
代码实现
初始化变量与定义
// 初始化变量
float mf = 0.0;
float mb = 0.0;
float hist[256];
float t = 127.0;
float t1 = 0.0;
float err = 1;
int max_count = 5;
for (int i = 0; i < 256; i++) {
hist[i] = 0;
}
计算直方图分布
// 计算直方图
int h = gray.rows;
int w = gray.cols;
for (int row = 0; row < h; row++) {
for (int col = 0; col < w; col++) {
int pv = gray.at<uchar>(row, col);
hist[pv]++;
}
}
// 归一化
float total = w*h;
for (int i = 0; i < 256; i++) {
hist[i] = hist[i]/total;
}
迭代寻找阈值
// 寻找阈值
int index = 0;
while (true) {
for (int i = 0; i < 256; i++) {
if (i > t) {
mf += hist[i] * i;
}
else {
mb += hist[i] * i;
}
}
t1 = (mb + mf) / 2.0;
index++;
if (abs(t1 - t) <= err || index > max_count) {
break;
}
else {
mf = 0.0;
mb = 0.0;
t = t1;
}
}
printf("final threshold value: %.2f \n", t);
使用阈值完成二值化
// 阈值使用
for (int row = 0; row < h; row++) {
for (int col = 0; col < w; col++) {
int pv = gray.at<uchar>(row, col);
if (pv > t) {
binary.at<uchar>(row, col) = 255;
}
else {
binary.at<uchar>(row, col) = 0;
}
}
}
测试运行结果如下: