Link to home
Start Free TrialLog in
Avatar of Frosty555
Frosty555Flag for Canada

asked on

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?
ASKER CERTIFIED SOLUTION
Avatar of iboutchkine
iboutchkine

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
https://filedb.experts-exchange.com/incoming/ee-stuff/6528-XORBitmap.zip

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.Dispose()
        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)
        g.Dispose()
 
        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)
        Next
 
        System.Runtime.InteropServices.Marshal.Copy(buffer3, 0, bmpData3.Scan0, bytes)
 
        bmp1.UnlockBits(bmpData1)
        bmp2.UnlockBits(bmpData2)
        bmp3.UnlockBits(bmpData3)
 
        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

Avatar of Frosty555

ASKER

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?
You might find the filters used here of interest:
Motion Detection Algorithms
http://www.codeproject.com/KB/audio-video/Motion_Detection.aspx
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.
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.