1.简介:
一维Otsu算法有计算简洁、稳定、自适应强等优点,被广泛用于图像分割中。但一维Otsu算法没有考虑图像像素点之间的关系,当图像中有噪声时,会导致分割的效果不理想。因此,刘健庄等人在1993年提出了二维的Otsu算法,提升了算法的抗噪声能力。
2.算法思想:
同时考虑像素的灰度值分布和它们邻域像素的平均灰度值分布,因此形成的阈值是一个二维矢量,最佳的阈值在一个二维的测度准则下确定最大值时得到。
3.算法过程:
(1)设图像I(x,y),的灰度级为L级,那么图像的邻域平均灰度也分为L级。
(2)设f(x,y)为像素点(x,y)的灰度值,g(x,y)为像素点(x,y)为中心的K*K的像
素点集合的灰度平均值。令f(x,y)=i,g(x,y)=j,然后就形成了一个二元组(i,j)。
(3)设二元组(i,j)出现的次数为fij,然后求出二元组对应的概率密度Pij,
Pij=fij/N, i,j=1,2,…,L,其中N为图像像素点总数。
(4)任意选取一个阈值向量(s,t)选取的阈值向量将图像的二维直方图划分成4个
区域,B、C区域代表图像的前景和背景,A、D区域代表噪声点。
(5)设C、B两个区域对应的概率分别为w1,w2,对应的均值矢量为u1,u2。整个图
片所对应的均值矢量为uT。
4.代码实现(opencv3):
#include "opencv.hpp" #include "imgproc.hpp" #include "highgui.hpp" #include "iostream" #include "core.hpp" using namespace cv; using namespace std; int Otsu2D(Mat srcimage); //二维Otsu算法 int main() { Mat srcimage, grayimage, dstimage; srcimage = imread("lena.jpg"); namedWindow("原图", 0); imshow("原图", srcimage); //显示原图 cvtColor(srcimage, grayimage, COLOR_RGB2GRAY); //得到灰度图 double time0 = static_cast<double>(getTickCount()); //记录程序开始时间 int thresholdValue = Otsu2D(grayimage); //调用二维Otsu函数 time0 = ((double)getTickCount() - time0) / getTickFrequency(); cout << "算法运行时间为:" << time0 << endl; cout << "Otsu阈值为:" << thresholdValue << endl; threshold(grayimage, dstimage, thresholdValue, 255, THRESH_BINARY); //将得到的阈值传入函数,得到分割效果图 namedWindow("Otsu算法结果", 0); imshow("Otsu算法结果", dstimage); waitKey(); return 0; }
int Otsu2D(Mat srcimage) { double Histogram[256][256]; //建立二维灰度直方图 double TrMax = 0.0; //用于存储矩阵的迹(矩阵对角线之和) int height = srcimage.rows; //矩阵的行数 int width = srcimage.cols; //矩阵的列数 int N = height*width; //像素的总数 int T; //最终阈值 uchar *data = srcimage.data; for (int i = 0; i < 256; i++) { for (int j = 0; j < 256; j++) { Histogram[i][j] = 0; //初始化变量 } } for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int Data1 = data[i*srcimage.step + j]; //获取当前灰度值 int Data2 = 0; //用于存放灰度的平均值 for (int m = i - 1; m <= i + 1; m++) { for (int n = j - 1; n <= j + 1; n++) { if ((m >= 0) && (m < height) && (n >= 0) && (n < width)) Data2 += data[m*srcimage.step + n];//邻域灰度值总和 } } Data2 = Data2 / 9; Histogram[Data1][Data2]++; //记录(i,j)的数量 } } for (int i = 0; i < 256; i++) for (int j = 0; j < 256; j++) Histogram[i][j] /= N; //归一化的每一个二元组的概率分布 double Fgi = 0.0; //前景区域均值向量i分量 double Fgj = 0.0; //前景区域均值向量j分量 double Bgi = 0.0; //背景区域均值向量i分量 double Bgj = 0.0; //背景区域均值向量j分量 double Pai = 0.0; //全局均值向量i分量 panorama(全景) double Paj = 0.0; //全局均值向量j分量 double w0 = 0.0; //前景区域联合概率密度 double w1 = 0.0; //背景区域联合概率密度 double num1 = 0.0; //遍历过程中前景区i分量的值 double num2 = 0.0; //遍历过程中前景区j分量的值 double num3 = 0.0; //遍历过程中背景区i分量的值 double num4 = 0.0; //遍历过程中背景区j分量的值 int Threshold_s = 0; //阈值s int Threshold_t = 0; //阈值t double temp = 0.0; //存储矩阵迹的最大值 for(int i=0;i<256;i++) { for (int j = 0; j < 256; j++) { Pai += i*Histogram[i][j]; //全局均值向量i分量计算 Paj += j*Histogram[i][j]; //全局均值向量j分量计算 } } for (int i = 0; i < 256; i++) { for (int j = 0; j < 256; j++) { w0 += Histogram[i][j]; //前景的概率 num1 += i*Histogram[i][j]; //遍历过程中前景区i分量的值 num2 += j*Histogram[i][j]; //遍历过程中前景区j分量的值 w1 = 1 - w0; //背景的概率 num3 = Pai - num1; //遍历过程中背景区i分量的值 num4 = Paj - num2; //遍历过程中背景区j分量的值 Fgi = num1 / w0; Fgj = num2 / w1; Bgi = num3 / w0; Bgj = num4 / w1; TrMax = ((w0*Pai - num1)*(w0*Pai - num1) + (w0*Paj - num2)*(w0*Paj - num2)) / (w0*w1); if (TrMax > temp) { temp = TrMax; Threshold_s = i; Threshold_t = j; } } } cout << Threshold_s << " " << Threshold_t << endl; T = Threshold_s; return T; }
5.运行结果(得到2个阈值,取其中一个即可):
6.修改后的代码
int Otsu2D(Mat srcimage) { double Histogram[256][256]; //建立二维灰度直方图 double TrMax = 0.0; //用于存储矩阵的迹(矩阵对角线之和) int height = srcimage.rows; //矩阵的行数 int width = srcimage.cols; //矩阵的列数 int N = height*width; //像素的总数 int T; //最终阈值 uchar *data = srcimage.data; for (int i = 0; i < 256; i++) { for (int j = 0; j < 256; j++) { Histogram[i][j] = 0; //初始化变量 } } for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int Data1 = data[i*srcimage.step + j]; //获取当前灰度值 int Data2 = 0; //用于存放灰度的平均值 for (int m = i - 1; m <= i + 1; m++) { for (int n = j - 1; n <= j + 1; n++) { if ((m >= 0) && (m < height) && (n >= 0) && (n < width)) Data2 += data[m*srcimage.step + n];//邻域灰度值总和 } } Data2 = Data2 / 9; Histogram[Data1][Data2]++; //记录(i,j)的数量 } } for (int i = 0; i < 256; i++) { for (int j = 0; j < 256; j++) { Histogram[i][j] /= N; //归一化的每一个二元组的概率分布 } } double S[256]; //统计前i行概率的数组 double N1[256]; //统计遍历过程中前景区i分量的值 double N2[256]; //统计遍历过程中前景区j分量的值 S[0] = 0; N1[0] = 0; N2[0] = 0; for (int i = 1; i < 256; i++) { double x = 0,n1=0,n2=0; for (int j = 0; j < 256; j++) { x += Histogram[i - 1][j]; n1 += ((i-1)*Histogram[i - 1][j]); //遍历过程中前景区i分量的值 n2 += (j*Histogram[i - 1][j]); //遍历过程中前景区j分量的值 } S[i] = x + S[i - 1]; N1[i] = n1 + N1[i - 1]; N2[i] = n2 + N2[i - 1]; } double Pai = 0.0; //全局均值向量i分量 panorama(全景) double Paj = 0.0; //全局均值向量j分量 int Threshold_s = 0; //阈值s int Threshold_t = 0; //阈值t int M = 0; //中间变量 double temp = 0.0; //存储矩阵迹的最大值 double num3 = 0.0; //遍历过程中背景区i分量的值 double num4 = 0.0; //遍历过程中背景区j分量的值 double Fgi = 0.0; //前景区域均值向量i分量 double Fgj = 0.0; //前景区域均值向量j分量 double Bgi = 0.0; //背景区域均值向量i分量 double Bgj = 0.0; //背景区域均值向量j分量 for(int i=0;i<256;i++) { for (int j = 0; j < 256; j++) { Pai += i*Histogram[i][j]; //全局均值向量i分量计算 Paj += j*Histogram[i][j]; //全局均值向量j分量计算 } } for (int i = 0; i < 256; i++) { double w0 = 0.0; //前景区域联合概率密度 double w1 = 0.0; //背景区域联合概率密度 double num1 = 0.0; //遍历过程中前景区i分量的值 与w0一样做相关处理 double num2 = 0.0; //遍历过程中前景区j分量的值 if (i >= 1) { w0 += S[i - 1]; num1 += N1[i - 1]; num2 += N2[i - 1]; } for (int j = 0; j < 256; j++) { w0 += Histogram[i][j]; //前景的概率 num1 += i*Histogram[i][j]; //遍历过程中前景区i分量的值 num2 += j*Histogram[i][j]; //遍历过程中前景区j分量的值 w1 = 1 - w0; //背景的概率 num3 = Pai - num1; //遍历过程中背景区i分量的值 num4 = Paj - num2; //遍历过程中背景区j分量的值 } Fgi = num1 / w0; Fgj = num2 / w1; Bgi = num3 / w0; Bgj = num4 / w1; TrMax = ((w0*Pai - num1)*(w0*Pai - num1) + (w0*Paj - num2)*(w0*Paj - num2)) / (w0*w1); if (TrMax > temp) { temp = TrMax; Threshold_s = i; } } cout << Threshold_s <<endl; T = Threshold_s; return T; }