技术标签: 算法 三角法二值化 图像知识点 自适应阈值图像二值化 大津法 OpenCV自适应阈值二值化 图像处理--Opencv otsu二值化
关于二值化的介绍,以前的博客中有介绍,这里就不再描述了,二值化介绍;二值化分为固定阈值二值化和自适应阈值二值化,固定阈值二值化方式是我们常用的二值化方式,需要自己摸索一个经验阈值,不断调整,直到找到最佳阈值,这种方式在刚刚的链接中已经介绍;而这篇文档主要介绍的就是另一种二值化方式:自适应阈值二值化。
图像进行二值化,且做到自适应阈值参数,有4种自适应阈值二值化方法;先从自适应阈值的作用范围来区分,自适应阈值分为:
使用自适应全局阈值的全局二值化方法有:大津法图像二值化、三角法图像二值化;
使用自适应局部阈值的局部二值化方法有:局部均值处理、局部高斯处理;
OTSU算法也称最大类间差法,有时也称之为大津算法,由大津于1979年提出,被认为是图像分割中阈值选取的最佳算法,计算简单,不受图像亮度和对比度的影响,因此在数字图像处理上得到了广泛的应用。它是按图像的灰度特性,将图像分成背景和前景两部分。因方差是灰度分布均匀性的一种度量,背景和前景之间的类间方差越大,说明构成图像的两部分的差别越大,当部分前景错分为背景或部分背景错分为前景都会导致两部分差别变小。因此,使类间方差最大的分割意味着错分概率最小。
大津法二值化适用于图像直方图中存在双峰的图像(直方图中的双峰就是指背景像素和前景像素),最佳阈值就是双峰之间的某个参数,即将背景像素和前景像素分割开,其原理就是最大类间方差法。
大津法二值化的大致算法思路如下:
思路细化:
除了最大类间方差,也可以通过计算最小类内方差来得到最佳阈值,这里有篇博客介绍到:链接
对于一些噪声较多的图像,可以先使用高斯滤波去噪,再用大津法对图像进行二值化,这样会使二值化的图像效果更好
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就是阈值T
if (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_OTSU
cout << "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
(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 - nBestTH
if (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_TRIANGLE
cout << "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官网找到的介绍供参考:
文章浏览阅读3.4k次,点赞6次,收藏16次。CSS实现鼠标悬停改变其他标签样式前言: 据我了解目前CSS只能控制悬停时改变该标签下面的兄弟标签和子标签样式,如有大佬有好的方法请指教!控制其他标签(根据控制标签与被控制标签之间的关系)可分为三种类型如下:本文中控制标签为 .div1 被控制标签为 .div2控制子标签(.div1:hover和.div2之间使用空格) <!DOCTYPE html><html> <head> <meta charset=..._css超链接鼠标悬停顶替li样式
文章浏览阅读7.3k次,点赞2次,收藏9次。查看内存使用情况命令:top[root@centos mysdb]# toptop - 10:00:28 up 4:47, 2 users, load average: 0.00, 0.01, 0.05Tasks: 130 total, 2 running, 126 sleeping, 2 stopped, 0 zombie%Cpu(s): 0.0 us, 0.2 ..._登录centos7 显示服务器名 ip cpu 内存 硬盘信息
文章浏览阅读43次。#include<stdio.h>#include<algorithm>#include<string.h>#include<math.h>using namespace std;#define inf 100000000#define MAXN 5010#define MAXN 20010struct e...
文章浏览阅读99次。1.1 Tomcat简介Tomcat是Apache软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,由Apache、Sun和其他一些公司及个人共同开发而成。Tomcat服务器是一个免费的开放源代码的Web应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP程序的..._一般企业的服务器布置的tomcat,防火墙
文章浏览阅读1.7k次,点赞5次,收藏7次。1 聚类任务聚类试图将数据集中的样本划分为若干个通常是不相交的子集,每个子集 称为一个"簇" (cluster)。通过这样的划分,每个簇可能对应于一些潜在的概 念(类别) ,如"浅色瓜" “深色瓜”,“有籽瓜” “无籽瓜”,甚至"本地瓜""外地瓜"等。这些概念对聚类算法而言事先是未知的,聚类过程 仅能自动形成簇结构,簇所对应的概念语义需由使用者来把握和命名。2 聚类性能度量聚类性能度量..._簇之间的距离
文章浏览阅读1.1k次。下载后解压缩,将解压后的文件放在某个文件目录中(位置随意)运行命令后会出现一个默认密码(记下这个密码,后面会用到)进入mysql文件夹,创建一个文件,文件名为。以管理员身份运行cmd窗口,进入mysql的。输入之前初始化后得到的默认密码,即可登录。退出后再次用新密码验证即可。按回车即可修改成功,输入。_mysqld --install
文章浏览阅读889次,点赞6次,收藏3次。关于Sublime Text对于我的使用体验,只能说内置的代码主题真的都太low了,一点都不好看。所以接下来我分享一下我自定义代码配色。当然,大家也可以通过我给的中文翻译注释来自定义自己喜欢的颜色。废话不多说,直接上代码直接将代码复制粘贴到:Preferences > Customize Color Scheme 即可。},"globals":"foreground": "var(white)", // 前景色。_sublime编写代码怎么变色
文章浏览阅读611次。我的机器上,Winform控件中的 LookUp有1+3种,在我们国家也被称为 4 种,你们那儿呢? 这 1+3种LookUpEdit分别是LookUpEdit、GridLookUpEdit、SearchLookUpEdit 和 TreeListLookUpEdit,之所以我用 1+3种是因为个人觉得LookUpEdit是最简单的,也是最基础的一种,其它三个..._devexpress lookupedit 选中值触发事件
文章浏览阅读2w次,点赞20次,收藏177次。XML简要教程XML 指可扩展标记语言(eXtensible Markup Language), 被设计用来传输和存储数据。是各种应用程序之间进行数据传输的最常用的工具。XML提供了一套夸平台,跨网络,跨应用程序的语言的描述方式。使用XML可以方便的实现数据交换,系统配置,内容管理等。XML 可被类似记事本这样的简单的文本编辑器来创建和编辑。不过,在您开始使用 XML 进行工作时,您很快会发现..._怎么通过 xmlutil.toxmlwithhead(root);生成指定格式xml,如
文章浏览阅读782次。What Is The Max LUN Size In ASM 12c ? (Doc ID 1667736.1)Before ASM 12c there was an limitation of 2 Tb LUNs.SOLUTION12cR1The following information was taken from theOracle Automatic Storage Management Administrator's Guide12cRelease 1 (12.1)u..._you can not add disk more than 2 tb as a asm disk
文章浏览阅读3.3k次,点赞15次,收藏8次。快速排序是一种分治算法,它的基本思想是将一个大问题分解成两个或更多的相同或相似的子问题,然后递归地解决这些子问题,最后将这些子问题的解合并以得到原问题的解。选择一个基准元素(pivot)。将所有小于基准的元素移动到基准的左边,所有大于基准的元素移动到基准的右边。这个过程称为分区(partition)操作。对基准左边和右边的两个子数组分别进行快速排序。快速排序是一种非常高效的排序算法,它的平均时间复杂度为O(nlogn),最坏情况下的时间复杂度为O(n^2)。
文章浏览阅读407次。文丨互链脉搏·元尚未经授权,不得转载!7月28日,暴风集团CEO冯鑫被捕的消息在科技圈炸响。因涉及暴风集团2016年与光大资本共同发起的一起收购案,消息称,冯鑫在此项目的...