图像的OTSU阈值化、双阈值化、半阈值化的原理及OpenCV代码实现
本文的基础是OpenCV的函数threshold(),关于函数threshold()的详细介绍,大家可以参考下面这篇博文:
https://www.hhai.cc/thread-162-1-1.html
一、图像的OTSU阈值化
在阈值化处理中,OTSU是一种常用的算法,中文译为大津法或最大类间方差法。
它是1979年由日本学者大津展之提出的一种对图像进行二值化的高效算法,是在判别与最小二乘法原理的基础上推导出来的。
它的基本思路是把直方图在某一阈值处分割成两组,当被分成的两组间方差为最大时,这一阈值为“决定阈值”。
OTSU的算法步骤如下:
⑴统计灰度级中每个像素在整幅图像中的个数。
⑵计算每个像素在整幅图像的概率分布。
⑶对灰度级进行遍历搜索,计算当前灰度值下前景背景类间概率。
⑷通过目标函数计算出类内与类间方差下对应的阈值。
更具体的原理介绍大家可以参考下面这篇博文:
https://blog.csdn.net/weixin_44227356/article/details/116031602
图像的OTSU阈值化可以直接由函数threshold()实现,具体实现的代码我已经写在下面这篇博文中了:
https://www.hhai.cc/thread-162-1-1.html【打开页面后搜索“当type取值为THRESH_OTSU 、THRESH_TRIANGLE时的示例代码”】
在上面链接的博文中我已经说了,函数threshold()的Python版才会返回进行阈值化时所使用的阈值,C++版本则不会,所以在OpenCV-C++环境下,函数threshold()使用OTSU算法进行阈值化处理时用的阈值我们是不知道的。如果想知道OTSU算法的阈值是多少,只有自己根据OTSU算法的原理写出代码进行计算。
这里昊虹君根据OTSU算法的原理写了一个计算OTSU阈值计算的函数,代码如下:
下面的示例代码中用到的图像下载链接如下:
https://pan.baidu.com/s/1PqeJG6EbONmYg03jYQb2MQ?pwd=xu7b
[C++] 纯文本查看 复制代码
//出处:昊虹AI笔记网(hhai.cc)
//用心记录计算机视觉和AI技术
//博主微信/QQ 2487872782
//QQ群 271891601
//欢迎技术交流与咨询
//OpenCV版本 OpenCV3.0
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int OTSU(cv::Mat srcImage)
{
int nCols = srcImage.cols;
int nRows = srcImage.rows;
int threshold = 0;
// 初始化统计参数
int nSumPix[256];
float nProDis[256];
for (int i = 0; i < 256; i++)
{
nSumPix[i] = 0;
nProDis[i] = 0;
}
// 统计灰度级中每个像素在整幅图像中的个数
for (int i = 0; i < nRows; i++)
{
for (int j = 0; j < nCols; j++)
{
nSumPix[(int)srcImage.at<uchar>(i, j)]++;
}
}
// 计算每个灰度级占图像中的概率分布
for (int i = 0; i < 256; i++)
{
nProDis[i] = (float)nSumPix[i] / (nCols * nRows);
}
// 遍历灰度级[0,255],计算出最大类间方差下的阈值
float w0, w1, u0_temp, u1_temp, u0, u1, delta_temp;
double delta_max = 0.0;
for (int i = 0; i < 256; i++)
{
// 初始化相关参数
w0 = w1 = u0_temp = u1_temp = u0 = u1 = delta_temp = 0;
for (int j = 0; j < 256; j++)
{
//背景部分
if (j <= i)
{
// 当前i为分割阈值,第一类总的概率
w0 += nProDis[j];
u0_temp += j * nProDis[j];
}
//前景部分
else
{
// 当前i为分割阈值,第一类总的概率
w1 += nProDis[j];
u1_temp += j * nProDis[j];
}
}
// 分别计算各类的平均灰度
u0 = u0_temp / w0;
u1 = u1_temp / w1;
delta_temp = (float)(w0 *w1* pow((u0 - u1), 2));
// 依次找到最大类间方差下的阈值
if (delta_temp > delta_max)
{
delta_max = delta_temp;
threshold = i;
}
}
return threshold;
}
int main()
{
// 读取源图像
cv::Mat image = cv::imread("F:/material/images/2022/2022-06/img_300_320.jpg", 1);
if (image.empty())
return -1;
//imshow("image", image);
Mat img_gray;
cvtColor(image, img_gray, COLOR_BGR2GRAY);
imshow("img_gray", img_gray);
int OTSU_threshold;
OTSU_threshold = OTSU(img_gray);
cout << "OTSU_threshold is: " << OTSU_threshold << endl << endl;
cv::waitKey(0);
return 0;
}
运行结果如下:
上面的阈值与我们在Python-OpenCV环境下使用函数threshold()得到的OTSU阈值是一样的:
上面这张截图来源于下面这篇博文:
https://www.hhai.cc/thread-162-1-1.html
二、图像的双阈值化
有时候图像中有明显的双分界特征,我们考虑用双阈值法进行阈值化操作。
所谓双阈值化是指对于两个阈值thresh1<thresh2,将大于thresh1且小于thresh2的灰度值设定为maxval,其余的灰度值设为0。
其数学表达式如下:
可以用函数threshold()实现图像的双阈值化,其C++代码如下:
[C++] 纯文本查看 复制代码
//出处:昊虹AI笔记网(hhai.cc)
//用心记录计算机视觉和AI技术
//博主微信/QQ 2487872782
//QQ群 271891601
//欢迎技术交流与咨询
//OpenCV版本 OpenCV3.0
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main()
{
cv::Mat A1 = (cv::Mat_<uchar>(2, 3) << 0, 10, 50,
80, 150, 255);
cout << "A1中的数据为:\n" << A1 << endl << endl;
double low_threshold = 70;
double high_threshold = 180;
double maxval1 = 222;
Mat B1, B2, B3, B4, B5;
// 小阈值对图像或矩阵进行阈值化操作
threshold(A1, B1, low_threshold, maxval1, THRESH_BINARY);
cout << "B1中的数据为:\n" << B1 << endl << endl;
// 大阈值对图像或矩阵进行阈值化操作
threshold(A1, B2, high_threshold, maxval1, THRESH_BINARY_INV);
cout << "B2中的数据为:\n" << B2 << endl << endl;
// 矩阵与运算得到阈值化结果
cv::bitwise_and(B1, B2, B3);
cout << "B3中的数据为:\n" << B3 << endl << endl;
return(0);
}
运行结果如下图所示:
从上面的运行结果我们可以看出,原矩阵中介于阈值70到180之间的灰度值被置为了222,其它灰度值被置为了0。
二、图像的半阈值化
如果图像中有明显的目标与背景的差异特征,那么进行阈值化操作时,可以使用半阈值化进行操作。
半阈值化操作时,大于thresh的灰度值不变,其余灰度值置为0。
半阈值化的数学表达式如下:
函数threshold(),当参数type取值为“THRESH_TOZERO”时,实际上就是作的图像的半阈值化操作。
示例代码之前在博文https://www.hhai.cc/thread-162-1-1.html中就已经给出了,这里就不再给了。
|