本篇所有代码都是基于24位BMP图像
一. 基本的全局阈值处理
当图像的前景和背景相关的只方图之间存在一个相当清晰的波谷时,这个算法效果很好。这部分算法比较简单,由于时间关系,并没有写相关代码
算法步骤:
一、为全局阈值T选择一个初始的估计值(一般选平均灰度)
二、用T分割该图像产生的像素组,G1为灰度值大于T的所有像素组成,G2为灰度值小于T的所有像素组成
三、对G1和G2分别计算平均灰度值m1和m2,计算一个新的阈值T=(m1+m2)/2
四、重复步骤二、步骤三,知道连续迭代中T值之间的差小于一个预定义的△T值为止。
二. otsu(最佳阈值处理也叫最大类间差法)
最大类间差法是一种使用类间方差最大的自动确定阈值的方法。
算法步骤:
一、首先直方图统计Pi,C1为灰度值小于T的所有像素组成,C2为灰度值大于T的所有像素组成
二、令阈值T从0-255开始,遍历计算C1组和C2组的平均灰度m1和m2,以及全局均值mG,求类间方差的最大值
分配到c1像素的平均灰度m1 = mk/p1;
K级累加均值即T=k时,mk = 求和(i*Pi) i = 0,1,2,...,k
全局均值 mG = 求和(i*Pi) i = 0,1,2...L-1
C1发生的概率 P1 = 求和(Pi) i = 0,1,2,...,k
C2发生的概率 P2 = 1-P1
因为 P1*m1 + P2*m2 = mG,所以m2 = (mG-P1*m1)/P2
而类间方差 = P1(m1 - mG)*(m1 - mG) + P2(m2 - mG)*(m2 - mG)
= P1*P2*(m1-m2)*(m1-m2)
所以,最终变量只需要求mk,P1.全局均值mG是常量,一开始就能算出来
三、阈值确定后,小于等于K的为0,大于K的为255.
代码如下
MFC建立类向导
void CImageProcessingView::OnTxfgOtsu()
{
// TODO: 在此添加命令处理程序代码
if (numPicture == 0)
{
AfxMessageBox("请输入一张图片", MB_OK, 0);
return;
}
if (m_nBitCount != 24)
{
AfxMessageBox("输入图片不是24位", MB_OK, 0);
return;
}
AfxMessageBox("图像分割,otsu全局阈值分割", MB_OK, 0);
int num;//记录每一行需要填充的字节
if (m_nWidth * 3 % 4 != 0)
{
num = 4 - m_nWidth * 3 % 4;
}
else
{
num = 0;
}
//打开临时的图片
FILE *fpo = fopen(BmpName, "rb");
FILE *fpw = fopen(BmpNameLin, "wb+");
fread(&bfh, sizeof(BITMAPFILEHEADER), 1, fpo);
fread(&bih, sizeof(BITMAPINFOHEADER), 1, fpo);
fwrite(&bfh, sizeof(BITMAPFILEHEADER), 1, fpw);
fwrite(&bih, sizeof(BITMAPINFOHEADER), 1, fpw);
fread(m_pImage, m_nImage, 1, fpo);
unsigned char *ImageSize;
ImageSize = new unsigned char[m_nImage];
//统计直方图
//这个代码是基于24位真彩色图像,有三个通道,这里我只以统计一个通道。
int hist[256] = { 0 };//存放每个灰度的数量
double pro_hist[256] = { 0.0 };// 存放每个灰度数量占所有像素数量的概率
int x,y;//对应X轴和Y轴
int temp;//对应的灰度值
int i,j;
//统计每个灰度的数量
for (y = 0; y < m_nHeight; y++)
{
for (x = 0; x < m_nWidth; x++)
{
temp = m_pImage[(x + y*m_nWidth) * 3 + y*num];
hist[temp]++;
}
}
//统计每个灰度所占的概率
for (i = 0; i < 256; i++)
{
pro_hist[i] = 1.0 * hist[i] / (m_nWidth*m_nHeight);
}
//计算平均灰度值
double mG = 0.0,m1 = 0.0,m2 = 0.0,p1 = 0.0,p2 = 0.0,mk;
for (i = 0; i++; i < 256)
{
mG += i * pro_hist[i];
}
//计算前景和背景的类间方差,并得出最大阈值
double maxc=0.0,c;//定义类间方差
int maxT=0;//最大阈值
for (i = 0; i < 256; i++)
{
m1 = 0.0, m2 = 0.0, p1 = 0.0, p2 = 0.0,mk = 0.0;//每一次循环归0//一般只需要计算p1和mk。
for (j = 0; j < i; j++)
{
p1 += pro_hist[i];
mk += i*pro_hist[i];
}
m1 = mk / p1; //mk = m1*p1
p2 = 1 - p1;
m2 = (mG - mk) / p2;
c = p1*p2*(m1 - m2)*(m1 - m2);
if (c > maxc)
{
maxc = c;
maxT = i;
}
}
//最佳阈值得到后,对图像进行阈值处理,小于等于maxT的为0,大于maxT的为255
for (y = 0; y < m_nHeight; y++)
{
for (x = 0; x < m_nWidth; x++)
{
if (m_pImage[(x + y*m_nWidth) * 3 + y*num] > maxT)
{
ImageSize[(x + y*m_nWidth) * 3 + y*num] = 255;
ImageSize[(x + y*m_nWidth) * 3 + y*num + 1] = 255;
ImageSize[(x + y*m_nWidth) * 3 + y*num + 2] = 255;
}
else
{
ImageSize[(x + y*m_nWidth) * 3 + y*num] = 0;
ImageSize[(x + y*m_nWidth) * 3 + y*num + 1] = 0;
ImageSize[(x + y*m_nWidth) * 3 + y*num + 2] = 0;
}
}
}
fwrite(ImageSize, m_nImage, 1, fpw);
fclose(fpo);
fclose(fpw);
numPicture = 2;
level = 400;
Invalidate();
}
效果如下
三. 多阈值处理
这里简单提一下多阈值otsu。其实和单阈值思想差不多,以双阈值为例,目的就是找到两个阈值T1和T2将图像小于等于T1,大于T1但小于等于T2,大于T2三部分
一、首先直方图统计Pi,C1为灰度值小于等于T1的所有像素组成,C2为灰度值大于T1小于等于T2的所有像素组成
C3为大于T2像素组成
二、令阈值k1从1-255开始,阈值k2从K1-255,循环遍历计算C1、C2、C3组的平均灰度m1、m2和m3,以及全局均值mG,求类间方差的最大值
分配到c1像素的平均灰度 m1 = mk1/P1;
分配到c2像素的平均灰度 m2 = mk2/P2;
分配到c3像素的平均灰度 m3 = mk3/P3;
K级累加均值即T=k时,mk = 求和(i*Pi) i = 0,1,2,...,k
全局均值 mG = 求和(i*Pi) i = 0,1,2...L-1
C1发生的概率 P1 = 求和(Pi) i = 0,1,2,...,k
有 P1 + P2 + P3=1;
而类间方差 = P1(m1 - mG)*(m1 - mG) + P2(m2 - mG)*(m2 - mG)+ P3(m3-mG)*(m3-mG)
所以,求k1和k2使类间方差最大
三、阈值确定后,将图像分成 小于等于T1,大于T1但小于等于T2,大于T2三部分
四. 可变阈值处理(自适应阈值分割)
我理解成将图像分成N个小窗口,对每个窗口设置阈值,一种比较简单的方法就是寻找窗口内的最大值和最小值,选取均值作为阈值。当然,也可以每个窗口用otsu。
五. 多变量阈值处理
之前讨论的都是基于灰度的,但有的情况下会有多个变量,如彩色图像R、G、B.
一、假设a是我们感兴趣的平均微红色,一种用来分割彩色图像的方法是计算任意彩色点z 与a 的距离度量
D(z,a),为了表达方便,我记为D。则图像小于D的为1,大于D的为0;
欧氏距离:D=||z-a || = sqrt((z-a)T * (z-a)),(z-a)T为转置
还有一种更有用的距离测量,马氏距离
D = sqrt((z-a)T * C(-1)(z-a)),C(-1)为C的逆矩阵,C是Z的协方差矩阵