八、灰度图像二值化
三角法
/*
1. 图像转灰度
2. 计算图像灰度直方图
3. 寻找直方图中两侧边界
4. 寻找直方图最大值
5. 检测是否最大波峰在亮的一侧,否则翻转
6. 计算阈值得到阈值T,如果翻转则255-T
//三角法图像二值化
*/
public void Triangle()
{
//图像数据,需要是灰度图,否则需要自己转灰度
System.Drawing.Bitmap bmpHist = new Bitmap(@"C:\\Users\\xiao\\Desktop\\123.bmp");
//灰度等级
int[] countPixel = new int[256];
//记录最大的灰度级个数
int maxPixel;
//是否翻转
bool isflipped = false;
//锁定8位灰度位图
Rectangle rect = new Rectangle(0, 0, bmpHist.Width, bmpHist.Height);
System.Drawing.Imaging.BitmapData bmpData = bmpHist.LockBits(rect,
System.Drawing.Imaging.ImageLockMode.ReadWrite, bmpHist.PixelFormat);
IntPtr ptr = bmpData.Scan0;
int bytes = bmpHist.Width * bmpHist.Height;
byte[] grayValues = new byte[bytes];
System.Runtime.InteropServices.Marshal.Copy(ptr, grayValues, 0, bytes);//灰度值数据存入grayValues中
byte temp = 0;
maxPixel = 0;
//灰度等级数组清零
Array.Clear(countPixel, 0, 256);
//计算各个灰度级的像素个数
for (int i = 0; i < bytes; i++)
{
//灰度级
temp = grayValues[i];
//计数加1
countPixel[temp]++;
if (countPixel[temp] > maxPixel)
{
//找到灰度频率最大的像素数,用于绘制直方图
maxPixel = countPixel[temp];
}
}
//解锁
System.Runtime.InteropServices.Marshal.Copy(grayValues, 0, ptr, bytes);
bmpHist.UnlockBits(bmpData);
//3. 寻找直方图中两侧边界
int left_bound = 0;
int right_bound = 0;
int max = 0;
int max_index = 0;
//左侧为零的位置
for (int i = 0; i < 256; i++)
{
if (countPixel[i] > 0)
{
left_bound = i;
break;
}
}
//直方图为零的位置
if (left_bound > 0)
{
left_bound--;
}
//直方图右侧为零的位置
for (int i = 255; i > 0; i--)
{
if (countPixel[i] > 0)
{
right_bound = i;
break;
}
}
//直方图为零的地方
if (right_bound > 0)
{
right_bound++;
}
//4. 寻找直方图最大值
for (int i = 0; i < 256; i++)
{
if (countPixel[i] > max)
{
max = countPixel[i];
max_index = i;
}
}
//判断最大值是否在最左侧,如果是则不用翻转
//因为三角法二值化只能适用于最大值在最右侧
if (max_index - left_bound < right_bound - max_index)
{
isflipped = true;
int i = 0;
int j = 255;
while (i < j)
{
// 左右交换
temp = (byte)countPixel[i];
countPixel[i] = countPixel[j];
countPixel[j] = temp;
i++; j--;
}
left_bound = 255 - right_bound;
max_index = 255 - max_index;
}
// 计算求得阈值
double thresh = left_bound;
double a, b, dist = 0, tempdist;
a = max; b = left_bound - max_index;
for (int i = left_bound + 1; i <= max_index; i++)
{
// 计算距离 - 不需要真正计算
tempdist = a * i + b * countPixel[i];
if (tempdist > dist)
{
dist = tempdist;
thresh = i;
}
}
thresh--;
// 对已经得到的阈值T,如果前面已经翻转了,则阈值要用255-T
if (isflipped)
{
thresh = 255 - thresh;
}
}
Otsu阈值法
OTSU算法也称最大类间差法,有时也称之为大津算法,由大津于1979年提出,被认为是图像分割中阈值选取的最佳算法,计算简单,不受图像亮度和对比度的影响,因此在数字图像处理上得到了广泛的应用。它是按图像的灰度特性,将图像分成背景和前景两部分。因方差是灰度分布均匀性的一种度量,背景和前景之间的类间方差越大,说明构成图像的两部分的差别越大,当部分前景错分为背景或部分背景错分为前景都会导致两部分差别变小。因此,使类间方差最大的分割意味着错分概率最小。
public voidOtsuThreshold(Bitmap b)
{
// 图像灰度化
// b = Gray(b);
int width = b.Width;
int height = b.Height;
byte threshold = 0;
int[] hist = new int[256];
int AllPixelNumber = 0, PixelNumberSmall = 0, PixelNumberBig = 0;
double MaxValue, AllSum = 0, SumSmall = 0, SumBig, ProbabilitySmall, ProbabilityBig, Probability;
BitmapData data = b.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
unsafe
{
byte* p = (byte*)data.Scan0;
int offset = data.Stride - width * 4;
for (int j = 0; j < height; j++)
{
for (int i = 0; i < width; i++)
{
hist[p[0]]++;
p += 4;
}
p += offset;
}
b.UnlockBits(data);
}
//计算灰度为I的像素出现的概率
for (int i = 0; i < 256; i++)
{
AllSum += i * hist[i]; // 质量矩
AllPixelNumber += hist[i]; // 质量
}
MaxValue = -1.0;
for (int i = 0; i < 256; i++)
{
PixelNumberSmall += hist[i];
PixelNumberBig = AllPixelNumber - PixelNumberSmall;
if (PixelNumberBig == 0)
{
break;
}
SumSmall += i * hist[i];
SumBig = AllSum - SumSmall;
ProbabilitySmall = SumSmall / PixelNumberSmall;
ProbabilityBig = SumBig / PixelNumberBig;
Probability = PixelNumberSmall * ProbabilitySmall * ProbabilitySmall + PixelNumberBig * ProbabilityBig * ProbabilityBig;
if (Probability > MaxValue)
{
MaxValue = Probability;
threshold = (byte)i;
}
}
}