locked
How to count black pixels in a bitmap. RRS feed

  • Question

  • I don't understand image formats. Terms such as 8bpp mean nothing to me.  That's why I need help.

    My goal is to iterate through each pixel determining whether it is black or white. Ideally I want to do this one row at a time (so I can count the number of black pixels in each row). This is a bitonal image.

    I tried to get it started, but I don't understand what value(s) I'm supposed to be polling for.

    The following code takes a stab at it (i.e. makes  a guess as to what value I'm supposed to check) and then outputs that value to a textfile.  When I read the textfile, though, it doesn't look like anything useful.    This code is an attempted modification of some code found in a CodeProject article.

    Private Sub getPixelValues(ByVal source As Bitmap)
            Dim sourceData As BitmapData = source.LockBits(New Rectangle(0, 0, source.Width, source.Height), ImageLockMode.[ReadOnly], PixelFormat.Format1bppIndexed)
            Dim imageSize As Integer = sourceData.Stride * sourceData.Height
            Dim sourceBuffer As Byte() = New Byte(imageSize - 1) {}
            System.Runtime.InteropServices.Marshal.Copy(sourceData.Scan0, sourceBuffer, 0, imageSize)
            source.UnlockBits(sourceData)
            Dim sourceIndex As Integer = 0
            Dim sw As New StreamWriter("C:\pixels.txt")
            For y As Integer = 0 To source.Height - 1
                sourceIndex = y * sourceData.Stride
                For x As Integer = 0 To source.Width - 1
                    Dim pixelValue As Integer = Convert.ToInt32(sourceBuffer(sourceIndex))
                    sw.Write(pixelvalue & " ")
                    sourceIndex = 1
                Next
                sw.WriteLine()
                sw.WriteLine()
            Next
            sw.Close()
        End Sub
    

    Sunday, April 19, 2009 3:48 PM

Answers

  • This should provide a csv text file with the Row number, white pixel count and  black pixel count.

      Private Function getPixelValues(ByVal source As Bitmap) As Boolean
        If Not (source.PixelFormat = PixelFormat.Format1bppIndexed) Then Return False
        Dim sourceData As BitmapData = source.LockBits(New Rectangle(0, 0, source.Width, source.Height), ImageLockMode.[ReadOnly], PixelFormat.Format1bppIndexed)
        Dim PadBits As Integer
        Dim LineBits As Integer = Math.DivRem(source.Width, 8, PadBits) - 1
        PadBits -= 1
        Dim imageSize As Integer = sourceData.Stride * sourceData.Height - 1
        Dim sourceBuffer(imageSize) As Byte
        Marshal.Copy(sourceData.Scan0, sourceBuffer, 0, imageSize)
        source.UnlockBits(sourceData)
        Dim sourceindex, y, ymax, x, z As Integer
        Dim Mask As Byte
        ymax = source.Height - 1
        Dim RowPixelCount As Integer
        Dim FileStrings(ymax + 1) As String
        FileStrings(0) = "Row,WhitePixelCount,BlackPixelCount"
        For y = 0 To ymax
          sourceindex = y * sourceData.Stride
          RowPixelCount = 0
          For x = 0 To LineBits
            Mask = &H80
            For z = 0 To 7
              If (sourceBuffer(sourceindex) And Mask) > 0 Then RowPixelCount += 1
              Mask >>= 1
            Next
            sourceindex += 1
          Next
          If PadBits > 0 Then
            Mask = &H80
            For z = 0 To PadBits
              If (sourceBuffer(sourceindex) And Mask) > 0 Then RowPixelCount += 1
              Mask >>= 1
            Next
          End If
          FileStrings(y + 1) = y.ToString & "," & RowPixelCount.ToString & "," & (source.Width - RowPixelCount).ToString
        Next
        File.WriteAllLines("C:\pixels.csv", FileStrings)
        Return True
      End Function

    Edited to sum RowPixelCount.
    Edited to subtract 1 from LineBits.
    Edited to Mask bytes and tested valid.





    Here is a test bitmap to verify the code:
      Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Dim B(1599), Mask As Byte
        Dim Y, YX, X, XMax, Z, ZMax As Integer
        For Y = 0 To 99
          YX = Y * 16
          XMax = Math.DivRem(Y, 8, ZMax) - 1
          ZMax -= 1
          For X = 0 To XMax
            B(YX + X) = &HFF
          Next
          Mask = &H80
          For Z = 0 To ZMax
            B(YX + X) = B(YX + X) Or Mask
            Mask >>= 1
          Next
        Next
        Dim Bmp As New Bitmap(100, 100, PixelFormat.Format1bppIndexed)
        Dim BmpData As BitmapData = Bmp.LockBits(New Rectangle(0, 0, Bmp.Width, Bmp.Height), ImageLockMode.WriteOnly, PixelFormat.Format1bppIndexed)
        Marshal.Copy(B, 0, BmpData.Scan0, 1600)
        Bmp.UnlockBits(BmpData)
        Bmp.Save("C:\CountPixels.bmp")
        getPixelValues(Bmp)
      End Sub
    • Marked as answer by jal2 Monday, April 20, 2009 1:31 AM
    Sunday, April 19, 2009 6:38 PM
  • try this

     Function countPixels() As Integer
            Dim bm1 As Bitmap = Image.FromFile(TextBox3.Text)
    
            ' Make a difference image.
            Dim wid As Integer = bm1.Width ', bm1.Width)
            Dim hgt As Integer = bm1.Height ', bm2.Height)
    
            ' Create the difference image.
            Dim ct As Integer = 0
            Dim c As Color
            For x As Integer = 0 To wid - 1
                For y As Integer = 0 To hgt - 1
                    c = bm1.GetPixel(x, y)
                    If c.R = 0 AndAlso c.G = 0 AndAlso c.B = 0 Then
                        ct += 1
                    End If
    
                Next y
            Next x
            bm1.Dispose()
            Return ct
        End Function
    kaymaf

     


    I hope this helps, if that is what you want, just mark it as answer so that we can move on
    • Marked as answer by jal2 Monday, April 20, 2009 1:30 AM
    Sunday, April 19, 2009 7:02 PM

All replies

  • This should provide a csv text file with the Row number, white pixel count and  black pixel count.

      Private Function getPixelValues(ByVal source As Bitmap) As Boolean
        If Not (source.PixelFormat = PixelFormat.Format1bppIndexed) Then Return False
        Dim sourceData As BitmapData = source.LockBits(New Rectangle(0, 0, source.Width, source.Height), ImageLockMode.[ReadOnly], PixelFormat.Format1bppIndexed)
        Dim PadBits As Integer
        Dim LineBits As Integer = Math.DivRem(source.Width, 8, PadBits) - 1
        PadBits -= 1
        Dim imageSize As Integer = sourceData.Stride * sourceData.Height - 1
        Dim sourceBuffer(imageSize) As Byte
        Marshal.Copy(sourceData.Scan0, sourceBuffer, 0, imageSize)
        source.UnlockBits(sourceData)
        Dim sourceindex, y, ymax, x, z As Integer
        Dim Mask As Byte
        ymax = source.Height - 1
        Dim RowPixelCount As Integer
        Dim FileStrings(ymax + 1) As String
        FileStrings(0) = "Row,WhitePixelCount,BlackPixelCount"
        For y = 0 To ymax
          sourceindex = y * sourceData.Stride
          RowPixelCount = 0
          For x = 0 To LineBits
            Mask = &H80
            For z = 0 To 7
              If (sourceBuffer(sourceindex) And Mask) > 0 Then RowPixelCount += 1
              Mask >>= 1
            Next
            sourceindex += 1
          Next
          If PadBits > 0 Then
            Mask = &H80
            For z = 0 To PadBits
              If (sourceBuffer(sourceindex) And Mask) > 0 Then RowPixelCount += 1
              Mask >>= 1
            Next
          End If
          FileStrings(y + 1) = y.ToString & "," & RowPixelCount.ToString & "," & (source.Width - RowPixelCount).ToString
        Next
        File.WriteAllLines("C:\pixels.csv", FileStrings)
        Return True
      End Function

    Edited to sum RowPixelCount.
    Edited to subtract 1 from LineBits.
    Edited to Mask bytes and tested valid.





    Here is a test bitmap to verify the code:
      Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Dim B(1599), Mask As Byte
        Dim Y, YX, X, XMax, Z, ZMax As Integer
        For Y = 0 To 99
          YX = Y * 16
          XMax = Math.DivRem(Y, 8, ZMax) - 1
          ZMax -= 1
          For X = 0 To XMax
            B(YX + X) = &HFF
          Next
          Mask = &H80
          For Z = 0 To ZMax
            B(YX + X) = B(YX + X) Or Mask
            Mask >>= 1
          Next
        Next
        Dim Bmp As New Bitmap(100, 100, PixelFormat.Format1bppIndexed)
        Dim BmpData As BitmapData = Bmp.LockBits(New Rectangle(0, 0, Bmp.Width, Bmp.Height), ImageLockMode.WriteOnly, PixelFormat.Format1bppIndexed)
        Marshal.Copy(B, 0, BmpData.Scan0, 1600)
        Bmp.UnlockBits(BmpData)
        Bmp.Save("C:\CountPixels.bmp")
        getPixelValues(Bmp)
      End Sub
    • Marked as answer by jal2 Monday, April 20, 2009 1:31 AM
    Sunday, April 19, 2009 6:38 PM
  • try this

     Function countPixels() As Integer
            Dim bm1 As Bitmap = Image.FromFile(TextBox3.Text)
    
            ' Make a difference image.
            Dim wid As Integer = bm1.Width ', bm1.Width)
            Dim hgt As Integer = bm1.Height ', bm2.Height)
    
            ' Create the difference image.
            Dim ct As Integer = 0
            Dim c As Color
            For x As Integer = 0 To wid - 1
                For y As Integer = 0 To hgt - 1
                    c = bm1.GetPixel(x, y)
                    If c.R = 0 AndAlso c.G = 0 AndAlso c.B = 0 Then
                        ct += 1
                    End If
    
                Next y
            Next x
            bm1.Dispose()
            Return ct
        End Function
    kaymaf

     


    I hope this helps, if that is what you want, just mark it as answer so that we can move on
    • Marked as answer by jal2 Monday, April 20, 2009 1:30 AM
    Sunday, April 19, 2009 7:02 PM
  • Thanks so much guys !!!

    I'll start with Kaymaf's code because it is easier for me to understand.  John Wein is way over my head in all this. But I'll probably come back to his code because, even though it's more complicated, it probably performs faster.

    Monday, April 20, 2009 1:30 AM
  • GetPixel is slower Lockbits will work on the bitmap data in a buffer instead of a function that gets one pixel at a time from the bitmap .
    Coding for fun Be a good forum member mark posts that contain the answers to your questions or those that are helpful
    Monday, April 20, 2009 2:30 AM
  • Yep,  I decided to go with John's approach because this is an OCR app so I need a little more performance. I'll save the GetPixel approach for cases where I don't need so much performance.  Thanks everyone !!!
    Monday, April 20, 2009 11:50 AM