Entwickler-Ecke

Multimedia / Grafik - Grafik im Bildschirminhalt finden


Clemens L. - Fr 01.08.08 17:13
Titel: Grafik im Bildschirminhalt finden
Hallöchen mal wieder, :wave:

habe mal wieder ein kleines Problem. Und zwar muss ich auf dem aktuellen Bildschirminhalt ein bestimmtes Bild finden, das ich aus einer Bitmap lade.
Natürlich muss nicht immer der ganze Bildschirm abgesucht werden, sondern es ist immer ein Bereich gegeben. Nun weiss ich aber leider garnicht, wie ich das anstellen soll, denn es muss auch mit annehmbarer Geschwindigkeit laufen.

Den Bildschirminhalt ermittle ich so:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
procedure GetScreenShot (ABitmap : TBitmap);  stdcall;
var 
  DC : THandle; 
begin
  if Assigned(ABitmap) then
  begin 
    DC := GetDC(0);
    try
      ABitmap.Width := Screen.Width;
      ABitmap.Height := Screen.Height;
      BitBlt(ABitmap.Canvas.Handle,
             0,0,Screen.Width,Screen.Height,
             DC,
             0,0,
             SrcCopy
        ); 
    finally 
      ReleaseDC(0, DC);                       
    end
  end
end;


Außerdem zum Vergleichen von Farben: (mit angegebener Toleranz)


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
function CompareColors(Color1, Color2, Tolerance: Integer): Boolean; stdcall;
var
  C1, C2: TColor;
begin
  Result := False;
  C1 := ColorToRGB(Color1);
  C2 := ColorToRGB(Color2);
  if (Abs(Round(GetRValue(C1) - GetRValue(C2))) <= Tolerance) then
    if (Abs(Round(GetGValue(C1) - GetGValue(C2))) <= Tolerance) then
      if (Abs(Round(GetBValue(C1) - GetBValue(C2))) <= Tolerance) then
        Result := True;
end;


Über Vorschläge, wie ich das am besten realisieren könnte, würd ich mich sehr freuen! :D


JJ - Fr 01.08.08 22:41

Also ich habe deinen Code nur kurz überflogen aber ich würde es folgendermaßen realisieren:
du suchst dir von der grafik 3 oder 4 pixel aus (oder eben mehr wenn du ganz sicher sein willst)!
Dann suchst du deinen Bildschirm per forschleife nach dem ersten pixel ab und überprüfst, ob die anderen Pixel auf dem bildschirm das selbe verhältnis haben, wie die in deiner Grafik... wenn nicht soll die forschleife eben weiterlaufen!
Wenn die forschleife durchgelaufen ist, aber die Pixelabfolge nicht gefunden wurde, so kannst du davon ausgehen, dass die grafik im bild nicht zu finden ist!

Du kannst die Farbe ja überprüfen und als kriterium nehmen

ich gebe dir hier mal ein bisschen Pseudocode (so wie ichs machen würde)


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
for Scanhorizontal:=0 to Screen.width do
     begin
     for Scanvertical:=0 to Screen.height do
          begin
          //NUR PSEUDOCODE! Such dir bitte woanders wie du Pixelfarben vom Bildschirm auslesen kannst (3 Pixel im Beispiel)
          //Das ist aber nicht schwer ;-)
          If Pixelfarbe(Scanhorizontal,ScanVertical= Gesuchter 1.Pixel then
                begin
                If Pixelfarbe(Scanhorizontal+3,ScanVertical+3= Gesuchter 2.Pixel then     
                     begin
                     If Pixelfarbe(Scanhorizontal+6,ScanVertical+6= Gesuchter 3.Pixel then 
                          begin
                          //GEFUNDEN!!! Die Pixelanordnung entspricht der in der Grafik
            ...


Je genauer du vergleichen willst, desto mehr Pixel benötigst du, aber man muss ja die bilder nicht gleich komplett vergleichen. So gehts eindeutig schneller und die Laufzeit sieht auch besser aus...
Ist jedenfalls mein Vorschlag :-) Hoffe es hilft dir


Clemens L. - Sa 02.08.08 12:08

Hallo JJ, danke für deinen Vorschlag! :)

Das Problem an deinem Vorschlag ist, dass die Bilder ja von Variabler größe sind, du aber nach einem festen Vergleichsmuster suchst. Außerdem kann es in meinem Fall da problematisch werden, da viele Farben mehrmals vorkommen, und man daher besser komplett vergleichen sollte. Aber möglicherweise könnte man nach der ersten If-Abfrage in deinem Code mit zwei weiteren For-Schleifen prüfen, ob das Bild an der Stelle vorhanden ist.

Ich werd mal schaun, ob ich auf die Art und Weise was hinbekomme. Wer einen besseren Vorschlag hat, immer her damit! :)


elundril - Sa 02.08.08 12:26

du könntest vielleicht vom Desktop immer ein stückchen ausschneiden das so groß ist wie das bild, kopieren, nen Hash daraus machen und mit dem Hash von deinem Vorgegebenen Bitmap vergleichen. und dann verschiebst du den ausschnitt um eins nach recht. Wenn sich in der ersten zeile dann kein vollständiges bild ausgeht dann um ein pixel nach runter.

die zeitersparniss? Ganz einfach: den Hash zu errechnen und die werte miteinander zu vergleichen ist denk ich mir schneller aus die pixel einzeln zu berrechnen.

oder du suchst dir einfach die Bytefolge von der Bytefolge des größeren heraus. Das machst du indem du die erste zeile vom gesuchten bild nimmst und als Bytes im großen Bild suchst, wenn die nicht da ist, abbruch. Wenn schon das schaust du noch ob in der Nächsten BILDSCHRIMZEILE (auflösung zum anfang des letzten bruchstück hinzuaddiert) die bytefolge von der zweiten Zeile ist.


Das is mir (und meinem Vater) so spontan eingefallen. Leider kann man dabei aber nur genau das Bild im Bild finden und es muss alles übereinstimmen.

lg elundril


Clemens L. - Sa 02.08.08 12:48

Hmm, das wäre leider nicht möglich, da bestimmte abweichungen innerhalb eines Bereiches erlaubt sind. Ich versuch nachher mal mir etwas zu basteln, was dem ähnlich ist was JJ geschrieben hat.

---Moderiert von user profile iconNarses: Beiträge zusammengefasst---

So, habe jetzt eine Funktion geschrieben, die in einer annehmbaren Zeit eine Grafik findet:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
type
  TRGB32 = packed record
  B, G, R, A: Byte;
  end;
  TRGB32Array = packed array[0..MaxInt div SizeOf(TRGB32) - 1of TRGB32;
  PRGB32Array = ^TRGB32Array;

  TColorArray = array of array of Integer;

{...}

procedure GetColorArray(Bmp: TBitmap; var ColorArray: TColorArray);
var
  cx, cy: Integer;
  Line: PRGB32Array;
begin
  If Assigned(Bmp) then
  begin
    Bmp.PixelFormat := pf32bit;
    SetLength(ColorArray,Bmp.Width,Bmp.Height);
    for cy := 0 to Bmp.Height - 1 do
    begin
      Line := Bmp.ScanLine[cy];
      for cx := 0 to Bmp.Width - 1 do
      begin
        ColorArray[cx,cy] := RGB(Line[cx].R,Line[cx].G,Line[cx].B);
      end;
    end;
    Line := nil;
  end;
end;


function FindImage(Image: PChar; xs, ys, xe, ye, Tolerance: Integer; Pos: PPoint): Boolean; stdcall;
var
  searchImg: TBitmap;
  screenImg: TBitmap;
  searchColors, screenColors: TColorArray;
  ScanH, ScanV: Integer;
  SrchH, SrchV: Integer;
  Found, Finished: Boolean;
begin
  Result := False;
  Finished := false;
  If not FileExists(Image) then Exit;
  try
    searchImg := TBitmap.Create;
    searchImg.LoadFromFile(Image);
    screenImg := TBitmap.Create;
    GetScreenShot(screenImg);
    GetColorArray(searchImg,searchColors);
    GetColorArray(screenImg,screenColors);
    for ScanH := xs to xe do
    begin
    If Finished then Break;
      For ScanV := ys to ye do
      begin
      If Finished then Break;
        Found := true;
        For SrchH := 0 to SearchImg.Width - 1 do
        begin
          For SrchV := 0 to SearchImg.Height - 1 do
          begin
            If not CompareColors(screenColors[ScanH + SrchH,ScanV + SrchV],searchColors[SrchH,SrchV],Tolerance) then
              Found := false;
          end;
        end;
        If found then
        begin
          Pos^.X := ScanH;
          Pos^.Y := ScanV;
          Finished := true;
        end;
      end;
    end;
   finally
    FreeAndNil(searchImg);
    FreeAndNil(screenImg);
  end;
end;


Bei Verbesserungsvorschlägen / Fragen einfach melden. Ich geh Essen :P


littlejo - So 19.10.08 20:51

user profile iconClemens L. hat folgendes geschrieben Zum zitierten Posting springen:

Bei Verbesserungsvorschlägen / Fragen einfach melden. Ich geh Essen :P


Hallo,

könntest du erläutern, ob es geht Screenshot (selbst gemacht) mit einem Screenshot vom Programm gemacht zu vergleichen? - Oder halt wie es direkt mit den Pixeln geht?

Ich steige da nicht ganz durch und bin auch etwas verunsichert - da du den Code durch [...] gekürzt hast.

Würde mich freuen, wenn du das mal erklären könntest.

Lg

Jo


jaenicke - So 19.10.08 21:19

Hallo und :welcome: im Forum!
user profile iconlittlejo hat folgendes geschrieben Zum zitierten Posting springen:
Ich steige da nicht ganz durch und bin auch etwas verunsichert - da du den Code durch [...] gekürzt hast.
Er hat doch gar nix wesentliches gekürzt, nur den Code für das Erstellen des Screenshots, aber der hat ja nichts damit zu tun. :nixweiss:

Die Frage ist eigentlich was du dabei nicht verstehst, werde mal etwas genauer.

user profile iconlittlejo hat folgendes geschrieben Zum zitierten Posting springen:
könntest du erläutern, ob es geht Screenshot (selbst gemacht) mit einem Screenshot vom Programm gemacht zu vergleichen? - Oder halt wie es direkt mit den Pixeln geht?
Der Quelltext findet ein Bild in einem gerade gemachten Screenshot.


Yogu - So 19.10.08 21:59

user profile iconlittlejo hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconClemens L. hat folgendes geschrieben Zum zitierten Posting springen:

Bei Verbesserungsvorschlägen / Fragen einfach melden. Ich geh Essen :P
könntest du erläutern, ob es geht Screenshot (selbst gemacht) mit einem Screenshot vom Programm gemacht zu vergleichen? - Oder halt wie es direkt mit den Pixeln geht?

Du gehst einfach alle Pixel durch, und vergleichst die von beiden Bildern.

Das hier ist zwar wahnsinnig langsam, aber dafür leicht zu verstehen:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
function ComparePictures(Pic1, Pic2: TBitmap): Boolean;
var x, y: Integer;
begin
  // Das macht es leichter: Wenn irgendetwas nicht stimmt, wird einfach die Methode verlassen
  Result := False;

  // Natürlich müssen Breite und Höhe übereinstimmen
  if (Pic1.Width <> Pic2.Height) or (Pic1.Height <> Pic2.Height) then Exit;

  // Alle Pixel durchgehen und vergleichen; wenn Farbe unterschiedlich, Prozedur verlassen
  for x := 0 to Pic1.Width-1 do
    for y := 0 to Pic1.Height-1 do
      if Pic1.Pixels[x, y] <> Pic2.Pixels[x, y] then Exit;

  // Wenn der Code bis hier richtig ausgeführt wurde, sind die beiden Bilder wohl gleich
  Result := True;
end;
(Ungetestet)


Mit ScanLine kann man das ganze noch beschleunigen.


jaenicke - So 19.10.08 22:11

Ich habe mir den Quelltext von user profile iconClemens L. mal angeschaut, das lässt sich noch beträchtlich beschleunigen, da im oben geposteten Quelltext nicht abgebrochen wird, obwohl schon klar ist, dass ein Pixel nicht übereingestimmt hat.

Zudem wird gar nicht True zurückgegeben, wenn etwas gefunden wird. So ist es besser würde ich sagen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
      For ScanV := ys to ye do
      begin
      If Finished then Break;
        Found := true;
        For SrchH := 0 to SearchImg.Width - 1 do
        begin
          if not Found then
            Break;
          For SrchV := 0 to SearchImg.Height - 1 do
          begin
            If not CompareColors(screenColors[ScanH + SrchH,ScanV + SrchV],
              searchColors[SrchH,SrchV],Tolerance) then
            begin
              Found := false;
              Break;
            end;
          end;
        end;
        If found then
        begin
          Pos^.X := ScanH;
          Pos^.Y := ScanV;
          Finished := true;
          Result := True;
        end;
      end;