Determine changed pixels in a Bitmap

I hope this isn't too hard a question.

I have a Bitmap object in VB.NET which I drew to using Graphics.DrawImage() (it was a screenshot of the screen which I scaled down. I don't have to scale it down before I process it for changes... whichever way works faster)

Later on, I do the exact same process, and I have a second bitmap object.

Now I need a way to (quickly) determine the basic areas that have changed in the image. Ideally I'd like to know the exact pixels that have changed, but if I, say, could just split the bitmap into some number of conceptual "pieces" and determine if each piece has changed, that's perfect as well.

I can use GetPixel on each and compare the values all the way through the bitmap object... but I imagine it would be painfully slow. I need to do it quickly. Under 50ms preferably for a 1600x1200 size screen o.O I know that's a tall order. I considered maybe comparing every 100th pixel, or comparing down just a few columns and rows, but I don't think it would accurately tell me what areas have changed.

Is there any other way to compare two bitmap objects in VB.NET?
LVL 31
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Public Class Form1
    Inherits System.Windows.Forms.Form

#Region " Windows Form Designer generated code "

    Public Sub New()

        'This call is required by the Windows Form Designer.

        'Add any initialization after the InitializeComponent() call

    End Sub

    'Form overrides dispose to clean up the component list.
    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
        If disposing Then
            If Not (components Is Nothing) Then
            End If
        End If
    End Sub

    'Required by the Windows Form Designer
    Private components As System.ComponentModel.IContainer

    'NOTE: The following procedure is required by the Windows Form Designer
    'It can be modified using the Windows Form Designer.  
    'Do not modify it using the code editor.
    Friend WithEvents Label1 As System.Windows.Forms.Label
    Friend WithEvents btnFile1 As System.Windows.Forms.Button
    Friend WithEvents btnGo As System.Windows.Forms.Button
    Friend WithEvents Panel1 As System.Windows.Forms.Panel
    Friend WithEvents btnFile2 As System.Windows.Forms.Button
    Friend WithEvents Label2 As System.Windows.Forms.Label
    Friend WithEvents dlgSelectFile As System.Windows.Forms.OpenFileDialog
    Friend WithEvents txtFile1 As System.Windows.Forms.TextBox
    Friend WithEvents txtFile2 As System.Windows.Forms.TextBox
    Friend WithEvents picResult As System.Windows.Forms.PictureBox
    <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
        Dim resources As System.Resources.ResourceManager = New System.Resources.ResourceManager(GetType(Form1))
        Me.txtFile1 = New System.Windows.Forms.TextBox
        Me.Label1 = New System.Windows.Forms.Label
        Me.btnFile1 = New System.Windows.Forms.Button
        Me.btnGo = New System.Windows.Forms.Button
        Me.Panel1 = New System.Windows.Forms.Panel
        Me.picResult = New System.Windows.Forms.PictureBox
        Me.btnFile2 = New System.Windows.Forms.Button
        Me.Label2 = New System.Windows.Forms.Label
        Me.txtFile2 = New System.Windows.Forms.TextBox
        Me.dlgSelectFile = New System.Windows.Forms.OpenFileDialog
        Me.txtFile1.Anchor = CType(((System.Windows.Forms.AnchorStyles.Top Or System.Windows.Forms.AnchorStyles.Left) _
                    Or System.Windows.Forms.AnchorStyles.Right), System.Windows.Forms.AnchorStyles)
        Me.txtFile1.Location = New System.Drawing.Point(32, 0)
        Me.txtFile1.Name = "txtFile1"
        Me.txtFile1.Size = New System.Drawing.Size(440, 20)
        Me.txtFile1.TabIndex = 0
        Me.txtFile1.Text = ""
        Me.Label1.AutoSize = True
        Me.Label1.Location = New System.Drawing.Point(0, 0)
        Me.Label1.Name = "Label1"
        Me.Label1.Size = New System.Drawing.Size(32, 16)
        Me.Label1.TabIndex = 1
        Me.Label1.Text = "File 1"
        Me.btnFile1.Anchor = CType((System.Windows.Forms.AnchorStyles.Top Or System.Windows.Forms.AnchorStyles.Right), System.Windows.Forms.AnchorStyles)
        Me.btnFile1.BackColor = System.Drawing.Color.Aqua
        Me.btnFile1.Image = CType(resources.GetObject("btnFile1.Image"), System.Drawing.Image)
        Me.btnFile1.Location = New System.Drawing.Point(480, 0)
        Me.btnFile1.Name = "btnFile1"
        Me.btnFile1.Size = New System.Drawing.Size(24, 20)
        Me.btnFile1.TabIndex = 2
        Me.btnGo.Anchor = System.Windows.Forms.AnchorStyles.Top
        Me.btnGo.Location = New System.Drawing.Point(228, 48)
        Me.btnGo.Name = "btnGo"
        Me.btnGo.Size = New System.Drawing.Size(48, 23)
        Me.btnGo.TabIndex = 3
        Me.btnGo.Text = "Go"
        Me.Panel1.Anchor = CType((((System.Windows.Forms.AnchorStyles.Top Or System.Windows.Forms.AnchorStyles.Bottom) _
                    Or System.Windows.Forms.AnchorStyles.Left) _
                    Or System.Windows.Forms.AnchorStyles.Right), System.Windows.Forms.AnchorStyles)
        Me.Panel1.AutoScroll = True
        Me.Panel1.Location = New System.Drawing.Point(0, 80)
        Me.Panel1.Name = "Panel1"
        Me.Panel1.Size = New System.Drawing.Size(504, 292)
        Me.Panel1.TabIndex = 4
        Me.picResult.Location = New System.Drawing.Point(0, 0)
        Me.picResult.Name = "picResult"
        Me.picResult.Size = New System.Drawing.Size(56, 50)
        Me.picResult.SizeMode = System.Windows.Forms.PictureBoxSizeMode.AutoSize
        Me.picResult.TabIndex = 0
        Me.picResult.TabStop = False
        Me.btnFile2.Anchor = CType((System.Windows.Forms.AnchorStyles.Top Or System.Windows.Forms.AnchorStyles.Right), System.Windows.Forms.AnchorStyles)
        Me.btnFile2.BackColor = System.Drawing.Color.Aqua
        Me.btnFile2.Image = CType(resources.GetObject("btnFile2.Image"), System.Drawing.Image)
        Me.btnFile2.Location = New System.Drawing.Point(480, 24)
        Me.btnFile2.Name = "btnFile2"
        Me.btnFile2.Size = New System.Drawing.Size(24, 20)
        Me.btnFile2.TabIndex = 7
        Me.Label2.AutoSize = True
        Me.Label2.Location = New System.Drawing.Point(0, 24)
        Me.Label2.Name = "Label2"
        Me.Label2.Size = New System.Drawing.Size(32, 16)
        Me.Label2.TabIndex = 6
        Me.Label2.Text = "File 2"
        Me.txtFile2.Anchor = CType(((System.Windows.Forms.AnchorStyles.Top Or System.Windows.Forms.AnchorStyles.Left) _
                    Or System.Windows.Forms.AnchorStyles.Right), System.Windows.Forms.AnchorStyles)
        Me.txtFile2.Location = New System.Drawing.Point(32, 24)
        Me.txtFile2.Name = "txtFile2"
        Me.txtFile2.Size = New System.Drawing.Size(440, 20)
        Me.txtFile2.TabIndex = 5
        Me.txtFile2.Text = ""
        Me.dlgSelectFile.Filter = "Graphics Files|*.bmp;*.gif;*.jpg;*.jpeg;*.ico;*.png;*.tif;*.tiff|All Files|*.*"
        Me.dlgSelectFile.Title = "Select File"
        Me.AcceptButton = Me.btnGo
        Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
        Me.ClientSize = New System.Drawing.Size(504, 373)
        Me.Name = "Form1"
        Me.Text = "Form1"

    End Sub

#End Region

    Private Sub btnFile1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnFile1.Click
        If dlgSelectFile.ShowDialog(Me) = DialogResult.OK Then
            txtFile1.Text = dlgSelectFile.FileName
        End If
    End Sub

    Private Sub btnFile2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnFile2.Click
        If dlgSelectFile.ShowDialog(Me) = DialogResult.OK Then
            txtFile2.Text = dlgSelectFile.FileName
        End If
    End Sub

    Private Sub btnGo_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnGo.Click
        Me.Cursor = Cursors.WaitCursor

        ' Load the images.
        Dim bm1 As Bitmap = Image.FromFile(txtFile1.Text)
        Dim bm2 As Bitmap = Image.FromFile(txtFile2.Text)

        ' Make a difference image.
        Dim wid As Integer = Math.Min(bm1.Width, bm2.Width)
        Dim hgt As Integer = Math.Min(bm1.Height, bm2.Height)
        Dim bm3 As New Bitmap(wid, hgt)

        ' Create the difference image.
        Dim are_identical As Boolean = True
        Dim r1, g1, b1, r2, g2, b2, r3, g3, b3 As Integer
        Dim color1, color2, color3 As Color
        For x As Integer = 0 To wid - 1
            For y As Integer = 0 To hgt - 1
                color1 = bm1.GetPixel(x, y)
                r1 = color1.R : g1 = color1.G : b1 = color1.B

                color2 = bm2.GetPixel(x, y)
                r2 = color2.R : g2 = color2.G : b2 = color2.B

                r3 = 128 + (r1 - r2) \ 2
                g3 = 128 + (g1 - g2) \ 2
                b3 = 128 + (b1 - b2) \ 2

                color3 = Color.FromArgb(255, r3, g3, b3)
                bm3.SetPixel(x, y, color3)

                If (r1 <> r2) OrElse (g1 <> g2) OrElse (b1 <> b2) Then are_identical = False
            Next y
        Next x

        ' Display the result.
        picResult.Image = bm3

        Me.Cursor = Cursors.Default
        If (bm1.Width <> bm2.Width) OrElse (bm1.Height <> bm2.Height) Then are_identical = False
        If are_identical Then
            MessageBox.Show("The images are identical")
            MessageBox.Show("The images are different")
        End If

    End Sub
End Class

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial

Two methods to output changed pixels to third picturebox:
Public Class Form1
    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Dim bmp1 As New Bitmap(100, 50)
        Dim bmp2 As New Bitmap(100, 50)
        Dim g As Graphics = Graphics.FromImage(bmp1)
        g.FillRectangle(Brushes.DeepSkyBlue, New RectangleF(0, 0, 100, 50))
        g = Graphics.FromImage(bmp2)
        g.FillRectangle(Brushes.DeepSkyBlue, New RectangleF(0, 0, 100, 50))
        g.DrawString("Notbono wuz here", New Font("Arial", 8), Brushes.White, 2, 4)
        PictureBox1.Image = bmp1
        PictureBox2.Image = bmp2
    End Sub
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        'slow way, but fully .net
        Dim bmp1 As Bitmap = CType(PictureBox1.Image, Bitmap)
        Dim bmp2 As Bitmap = CType(PictureBox2.Image, Bitmap)
        Dim bmp3 As Bitmap = New Bitmap(100, 50)
        Dim bmpData1 As Imaging.BitmapData = bmp1.LockBits(New Rectangle(0, 0, bmp1.Width, bmp1.Height), Imaging.ImageLockMode.ReadWrite, Imaging.PixelFormat.Format24bppRgb)
        Dim bmpData2 As Imaging.BitmapData = bmp2.LockBits(New Rectangle(0, 0, bmp2.Width, bmp2.Height), Imaging.ImageLockMode.ReadWrite, Imaging.PixelFormat.Format24bppRgb)
        Dim bmpData3 As Imaging.BitmapData = bmp3.LockBits(New Rectangle(0, 0, bmp3.Width, bmp1.Height), Imaging.ImageLockMode.ReadWrite, Imaging.PixelFormat.Format24bppRgb)
        Dim bytes As Integer = bmpData1.Stride * bmp1.Height
        Dim buffer1(bytes - 1), buffer2(bytes - 1), buffer3(bytes - 1) As Byte
        System.Runtime.InteropServices.Marshal.Copy(bmpData1.Scan0, buffer1, 0, bytes)
        System.Runtime.InteropServices.Marshal.Copy(bmpData2.Scan0, buffer2, 0, bytes)
        For i As Integer = 0 To buffer1.GetUpperBound(0)
            buffer3(i) = buffer1(i) Xor buffer2(i)
        System.Runtime.InteropServices.Marshal.Copy(buffer3, 0, bmpData3.Scan0, bytes)
        PictureBox3.Image = bmp3
    End Sub
    'Faster (?) API way
    Private Declare Auto Function BitBlt Lib "gdi32.dll" (ByVal _
        hdcDest As IntPtr, ByVal nXDest As Integer, ByVal _
        nYDest As Integer, ByVal nWidth As Integer, ByVal _
        nHeight As Integer, ByVal hdcSrc As IntPtr, ByVal nXSrc _
        As Integer, ByVal nYSrc As Integer, ByVal dwRop As _
        RasterOp) As Boolean
    Public Enum RasterOp
        SRCCOPY = &HCC0020
        PATINVERT = &H5A0049
        SRCINVERT = &H660046
    End Enum
    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
        CopyImage(PictureBox1, PictureBox3, RasterOp.SRCCOPY)
        CopyImage(PictureBox2, PictureBox3, RasterOp.SRCINVERT)
    End Sub
    Sub CopyImage(ByVal src As PictureBox, ByVal des As PictureBox, ByVal RasterOP As RasterOp)
        ' Get device context of source and destination picture boxes
        Dim srcHDC As IntPtr = src.CreateGraphics.GetHdc
        Dim desHDC As IntPtr = des.CreateGraphics.GetHdc
        ' Use api function
        BitBlt(desHDC, 0, 0, src.Width, _
        src.Height, srcHDC, 0, 0, RasterOP)
    End Sub
End Class

Open in new window

Frosty555Author Commented:
iboutchkine: Your solution is like I described in the question, comparing every pixel using the GetPixel() function. It will  be too slow to do anything productive with it.

PaulHews: I see where you're going, you're X-ORing the bits, so that a change in the bits will result in a nonzero value in the output bit (so, pixels that have changed will be not-black).

The next step is actually analyzing the final image to figure out what parts are non-black... can you think of any way to do this without resorting to GetPixel()?

What I need in the end is the coordinates of some kind of "dirty rectange" of the image. It doesn't have to be perfect. It just needs to enclose all the dirty pixels. If we could find some fast way to crop the image to the rectangle encasing all the changed pixels, that'd be perfect...
 Maybe some kind of .net-based cropping that crops all the black off the outside border of the image?
Introducing Cloud Class® training courses

Tech changes fast. You can learn faster. That’s why we’re bringing professional training courses to Experts Exchange. With a subscription, you can access all the Cloud Class® courses to expand your education, prep for certifications, and get top-notch instructions.

You might find the filters used here of interest:
Motion Detection Algorithms
Frosty555Author Commented:
Those do look pretty interesting... but all of it is in C#. Is there a way to still use it in VB?

I'm actually finding that my original program is running atrociously slow right now. I have no room for additional overhead. I might have to put this on hold while I figure out what's taking all the time elsewhere.
Frosty555Author Commented:
The solution works, and you can get the changed pixels this way. It's just way to slow to be used in an environment where changed pixels need to be determined several times a second. It would take longer to determine the changes pixels then it would take to just send everything down the network.
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
.NET Programming

From novice to tech pro — start learning today.