Zwei Bilder am schnellsten/besten Vergleichen?

  • Allgemein

Es gibt 17 Antworten in diesem Thema. Der letzte Beitrag () ist von picoflop.

    Zwei Bilder am schnellsten/besten Vergleichen?

    Hallo.

    Eine Frage: Wie kann man zwei Bilder am schnellsten, effektivsten und performance-schonensten vergleichen und dann die Unterschiede anzeigen zu können (quasi wie Teamviewer oder ähnliche Programme das machen). Also von der Theorie her.

    Ich denke, es wird ein Screenshot gemacht, dann in einzelne Quadrate unterteilt und dann verglichen. Oder erst in schwarz-weiß umwandeln? Und wie kann man diese dann am effektivsten vergleichen?

    mfG
    Für ein Mindestmaß an Rechtschreibung, Interpunktion und Majuskeln!
    TeamViewer unterteilt in Quadrate richtig...
    Anschließend werden die einzelnen Quadrate verglichen und ein Unterschied zurückgegeben z.B. in Prozent...Überschreitet dieser Wert eine gewisse Grenze, so wird dieses Quadrat verschickt...
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    Was soll denn das Ziel des Vergleiches sein?
    Ist das Bild jpg- (oder so) komprimiert, ist der Bildinhalt ja bereits gegenüber dem Original verändert und Du hast gewisse Unterschiede, je nach Grad der Kompression. Willst Du Messbilder vergleichen, benötigst Du einen Pixel-Vergleichs-Algorithmus. Willst Du grobe Raster-Bilder vergleichen, kannst Du einen "Grobschmied"-Algorithmus verwenden.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Ja, es handelt sich dabei um Screenshots, die vorerst nicht komprimiert werden.
    Ich finde jetzt leider nichts zum "Grobschmied"-Algorithmus.

    Welches ich nutzen sollte, weiß ich auch nicht. Ich denke aber mal, dass man das Raster vergleichen muss, um es performanter gestalten zu können. Wie groß sollte so ein Raster denn sein?
    Für ein Mindestmaß an Rechtschreibung, Interpunktion und Majuskeln!
    du könntest die bilder erstmal in raster aufteilen (z.b. zu 64*64 pixeln)
    und innerhalb eines rasters könntest du nun jeden zweiten (oder auch nur jeden vierten pixel) vergleichen. falls dann zuviele pixel abweichen -> neu senden.
    dadurch hättest du zwar keinen perfekten vergleich, aber möglicherweise reicht das ja schon für dein vorhaben.
    schon ein einfacher Pixel-weiser Vergleich ist kompliziert genug - da muß man mit bitmap.LockBit die Bytes in ein Array zaubern, und das Array durchwühlen, um flott voranzukommen (bmp.GetPixel ist grauenhaft langsam!).
    Dann noch einen Grobschmied-Algo sich ausdenken wird noch 3mal so kompliziert.

    Beide Verfahren schlagen vmtl. fehl, wenn der eine Screenshot einen etwas anderen BildAusschnitt hat wie der andere.

    ~blaze~ schrieb:

    Doch nicht in ein Array. Marshal.ReadInt32 oder Ähnliches ist da wesentlich besser geeignet.

    Der Zugriff auf ein Array-Element ist aber ca. 8mal schneller als über Marshal.Read.

    mein Testcode:

    VB.NET-Quellcode

    1. Dim numbs(44999) As Integer
    2. numbs(3) = 90099
    3. Dim sw As Stopwatch
    4. sw = Stopwatch.StartNew()
    5. For n As Integer = 0 To 999
    6. For i As Integer = 0 To numbs.Length - 1
    7. Dim numb As Integer = numbs(i)
    8. Next
    9. Next
    10. Console.WriteLine(sw.ElapsedMilliseconds.ToString)
    11. Dim GCH As GCHandle = GCHandle.Alloc(numbs, GCHandleType.Pinned)
    12. Dim Scan0 As IntPtr = GCH.AddrOfPinnedObject
    13. sw = Stopwatch.StartNew()
    14. For n As Integer = 0 To 999
    15. For i As Integer = 0 To numbs.Length - 1 Step 4
    16. Dim numb As Integer = Marshal.ReadInt32(Scan0, i)
    17. Next
    18. Next
    19. Console.WriteLine(sw.ElapsedMilliseconds.ToString)
    20. GCH.Free()

    Ausgabe:

    Ausgabe schrieb:

    206
    1619

    Das Umkopieren hat den weiteren Vorteil, dass man ebensogut in ein 2-dim Array umkopieren kann, was man dann (für Bildbearbeitung sehr praktisch) zeilen- und spalten-weise durchlaufen kann.
    gugge Convolution-Filter

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „ErfinderDesRades“ ()

    na super...bei einem solchen Vergleich...
    damit der Vergleich korrekt wird müsstest du per Marshal.Copy das ganze in ein temporäres zweites Array legen und anschließend das durchlaufen, dann weißt du was schneller ist...bei Copy wirst du wohl sozusagen zwei Schleifen haben und bei Read nur eine...
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    Marshal.Copy ist offenbar superschnell - fällt kaum ins Gewicht

    jetzt lasse ich den Test 3 mal durchlaufen

    VB.NET-Quellcode

    1. Dim numbs(44999) As Integer
    2. numbs(3) = 90099
    3. numbs(44999) = 99999 'um zu gucken, ob das letzte Element auch mitkommt
    4. Dim numb As Integer
    5. Dim GCH As GCHandle = GCHandle.Alloc(numbs, GCHandleType.Pinned)
    6. Dim Scan0 As IntPtr = GCH.AddrOfPinnedObject
    7. Dim sw As Stopwatch
    8. Dim numbs2(numbs.Length - 1) As Integer
    9. Marshal.Copy(Scan0, numbs2, 0, (numbs.Length))
    10. numb = numbs2(3)
    11. numb = numbs2(numbs.Length - 1)
    12. For tryal As Integer = 0 To 2
    13. sw = Stopwatch.StartNew()
    14. For n As Integer = 0 To 999
    15. For i As Integer = 0 To numbs.Length * 4 - 1 Step 4
    16. numb = Marshal.ReadInt32(Scan0, i)
    17. Next
    18. Next
    19. Console.WriteLine("Read from Pointer: " & sw.ElapsedMilliseconds.ToString)
    20. sw = Stopwatch.StartNew()
    21. For n As Integer = 0 To 999
    22. Marshal.Copy(Scan0, numbs2, 0, (numbs.Length))
    23. For i As Integer = 0 To numbs.Length - 1
    24. numb = numbs2(i)
    25. Next
    26. Next
    27. Console.WriteLine("access array: " & sw.ElapsedMilliseconds.ToString)
    28. Next


    Aber ich kriege für Read häufig auch noch viel höhere zeiten, mal so

    Ausgabe schrieb:

    Read from Pointer: 1601
    access array: 230
    Read from Pointer: 1601
    access array: 229
    Read from Pointer: 1600
    access array: 229


    mal so

    Ausgabe schrieb:

    Read from Pointer: 6017
    access array: 232
    Read from Pointer: 5961
    access array: 230
    Read from Pointer: 5908
    access array: 230

    Ich kann dazu nur sagen, dass ich mir irgendeine abgef***** "kleine" Funktion gebastelt habe, um Pixel-Muster einer Bitmap in einer anderen wiederzufinden... Das ganze dann schön mit GetPixel...
    Also das kannste dann schonmal schön knicken :D Wenn du das einfach mit 2 For-Schleifen umsetzt, Wartest du eine Ewigkeit, bis er alles abgeglichen hat...

    MfG,
    X-Zat / Momo

    ~blaze~ schrieb:

    Doch nicht in ein Array. Marshal.ReadInt32 oder Ähnliches ist da wesentlich besser geeignet.


    VB.NET-Quellcode

    1. Public Shared Function BitmapPixelDataEquals(ByVal left As Bitmap, ByVal right As Bitmap) As Boolean
    2. Dim leftBounds As New Rectangle(0, 0, left.Width, left.Height)
    3. Dim rightBounds As New Rectangle(0, 0, right.Width, right.Height)
    4. If leftBounds = rightBounds Then
    5. If (left.PixelFormat And (Imaging.PixelFormat.Format32bppArgb Or Imaging.PixelFormat.Format32bppRgb)) <> Imaging.PixelFormat.Undefined AndAlso (right.PixelFormat And (Imaging.PixelFormat.Format32bppArgb Or Imaging.PixelFormat.Format32bppRgb)) <> Imaging.PixelFormat.Undefined Then
    6. '32 Bits --> 4 Bytes pro Pixel = Integer
    7. Dim bdLeft As Imaging.BitmapData = left.LockBits(leftBounds, Imaging.ImageLockMode.ReadOnly, Imaging.PixelFormat.Format32bppArgb)
    8. Dim bdRight As Imaging.BitmapData = right.LockBits(rightBounds, Imaging.ImageLockMode.ReadOnly, Imaging.PixelFormat.Format32bppArgb)
    9. 'Properties brauchen laenger als Felder-->Felder verwenden
    10. Dim leftData As IntPtr = bdLeft.Scan0
    11. Dim rightData As IntPtr = bdRight.Scan0
    12. For i As Integer = 0 To (bdLeft.Height * bdLeft.Width - 1) * 4 Step 4
    13. If Runtime.InteropServices.Marshal.ReadInt32(leftData, i) <> Runtime.InteropServices.Marshal.ReadInt32(rightData, i) Then
    14. left.UnlockBits(bdLeft)
    15. right.UnlockBits(bdRight)
    16. Return False
    17. End If
    18. Next
    19. left.UnlockBits(bdLeft)
    20. right.UnlockBits(bdRight)
    21. Return True
    22. ElseIf (left.PixelFormat And Imaging.PixelFormat.Format24bppRgb) = Imaging.PixelFormat.Format24bppRgb AndAlso (right.PixelFormat And (Imaging.PixelFormat.Format24bppRgb)) = Imaging.PixelFormat.Format24bppRgb Then
    23. '24 Bits --> 3 Bytes pro Pixel
    24. Dim bdLeft As Imaging.BitmapData = left.LockBits(leftBounds, Imaging.ImageLockMode.ReadOnly, Imaging.PixelFormat.Format32bppArgb)
    25. Dim bdRight As Imaging.BitmapData = right.LockBits(rightBounds, Imaging.ImageLockMode.ReadOnly, Imaging.PixelFormat.Format32bppArgb)
    26. 'Properties brauchen laenger als Felder-->Felder verwenden
    27. Dim leftData As IntPtr = bdLeft.Scan0
    28. Dim rightData As IntPtr = bdRight.Scan0
    29. For i As Integer = 0 To (bdLeft.Height * bdLeft.Width - 1) * 3 Step 3
    30. If (Runtime.InteropServices.Marshal.ReadInt32(leftData, i) And &HFFFFFF) <> (Runtime.InteropServices.Marshal.ReadInt32(rightData, i) And &HFFFFFF) Then
    31. left.UnlockBits(bdLeft)
    32. right.UnlockBits(bdRight)
    33. Return False
    34. End If
    35. Next
    36. left.UnlockBits(bdLeft)
    37. right.UnlockBits(bdRight)
    38. Return True
    39. Else
    40. 'Hier andere Formate implementieren (wie oben halt)
    41. Throw New NotSupportedException
    42. End If
    43. Else
    44. Return False
    45. End If

    Der Code könnte schneller sein, als der von ErfinderDesRades. Da Bitmaps nicht nur 32-Bit Argbs sind, sollte man für jedes Pixelformat eine eigene Routine schreiben oder so. Hab mal ein Beispiel für 32-Bit und für 24-Bit Bitmaps gemacht (habe das 24-Bit-Bild nicht ausgiebig testen können, da ich keine solchen da hatte und zu faul war, welche zu zeichnen, das Prinzip ist das wichtige).

    Gruß
    ~blaze~
    Weils hierher passt ...
    Es gibt von AMD eine "Performance" Lib: Framewave (von Intel gibts IPP). Wenn man die verwendet gehts noch fixer ;)
    Benötigt wird also die FW-Lib (gibts bei Sourcefore als Binary für Win32 und win64)

    Vergleicher:

    VB.NET-Quellcode

    1. Imports System.Runtime.InteropServices
    2. Imports System.Drawing.Imaging
    3. Public Class BitmapComparer
    4. <DllImport("fwSignal.dll")> Private Shared Function fwsXor_8u(ByVal pSrc1 As IntPtr, _
    5. ByVal pSrc2 As IntPtr, _
    6. ByVal pDst As IntPtr, _
    7. ByVal len As Integer) As Integer
    8. End Function
    9. <DllImport("fwSignal.dll")> Private Shared Function fwsXor_32u(ByVal pSrc1 As IntPtr, _
    10. ByVal pSrc2 As IntPtr, _
    11. ByVal pDst As IntPtr, _
    12. ByVal len As Integer) As Integer
    13. End Function
    14. <DllImport("fwSignal.dll")> Private Shared Function fwsMaxAbs_32s(ByVal pSrc As IntPtr, _
    15. ByVal len As Integer, _
    16. ByRef max As Integer) As Integer
    17. End Function
    18. Public Shared Function BitmapsAreEqual(ByVal b1 As Bitmap, ByVal b2 As Bitmap) As Boolean
    19. If b1.PixelFormat <> b2.PixelFormat OrElse b1.Height <> b2.Height OrElse b1.Width <> b2.Width Then Return False
    20. Dim bmdata1, bmdata2 As BitmapData
    21. Dim unmanagedptr As IntPtr
    22. Dim maxdiff As Integer = 0
    23. Try
    24. bmdata1 = b1.LockBits(New Rectangle(0, 0, b1.Width, b1.Height), Imaging.ImageLockMode.ReadOnly, b1.PixelFormat)
    25. bmdata2 = b2.LockBits(New Rectangle(0, 0, b2.Width, b2.Height), Imaging.ImageLockMode.ReadOnly, b2.PixelFormat)
    26. Dim size As Integer = bmdata1.Height * bmdata1.Stride
    27. unmanagedptr = Marshal.AllocHGlobal(size)
    28. Dim r1 As Integer = fwsXor_8u(bmdata1.Scan0, bmdata2.Scan0, unmanagedptr, size \ 4)
    29. Dim r2 As Integer = fwsMaxAbs_32s(unmanagedptr, size \ 4, maxdiff)
    30. b1.UnlockBits(bmdata1)
    31. b2.UnlockBits(bmdata2)
    32. Marshal.FreeHGlobal(unmanagedptr)
    33. Catch ex As Exception
    34. ' ggf unmanaged kram aufräumen?
    35. End Try
    36. If maxdiff = 0 Then Return True Else Return False
    37. End Function
    38. End Class


    Tester:

    VB.NET-Quellcode

    1. Dim stp As New Stopwatch
    2. Dim gleich As Boolean
    3. Using bm1 As New Bitmap("DSCF0024Kopie.jpg")
    4. Using bm2 As New Bitmap("DSCF0024.jpg")
    5. GC.Collect()
    6. stp.Start()
    7. gleich = BitmapComparer.BitmapsAreEqual(bm1, bm2)
    8. stp.Stop()
    9. End Using
    10. End Using
    11. Debug.Print(stp.ElapsedMilliseconds)


    Getestet habe ich mit Bildern von ner DigiCam. Größe im speicher rund 24MB. Bei Gleichheit dauert das ganze auf meinem betagten Turion X2 (1.8GHz) zwischen 40 und 60 Millisekunden?
    Gehts schneller?

    bla schrieb:

    den

    "den" wird schwierig ;) Denn die Bilder können ja auch "komplett" verschieden sein. Jedenfalls hast du nach dem XORen alle "Pixel" (eigentlich Speicherstellen), die verschieden sind.
    Wer "mehr" will, sollte vermutlich
    code.google.com/p/aforge/
    nehmen

    Wer bereit ist, sich ne C# DLL zu kompilieren:

    Quellcode

    1. public static Boolean BitmapsAreEqual(Bitmap bm1, Bitmap bm2)
    2. {
    3. Boolean AreEqual = true;
    4. unsafe
    5. {
    6. BitmapData bmd1 = bm1.LockBits(new Rectangle(0, 0, bm1.Width, bm1.Height), ImageLockMode.ReadOnly, bm1.PixelFormat);
    7. BitmapData bmd2 = bm2.LockBits(new Rectangle(0, 0, bm2.Width, bm2.Height), ImageLockMode.ReadOnly, bm2.PixelFormat);
    8. UInt64* b1ptr = (UInt64*)bmd1.Scan0.ToPointer();
    9. UInt64* b2ptr = (UInt64*)bmd2.Scan0.ToPointer();
    10. Int32 size = bmd1.Stride * bmd1.Height;
    11. for (int i = 0; i < (size>>3); i++)
    12. {
    13. if (b1ptr[i] != b2ptr[i]) {
    14. AreEqual = false;
    15. break;
    16. }
    17. }
    18. bm1.UnlockBits(bmd1);
    19. bm2.UnlockBits(bmd2);
    20. }
    21. return AreEqual;
    22. }


    Braucht etwa 10 ms länger als der Code mit der Framewave Lib (die läuft aber ja auch zweimal drüber).
    Code MÜSSTE stimmen ... (zur Not selber schreiben)

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „picoflop“ ()