目录
1、Otsu阈值分割
2、自适应阈值分割
3、 最大熵阈值分割法
4、 迭代阈值分割
5、测验
Otsu(大津法或最大类间方差法)使用的是聚类的思想,把图像的灰度数按灰度级分成2个部分,使得两个部分之间的灰度值差异最大,每个部分之间的灰度差异最小,通过方差的计算来寻找一个合适的灰度级别来划分。 所以可以在二值化的时候采用otsu算法来自动选取阈值进行二值化。otsu算法被认为是图像分割中阈值选取的最佳算法,计算简单,不受图像亮度和对比度的影响。因此,使类间方差最大的分割意味着错分概率最小。
参考链接:https://www.cnblogs.com/moon1992/p/5092726.html
opencv调用格式为:threshold(sourceImage, dstImage, 0, 255, CV_THRESH_OTSU);
sourceImage:输入图像;
dstImage:输出图像;
otsu算法的源码如下:
Mat OtsuAlgThreshold(Mat &image)
{
if (image.channels() != 1)
{
cout << "Please input Gray-image!" << endl;
}
int T = 0; //Otsu算法阈值
double varValue = 0; //类间方差中间值保存
double w0 = 0; //前景像素点数所占比例
double w1 = 0; //背景像素点数所占比例
double u0 = 0; //前景平均灰度
double u1 = 0; //背景平均灰度
double Histogram[256] = { 0 }; //灰度直方图,下标是灰度值,保存内容是灰度值对应的像素点总数
uchar *data = image.data;
double totalNum = image.rows*image.cols; //像素总数
for (int i = 0; i < image.rows; i++)
{
for (int j = 0; j < image.cols; j++)
{
if (image.at(i, j) != 0) Histogram[data[i*image.step + j]]++;
}
}
int minpos, maxpos;
for (int i = 0; i < 255; i++)
{
if (Histogram[i] != 0)
{
minpos = i;
break;
}
}
for (int i = 255; i > 0; i--)
{
if (Histogram[i] != 0)
{
maxpos = i;
break;
}
}
for (int i = minpos; i <= maxpos; i++)
{
//每次遍历之前初始化各变量
w1 = 0; u1 = 0; w0 = 0; u0 = 0;
//***********背景各分量值计算**************************
for (int j = 0; j <= i; j++) //背景部分各值计算
{
w1 += Histogram[j]; //背景部分像素点总数
u1 += j*Histogram[j]; //背景部分像素总灰度和
}
if (w1 == 0) //背景部分像素点数为0时退出
{
break;
}
u1 = u1 / w1; //背景像素平均灰度
w1 = w1 / totalNum; // 背景部分像素点数所占比例
//***********背景各分量值计算**************************
//***********前景各分量值计算**************************
for (int k = i + 1; k < 255; k++)
{
w0 += Histogram[k]; //前景部分像素点总数
u0 += k*Histogram[k]; //前景部分像素总灰度和
}
if (w0 == 0) //前景部分像素点数为0时退出
{
break;
}
u0 = u0 / w0; //前景像素平均灰度
w0 = w0 / totalNum; // 前景部分像素点数所占比例
//***********前景各分量值计算**************************
//***********类间方差计算******************************
double varValueI = w0*w1*(u1 - u0)*(u1 - u0); //当前类间方差计算
if (varValue < varValueI)
{
varValue = varValueI;
T = i;
}
}
Mat dst;
threshold(image, dst, T, 255, CV_THRESH_OTSU);
return dst;
}
这一部分讲解的是opencv自带的adaptiveThreshold()函数,其算法流程:
二值化算法是用输入像素的值I与一个值C来比较,根据比较结果确定输出值。
自适应二值化的每一个像素的比较值C都不同,比较值C由这个像素为中心的一个块范围计算在减去差值delta得到。
其中,C的常用计算方法有两种:
a、平均值减去差值delta(使用盒过滤boxfilter,性能会非常不错)
b、高斯分布加权和减去差值delta (使用高斯滤波GaussionBlur)
adaptiveThreshold()源码如下:
void myadaptive(InputArray _src, OutputArray _dst, double maxValue,
int method, int type, int blockSize, double delta)
{
Mat src = _src.getMat();
CV_Assert(src.type() == CV_8UC1);
CV_Assert(blockSize % 2 == 1 && blockSize > 1);
Size size = src.size();
_dst.create(size, src.type());
Mat dst = _dst.getMat();
if (maxValue < 0)
{
dst = Scalar(0);
return;
}
Mat mean;
if (src.data != dst.data)
mean = dst;
if (method == ADAPTIVE_THRESH_GAUSSIAN_C)
{
GaussianBlur(src, mean, Size(blockSize, blockSize), 0, 0, BORDER_REPLICATE);
}
else if (method == ADAPTIVE_THRESH_MEAN_C)
{
boxFilter(src, mean, src.type(), Size(blockSize, blockSize),
Point(-1, -1), true, BORDER_REPLICATE);
}
else
{
CV_Error(CV_StsBadFlag, "Unknown/unsupported adaptive threshold method");
}
int i, j;
uchar imaxval = saturate_cast(maxValue);
int idelta = type == THRESH_BINARY ? cvCeil(delta) : cvFloor(delta);
uchar tab[768];
if (type == CV_THRESH_BINARY)
for (i = 0; i < 768; i++)
tab[i] = (uchar)(i - 255 > -idelta ? imaxval : 0);
else if (type == CV_THRESH_BINARY_INV)
for (i = 0; i < 768; i++)
tab[i] = (uchar)(i - 255 <= -idelta ? imaxval : 0);
else
{
CV_Error(CV_StsBadFlag, "Unknown/unsupported threshold type");
}
if (src.isContinuous() && mean.isContinuous() && dst.isContinuous())
{
size.width *= size.height;
size.height = 1;
}
for (i = 0; i < size.height; i++)
{
const uchar* sdata = src.data + src.step*i;
const uchar* mdata = mean.data + mean.step*i;
uchar* ddata = dst.data + dst.step*i;
for (j = 0; j < size.width; j++)
// 将[-255, 255] 映射到[0, 510]然后查表
ddata[j] = tab[sdata[j] - mdata[j] + 255];
}
}
参考链接:https://blog.csdn.net/qq_27668313/article/details/77949596
https://blog.csdn.net/robin__chou/article/details/53931442
https://blog.csdn.net/xw20084898/article/details/22760169
最大熵阈值分割函数源码如下:
Mat EntropySeg(Mat src)
{
int tbHist[256] = { 0 };
int index = 0;
double Property = 0.0;
double maxEntropy = -1.0;
double frontEntropy = 0.0;
double backEntropy = 0.0;
int TotalPixel = 0;
int nCol = src.cols*src.channels();
for (int i = 0; i < src.rows; i++)
{
uchar* pData = src.ptr(i);
for (int j = 0; j < nCol; j++)
{
++TotalPixel;
tbHist[pData[j]] += 1;
}
}
for (int i = 0; i < 256; i++)
{
double backTotal = 0;
for (int j = 0; j < i; j++)
{
backTotal += tbHist[j];
}
for (int j = 0; j < i; j++)
{
if (tbHist[j] != 0)
{
Property = tbHist[j] / backTotal;
backEntropy += -Property*logf((float)Property);
}
}
for (int k = i; k < 256; k++)
{
if (tbHist[k] != 0)
{
Property = tbHist[k] / (TotalPixel - backTotal);
frontEntropy += -Property * logf((float)Property);
}
}
if (frontEntropy + backEntropy > maxEntropy)
{
maxEntropy = frontEntropy + backEntropy;
index = i;
}
frontEntropy = 0.0;
backEntropy = 0.0;
}
Mat dst;
threshold(src, dst, index, 255, 0);
return dst;
}
通过迭代方法选择阈值, 计算方法如下:
(1)选择灰度图的平均值作为初始阈值T0 ;
(2)计算小于等于T0的平均值T1, 和大于T0的平均值T2;
(3)新的阈值为T = (T1 + T2)/ 2;
(4)比较T和T0,若相等,则返回T,即为迭代阈值; 否则 T0 = T,重复(1)-(3)
迭代阈值分割的源码如下:
Mat IterationThreshold(Mat src)
{
int width = src.cols;
int height = src.rows;
int hisData[256] = { 0 };
for (int j = 0; j < height; j++)
{
uchar* data = src.ptr(j);
for (int i = 0; i < width; i++)
hisData[data[i]]++;
}
int T0 = 0;
for (int i = 0; i < 256; i++)
{
T0 += i*hisData[i];
}
T0 /= width*height;
int T1 = 0, T2 = 0;
int num1 = 0, num2 = 0;
int T = 0;
while (1)
{
for (int i = 0; i < T0 + 1; i++)
{
T1 += i*hisData[i];
num1 += hisData[i];
}
if (num1 == 0)
continue;
for (int i = T0 + 1; i < 256; i++)
{
T2 += i*hisData[i];
num2 += hisData[i];
}
if (num2 == 0)
continue;
T = (T1 / num1 + T2 / num2) / 2;
if (T == T0)
break;
else
T0 = T;
}
Mat dst;
threshold(src, dst, T, 255, 0);
return dst;
}
void main()
{
Mat src = imread("1.jpg");
cvtColor(src, src, COLOR_RGB2GRAY);
Mat bw1, bw2, bw3, bw4;
myadaptive(src, bw1, 255, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY, 15, 10);
bw2 = EntropySeg(src);
bw3 = OtsuAlgThreshold(src);
bw4 = IterationThreshold(src);
imshow("source", src);
imshow("自适应阈值分割", bw1);
imshow("最大熵阈值分割", bw2);
imshow("Otsu阈值分割", bw3);
imshow("迭代阈值分割", bw4);
waitKey(0);
}
测验结果: