详解-OTUS(大津法-最大类间方差)原理及C语言代码实现
灰度图二值化:
我们在对灰度图像进行处理的时候,为了便于观察和分析,经常需要将图像中的目标主体和背景分割开来,变成二值化图像(只有黑和白,黑白图像)
加菲猫--灰度图——二值化
而我们知道灰度图像是有256个灰度级 255代表全白,0表示全黑,那么在进行二值化的时候,是设定一个阈值,根据灰度值大于或小于阈值进行黑白显示,我们假设背景用白色0表示,目标物体用黑色1表示,不同阈值的选取,对于图像二值化的效果影响是非常大的
上图可以看出,阈值的选取对于灰度图二值化有着至关重要的作用,那么怎么选取一个合适的阈值,使得我们的背景和前景能够很好的分开,并且最大程度的减少误判呢?
灰度直方图
这就需要我们对图像的灰度直方图进行分析,
灰度直方图就是图像的像素点在不同灰度级的分布情况
看下面的图片,横轴代表灰度级,纵轴代表像素点数量
从上图中我们可以看出该图片共有两个尖峰,第一个尖峰表示人的像素灰度值分布在0~ 20之间,第二个尖峰表示背景部分像素灰度值分布在灰度100~180之间 ,这个时候做二值化,我们可以在30-100之间设定一个阈值,大于阈值的背景像素全为0(白色),小于阈值的目标像素全为1(黑色) 这就是图像二值化
那么阈值取多少合适呢?观察直方图我们可以得到一个大致的区间,但是实际上的取值不能确定,所以我们需要一个算法来找到最优阈值才行。而大津法(OTUS),就给我们提供了怎么找到这个最佳阈值的方法。
图一为阈值120 图二为阈值75
我们可以看到,图一的特征值更多,图二只有人像部分,其余背景过滤较好。
类间方差
首先我们要知道什么是方差,根据百度百科:方差是一组数据离散程度的度量,方差越大,表示离散程度越大
请注意上面这句话, 既然我们可以用方差
来表示系统的离散程度
,我们图像二值化取最佳阈值时,背景应该与前景差别最大,这时方差也应该是最大的,那这样,是不是可以用方差来表示当前图像的二值化质量?
所以,我们假设图像阈值为T
,
小于T
的像素为目标,数量占总图像比例为 w0
,平均灰度值为u0
,
大于T
的像素为背景,数量占图像比例为 w1
,平均灰度值为u1
那么 w0+w1=1
(公式1) 概率和为1
图像的平均灰度值u
为:(公式2)
那么类内方差g
的定义为:(公式3)
(注意现在还是类内方差,两个类别的类内方差按比例求和)
目标部分和平均灰度值的方差,加上背景部分和平均灰度值的方差,这个值最大的时候,表示当前阈值为最佳阈值。
那么我们把上面的公式3简化一下,可以得到类间方差的定义:(公式4)
我们现在需要做的事,就是修改阈值T的值,求方差g的最大值。
推导过程:
大津法(OTUS)
这个时候来看大津法的概念:
最大类间方差是由日本学者大津(Nobuyuki Otsu)于1979年提出,是一种确定图像二值化分割阈值的算法。算法假设图像像素能够根据全局阈值,被分成背景[background]
和目标[objects]
两部分。然后,计算该最佳阈值来区分这两类像素,使得两类像素区分度最大。
大津法阈值采用最大类间方差的原理,适合于图像灰度分布整体呈现“双峰”的情况。大津法会自动找出一个阈值,使得分割后的两部分类间方差最大。
特性:
大津法对噪音十分敏感,在处理之前应对图片进行去噪处理。
如果图像有存在局部噪声,则会影响大津法的判断
当目标与背景的面积比例悬殊的时候,类间方差函数可能呈现双峰或者多峰,这个时候 大津法的效果不好
双峰图像,目标和背景面积差距不大,可以很好的判断
当图像中的目标与背景的面积相差很大时,灰度直方图没有明显的双峰,或者两个峰的大小相差很大,分割效果不佳
目标像素点远小于背景像素点,分割效果不佳
代码实现:
基本所有的博客都是讲完原理,然后贴一整个代码,这样很多读者可能搞懂了原理,但是在阅读代码的时候突然又看不懂了,所以代码部分会按照上面的公式,推导编写,下面以C语言为例:
首先建立一个函数,定义需要用到的参数:
//------------------摄像头参数--------------//
uint8 dis_image[60][80];大津法二值化///*! * @brief 大津法二值化0.8ms程序* @date: 2018-9 * @since v1.2* *image :图像地址* width: 图像宽* height:图像高* @author Z小旋*/uint8 image_threshold ; //图像阈值uint8 otsuThreshold(uint8 *image, uint16 col, uint16 row)
{#define GrayScale 256//定义256个灰度级uint16 width = col; //图像宽度uint16 height = row; //图像长度int pixelCount[GrayScale]; //每个灰度值所占像素个数float pixelPro[GrayScale]; //每个灰度值所占总像素比例int i, j;int sumPixel = width * height;//总像素点uint8 threshold = 0; //最佳阈值uint8* data = image; //指向像素数据的指针
这里需要注意,因为我们定义了两个局部变量数组,局部变量处于堆栈区,数值随机,所以需要对数组做一个简单的初始化
初始化的方法有三种:
1用for循环赋值
for (i = 0; i < GrayScale; i++)
{ pixelCount[i] = 0;
pixelPro[i] = 0;
}
2使用memset
memset(pixelCount, 0, GrayScale); //使用memset方法 memset(pixelPro, 0, GrayScale); //使用memset方法
3声明时,使用 {0} 初始化
int pixelCount[GrayScale] = {0};//每个灰度值所占像素个数
float pixelPro[GrayScale] = {0};//每个灰度值所占总像素比例
上面三种初始化,实测for循环浪费的时间最多,{0} 与memset 耗时差不多。
然后我们需要统计每个灰度级在整个图像中的个数,就是统计灰度值为1的点有多少多少个,灰度值为2的点有多少多少个…
//统计灰度级中每个像素在整幅图像中的个数 for (i = 0; i < height; i++){for (j = 0; j < width; j++){pixelCount[(int)data[i * width + j]]++; //将像素值作为计数数组的下标// pixelCount[(int)image[i][j]]++; 若不用指针用这个}}
计算每个灰度级像素数占图像总像素的比例,以及图像的总平均灰度u
//遍历灰度级[0,255] float w0=0, w1=0, u0Sum=0, u1Sum=0, u0=0, u1=0, u=0, variance=0, maxVariance = 0;//目标像素占总图像比例w0//背景像素占总图像比例w1 //目标平均灰度值u0 //背景平均灰度值u1//总平均灰度值u//类间方差 variance//最大类间方差 maxVariance//计算每个像素在整幅图像中的比例 for (i = 0; i < GrayScale; i++){pixelPro[i] = (float)pixelCount[i] / sumPixel;u += i * pixelPro[i]; //总平均灰度}
接下来,就是我们假设图像阈值T
从1-255遍历,然后求每次的目标像素数量占总图像比例w0
,平均灰度值u0
,背景像素数量占图像比例 w1
,平均灰度值u1
然后比较256次情况下
(公式3)
公式g的最大值
//计算每个像素在整幅图像中的比例 for (i = 0; i < GrayScale; i++){pixelPro[i] = (float)pixelCount[i] / sumPixel;u += i * pixelPro[i]; //总平均灰度}for (i = 0; i < GrayScale; i++) // i作为阈值 阈值从1-255遍历 {for (j = 0; j < GrayScale; j++) //求目标前景和背景{if (j <= i) //前景部分 {w0 += pixelPro[j]; u0Sum += j * pixelPro[j];}else //背景部分 {w1 += pixelPro[j];u1Sum += j * pixelPro[j];}}u0 = u0Sum / w0;u1 = u1Sum / w1;variance = w0 * pow((u0 - u), 2) + w1 * pow((u1 - u), 2); //类间方差计算公式if (variance > maxVariance) //判断是否为最大类间方差{maxVariance = variance;threshold = i;}}return threshold;}
完整代码如下:
//------------------摄像头参数--------------//
uint8 dis_image[60][80];大津法二值化//
uint8 image_threshold = 46; //图像阈值//0x4D;0x18-0x1A;
uint8 otsuThreshold(uint8 *image, uint16 col, uint16 row)
{#define GrayScale 256//定义256个灰度级uint16 width = col; //图像宽度uint16 height = row; //图像长度int pixelCount[GrayScale]; //每个灰度值所占像素个数float pixelPro[GrayScale]; //每个灰度值所占总像素比例int i, j;int sumPixel = width * height;//总像素点uint8 threshold = 0; //最佳阈值uint8* data = image; //指向像素数据的指针for (i = 0; i < GrayScale; i++){pixelCount[i] = 0;pixelPro[i] = 0;}//统计灰度级中每个像素在整幅图像中的个数 for (i = 0; i < height; i++){for (j = 0; j < width; j++){pixelCount[(int)data[i * width + j]]++; //将像素值作为计数数组的下标// pixelCount[(int)image[i][j]]++; 若不用指针用这个}}//遍历灰度级[0,255] float w0=0, w1=0, u0Sum=0, u1Sum=0, u0=0, u1=0, u=0, variance=0, maxVariance = 0;//目标像素占总图像比例w0//背景像素占总图像比例w1 //目标平均灰度值u0 //背景平均灰度值u1//总平均灰度值u//类间方差 variance//最大类间方差 maxVariance//计算每个像素在整幅图像中的比例 for (i = 0; i < GrayScale; i++){pixelPro[i] = (float)pixelCount[i] / sumPixel;u += i * pixelPro[i]; //总平均灰度}for (i = 0; i < GrayScale; i++) // i作为阈值 阈值从1-255遍历 {for (j = 0; j < GrayScale; j++) //求目标前景和背景{if (j <= i) //前景部分 {w0 += pixelPro[j]; u0Sum += j * pixelPro[j];}else //背景部分 {w1 += pixelPro[j];u1Sum += j * pixelPro[j];}}u0 = u0Sum / w0;u1 = u1Sum / w1;variance = w0 * pow((u0 - u), 2) + w1 * pow((u1 - u), 2); //类间方差计算公式if (variance > maxVariance) //判断是否为最大类间方差{maxVariance = variance;threshold = i;}}return threshold;}image_threshold = otsuThreshold(image[0],COL,ROW); //大津法计算阈值
这就是OTUS的代码实现部分,当然上面的代码使用到了两个for循环判断,代码运行时间较长,实际上我们可以根据
w0+w1=1
(公式1) 概率和为1
(公式4)
公式1 和公式4 对代码来进行简化,将两个for循环进行叠加,最终简化代码如下,在STM32F4系列下实测用时为0.8ms左右
//------------------摄像头参数--------------//uint8 image_threshold = 46; //图像阈值
uint8 dis_image[60][80];大津法二值化///*! * @brief 大津法二值化0.8ms程序* @date: 2018-10 * @since v1.2* *image :图像地址* width: 图像宽* height:图像高* @author Z小旋*/uint8 otsuThreshold(uint8 *image, uint16 width, uint16 height)
{#define GrayScale 256int pixelCount[GrayScale] = {0};//每个灰度值所占像素个数float pixelPro[GrayScale] = {0};//每个灰度值所占总像素比例int i,j; int Sumpix = width * height; //总像素点uint8 threshold = 0;uint8* data = image; //指向像素数据的指针//统计灰度级中每个像素在整幅图像中的个数 for (i = 0; i < height; i++){for (j = 0; j < width; j++){pixelCount[(int)data[i * width + j]]++; //将像素值作为计数数组的下标// pixelCount[(int)image[i][j]]++; 若不用指针用这个}}float u = 0; for (i = 0; i < GrayScale; i++){pixelPro[i] = (float)pixelCount[i] / Sumpix; //计算每个像素在整幅图像中的比例 u += i * pixelPro[i]; //总平均灰度}float maxVariance=0.0; //最大类间方差float w0 = 0, avgValue = 0; //w0 前景比例 ,avgValue 前景平均灰度for(int i = 0; i < 256; i++) //每一次循环都是一次完整类间方差计算 (两个for叠加为1个){ w0 += pixelPro[i]; //假设当前灰度i为阈值, 0~i 灰度像素所占整幅图像的比例即前景比例avgValue += i * pixelPro[i]; float variance = pow((avgValue/w0 - u), 2) * w0 /(1 - w0); //类间方差 if(variance > maxVariance) { maxVariance = variance; threshold = i; } } return threshold;}
使用:
threshold = otsuThreshold(image[0],COL,ROW); //大津法计算阈值 threshold = threshold > 50?50:threshold;
详解-OTUS(大津法-最大类间方差)原理及C语言代码实现相关推荐
- c语言多组数据判断回文字符串,详解判断回文字符串跟回文数算法的C语言代码...
详解判断回文字符串和回文数算法的C语言代码! 一.判断一个字符串是否为回文字符串 #include #include #include //包含strlen #define YES 1 #define ...
- 图像处理_Ostu算法(大律法、最大类间方差法)
一.算法简述 Otsu算法是一种用于二值化最佳阈值的选取方法.基本原理是根据阈值T将图像中的像素点分为C1和C2两类,不断的调整阈值T之后若此时两类之间存在最大的类间方差,那么此阈值即是最佳阈值. 二 ...
- c语言程序判断一个字符串是否是回文数,详解判断回文字符串和回文数算法的C语言代码!...
一.判断一个字符串是否为回文字符串 #include #include #include //包含strlen #define YES 1 #define NO 0 #define uchar un ...
- 基于OTSU最大类间方差法的ROI分割、提取图像中的形状特征--面积、周长、离心率、zernike矩
分享一下最近学习的图像分类方面知识,整体的思路如下(之前的汇报ppt里截的) 把这个过程拆分几个部分共同学习一下吧 1.Otsu法原理 最大类间方差法OTSU是一种自适应的全局阈值确定的方法,根据灰度 ...
- matlab大津法函数,matlab 大津阈值分割【相关词_ 大津法阈值分割matlab】
阈值法 阈值分割程序 Otsu Thresholding 赞(0) 踩(0) 收藏(0) 说明:大津法实现图像阈值分割的matlab源程序代码 (Otsu Thresholding Image sou ...
- 最大类间方差(大津法)详解
参考: https://www.cnblogs.com/xiaomanon/p/4110006.html 一.算法介绍 最大类间方差法是1979年由日本学者大津提出的,是一种自适应阈值确定的方法,又叫 ...
- 图像二值化----otsu(最大类间方差法、大津算法)(二)
转自:http://blog.stevenwang.name/ostu-threshold-56002.html OTSU算法也称最大类间差法,有时也称之为大津算法,被认为是图像分割中阈值选取的最佳算 ...
- 图像二值化之最大类间方差法(大津法,OTSU)
参考文章1:图像二值化与otsu算法介绍 参考文章2:python opencv cv2.threshold() (将固定级别的阈值应用于每个数组元素)ThresholdTypes 最大类间方差法(大 ...
- 【机器视觉学习笔记】大津法/Otsu最大类间方差法 最佳阈值处理(C++)
目录 概念 C++源码 OtsuThreshold 主函数 效果 完整源码 平台:Windows 10 20H2 Visual Studio 2015 OpenCV 4.5.3 本文所用源码修改自C+ ...
最新文章
- Java基础(七)--Exception异常处理
- 您拒绝了位置共享服务器,共享服务器拒绝访问
- why SAP CRM My Opportunity Master page must be renamed as S2
- 胡玥的侦探小说《做局》及自传《时间之夜》
- Python 第三方库之docx
- mysql boolean 和bool_关于 MySQL 的 boolean 和 tinyint(1)
- 前端JAVASCRIPT模块化开发
- ffmpeg系列-编译
- 拓端tecdat|matlab使用Copula仿真优化市场风险
- 电子科技大学信通2018级学生上早自习缺勤率情况分析
- 「系统介绍」简单介绍下PMS系统
- Python 取代了 Excel 在银行业务中的地位?
- 图像形态学(腐蚀、膨胀、开运算、闭运算、梯度、顶帽、黑帽)
- 让你实现财富自由,从此不再缺资金
- 微信小程序参数二维码的8大使用场景
- 「Deep Learning」读书分享系列
- 如何使用免费手机号归属地查询API快速获取手机号归属地,两种方法【源码可用】
- dlmalloc 图解
- navicat永久使用权
- Linux命令之分屏显示文件内容less
热门文章
- 离群点、异常点检测及Python实现(正态分布3∂,Z-score 异常值检测,基于MAD的Z-score 异常值检测,杠杆值点、DFFITS值、SR学生化残差、cook距离和covratio值)
- python随机化序列与设置随机种子
- 医用应用计算机,计算机在医疗方面应用.doc
- oracle dg巡检,oracle dg状态检查及相关命令
- U盘安装centos7
- fetch移动端浏览器兼容问题
- NOIP模拟 color(DP)
- hdu-2717(基础搜索bfs)
- 9.22 keep studying
- 每天一道Java题[4]