干货 | 简单粗暴二分类分割方法Riddler-Calvard 详解!
点击上方蓝字关注我们
微信公众号:OpenCV学堂
关注获取更多计算机视觉与深度学习知识
二值图像
图像二值化就就是把灰度图像分割为只有白色(前景)与黑色(背景)两种颜色的图像,通常用
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实现图像二值分割。
代码实现
初始化变量与定义
1// 初始化变量
2float mf = 0.0;
3float mb = 0.0;
4float hist[256];
5float t = 127.0;
6float t1 = 0.0;
7float err = 1;
8int max_count = 5;
9for (int i = 0; i < 256; i++) {
10 hist[i] = 0;
11}
计算直方图分布
1// 计算直方图
2int h = gray.rows;
3int w = gray.cols;
4for (int row = 0; row < h; row++) {
5 for (int col = 0; col < w; col++) {
6 int pv = gray.at<uchar>(row, col);
7 hist[pv]++;
8 }
9}
10
11// 归一化
12float total = w*h;
13for (int i = 0; i < 256; i++) {
14 hist[i] = hist[i]/total;
15}
迭代寻找阈值
1// 寻找阈值
2int index = 0;
3while (true) {
4 for (int i = 0; i < 256; i++) {
5 if (i > t) {
6 mf += hist[i] * i;
7 }
8 else {
9 mb += hist[i] * i;
10 }
11 }
12 t1 = (mb + mf) / 2.0;
13 index++;
14 if (abs(t1 - t) <= err || index > max_count) {
15 break;
16 }
17 else {
18 mf = 0.0;
19 mb = 0.0;
20 t = t1;
21 }
22}
23printf("final threshold value: %.2f \n", t);
使用阈值完成二值化
1// 阈值使用
2for (int row = 0; row < h; row++) {
3 for (int col = 0; col < w; col++) {
4 int pv = gray.at<uchar>(row, col);
5 if (pv > t) {
6 binary.at<uchar>(row, col) = 255;
7 }
8 else {
9 binary.at<uchar>(row, col) = 0;
10 }
11 }
12}
测试运行结果如下:
推荐阅读
对象检测新趋势anchor-free模型之CenterNet
OpenCV4 | 如何一行代码搞定SSD模型推理与结果解析