基于阈值方法的大津法(OTSU算法)---图像分割
主要分为三部分去实现:
1.基本概念
大津法:属于阈值分割的范畴。阈值分割方法:利用图像中要提取的目标物体和背景在灰度上的差异,选择一个合适的阈值,通过判断每一个像素点的特征属性来确定该像素点属于目标区还是背景区域,从而产生二值图像。确定一个最优阈值是分割的关键
其确定阈值的过程为:记为前景与背景的分割阈值t.前景点数占图像比例为w0,平均灰度为u0;背景点数占图像比例为w1,平均灰度为u1;图像的总平均灰度为u=wo*u0+w1*u1:从最小到最大灰度值遍历t,当使得类间方差值a=w0*(u1-u)^2+w1*(u0-u)^2最大时,即为分割的最佳阈值。
对大津法可作如下理解:该式实际上就是类间方差值,阈值t分割出的前景和背景两部分构成了整幅图像,而前景取值u0,概率为w0,背景取值u1,概率为w1,总均值为u
2代码如下及其算法:
OTSU算法步骤如下:
❶统计灰度级中每个像素在整幅图像中的个数
❷计算每个像素在整幅图像的概率分布
❸对灰度级进行遍历搜索,计算当前灰度值下前景背景类间概率
❹通过目标函数计算类内与类间方差下对应阈值
代码如下:(代码源自朱伟老师著书籍《opencv图像处理编程实例》)
#include <stdio.h>
#include <string>
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/opencv.hpp"
using namespace std;using namespace cv;
// 大津法函数实现
int OTSU(cv::Mat srcImage)//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;
}
//将数组里面的内容赋值为0
// 统计灰度级中每个像素在整幅图像中的个数
for (int i = 0; i < nCols; i++) //遍历列
{
for (int j = 0; j < nRows; j++)//遍历行
{
nSumPix[(int)srcImage.at<uchar>(i, j)]++;
//下面有此函数的详细解释
}
}
// 计算每个灰度级占图像中的概率分布
for (int i = 0; i < 256; i++)
{
nProDis[i] = (float)nSumPix[i] / (nCols * nRows);
//nSumPix[i]存放对应像素值的个数,(nCols * nRows)为整个图像的像素总数,nProDis[i]存放每个像素值的概率
}
// 遍历灰度级[0,255],计算出最大类间方差下的阈值
float w0, w1, u0_temp, u1_temp, u0, u1, delta_temp;
//w0前景点数占图像比例,w1背景点数占图像比例
//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];
//相当于w0=w0+nProDis[j],满足条件的像素的概率值 不断累加,存入w0
u0_temp += j * nProDis[j];
// 相当于u0_temp=u0_temp +j * nProDis[j],j * nProDis[j]表示像素值与对应概率乘积 ,所有满足条件的像素值与对应概率乘积的累加和放入u0_temp
}
//前景部分
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));
//delta_temp类间方差值
//下面有此语句的详细解释
// 依次找到最大类间方差下的阈值
if (delta_temp > delta_max)
{
delta_max = delta_temp;
threshold = i;
}
}
return thrmaeshold;//返回最大
}
int main()
{
// 图像读取及判断
cv::Mat srcImage = cv::imread("E:\\实验数据文件\\image\\laure.jpg");
if (!srcImage.data)
return 1;
// 灰度转换
cv::Mat srcGray;
cv::cvtColor(srcImage, srcGray, CV_RGB2GRAY);
cv::imshow("srcGray", srcGray);
// 调用OTSU二值化算法得到阈值
int ostuThreshold = OTSU(srcGray);
std::cout << ostuThreshold << std::endl;
// 定义输出结果图像
cv::Mat otsuResultImage = cv::Mat::zeros(srcGray.rows, srcGray.cols, CV_8UC1);
// 利用得到的阈值实现二值化操作
for (int i = 0; i < srcGray.rows; i++)
{
for (int j = 0; j < srcGray.cols; j++)
{
// 满足大于阈值ostuThreshold置255
if (srcGray.at<uchar>(i, j) > ostuThreshold)
otsuResultImage.at<uchar>(i, j) = 255;
else
otsuResultImage.at<uchar>(i, j) = 0;
}
}
cv::imshow("otsuResultImage", otsuResultImage);
cv::waitKey(0);
return 0;}
代码分析:
(1)nSumPix[(int)srcImage.at(i, j)]++:at(i, j):实现对矩阵中某个像素进行读取或者赋值。此条语句的作用举例如下:例如:某一点像素返回值是整数6,那么对应nSumPix[6]里面的值加一(初始值为0),如果整幅图像素值为6的像素点有8个,那么最终的结果是nSumPix[7]里的值为8
(2)u0 = u0_temp / w0 ,如下图解释
(3)delta_temp = (float)(w0
w1
pow((u0 – u1), 2));
3.结果显示
上原图,下结果图。