C
Celarian
I've written a function, Despeckle, to remove noise from Bitmaps. The
English description of the algorithm I've implemented might read as follows:
For each pixel in the image, create an array which contains the color of
that pixel and every adjacent pixel. Sort that array, and set the color of
the corresponding pixel in the target bitmap to the median value of the
array.
My code fails in these respects:
1. it does not despeckle the edges of an image, only from (1,1) to (width-1,
height-1)
2. it cannot work with indexed images; i have made the unfortunate
assumption that all images passed in will be 24 bpp.
3. it is slow as molasses in january
I realise that I could accelerate the speed of this function by rewriting it
in C# and using unsafe code, but that is not an option for this project. It
must be written in VB. I have already overcome the slowness of
GetPixel/SetPixel by using Marshaling. How else can I improve this code?
Code follows.
--------------------------------------------------------------------------
Structure RGBPixel
Public R As Byte
Public G As Byte
Public B As Byte
End Structure
Public Function RGB2Int(ByVal r As Byte, ByVal g As Byte, ByVal b As Byte)
As Integer
Return ((r * 256) + g) * 256 + b
End Function
Public Function Int2RGB(ByVal c As Integer) As RGBPixel
Dim nulpad As String = "000000"
Dim inp As String = Hex$(c)
inp = nulpad.Substring(0, 6 - Len(inp)) + inp
Dim p As RGBPixel
p.B = Convert.ToByte(Convert.ToInt32(inp.Substring(0, 2), 16))
p.G = Convert.ToByte(Convert.ToInt32(inp.Substring(2, 2), 16))
p.R = Convert.ToByte(Convert.ToInt32(inp.Substring(4, 2), 16))
Return p
End Function
Public Function Despeckle(ByVal bmp As Bitmap) As Bitmap
' eliminate noise from the source image and return the results
Dim bmpSource As Bitmap = bmp.Clone()
Dim bmpTarget As Bitmap = New Bitmap(bmpSource.Width, bmpSource.Height,
bmpSource.PixelFormat)
bmpTarget.SetResolution(bmpSource.HorizontalResolution,
bmpSource.VerticalResolution)
Dim bmpSourceData As BitmapData = _
bmpSource.LockBits(New Rectangle(0, 0, bmpSource.Width,
bmpSource.Height), _
ImageLockMode.ReadOnly, bmpSource.PixelFormat)
Dim bmpTargetData As BitmapData = _
bmpTarget.LockBits(New Rectangle(0, 0, bmpTarget.Width,
bmpTarget.Height), _
ImageLockMode.WriteOnly, bmpTarget.PixelFormat)
Dim advance As Integer ' iAdvance will equal the number of bits per
pixel / 8
' assume 24bpp for the time being (unfortunate; this code won't work to
despeckle
' bitonal TIFFs as-is
If (bmpSource.PixelFormat = PixelFormat.Format24bppRgb) Then
advance = 3
Else
Throw New ApplicationException("PixelFormat was not Format24bppRgb.")
End If
' allocate space to hold bits in source and target images
Dim sourceArray(bmpSource.Height * bmpSourceData.Stride) As Byte
Dim targetArray(bmpTarget.Height * bmpTargetData.Stride) As Byte
' extract source bits to managed space
Marshal.Copy(bmpSourceData.Scan0, sourceArray, 0, sourceArray.Length)
' map the pixels from source to target
System.Diagnostics.Trace.WriteLine("Beginning despeckle")
Dim dtStart As DateTime = DateTime.Now
Dim x, y As Integer 'pixel locations
Dim r, g, b As Byte 'RGB values of the current pixel
'Dim pixColor As Color 'used to convert between RGB values and integers
Dim p As RGBPixel
Dim prevOffset, currOffset, nextOffset As Integer
'we're using these ranges to stay entirely within the meat of the
image --
'not ready to try dealing with edges yet
For y = 1 To bmpSource.Height - 2
' calculate the offsets of the lines
prevOffset = (y - 1) * bmpSourceData.Stride
currOffset = y * bmpSourceData.Stride
nextOffset = (y + 1) * bmpSourceData.Stride
For x = 1 To bmpSource.Width - 2
Dim al As New ArrayList(10)
' gdi+ still lies to us -- the format is BGR, not RGB
'previous row
r = sourceArray(prevOffset + ((x - 1) * advance) + 2)
g = sourceArray(prevOffset + ((x - 1) * advance) + 1)
b = sourceArray(prevOffset + ((x - 1) * advance))
al.Add(RGB2Int(r, g, b))
r = sourceArray(prevOffset + (x * advance) + 2)
g = sourceArray(prevOffset + (x * advance) + 1)
b = sourceArray(prevOffset + (x * advance))
al.Add(RGB2Int(r, g, b))
r = sourceArray(prevOffset + ((x + 1) * advance) + 2)
g = sourceArray(prevOffset + ((x + 1) * advance) + 1)
b = sourceArray(prevOffset + ((x + 1) * advance))
al.Add(RGB2Int(r, g, b))
'current row
r = sourceArray(currOffset + ((x - 1) * advance) + 2)
g = sourceArray(currOffset + ((x - 1) * advance) + 1)
b = sourceArray(currOffset + ((x - 1) * advance))
al.Add(RGB2Int(r, g, b))
r = sourceArray(currOffset + (x * advance) + 2)
g = sourceArray(currOffset + (x * advance) + 1)
b = sourceArray(currOffset + (x * advance))
al.Add(RGB2Int(r, g, b))
r = sourceArray(currOffset + ((x + 1) * advance) + 2)
g = sourceArray(currOffset + ((x + 1) * advance) + 1)
b = sourceArray(currOffset + ((x + 1) * advance))
al.Add(RGB2Int(r, g, b))
'next row
r = sourceArray(nextOffset + ((x - 1) * advance) + 2)
g = sourceArray(nextOffset + ((x - 1) * advance) + 1)
b = sourceArray(nextOffset + ((x - 1) * advance))
al.Add(RGB2Int(r, g, b))
r = sourceArray(nextOffset + (x * advance) + 2)
g = sourceArray(nextOffset + (x * advance) + 1)
b = sourceArray(nextOffset + (x * advance))
al.Add(RGB2Int(r, g, b))
r = sourceArray(nextOffset + ((x + 1) * advance) + 2)
g = sourceArray(nextOffset + ((x + 1) * advance) + 1)
b = sourceArray(nextOffset + ((x + 1) * advance))
al.Add(RGB2Int(r, g, b))
'write out the target pixel
al.Sort()
p = Int2RGB(al(4))
targetArray(currOffset + (x * advance) + 2) = p.R
targetArray(currOffset + (x * advance) + 1) = p.G
targetArray(currOffset + (x * advance) + 0) = p.B
Next
Next
' copy the target bits from managed space
Marshal.Copy(targetArray, 0, bmpTargetData.Scan0, targetArray.Length)
' unlock the bits
bmpTarget.UnlockBits(bmpTargetData)
bmpSource.UnlockBits(bmpSourceData)
Return bmpTarget
End Function
English description of the algorithm I've implemented might read as follows:
For each pixel in the image, create an array which contains the color of
that pixel and every adjacent pixel. Sort that array, and set the color of
the corresponding pixel in the target bitmap to the median value of the
array.
My code fails in these respects:
1. it does not despeckle the edges of an image, only from (1,1) to (width-1,
height-1)
2. it cannot work with indexed images; i have made the unfortunate
assumption that all images passed in will be 24 bpp.
3. it is slow as molasses in january
I realise that I could accelerate the speed of this function by rewriting it
in C# and using unsafe code, but that is not an option for this project. It
must be written in VB. I have already overcome the slowness of
GetPixel/SetPixel by using Marshaling. How else can I improve this code?
Code follows.
--------------------------------------------------------------------------
Structure RGBPixel
Public R As Byte
Public G As Byte
Public B As Byte
End Structure
Public Function RGB2Int(ByVal r As Byte, ByVal g As Byte, ByVal b As Byte)
As Integer
Return ((r * 256) + g) * 256 + b
End Function
Public Function Int2RGB(ByVal c As Integer) As RGBPixel
Dim nulpad As String = "000000"
Dim inp As String = Hex$(c)
inp = nulpad.Substring(0, 6 - Len(inp)) + inp
Dim p As RGBPixel
p.B = Convert.ToByte(Convert.ToInt32(inp.Substring(0, 2), 16))
p.G = Convert.ToByte(Convert.ToInt32(inp.Substring(2, 2), 16))
p.R = Convert.ToByte(Convert.ToInt32(inp.Substring(4, 2), 16))
Return p
End Function
Public Function Despeckle(ByVal bmp As Bitmap) As Bitmap
' eliminate noise from the source image and return the results
Dim bmpSource As Bitmap = bmp.Clone()
Dim bmpTarget As Bitmap = New Bitmap(bmpSource.Width, bmpSource.Height,
bmpSource.PixelFormat)
bmpTarget.SetResolution(bmpSource.HorizontalResolution,
bmpSource.VerticalResolution)
Dim bmpSourceData As BitmapData = _
bmpSource.LockBits(New Rectangle(0, 0, bmpSource.Width,
bmpSource.Height), _
ImageLockMode.ReadOnly, bmpSource.PixelFormat)
Dim bmpTargetData As BitmapData = _
bmpTarget.LockBits(New Rectangle(0, 0, bmpTarget.Width,
bmpTarget.Height), _
ImageLockMode.WriteOnly, bmpTarget.PixelFormat)
Dim advance As Integer ' iAdvance will equal the number of bits per
pixel / 8
' assume 24bpp for the time being (unfortunate; this code won't work to
despeckle
' bitonal TIFFs as-is
If (bmpSource.PixelFormat = PixelFormat.Format24bppRgb) Then
advance = 3
Else
Throw New ApplicationException("PixelFormat was not Format24bppRgb.")
End If
' allocate space to hold bits in source and target images
Dim sourceArray(bmpSource.Height * bmpSourceData.Stride) As Byte
Dim targetArray(bmpTarget.Height * bmpTargetData.Stride) As Byte
' extract source bits to managed space
Marshal.Copy(bmpSourceData.Scan0, sourceArray, 0, sourceArray.Length)
' map the pixels from source to target
System.Diagnostics.Trace.WriteLine("Beginning despeckle")
Dim dtStart As DateTime = DateTime.Now
Dim x, y As Integer 'pixel locations
Dim r, g, b As Byte 'RGB values of the current pixel
'Dim pixColor As Color 'used to convert between RGB values and integers
Dim p As RGBPixel
Dim prevOffset, currOffset, nextOffset As Integer
'we're using these ranges to stay entirely within the meat of the
image --
'not ready to try dealing with edges yet
For y = 1 To bmpSource.Height - 2
' calculate the offsets of the lines
prevOffset = (y - 1) * bmpSourceData.Stride
currOffset = y * bmpSourceData.Stride
nextOffset = (y + 1) * bmpSourceData.Stride
For x = 1 To bmpSource.Width - 2
Dim al As New ArrayList(10)
' gdi+ still lies to us -- the format is BGR, not RGB
'previous row
r = sourceArray(prevOffset + ((x - 1) * advance) + 2)
g = sourceArray(prevOffset + ((x - 1) * advance) + 1)
b = sourceArray(prevOffset + ((x - 1) * advance))
al.Add(RGB2Int(r, g, b))
r = sourceArray(prevOffset + (x * advance) + 2)
g = sourceArray(prevOffset + (x * advance) + 1)
b = sourceArray(prevOffset + (x * advance))
al.Add(RGB2Int(r, g, b))
r = sourceArray(prevOffset + ((x + 1) * advance) + 2)
g = sourceArray(prevOffset + ((x + 1) * advance) + 1)
b = sourceArray(prevOffset + ((x + 1) * advance))
al.Add(RGB2Int(r, g, b))
'current row
r = sourceArray(currOffset + ((x - 1) * advance) + 2)
g = sourceArray(currOffset + ((x - 1) * advance) + 1)
b = sourceArray(currOffset + ((x - 1) * advance))
al.Add(RGB2Int(r, g, b))
r = sourceArray(currOffset + (x * advance) + 2)
g = sourceArray(currOffset + (x * advance) + 1)
b = sourceArray(currOffset + (x * advance))
al.Add(RGB2Int(r, g, b))
r = sourceArray(currOffset + ((x + 1) * advance) + 2)
g = sourceArray(currOffset + ((x + 1) * advance) + 1)
b = sourceArray(currOffset + ((x + 1) * advance))
al.Add(RGB2Int(r, g, b))
'next row
r = sourceArray(nextOffset + ((x - 1) * advance) + 2)
g = sourceArray(nextOffset + ((x - 1) * advance) + 1)
b = sourceArray(nextOffset + ((x - 1) * advance))
al.Add(RGB2Int(r, g, b))
r = sourceArray(nextOffset + (x * advance) + 2)
g = sourceArray(nextOffset + (x * advance) + 1)
b = sourceArray(nextOffset + (x * advance))
al.Add(RGB2Int(r, g, b))
r = sourceArray(nextOffset + ((x + 1) * advance) + 2)
g = sourceArray(nextOffset + ((x + 1) * advance) + 1)
b = sourceArray(nextOffset + ((x + 1) * advance))
al.Add(RGB2Int(r, g, b))
'write out the target pixel
al.Sort()
p = Int2RGB(al(4))
targetArray(currOffset + (x * advance) + 2) = p.R
targetArray(currOffset + (x * advance) + 1) = p.G
targetArray(currOffset + (x * advance) + 0) = p.B
Next
Next
' copy the target bits from managed space
Marshal.Copy(targetArray, 0, bmpTargetData.Scan0, targetArray.Length)
' unlock the bits
bmpTarget.UnlockBits(bmpTargetData)
bmpSource.UnlockBits(bmpSourceData)
Return bmpTarget
End Function