自适应阈值图像二值化
一、二值化
关于二值化的介绍,以前的博客中有介绍,这里就不再描述了,二值化介绍;二值化分为固定阈值二值化和自适应阈值二值化,固定阈值二值化方式是我们常用的二值化方式,需要自己摸索一个经验阈值,不断调整,直到找到最佳阈值,这种方式在刚刚的链接中已经介绍;而这篇文档主要介绍的就是另一种二值化方式:自适应阈值二值化。
二、自适应阈值二值化
图像进行二值化,且做到自适应阈值参数,有4种自适应阈值二值化方法;先从自适应阈值的作用范围来区分,自适应阈值分为:
- 全局阈值
使用自适应全局阈值的全局二值化方法有:大津法图像二值化、三角法图像二值化;
- 局部阈值
使用自适应局部阈值的局部二值化方法有:局部均值处理、局部高斯处理;
三、大津法图像二值化
OTSU算法也称最大类间差法,有时也称之为大津算法,由大津于1979年提出,被认为是图像分割中阈值选取的最佳算法,计算简单,不受图像亮度和对比度的影响,因此在数字图像处理上得到了广泛的应用。它是按图像的灰度特性,将图像分成背景和前景两部分。因方差是灰度分布均匀性的一种度量,背景和前景之间的类间方差越大,说明构成图像的两部分的差别越大,当部分前景错分为背景或部分背景错分为前景都会导致两部分差别变小。因此,使类间方差最大的分割意味着错分概率最小。
大津法二值化适用于图像直方图中存在双峰的图像(直方图中的双峰就是指背景像素和前景像素),最佳阈值就是双峰之间的某个参数,即将背景像素和前景像素分割开,其原理就是最大类间方差法。
大津法二值化的大致算法思路如下:
- 计算灰度图像的直方图,计算出0 - 255 每个像素值所占的像素个数;
- 遍历阈值 0 - 255,小于或等于阈值的像素为背景,大于阈值的像素为前景;
- 计算背景像素个数所占总像素个数的比例、背景像素的平均值;
- 计算前景像素个数所占总像素个数的比例、前景像素的平均值;
- 计算类间方差或类内方差,当使类间方差最大或者使类内方差最小的阈值,即为最佳阈值;
- 使用最佳阈值,对图像进行二值化处理。
思路细化:
- 图像宽[w],图像高度[h],灰度阈值[T];遍历T,从0 - 255 ;
- 小于阈值T的像素个数[c0],c0为背景像素个数;大于阈值T的像素个数[c1],c1为前景像素个数;c0 + c1 = w * h ;
- 背景像素个数占总像素个数的比例[w0],w0 = c0 / (w * h) ; 前景像素个数占总像素个数的比例[w1],w1 = c1 / (w * h) ; 且w0 + w1 = 1 ;
- 背景的平均像素灰度值[u0],u0 = c0个背景像素灰度值之和 / c0 ;前景的平均像素灰度值[u1],u1 = c1个前景像素灰度值之和 / c1 ;
- 整张图像的像素灰度平均值[u],u = (c0个背景像素灰度值之和 + c1个前景像素灰度值之和) / (w * h) ;
- 类间方差[g],g = w0 * (u0 - u)^2 + w1 * (u1 - u)^2 ;类间方差指的是前景和背景之间的差异,显然该差异越大,说明分离度越好。
- 根据第(6)步,推导后类间方差g = w0 * w1 * (u0 - u1) ^ 2 ;
- 找到最大类间方差对应的灰度阈值T,即是最佳阈值。
除了最大类间方差,也可以通过计算最小类内方差来得到最佳阈值,这里有篇博客介绍到:链接
对于一些噪声较多的图像,可以先使用高斯滤波去噪,再用大津法对图像进行二值化,这样会使二值化的图像效果更好
OpenCV中有大津法二值化的接口:
double threshold(InputArray src, OutputArray dst, double thresh, double maxVal, int thresholdType)
将第5个参数 thresholdType 设置成 THRESH_OTSU 即可,可以将THRESH_OTSU 和THRESH_BINARY等类型配合使用;当使用了THRESH_OTSU,函数threshold()返回值即是找到的最佳阈值,且函数中第三个参数thresh将不起作用。
最大类间方差法 实现代码:
//二值化处理,自适应阈值 大津法
int Binarization::BinaryProcessing_OTSU(Mat& srcImg)
{//【1】安全性检查if (!srcImg.data || srcImg.data == NULL) {cout << "BinaryProcessing_OTSU() --> srcImg读取失败" << endl;return MTC_FAIL;}//【2】图像灰度化Mat grayImg;cvtColor(srcImg, grayImg, CV_BGR2GRAY);//【3】获取最佳二值化阈值int nBestTH = 0;int nRet = GetBestTH_OTSU(grayImg, nBestTH);if (nRet != MTC_SUCCESS) {cout << "BinaryProcessing_OTSU() --> 获取最佳二值化阈值 失败" << endl;return MTC_FAIL;}cout << "BinaryProcessing_OTSU() --> 最佳二值化阈值 = " << nBestTH << endl;//【4】图像二值化Mat binaryImg;threshold(grayImg, binaryImg, nBestTH, 255, CV_THRESH_BINARY);//【5】显示图像imshow("二值化图像", binaryImg);return MTC_SUCCESS;
}//获取最佳阈值,自适应阈值 大津法(最大类间差法)
int Binarization::GetBestTH_OTSU(Mat& grayImg, int& nBestTH)
{//【1】安全性检查if (!grayImg.data || grayImg.data == NULL){cout << "GetBestTH_OTSU() --> grayImg读取失败" << endl;return MTC_FAIL;}if (grayImg.channels() != 1) {cout << "GetBestTH_OTSU() --> grayImg不是灰度图像" << endl;return MTC_FAIL;}//【2】参数准备double sum = 0.0; //所有像素灰度之和double w0 = 0.0; //背景像素所占比例double w1 = 0.0; //前景像素所占比例double u0_temp = 0.0;double u1_temp = 0.0;double u0 = 0.0; //背景平均灰度double u1 = 0.0; //前景平均灰度double delta_temp = 0.0; //类间方差double delta_max = 0.0; //最大类间方差const int GrayScale = 256;//src_image灰度级 int pixel_count[GrayScale] = { 0 }; //每个灰度级的像素数目float pixel_pro[GrayScale] = { 0 }; //每个灰度级的像素数目占整幅图像的比例 int height = grayImg.rows;int width = grayImg.cols;//统计每个灰度级中像素的个数 for (int i = 0; i < height; i++){for (int j = 0; j < width; j++){int index = i * width + j;pixel_count[(int)grayImg.data[index]]++; //每个灰度级的像素数目sum += (int)grayImg.data[index]; //灰度之和}}cout << "平均灰度:" << sum / (height * width) << endl;//计算每个灰度级的像素数目占整幅图像的比例 int imgArea = height * width;for (int i = 0; i < GrayScale; i++){pixel_pro[i] = (float)pixel_count[i] / imgArea;}//遍历灰度级[0,255],寻找合适的threshold for (int i = 0; i < GrayScale; i++){w0 = w1 = u0_temp = u1_temp = u0 = u1 = delta_temp = 0;for (int j = 0; j < GrayScale; j++){if (j <= i) //背景部分 {w0 += pixel_pro[j]; //背景像素比例u0_temp += j * pixel_pro[j];}else //前景部分 {w1 += pixel_pro[j]; //前景像素比例u1_temp += j * pixel_pro[j];}}u0 = u0_temp / w0; //背景像素点的平均灰度u1 = u1_temp / w1; //前景像素点的平均灰度delta_temp = (float)(w0 * w1 * pow((u0 - u1), 2)); //类间方差 g=w0*w1*(u0-u1)^2//当类间方差delta_temp最大时,对应的i就是阈值Tif (delta_temp > delta_max){delta_max = delta_temp;nBestTH = i;}}return MTC_SUCCESS;
}
OpenCV接口,实现代码:
//二值化处理,自适应阈值 大津法 opencv自带接口
int Binarization::BinaryProcessing_OTSU_OpenCV(Mat& srcImg)
{//【1】安全性检查if (!srcImg.data || srcImg.data == NULL){cout << "BinaryProcessing_OTSU() --> srcImg读取失败" << endl;return MTC_FAIL;}//【2】图像灰度化Mat grayImg;cvtColor(srcImg, grayImg, CV_BGR2GRAY);//【3】图像二值化Mat binaryImg;double dBestTH = threshold(grayImg, binaryImg, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU); //CV_THRESH_OTSUcout << "BinaryProcessing_OTSU_OpenCV() --> dBestTH = " << dBestTH << endl;//【4】显示图像imshow("二值化图像-opencv", binaryImg);return MTC_SUCCESS;
}
四、三角法图像二值化
三角法二值化,适用于图像直方图中存在单峰的图像,这是一种纯几何的方法来寻找最佳阈值,它的成立条件是假设直方图最大波峰在靠近最亮的一侧,然后通过三角形求得最大直线距离,根据最大直线距离对应的直方图灰度等级即为分割阈值,图示如下:
在直方图上从最高峰处bmx到最暗对应直方图bmin(p=0)%构造一条直线,从bmin处开始计算每个对应的直方图b到直线的垂直距离,直到bmax为止,其中最大距离对应的直方图位置即为图像二值化对应的阈值T。
三角法二值化算法步骤:
(1)图像转灰度
(2)计算图像灰度直方图
(3)寻找直方图中两侧边界
(4)寻找直方图最大值
(5)检测是否最大波峰在亮的一侧,否则翻转
(6)求解到直线的最大值
设灰度级别为L,频率为α,当频率αmax最大的时候设L=L_αmax,当Lmin时,α=α_Lmin
- 求解直线方程:根据点(Lmin,α_Lmin)和点(L_αmax,αmax)可以确定直线l的方程;
- 求解各点到直线的距离:各点(L,α)到直线l的距离d,根据点到直线的距离公式可以求得,用一个列表去存放所有的距离d,然后利用max函数即可求得dmax;
- 找到当点(L,α)到直线l的距离d最大时,灰度级别L的值即为最佳阈值;
(7)确定最佳阈值T,如果翻转则最佳阈值为255 - T
(8)使用最佳阈值,对图像进行二值化处理。
OpenCV中有三角法二值化的接口:
double threshold(InputArray src, OutputArray dst, double thresh, double maxVal, int thresholdType)
将第5个参数 thresholdType 设置成 THRESH_TRIANGLE即可,可以将THRESH_TRIANGLE 和THRESH_BINARY等类型配合使用;当使用了THRESH_TRIANGLE,函数threshold()返回值即是找到的最佳阈值,且函数中第三个参数thresh将不起作用。
几何法 实现代码:
//二值化处理,自适应阈值 三角法
int Binarization::BinaryProcessing_Triangle(Mat& srcImg)
{//【1】安全性检查if (!srcImg.data || srcImg.data == NULL){cout << "BinaryProcessing_Triangle() --> srcImg读取失败" << endl;return MTC_FAIL;}//【2】图像灰度化Mat grayImg;cvtColor(srcImg, grayImg, CV_BGR2GRAY);//【3】获取最佳二值化阈值int nBestTH = 0;int nRet = GetBestTH_Triangle(grayImg, nBestTH);if (nRet != MTC_SUCCESS){cout << "BinaryProcessing_Triangle() --> 获取最佳二值化阈值 失败" << endl;return MTC_FAIL;}cout << "BinaryProcessing_Triangle() --> 最佳二值化阈值 = " << nBestTH << endl;//【4】图像二值化Mat binaryImg;threshold(grayImg, binaryImg, nBestTH, 255, CV_THRESH_BINARY);//【5】显示图像imshow("二值化图像", binaryImg);return MTC_SUCCESS;
}//获取最佳阈值,自适应阈值 三角法
int Binarization::GetBestTH_Triangle(Mat& grayImg, int& nBestTH)
{//【1】安全性检查if (!grayImg.data || grayImg.data == NULL){cout << "GetBestTH_Triangle() --> grayImg读取失败" << endl;return MTC_FAIL;}if (grayImg.channels() != 1){cout << "GetBestTH_Triangle() --> grayImg不是灰度图像" << endl;return MTC_FAIL;}//【2】参数准备const int GrayScale = 256;int pixel_count[GrayScale] = { 0 }; //每个灰度级的像素数目int height = grayImg.rows;int width = grayImg.cols;int left_bound = 0; //最左边零的位置int right_bound = 0; //最右边零的位置int max_mid = 0; //像素数量最多的灰度级位置bool bIsFlipped = false; //是否将直方图左右翻转//【3】统计每个灰度级的像素数目for (int i = 0; i < height; i++){for (int j = 0; j < width; j++){int index = i * width + j;pixel_count[grayImg.data[index]]++;}}//【4】找到最左边零的位置for (int i = 0; i < GrayScale; i++){if (pixel_count[i] > 0) {left_bound = i;break;}}//位置再移动一个步长,即为最左侧零位置if (left_bound > 0)left_bound--;//【5】找到最右边零的位置for (int i = GrayScale - 1; i >= 0; i--){if (pixel_count[i] > 0){right_bound = i;break;}}//位置再移动一个步长,即为最右侧零位置if (right_bound < GrayScale - 1)right_bound++;//【6】找到像素数量最多的灰度级位置int maxNum = 0;for (int i = 0; i < GrayScale; i++){if (pixel_count[i] > maxNum) {maxNum = pixel_count[i];max_mid = i;}}//【7】如果最大值(max_mid)位置落在靠左侧这样就无法满足三角法求阈值,所以要检测是否最大值(max_mid)位置是否靠近左侧//如果靠近左侧则通过翻转到右侧位置if (max_mid - left_bound < right_bound - max_mid) {int i = 0;int j = GrayScale - 1;int temp = 0;while (i < j){temp = pixel_count[i];pixel_count[i] = pixel_count[j];pixel_count[j] = temp;i++;j--;}bIsFlipped = true;left_bound = GrayScale - 1 - right_bound;max_mid = GrayScale - 1 - max_mid;}//【8】计算求得阈值nBestTH = left_bound;int a = maxNum;int b = left_bound - max_mid;float maxDist = 0;for (int i = left_bound + 1; i <= max_mid; i++){//计算距离(点到直线的距离 (Ax + Bx + C) / 根号[A的平方 + B的平方] //因为只有 Ax+Bx 是变化的,而我们的目的是比较距离大小,所以只计算 Ax+Bx 的值)float tempDist = a * i + b * pixel_count[i];if (tempDist > maxDist) {maxDist = tempDist;nBestTH = i;}}nBestTH--;//【9】对已经得到的最佳阈值,如果前面已经翻转了,则阈值要用 255 - nBestTHif (bIsFlipped)nBestTH = GrayScale - 1 - nBestTH;return MTC_SUCCESS;
}
OpenCV接口,实现代码:
//二值化处理,自适应阈值 三角法 opencv自带接口
int Binarization::BinaryProcessing_Triangle_OpenCV(Mat& srcImg)
{//【1】安全性检查if (!srcImg.data || srcImg.data == NULL){cout << "BinaryProcessing_Triangle_OpenCV() --> srcImg读取失败" << endl;return MTC_FAIL;}//【2】图像灰度化Mat grayImg;cvtColor(srcImg, grayImg, CV_BGR2GRAY);//【3】图像二值化Mat binaryImg;double dBestTH = threshold(grayImg, binaryImg, 0, 255, CV_THRESH_BINARY | CV_THRESH_TRIANGLE); //CV_THRESH_TRIANGLEcout << "BinaryProcessing_Triangle_OpenCV() --> dBestTH = " << dBestTH << endl;//【4】显示图像imshow("二值化图像-opencv", binaryImg);return MTC_SUCCESS;
}
五、自适应局部阈值图像二值化
全局阈值图像二值化 只可以对整张图像使用同一个阈值进行二值化,如果图像中亮度分布不均匀,每个区域亮度都有差别,那么再使用全局阈值图像二值化,会导致部分信息缺失。
而自适应局部阈值化能够根据图像不同区域亮度分布,来改变阈值。
OpenCV中集成了这样的方法,接口如下:
void adaptiveThreshold(InputArray src, OutputArray dst, double maxValue, int adaptiveMethod, int thresholdType, int blockSize, double C)
参数介绍:
src参数 表示输入图像(8位单通道图像);
maxValue参数 表示使用 THRESH_BINARY 和 THRESH_BINARY_INV 的最大值;
adaptiveMethod参数 表示自适应阈值算法,平均 (ADAPTIVE_THRESH_MEAN_C)或高斯(ADAPTIVE_THRESH_GAUSSIAN_C);
thresholdType参数表示阈值类型,必须为THRESH_BINARY或THRESH_BINARY_INV的阈值类型;
blockSize参数 表示块大小(奇数且大于1,比如3,5,7........ );
C参数是常数,表示从平均值或加权平均值中减去的数。通常情况下,这是正值,但也可能为零或负值。
(1)局部均值法图像二值化
将参数adaptiveMethod 设置为ADAPTIVE_THRESH_MEAN_C,自适应阈值T(x, y),通过计算像素(x, y)周围blockSize x blockSize大小像素块的平均值并减去常量 C 得到。
(2)局部高斯处理图像二值化
将参数adaptiveMethod 设置为ADAPTIVE_THRESH_GAUSSIAN_C,自适应阈值T(x, y),通过计算像素(x, y)周围blockSize x blockSize大小像素块的加权求和(与高斯窗口相关)并减去常量 C 得到。
如果使用平均的方法,则所有像素周围的权值相同;
如果使用高斯的方法,则每个像素周围像素的权值则根据其到中心点的距离通过高斯方程得到。
OpenCV接口 实现代码:
//自适应阈值二值化 均值
int Binarization::AdaptiveThreshold_Mean(Mat& srcImg)
{//【1】安全性检查if (!srcImg.data || srcImg.data == NULL){cout << "AdaptiveThreshold_Mean() --> srcImg读取失败" << endl;return MTC_FAIL;}//【2】图像灰度化Mat grayImg;cvtColor(srcImg, grayImg, CV_BGR2GRAY);//【3】自适应阈值二值化Mat binaryImg;adaptiveThreshold(grayImg, binaryImg, 255, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY, 11, 2);//【4】显示图像imshow("二值化图像", binaryImg);return MTC_SUCCESS;
}//自适应阈值二值化 高斯
int Binarization::AdaptiveThreshold_GAUSSIAN(Mat& srcImg)
{//【1】安全性检查if (!srcImg.data || srcImg.data == NULL){cout << "AdaptiveThreshold_GAUSSIAN() --> srcImg读取失败" << endl;return MTC_FAIL;}//【2】图像灰度化Mat grayImg;cvtColor(srcImg, grayImg, CV_BGR2GRAY);//【3】自适应阈值二值化Mat binaryImg;adaptiveThreshold(grayImg, binaryImg, 255, CV_ADAPTIVE_THRESH_GAUSSIAN_C, CV_THRESH_BINARY, 11, 2);//【4】显示图像imshow("二值化图像", binaryImg);return MTC_SUCCESS;
}
总结
(1)大津法的优点在于可以快速有效的找到类间分割阈值,但其缺点也很明显,就是只能针对单一目标分割,或者感兴趣的目标都属于同一灰度范围,若需探测目标灰度范围分布较大,则必将有一部分目标探测丢失。
(2)局部分割的优点在于可以进行多目标分割,缺点在于基于局部阈值分割出的目标连结性较差,包含噪声。
关于二值化,这里有几篇从OpenCV官网找到的介绍供参考:
链接1 链接2链接3
自适应阈值图像二值化相关推荐
- 【OpenCV 4开发详解】图像二值化
本文首发于"小白学视觉"微信公众号,欢迎关注公众号 本文作者为小白,版权归人民邮电出版社发行所有,禁止转载,侵权必究! 经过几个月的努力,小白终于完成了市面上第一本OpenCV 4 ...
- OpenCV-Python学习(10)—— OpenCV 图像二值化处理(cv.threshold)
1. 学习目标 理解图像的分类,不同类型的图像的区别: 对图像进行二值化处理,对[ cv.threshold ]函数的理解. 2. 图像分类 2.1 不同类型图像说明 按照颜色对图像进行分类,可以分为 ...
- Python使用openCV把原始彩色图像转化为灰度图、使用OpenCV把图像二值化(仅仅包含黑色和白色的简化版本)、基于自适应阈值预处理(adaptive thresholding)方法
Python使用openCV把原始彩色图像转化为灰度图.使用OpenCV把图像二值化(仅仅包含黑色和白色的简化版本).基于自适应阈值预处理(adaptive thresholding)方法 目录
- C#,图像二值化(05)——全局阈值的联高自适应算法(Legal Self-Adaptive Thresholding)及其源代码
阈值的选择当然希望智能.简单一些.应该能应付一般的图片. What is Binarization? Binarization is the process of transforming data ...
- 图像二值化处理(全局阈值 自适应阈值 手动阈值操作以及直方图画法)
文章目录 图像二值化处理 二值化原理 API介绍 手动设置阈值 均值法 迭代法 自动设置阈值 直方图法 全局阈值法 OTSU法 三角形法 自适应阈值法 API 绘制图像直方图 图像二值化处理 二值化原 ...
- opencv进阶学习9:图像阈值大全,图像二值化,超大图像二值化
基础版笔记链接: python3+opencv学习笔记汇总目录(适合基础入门学习) 进阶版笔记目录链接: python+opencv进阶版学习笔记目录(适合有一定基础) 基础版二值化讲解 opencv ...
- OTSU_图像二值化分割阈值的算法
简介: 大津法(OTSU)是一种确定图像二值化分割阈值的算法,由日本学者大津于1979年提出.从大津法的原理上来讲,该方法又称作最大类间方差法,因为按照大津法求得的阈值进行图像二值化分割后,前景与背景 ...
- C#,图像二值化(24)——局部阈值算法的NiBlack算法及源程序
1.局部阈值算法的NiBlack算法 摘要-医学图像的处理最为复杂人和计算机.磁性捐赠的脑组织共振成像(MRI)在许多领域是非常重要的问题例如手术和治疗.最常见的分割图像的最简单方法是使用阈值.在这项 ...
- 基于阈值的图像二值化方法MATLAB
文章目录 一.目录 二.摘要 三.实验步骤 3.1 固定阈值法 (1)计算均值和方差 (2)绘制和分析高斯分布图像 (3)以128为阈值进行固定阈值分割 (4)观察灰度直方图选择最优固定阈值 3.2 ...
最新文章
- BZOJ1192: [HNOI2006]鬼谷子的钱袋
- 南陵中学2021高考成绩查询,南陵中学2019高考成绩喜报、一本二本上线情况
- python判断端口是否开放_Python扫描IP段查看指定端口是否开放的方法
- 一个程序员的郁闷吐槽
- android之去掉actionbar
- docker命令总结(二)
- JAVA处理字符串压缩以及文件压缩
- AI虚拟偶像正崛起,是否可以取代你的idol?
- 信通方恒资产评估行业快讯 - 森林资源资产抵押贷款-金融机构对抵押物进行审核与权属认定注意事项
- vs2019 vs2022番茄助手重新安装失败问题处理
- 记住鲁迅的所有文章,因为他具有深远的意义,在当下意义重大。
- ts获取服务器数据_ts 流服务器
- MAF-YOLO: Multi-modal attention fusion based YOLO forpedestrian detection
- golang使用josn.Unmarshal报错:unexpected end of JSON input
- 谈谈如何发起一次会议
- 购物车的实现——淘淘商城(二十六)
- 计算机广东大专院校排名2018,重磅!广东85所专科院校官方排名刚刚出炉,这所高职回归第一!...
- oracle 结果集已耗尽_结果集已耗尽
- Win10打补丁KB4022725出现0x80073712错误
- Instagram: 从图片发布到聊天工具的蜕变