部分处理代码:
- ……
- Dim ts2 As IThresholder = New GlobalMeanThreshold(inbmp)
- Dim tsBMP As New Bitmap(PictureBox1.Width, PictureBox1.Height)
- ts2.RenderToBitmap(tsBMP)
- PictureBox6.Image = tsBMP
- PictureBox6.Height = PictureBox1.Height
- PictureBox6.Width = PictureBox1.Width
- PictureBox6.Left = 0
- PictureBox6.Top = 0
- ……
理论知识:
灰度图像的二值化处理就是讲图像上的点的灰度置为0或255,也就是讲整个图像呈现出明显的黑白效果。即将256个亮度等级的灰度图像通过适当的阀值选取而获得仍然可以反映图像整体和局部特征的二值化图像。在数字图像处理中,二值图像占有非常重要的地位,特别是在实用的图像处理中,以二值图像处理实现而构成的系统是很多的,要进行二值图像的处理与分析,首先要把灰度图像二值化,得到二值化图像,这样子有利于再对图像做进一步处理时,图像的集合性质只与像素值为0或255的点的位置有关,不再涉及像素的多级值,使处理变得简单,而且数据的处理和压缩量小。为了得到理想的二值图像,一般采用封闭、连通的边界定义不交叠的区域。所有灰度大于或等于阀值的像素被判定为属于特定物体,其灰度值为255表示,否则这些像素点被排除在物体区域以外,灰度值为0,表示背景或者例外的物体区域。如果某特定物体在内部有均匀一致的灰度值,并且其处在一个具有其他等级灰度值的均匀背景下,使用阀值法就可以得到比较的分割效果。如果物体同背景的差别表现不在灰度值上(比如纹理不同),可以将这个差别特征转换为灰度的差别,然后利用阀值选取技术来分割该图像。动态调节阀值实现图像的二值化可动态观察其分割图像的具体结果。
灰度图像的二值化没有固定的算法,根据图像区域中目标物体的不同而有不同的二值化算法.目前最主要的方法有:最大值法,平均值法,加权平均值法
如下边代码:
- Private Function SetBitMapToBlackAndWhite(bmp As Bitmap) As Bitmap
- Try
- Dim height As Integer = bmp.Height
- Dim Width As Integer = bmp.Width
- Dim newBitMap As New Bitmap(Width, height)
- Dim pixel As Color
- For x As Integer = 0 To Width - 1
- For y As Integer = 0 To height - 1
- pixel = bmp.GetPixel(x, y)
- Dim r As Integer
- Dim g As Integer
- Dim b As Integer
- Dim Result As Integer
- Result = 0
- r = pixel.R
- g = pixel.G
- b = pixel.B
- Dim iType As Integer = 2
- Select Case iType
- Case 0
- '平均值法
- Result = (r + g + b) / 3
- Exit Select
- Case 1
- '最大值法
- If (r > g) Then
- Result = r
- Else
- Result = g
- End If
- If (Result > b) Then
- Result = Result
- Else
- Result = b
- End If
- Exit Select
- Case 2
- '加权平均值
- Result = CInt(0.7) * r + CInt(0.2) * g + CInt(0.1) * b
- Exit Select
- End Select
- newBitMap.SetPixel(x, y, Color.FromArgb(Result, Result, Result))
- Next
- Next
- Return newBitMap
- Catch ex As Exception
- Return Nothing
- End Try
- End Function
该函数实现的简单的图像2值化。
实际使用中最简单的二值化算法就是根据每个像素的灰度值做舍入处理,比如二值化阀值可以设置为0-255的中值127,但是这种的二值化没有根基图像的整体灰度值所在范围做考虑,所以效果很差.
网上流传较广的是根据图像的直方图求取阀值:可以到网上搜索,我就不重复了
介绍我的方法:
图像数据处理函数:由于需要对图像进行指针操作,故使用C#编写源码
- public override unsafe void DoThresholding()
- {
- System.Drawing.Imaging.BitmapData indata = InBMP.LockBits(new Rectangle(0, 0, InBMP.Width, InBMP.Height),
- System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
- byte* ptr = (byte*)indata.Scan0.ToPointer();
- Histogram his = new Histogram(indata, ptr, 0, 0, InBMP.Width, InBMP.Height);
- int mean = Math.Max((int)his.Mean, 0) * 3;
- //图像阀值
- int stride = indata.Stride;
- for (int x = this.Width - 1; x >= 0; x--)
- {
- for (int y = this.Height - 1; y >= 0; y--)
- {
- int average = (ptr[(y * stride) + (x * 3)] +
- ptr[(y * stride) + (x * 3) + 1] +
- ptr[(y * stride) + (x * 3) + 2]);
- //byte average = (byte)((ptr[(y * indata.Stride) + (x * 3) + 2]));
- if (average > mean)
- Data[x, y] = 255;
- else
- Data[x, y] = 0;
- }
- }
- InBMP.UnlockBits(indata);
- }
- }
图像阀值的求取:
- byte[] HistogramData;
- unsafe public Histogram(System.Drawing.Imaging.BitmapData indata, byte* ptr,
- int left, int top, int width, int height)
- {
- HistogramData = new byte[256];
- for (int i = 0; i < 256; i++)
- HistogramData[i] = 0;
- int right = Math.Min(left + width, indata.Width);
- int bottom = Math.Min(top + height, indata.Height);
- int stride = indata.Stride;
- for (int x = left; x < right; x++)
- {
- for (int y = top; y < bottom; y++)
- {
- HistogramData[ptr[(y * stride) + (x * 3)]] += 1;
- HistogramData[ptr[(y * stride) + (x * 3) + 1]] += 1;
- HistogramData[ptr[(y * stride) + (x * 3) + 2]] += 1;
- }
- }
- CalculateMean();
- CalculateMinMax();
- }
- unsafe public Histogram(byte* ptr, int stride, int imgWidth, int imgHeight,
- int left, int top, int width, int height)
- {
- HistogramData = new byte[256];
- int right = Math.Min(left + width, imgWidth);
- int bottom = Math.Min(top + height, imgHeight);
- for (int x = left; x < right; x++)
- {
- for (int y = top; y < bottom; y++)
- {
- byte average = (byte)((ptr[(y * stride) + (x * 3)] +
- ptr[(y * stride) + (x * 3) + 1] +
- ptr[(y * stride) + (x * 3) + 2]) / 3);
- HistogramData[average] += 1;
- }
- }
- CalculateMean();
- CalculateVariance();
- }
- public byte Mean;
- private int Sum;
- private int WeightedSum;
- private void CalculateMean()
- {
- int sum = 0;
- int weightedSum = 0;
- for (int i = 0; i < 256; i++)
- {
- sum += HistogramData[i];
- weightedSum += HistogramData[i] * i;
- }
- Sum = sum;
- WeightedSum = weightedSum;
- if (sum > 0)
- Mean = (byte)(weightedSum / sum);
- else
- Mean = 0;
- }
生成黑白图像
- public unsafe void RenderToBitmap(Bitmap bmp)
- {
- if (Data == null)
- DoThresholding();
- System.Drawing.Imaging.BitmapData outdata = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),
- System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
- byte* ptr = (byte*)outdata.Scan0.ToPointer();
- for (int x = bmp.Width - 1; x >= 0; x--)
- {
- for (int y = bmp.Height - 1; y >= 0; y--)
- {
- if (this.Data[x, y] > 0)
- {
- ptr[(y * outdata.Stride) + (x * 3)] = this.Data[x, y];
- ptr[(y * outdata.Stride) + (x * 3) + 1] = this.Data[x, y];
- ptr[(y * outdata.Stride) + (x * 3) + 2] = this.Data[x, y];
- }
- else
- {
- ptr[(y * outdata.Stride) + (x * 3)] = 0;
- ptr[(y * outdata.Stride) + (x * 3) + 1] = 0;
- ptr[(y * outdata.Stride) + (x * 3) + 2] = 0;
- }
- }
- }
- bmp.UnlockBits(outdata);
- }
考虑到图像处理速度问题,所有图像都锁定在内存中进行操作。这样比直接操作速度快了几倍。
附件为Threshold控件,引用到工程里面即可, Threshold.rar