【拜小白opencv】20-OTSU阈值化实现;OTSU最大类间方差法

来源:互联网 发布:以下属于云计算的特征 编辑:程序博客网 时间:2024/05/17 05:12

常言道“温故而知新”,写此文章就是对自己目前学习内容的小小的总结与记录。

本文力求用最简洁的语言,详细的代码将此部分内容讲解清楚,但由于博主同样是刚刚接触OpenCV,或许表达上有些瑕疵,还望读者能够指教探讨,大家共同进步。

博主机器配置为:VS2013+opencv2.4.13+Win-64bit。

若本文能给读者带来一点点启示与帮助,我就很开心了。

===========================分割线========================


在阈值化处理中,常用的算法就是OTSU(最大类间方差法)。

OTUS算法的思想:选取一个阈值T,T∈[0,m−1],m为图像的灰度级。将图像直方图分成两部分,T值使得分成的两组间方差最大。

OTSU算法对不均匀光照的图片不能产生很好的效果,但计算简单,适用性强。


OTSU算法的步骤如下:

  1. 统计灰度级中每个像素在整幅图像中的个数。
  2. 计算每个像素在整幅图像的概率分布。
  3. 对灰度级进行遍历搜索,计算当前灰度值下前景与背景类间概率。
  4. 通过目标函数计算出类内与类间方差下对应的阈值。
=======================分割线=======================

算法的基本原理

OTSU方法是一种基于寻找合适阈值实现二值化的方法,其最重要的部分是寻找图像二值化阈值,然后根据阈值将图像分为前景(白色)或者背景(黑色)。假设有6x6的灰度图像,其像素数据及其对应的直方图如下图:



阈值寻找方法首先假设是为T=3。则背景像素的比重、均值、方差的计算结果如下:


根据前景像素直方图,计算比重、均值、方差的过程如下:



  • 上述整个计算步骤与结果是假设阈值T=3时候的结果,同样计算假设阈值为T=0、T=1、T=2、T=4、T=5的类内方差,比较类内方差之间的值,最大类内方差使用的阈值T即为图像二值化的阈值。
  • 上述是假设图像灰度值级别为0~5六个值,实际中图像灰度值取值范围为0~255之间,所以要循环计算使用每个灰度值作为阈值,得到类内方差,最终取最大类内方差对应的灰度值作为阈值实现图像二值化即可。

============================分割线===================

代码演示

/*OTSU阈值化。OTSU-最大类间方差法*/#include <opencv2/core/core.hpp>          #include <opencv2/highgui/highgui.hpp>          #include <opencv2/imgproc/imgproc.hpp>         #include <iostream>        using namespace std;using namespace cv;//------------【全局函数声明】-------------int OTSU(Mat grayImage);//OTSU法函数实现//-----------【主函数】---------------int main(){//------------【1】读取源图像并检查图像是否读取成功------------Mat srcImage = imread("D:\\OutPutResult\\ImageTest\\build.jpg");if (!srcImage.data){cout << "读取图片错误,请重新输入正确路径!\n";system("pause");return -1;}imshow("【源图像】", srcImage);//------------【2】灰度转换------------Mat srcGray;cvtColor(srcImage, srcGray, CV_RGB2GRAY);//转为灰度图imshow("【灰度图】", srcGray);//------------【3】调用OTSU()阈值化所得到阈值------------int otsuThreshold = OTSU(srcGray);cout << "调用OTSU()阈值化所得到阈值:" << otsuThreshold << endl;Mat otsuResultImage = Mat::zeros(srcGray.rows, srcGray.cols, CV_8UC1);//创建输出结果图像//------------【4】利用得到的阈值实现二值化操作------------for (int i = 0; i < srcGray.rows; i++){for (int j = 0; j < srcGray.cols; j++){//阈值判断。 若该点值大于阈值,则设为255,否则设为0。if (srcGray.at<uchar>(i, j) > otsuThreshold)otsuResultImage.at<uchar>(i, j) = 255;elseotsuResultImage.at<uchar>(i, j) = 0;}}imshow("【OTUS阈值化图像】", otsuResultImage);waitKey(10000);//等待10秒退出程序return 0;}//---------------【OTSU法函数实现】----------------------int OTSU(Mat grayImage){int nRows = grayImage.rows;//行数int nCols = grayImage.cols;//列数int threshold = 0;//初始化阈值//------------【1】初始化统计参数------------int nSumPix[256];//用于灰度级中每个像素在整幅图像中的个数float nProDis[256];//用于记录每个灰度级占图像中的概率分布for (int i = 0; i < 256; i++){nSumPix[i] = 0;nProDis[i] = 0;}//------------【2】统计灰度级中每个像素在整幅图像中的个数------------for (int i = 0; i < nRows; i++){for (int j = 0; j < nCols; j++){nSumPix[(int)grayImage.at<uchar>(i, j)]++;}}//------------【3】计算每个灰度级占图像中的概率分布------------for (int i = 0; i < 256; i++){nProDis[i] = (float)nSumPix[i] / (nCols*nRows);}//------------【4】遍历灰度级[0,255],计算出最大类间方差的阈值------------float wb, wf; //比重. wb-背景部分; wf-前景部分float u0_temp, u1_temp, u0, u1;//平均值float delta_temp;//存放临时方差double delta_max = 0.0;//初始化最大类间方差for (int i = 0; i < 256; i++){wb = wf = u0_temp = u1_temp = u0 = u1 = delta_temp = 0;//初始化相关参数for (int j = 0; j < 256; j++){//背景部分if (j <= i){//当前i为分割阈值,第一类总的概率wb += nProDis[j];//比重u0_temp += j*nProDis[j];}//前景部分else{//当前i为分割阈值,第一类总的概率wf += nProDis[i];//比重u1_temp += j*nProDis[j];}}//------------分别计算各类的平均值------------u0 = u0_temp / wb;u1 = u1_temp / wf;//-----------计算最大类间方差------------delta_temp = (float)(wb*wf*pow((u0 - u1), 2));//形如pow(x,y);其作用是计算x的y次方。//------------依次找到最大类间方差下的阈值------------if (delta_temp > delta_max){delta_max = delta_temp;threshold = i;}}//计算结束return threshold;//返回OTUS计算出的阈值}


========================分割线====================

显示结果



为了便于理解,换了幅图像,重新运行结果如下:


=====================分割线=====================

程序说明

二值化的过程是,先利用OTSU算法求出一个阈值,所输入的图像必须为灰度图。然后以这个阈值为门限,遍历图像中的像素点依次与阈值比较,若该点值大于阈值,则设为255,否则设为0。形成二值图像。
要注意:不同图像所得到的阈值是不同的。

下面说说我在这里遇到的几个问题:
1.我觉得程序中一行代码,计算最大类间方差的部分
//-----------计算最大类间方差------------delta_temp = (float)(wb*wf*pow((u0 - u1), 2));//形如pow(x,y);其作用是计算x的y次方。
感觉按照上面讲解的算法思想不太一致,我想这么改
//-----------计算最大类间方差------------delta_temp = (float)((wb+wf)*pow((u0 - u1), 2));//形如pow(x,y);其作用是计算x的y次方。
但是这么改后,结果很不理想。 这是问题一。

2.程序退出时,总是会出现死机状况,如下

具体什么情况,我是没整明白。我猜或许是什么资源没释放??

上面就是我没太想明白的地方,若有哪位知道,还望指导一二可怜

========================END=======================
参考文章:图像处理之基于Otsu阈值二值化