C# Image Objekte vergleichen

Hab den Screenshot jetzt nicht mit der Methode gemacht, aber so gehts bei mir:
Code:
static void Main(string[] args)
{
  char[] rgb = { 'r', 'g', 'b' };

  DateTime captStart = DateTime.Now;
  Bitmap pic1 = CaptureScreen();
  Bitmap pic2 = CaptureScreen();
  TimeSpan captDelta = DateTime.Now - captStart;
  Console.WriteLine("screen capture duration: {0} / {1}ms", captDelta, captDelta.TotalMilliseconds);
  
  Rectangle imgSize = new Rectangle(0, 0, pic1.Width, pic1.Height);
  BitmapData pic1Data = pic1.LockBits(imgSize, ImageLockMode.ReadOnly, pic1.PixelFormat);
  BitmapData pic2Data = pic2.LockBits(imgSize, ImageLockMode.ReadOnly, pic2.PixelFormat);

  int pixelCount = pic1.Width * pic1.Height;
  int Bpp = 4;
  int byteCount = pixelCount * Bpp;

  int minX = pic1.Width, minY = pic1.Height;
  int maxX = 0, maxY = 0;

  DateTime start = DateTime.Now;
  unsafe
  {
    byte* pic1ptr = (byte*)pic1Data.Scan0;
    byte* pic2ptr = (byte*)pic2Data.Scan0;
    for (int i = 0; i < byteCount; i++)
      if (pic1ptr[i] != pic2ptr[i])
      {
        int x = (i / Bpp) % pic1.Width;
        int y = (i / Bpp) / pic1.Width;
        if (x < minX) minX = x;
        if (x > maxX) maxX = x;
        if (y < minY) minY = y;
        if (y > maxY) maxY = y;
        //Console.WriteLine("ungleich bei {0} => ({1}, {2}, {3})", i, (i / Bpp) % pic1.Width, (i / Bpp) / pic1.Width, rgb[i%Bpp]);
      }
  }
  DateTime end = DateTime.Now;

  pic1.UnlockBits(pic1Data);
  pic2.UnlockBits(pic2Data);

  TimeSpan dauer = end - start;

  Console.WriteLine("fertig nach {0} / {1}ms", dauer.ToString(), dauer.TotalMilliseconds);
  Console.WriteLine("änderung in {0}, {1} / {2}, {3}", minX, minY, maxX, maxY);

  Graphics g = Graphics.FromImage(pic2);
  g.DrawRectangle(new Pen(Color.Red, 1.0f), minX, minY, maxX - minX, maxY - minY);
  pic1.Save("pic1.png", ImageFormat.Png);
  pic2.Save("pic2.png", ImageFormat.Png);
  Console.WriteLine("gespeichert.");
  Console.ReadLine();
}

private static Bitmap CaptureScreen()
{
  Bitmap screenBmp = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
  Graphics g = Graphics.FromImage(screenBmp);
  g.CopyFromScreen(0, 0, 0, 0, new Size(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height));
  g.Dispose();
  //Console.WriteLine(screenBmp.PixelFormat.ToString());
  return screenBmp;
}
 
Ok danke für die Hilfe =)
Werds mir später nochmal genauer anschauen.
Ergänzung ()

Also ich gibs auf...
wenn ich dein Beispiel kopiere, dann funktioniert es, aber sobald ich statt deiner CaptureScreen Methode meine verwende bzw. die aus der Lib ja, spuckt der mir
ne Exception aus bei der If-Anweisung...

Ich hab mal den Projekt-Ordner gepackt und lads mal hoch. Vielleicht findet jemand anderes den Fehler.

Sein muss es nicht unbedingt. Rein aus Interesse und dem Lehreffekt dahinter würde ich aber schon gerne wissen worin der Fehler liegt.

Aber immerhin funktioniert mein ursprüngliches Programm, vielleicht finde ich ja doch irgendwo, irgendwann mal nen schnelleren und/oder besseren Algorithmus für den Abgleich von Bildern und/oder das erstellen von Screenshots.
 

Anhänge

Zuletzt bearbeitet:
Jedes mal das selbe "Es wurde versucht im geschützten Speicher zu lesen oder zu schreiben. ..."
 
Zhen schrieb:
Ok danke für die Hilfe =)
Ergänzung ()

Also ich gibs auf...
wenn ich dein Beispiel kopiere, dann funktioniert es, aber sobald ich statt deiner CaptureScreen Methode meine verwende bzw. die aus der Lib ja, spuckt der mir
ne Exception aus bei der If-Anweisung...

Aber immerhin funktioniert mein ursprüngliches Programm, vielleicht finde ich ja doch irgendwo, irgendwann mal nen schnelleren und/oder besseren Algorithmus für den Abgleich von Bildern und/oder das erstellen von Screenshots.

zum 1. Wenn du die Exception bekommst, dass im geschützten Speicher gelesen/oder geschrieben wird, dann bist du mit deiner Loop über den eigentlichen Speicher drüber oder drunter hinaus geschossen. Das ist ein sicheres Indiz dafür, dass deine hartcodierte Farbtiefe (4 Byte in der Variable Bpp) definitiv nicht die ist, die tatsächlich in dem Bitmap verwendet wird. Also schau dir mal an welchen Wert die PixelFormat Eigenschaft hat und berechne danach die exakte Anzahl der Bytes für dein Bild.

zum 2. eine schnellere Lösung habe ich dir in meinem 2. Post geschrieben, ja sogar ein ganzes Projekt mit hochgeladen, musst einfach nur mal schauen.

Ansonsten viel Erfolg auf der Suche
Rossibaer
 
@Rossibaer:
mein aktuelles Programm verwendet ja bereits die Lösung von dir. Der Quelltext ist zwar etwas anders verfasst, aber das Prinzip und Aktion ist genau die selbe ^^
Derzeit bin ich eigentlich zufrieden mit der Funktion. Bild läuft flüssig und die Veränderungen betragen auch nicht allzuviel KB =)

Es passt also.

zu dem anderen Punkt wegen dem Speicher. Hab mir das Pixelformat bereits angeguckt. Es sind schon 32 Bit. Die Farbtiefe von 4 Byte stimmt also. Selbst wenn es nicht stimmen würde, dann sollte ich bei dem Index von 9184 noch lange nicht das Ende vom Bild erreicht haben (egal bei was für einer Farbtiefe :) ) :D

PS: falls ich mit irgendwas unrecht habe oder falsch liege, dann bitte korrigieren/aufklären :) danke ;-)
 
@Zhen. ok, war ein Missverständnis von meiner Seite. Du hattest halt geschrieben, dass du den ursprünglichen Code verwendest. Bin also davon ausgegangen, dass du da nach wievor die 2 geschachtelten "for" Schleifen hast, statt es über eine Schleife zu machen...

Zu deinem geposteten Projekt, ich hab selber nur VS 2005 und kann die Solution nicht direkt öffnen. Werde da mal etwas dran rumspielen. Evtl. könntest du den Capture Prozess noch beschleunigen indem du statt BitBlt dann die GetDIBits API Funktion verwendest. Ansonsten sieht das mit dem Vergleich für mich ok aus. Aber genaueres kann ich erst sagen, wenn ich das Ganze mal lauffähig auf meinem Rechner habe und debuggen kann.

Bis dahin...
Rossibaer

EDIT:
Ok, bin soweit das es erstmal läuft und bei der Exception dann aussteigt. Der Vergleich scheitert so ca. bei Byte 9184. So weit, so gut. Ich vermute das Capturen des Screens macht kein echtes Bitmap sondern irgendwas ala JPG wodurch die Byte-Anzahl nicht mehr mit der errechneten Größe übereinstimmt. Ich würde mal diese CaptureScreen dll einfach durch eigenen Code ersetzen und schauen, was dabei rauskommt. Denke es ist sowieso nicht so toll, abhängig von einer fremden DLL zu sein. Wenn ich was habe, lade ich es hier hoch...

EDIT2:
Habs jetzt. Es liegt definitiv an deiner CaptureDesktop Methode aus dieser CaptureScreen dll. Diese erstellt ein Memory Bitmap, welches ein anderes Format als ein herkömmliches Bitmap hat. Somit stimmt die Größe nicht mehr überein und dein Vergleich schlägt dann bei dem 9184 Byte auf. Ich gehe mal davon aus das das Memory Bitmap eine Farbpalette nimmt, statt die Pixel als rohes 32 Bit Array zu speichern.

Hier eine Alternative:

Code:
    public Bitmap CaptureDesktop()
    {
      Size szScreen = SystemInformation.PrimaryMonitorSize;
      Bitmap bmpScreen = new Bitmap(szScreen.Width, szScreen.Height, PixelFormat.Format32bppArgb);
      Graphics graphics = Graphics.FromImage(bmpScreen);
      graphics.CopyFromScreen(0, 0, 0, 0, szScreen, CopyPixelOperation.SourceCopy);
      return bmpScreen;
    }

BTW: Das Mergen funktioniert so nicht, da du immer bei X = 0 und Y = 0 zeichnest. Besser:

Code:
private void MergeNewScreen(Rectangle partialScreen) {
  if (partialScreen != Rectangle.Empty) {
    Graphics g = Graphics.FromImage(pbScreen.Image);
    g.DrawImage(newScreen, partialScreen.X, partialScreen.Y, partialScreen, GraphicsUnit.Pixel);
    g.Flush();
    g.Dispose();
  }
}
 
Zuletzt bearbeitet:
Vielen vielen Dank.

Also ich bin gerade dabei deinen Rat zu beherzigen und werd mal die GetDIBits Funktion implementieren. Hoffe die bringt nen guten Leistungsschub =)

PS: An die CopyFromScreen Methode dacht ich auch, aber die ist ja doch relative langsam für ein Fernwartungstool ^^
Mir wurd genau deswegen ja auch die CaptureScreen dll von Codeproject empfohlen.

Naja werd auf jeden fall es mal mit GetDIBits probieren. Hoffe nur, dass die dann mit der Vergleichsmethode funktioniert =)
 
CopyFromScreen macht nicht viel anderes als das, was diese CaptureScreen.dll macht. Intern wird ebenfalls BitBlt verwendet um den Screen in ein Bitmap zu feuern.

Desweiteren sind dein Zeitangaben fehlerhaft. Du verwendest die DateTime.Now Funktion. Schon gewußt das diese eine Ungenauigkeit von ca. 30 ms hat? Besser du nimmst die Stopwatch.GetTimestamp() Methode um die Ticks zu messen. Zum Schluß einfach (((Ende - Start) / Stopwatch.Frequency) * 1000.0D) rechnen und schon hast du einen relativ akuraten Wert in Millisekunden. Um meinen Code zu messen verwende ich mittlerweile nur noch diese Stopwatch, weil die anderen Methoden zu ungenau sind...

Mir kam noch eine Idee, warum der Vergleich jedesmal fehlschlägt. Ich würde vermuten, dass die Bitmap Größe zwar gleich der Screengröße ist, jedoch bei dem LockBits dann implizit die Größe der Picturebox verwendet wird, was wiederum dazu führt, dass das Bild wesentlich kleiner ist als das Original.
 
Zuletzt bearbeitet:
Rossibaer schrieb:
...Zum Schluß einfach (((Ende - Start) / Stopwatch.Frequency) * 1000.0D)
Besser wohl:
PHP:
(send - sstart) * 1000D / Stopwatch.Frequency
sonst kommt fast immer 0 raus.

Alternativ kann man auch:
PHP:
using System.Runtime.InteropServices;
...
    [DllImport("kernel32.dll")]
    static extern uint GetTickCount();
...
 uint begintick = GetTickCount();
 doSomeThing();
 uint neededtime = GetTickCount()-begintick;
verwenden.
 
@lynxx: ok, ich vergaß die Konvertierung zu double mit einzubeziehen.

Alternativ ...
Code:
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();

GetScreen();
GetMouse(ref mouse_x, ref mouse_y);
DrawScreen();
DrawMouse();

stopwatch.Stop();
lblMs.Text = stopwatch.ElapsedMilliseconds.ToString();

... und schon ist man diese Rechnerei ganz los.
 
Ach übrigens .. ich hab auch versucht nach Lock
per
unsafe {
byte* pic1ptr = (byte*)pic1Data.Scan0;
auf die Daten zuzugreifen (auslesen), aber bekomme sofort eine Exception.

Das klappt aber einwandfrei und sehr schnell (da es eine native Funktion ist):
PHP:
[DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false)]
public static extern IntPtr memcpy(IntPtr dest, IntPtr src, UIntPtr count);

...
Rectangle bmprect = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData picData = bmp.LockBits(bmprect, ImageLockMode.ReadWrite, bmp.PixelFormat);
memcpy(Marshal.UnsafeAddrOfPinnedArrayElement(picture, 0), picData.Scan0, (UIntPtr)(picture.Length * 4));
bmp.UnlockBits(picData);
 
ich steh grad irgendwie aufm schlauch... kann mir jemand verraten was genau die memcpy funktion den bewirkt?
 
hmm... danke für die rasche antwort, aber wenns einfach nur daten von einem speicherbereich in den anderen kopiert, dann erkenne ich den sinn darin nicht die funktion zu verwenden :D
zumindestens nicht in dieser anwendung. ^^
 
Naja, lynxx hat ja geschrieben, dass ne (Zugriffs?) Exception geworfen wird beim Versuch auf die Daten zuzugreifen --> Lösung: Daten einfach in ein Array kopieren auf das man dann zugreifen kann ;)
 
So nach meinem ersten ungültigen Versuch heute Mittag, hier nun die fehlerbereinigte und optimierte Version:

Anhang anzeigen Bildverarbeitung.zip

Nach meinen Messungen bin ich jetzt bei ca 90 ms inkl. dem ganzen GDI+ Zeug (Drawing.Graphics). Der Vergleich ist wesentlich schneller als vorher.

Im übrigen hast du noch so einige Sachen hinsichtlich der Maus falsch gehabt. Hab es soweit ich es sah, meine Fixes mit reingemacht.

[Edit]
Die aktuelle Version vergleicht jetzt 256 Pixel auf einmal, wodurch der gesamte Vergleich in der Ruhephase bei 6-10ms liegt und bei vielen Änderungen bei ca 50ms
 
Zuletzt bearbeitet:
Rossibaer schrieb:
[Edit]
Die aktuelle Version vergleicht jetzt 256 Pixel auf einmal, wodurch der gesamte Vergleich in der Ruhephase bei 6-10ms liegt und bei vielen Änderungen bei ca 50ms
Nur deswegen hab ich mal einen Blick reingeworfen, hast Du früher zufällig mal in Assembler programmiert ? :D

Der != Operator gefällt mir, aber ist das nicht noch schneller (zumindest noch kryptischer ist es :evillol:):
PHP:
public static bool operator !=(ComparePixel x, ComparePixel y)
{
  short i = 0;
  return
    x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||
    x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||
    x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||
    x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||
    x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||
    x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||
    x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||
    x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||
    x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||
    x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||
    x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||
    x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||
    x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||
    x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||
    x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||
    x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||
    x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||
    x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||
    x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||
    x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||
    x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||
    x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||
    x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||
    x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||
    x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||
    x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||
    x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||
    x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||
    x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||
    x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||
    x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||
    x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i++]||x.data[i]!=y.data[i];
}

Edit:
Hab noch nen kleinen Bug gefunden, am Ende der GetPartialScreen() sollte geprüft werden ob maxX größer als Bildschirmbreite ist, ohne den Check kann z.b bei 1920 Pixel Bildschirmbreite 2048 zurückgeliefert werden (1920 / 256 = 7,5):
PHP:
// zur Sicherheit noch mal etwas größer machen, sonst bleiben Artefakte im Screen
maxX += bufferSize;
if (maxX > newScreen.Width)
  maxX = newScreen.Width;

Und eigentlich ist der Kommentar falsch, man braucht keinen "Sicherheitspuffer", sondern es müsste eigentlich überall in den Schleifen:
if (x > maxX) maxX = x + bufferSize;
if (y > maxY) maxY = y + 1;
stehen, denn ComparePixel vergleicht ja 256 x & 1 y-Pixel.
Die Werte erst am Ende zu addieren macht aber mehr Sinn. :)
 
Zuletzt bearbeitet:
Zurück
Oben