Perspektivisches Entzerren eines Bildes [gelöst]

Für Probleme bezüglich Grafik, Audio, GL, ACS, ...
Benutzeravatar
corpsman
Lazarusforum e. V.
Beiträge: 1498
Registriert: Sa 28. Feb 2009, 08:54
OS, Lazarus, FPC: Linux Mint Mate, Lazarus GIT Head, FPC 3.0
CPU-Target: 64Bit
Wohnort: Stuttgart
Kontaktdaten:

Re: Perspektifisches Entzerren eines Bildes

Beitrag von corpsman »

hmm mir scheint meine Zeichenkünste sind nicht die besten ;)

Mein Usecase ist das jemand mit ner Kamera etwas Fotographiert (als ersatz für einen Scanner), derjenige gibt sich natürlich die größte Mühe den Winkel Kamera Sichtachse zu Oberfläche möglichst nah bei 90° zu halten (so wie man das halt macht, wenn man ein Blatt abfotographiert). Mein Beispiel das ich da die ganze Zeit Nutze ist maximal Bösartig, weil ich absichtlich den Winkel der Kamera Sichtachse zur Blattoberfläche flach gewählt habe. wp_xyz Beschreibung führt das ins extreme und setzt diesen Winkel auf 0 (und muss dass man überhaupt etwas sieht dafür das Blatt in y-Achse verschieben, so dass das Blatt durch den Öffnungswinkel der Camera unten wieder Rein Rutscht).

Bisher ging ich davon aus, dass wenn ich 4 Punkte auf dem Blatt markiere und dem Rechner sage, dass diese ein Rechteck Bilden ich mehr oder minder unabhängig von beschriebenen Winkel das wieder zurück rechnen können sollte. Interessant ist halt der Grenzfall in dem der Code aus BGRA erstaunlich gut abschneidet.

Bisher habe ich gesehen dass dieser Code 2 Matrizen Bildet (Quelle -> Quadrat, Quadrat -> Ziel) und diese letzteendes zu einer zusammenmultipliziert die dann die Transformation macht.

In meinem Buch scheinen die durch lösen des GL das wohl direkt zu machen, aber so schnell bin ich noch nicht.. ;)
--
Just try it

mschnell
Beiträge: 3444
Registriert: Mo 11. Sep 2006, 10:24
OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
CPU-Target: X32 / X64 / ARMv5
Wohnort: Krefeld

Re: Perspektivisches Entzerren eines Bildes

Beitrag von mschnell »

Mir wurde mal glaubhaft versichert, dass man (Profis) solche Projektionen am besten mit Quaternionen macht,
In dieser Anwendung ist der Vorteil, dass bei Quaternionen die normalen Rechenregeln gelten und es deshalb einfach sein sollte, die Umkehrfunktion (also Entzerrung statt Projektion) zu bestimmen.
-Michael

Benutzeravatar
corpsman
Lazarusforum e. V.
Beiträge: 1498
Registriert: Sa 28. Feb 2009, 08:54
OS, Lazarus, FPC: Linux Mint Mate, Lazarus GIT Head, FPC 3.0
CPU-Target: 64Bit
Wohnort: Stuttgart
Kontaktdaten:

Re: Perspektivisches Entzerren eines Bildes

Beitrag von corpsman »

Ich hab mich nun mal an dem Reverse Enginiering Versuch gemacht und mir den Source von BGRA angesehen.

Die stellen ihre 3x3 Matrix über Variablen dar und nicht als Matrix, ungewöhnlich aber ok
die Matrix sieht dann so aus

Code: Alles auswählen

// ( sx shx tx   )
// ( shy sy ty  )
// ( w0 w1 w2 )

// t* = Transliereung , sx, shx, shy, sy = Dreh /Scheermatrix, w* = ??
Im Code dreht sich dann alles um diese Stelle:

Code: Alles auswählen

procedure TBGRADefaultBitmap.FillQuadPerspectiveMapping(pt1, pt2, pt3,
  pt4: TPointF; texture: IBGRAScanner; tex1, tex2, tex3, tex4: TPointF;
  ADrawMode: TDrawMode);
var
  persp: TBGRAPerspectiveScannerTransform;
begin
  persp := TBGRAPerspectiveScannerTransform.Create(texture,[tex1,tex2,tex3,tex4],[pt1,pt2,pt3,pt4]); // Berechnen der Transformations Matrix
  FillPoly([pt1,pt2,pt3,pt4],persp,ADrawMode); 
  persp.Free;
end;

Wenn man dann die entsprechenden Stellen im Code mit Breakpoints versieht und meine nachprogrammierten Matrizen vergleicht zeigt mir der Debugger bei BGRA aber ab einem gewissen Punkt nur Unsinn an, muss wohl an den vielen const Array's liegen, bis zu dem Punkt sind meine Matrizen mit denen aus BGRA aber identisch.

Wenn ich dann aber die Ergebniss Matrix auf das Bild multipliziere kommt bei mir ein deutlich verzerrtes Bild und bei BGRA das richtige heraus. da muss ich also noch mal ran :roll:

[Edit]
*g* der Linux Debugger war freundlicher, hier zeigte sich, dass ich die Falsche Matrix invertiert habe. Nun muss ich nur noch den Fillpoly Teil nachprogrammieren, denn so wie es aussieht macht der auch noch mal ein Stretching des Bildes, so auf den Ersten Blick könnte es dann sogar stimmen ;)
Zuletzt geändert von corpsman am Mo 25. Jan 2021, 13:44, insgesamt 1-mal geändert.
--
Just try it

Benutzeravatar
corpsman
Lazarusforum e. V.
Beiträge: 1498
Registriert: Sa 28. Feb 2009, 08:54
OS, Lazarus, FPC: Linux Mint Mate, Lazarus GIT Head, FPC 3.0
CPU-Target: 64Bit
Wohnort: Stuttgart
Kontaktdaten:

Re: Perspektivisches Entzerren eines Bildes

Beitrag von corpsman »

mschnell hat geschrieben:
Mo 25. Jan 2021, 11:29
Mir wurde mal glaubhaft versichert, dass man (Profis) solche Projektionen am besten mit Quaternionen macht,
In dieser Anwendung ist der Vorteil, dass bei Quaternionen die normalen Rechenregeln gelten und es deshalb einfach sein sollte, die Umkehrfunktion (also Entzerrung statt Projektion) zu bestimmen.
-Michael
Mit Quartenionen Rechnen ist nicht schwer, ist wie mit Complexen Zahlen, nur halt mit mehr imaginären Anteilen ;).
Das Problem ist es den Rechenweg zu kennnen *g*, siehe meinen Post vorher..
--
Just try it

Benutzeravatar
corpsman
Lazarusforum e. V.
Beiträge: 1498
Registriert: Sa 28. Feb 2009, 08:54
OS, Lazarus, FPC: Linux Mint Mate, Lazarus GIT Head, FPC 3.0
CPU-Target: 64Bit
Wohnort: Stuttgart
Kontaktdaten:

Re: Perspektivisches Entzerren eines Bildes

Beitrag von corpsman »

Also mit dem folgenden Code und meiner MulImage Funktion kann man aus

Vorher.jpg

das hier extrahieren
Final.jpg
(120.18 KiB) Noch nie heruntergeladen
Jetzt muss ich es nur noch schaffen das selber zu machen und nicht den Umweg über BGRA nehmen zu müssen ..

Code: Alles auswählen

Procedure CorrectProjection(Const Bitmap: TBitmap; TopLeft, BottomLeft,
  TopRight, BottomRight: TPoint);
Var
  fp, tl, tr, bl, br: TVector2;
  b, SourceBGRAbitmap: Tbgrabitmap;
  _ap, _bp, _cp, _dp: TPointF;
Begin
  If Not IntersectLines(TopLeft, BottomLeft - TopLeft, TopRight, TopRight - BottomRight, fp) Then exit;
  If fp.y < 0 Then Begin
    // Berechnen der Schnittpunkte mit der Oberen Bildkante
    bl := v2(0, Bitmap.Height);
    br := v2(Bitmap.Width, Bitmap.Height);
    If Not IntersectLines(v2(0, 0), v2(1, 0), bl, fp - bl, tl) Then Raise exception.create('Invalid Error 3.');
    If Not IntersectLines(v2(0, 0), v2(1, 0), br, fp - br, tr) Then Raise exception.create('Invalid Error 4.');
  End
  Else Begin
    If fp.y > Bitmap.Height Then Begin
      // Berechnen der Schnittpunkte mit der unteren Bildkante
      tl := v2(0, 0);
      tr := v2(Bitmap.Width, 0);
      If Not IntersectLines(v2(0, Bitmap.Height), v2(1, 0), tl, fp - tl, bl) Then Raise exception.create('Invalid Error 7.');
      If Not IntersectLines(v2(0, Bitmap.Height), v2(1, 0), tr, fp - tr, br) Then Raise exception.create('Invalid Error 8.');
    End
    Else Begin
      // Der Fluchtpunkt liegt innerhalb des Bildes -> hier können wir nix machen, oder ?
      exit;
    End;
  End;
  _ap := Pointf(0, 0);
  _bp := Pointf(0, Bitmap.Height);
  _cp := Pointf(Bitmap.Width, Bitmap.Height);
  _dp := Pointf(Bitmap.Width, 0);
  SourceBGRAbitmap := TBGRABitmap.Create(Bitmap);
  b := TBGRABitmap.Create(Bitmap.Width, Bitmap.Height);
  b.FillQuadPerspectiveMapping(_ap, _bp, _cp, _dp, SourceBGRAbitmap, tl, bl, br, tr);
  Bitmap.Assign(b);
  SourceBGRAbitmap.free;
  b.free;
  // Ohne diese eigentlich Sinnlose Zeile ist das Ergebnis in der Paintbox müll
  MulImage(Bitmap, IdentityMatrix3x3, imNone);
End;  
Also ist schon mal bewiesen, dass es geht ;)
Dateianhänge
Vorher.jpg
Vorher.jpg (60.35 KiB) 3149 mal betrachtet
--
Just try it

Benutzeravatar
corpsman
Lazarusforum e. V.
Beiträge: 1498
Registriert: Sa 28. Feb 2009, 08:54
OS, Lazarus, FPC: Linux Mint Mate, Lazarus GIT Head, FPC 3.0
CPU-Target: 64Bit
Wohnort: Stuttgart
Kontaktdaten:

Re: Perspektivisches Entzerren eines Bildes

Beitrag von corpsman »

Sodale, hab mein Buch gewältzt und nun hab ich es auch verstanden und selbst implementieren können, das Ergebnis ist das Selbe wie mit BGRA nur eben selbst gemacht ;)

Der Algorithmus verlangt nun einfach 4 Punkte die auf zwei eigentlich Parallelen Linien liegen müssten und berechnet daraus die im vorherigen Post gezeigten Bilder.
Und für alle die das auch nachvollziehen wollen hier mein Code:

Code: Alles auswählen

(*
 * Entfernt aus einem Bild die Verzerrung die durch die Projektion entstanden ist.
 * Die Geraden durch (tl, bl) und (tr, br) müssen so gelegt werden, dass sie auf eigentlich Paralellen Geraden im Bild liegen.
 * Anschliesend wird das Bild so umgerechnet, dass diese Geraden im Ergebnis Senkrechte sind.
 *)
Procedure CorrectProjection(Const Bitmap: TBitmap; TopLeft, BottomLeft, TopRight, BottomRight: TPoint; Mode: TInterpolationMode = imBilinear);

(*
 * Lösen der Allgemeinen Gleichung der Perspektifischen Verzeichnung durch Einsetzen der 4 Bekannten Punkte
 *
 * Formeln und Lösungen entnommen aus: ISBN 978-3-540-21888-3 Seiten: 230 und 231
 *)
  Function SetMatrixByPoints(Const x, x_: Array Of TVector2): TMatrixNxM;
  Var
    i: Integer;
  Begin
    result := ZeroNxM(8, 9);
    For i := 0 To 3 Do Begin // Gleichung (15.9)
      // * = x_
      result[0, i] := 1;
      result[1, i] := x[i].x;
      result[2, i] := x[i].y;
      result[3, i] := 0;
      result[4, i] := 0;
      result[5, i] := 0;
      result[6, i] := -x_[i].x * x[i].x;
      result[7, i] := -x_[i].x * x[i].y;
      result[8, i] := x_[i].x;
      // * = y_
      result[0, i + 4] := 0;
      result[1, i + 4] := 0;
      result[2, i + 4] := 0;
      result[3, i + 4] := 1;
      result[4, i + 4] := x[i].x;
      result[5, i + 4] := x[i].y;
      result[6, i + 4] := -x_[i].y * x[i].x;
      result[7, i + 4] := -x_[i].y * x[i].y;
      result[8, i + 4] := x_[i].y;
    End;
  End;

  (*
   * Gleichung 15.7:
   *            a0 + a1*x + a2*y
   *  x'(x,y) = ----------------
   *            1 + c1*x + c2*y
   *
   *            b0 + b1*x + b2*y
   *  y'(x,y) = ----------------
   *            1 + c1*x + c2*y
   *)
Var
  a0, a1, a2, b0, b1, b2, c1, c2: Single;

  Procedure CreateFunctionParamsFromSolvedMatrix(Const M: TMatrixNxM);
  Begin
    a0 := M[8, 0];
    a1 := M[8, 1];
    a2 := M[8, 2];
    b0 := M[8, 3];
    b1 := M[8, 4];
    b2 := M[8, 5];
    c1 := M[8, 6];
    c2 := M[8, 7];
  End;

  Function f(x, y: Single): TVector2; // Implementierung der Gleichung 15.7
  Var
    xt, yt, denominator: Single;
  Begin
    denominator := 1 + c1 * x + c2 * y;
    xt := (a0 + a1 * x + a2 * y) / denominator;
    yt := (b0 + b1 * x + b2 * y) / denominator;
    result := v2(xt, yt);
  End;

Var
  Source_intf, Dest_intf: TLazIntfImage;
  ImgHandle, ImgMaskHandle: HBitmap;

  tl, bl, tr, br, // Die Eckpunkte des Quellbildes, das nachher auf das Zielbild Skalliert wird
  p, // f(x,y)
  fp: TVector2; // Der Fluchtpunkt
  NI: TBitmap;
  j, i: Integer;
  m: TMatrixNxM;
Begin
  // 1. Fluchtpunkt Berechnen
  If IntersectLines(TopLeft, BottomLeft - TopLeft, TopRight, TopRight - BottomRight, fp) Then Begin
    If fp.y < 0 Then Begin
      // Berechnen der Schnittpunkte mit der Oberen Bildkante
      bl := v2(0, Bitmap.Height);
      br := v2(Bitmap.Width, Bitmap.Height);
      If Not IntersectLines(v2(0, 0), v2(1, 0), bl, fp - bl, tl) Then Raise exception.create('Invalid Error 3.');
      If Not IntersectLines(v2(0, 0), v2(1, 0), br, fp - br, tr) Then Raise exception.create('Invalid Error 4.');
    End
    Else Begin
      If fp.y > Bitmap.Height Then Begin
        // Berechnen der Schnittpunkte mit der unteren Bildkante
        tl := v2(0, 0);
        tr := v2(Bitmap.Width, 0);
        If Not IntersectLines(v2(0, Bitmap.Height), v2(1, 0), tl, fp - tl, bl) Then Raise exception.create('Invalid Error 7.');
        If Not IntersectLines(v2(0, Bitmap.Height), v2(1, 0), tr, fp - tr, br) Then Raise exception.create('Invalid Error 8.');
      End
      Else Begin
        // Der Fluchtpunkt liegt innerhalb des Bildes -> hier können wir nix machen, oder ?
        exit;
      End;
    End;
    // Wir Berechnen die Matrix direkt Invertiert, der Mathe ist das Egal, aber dafür
    // Können wir das Zielbild dann sauber Abtasten ;)
    m := SetMatrixByPoints(
      [v2(0, 0), v2(0, Bitmap.Height), v2(Bitmap.Width, Bitmap.Height), v2(bitmap.Width, 0)]
      ,
      [tl, bl, br, tr]
      );
    GaussJordan(m); // GL-Lösen
    CreateFunctionParamsFromSolvedMatrix(m);
    // Nun muss nur noch das tl, tr, bl, br Rechteck auf das Ni Bild projiziert werden
    ni := TBitmap.Create;
    ni.Width := Bitmap.Width;
    ni.Height := Bitmap.Height;
    Source_intf := TLazIntfImage.Create(0, 0);
    Source_intf.LoadFromBitmap(Bitmap.Handle, Bitmap.MaskHandle);
    Dest_intf := TLazIntfImage.Create(0, 0);
    Dest_intf.LoadFromBitmap(ni.Handle, ni.MaskHandle);

    For j := 0 To ni.Height - 1 Do Begin
      For i := 0 To ni.Width - 1 Do Begin
        p := f(i, j);
        SetPixel(Dest_intf, i, j, GetPixel(Source_intf, p.x, p.y, mode));
      End;
    End;
    Dest_intf.CreateBitmaps(ImgHandle, ImgMaskHandle, false);
    Bitmap.Handle := ImgHandle;
    Bitmap.MaskHandle := ImgMaskHandle;
    Source_intf.free;
    Dest_intf.free;
    ni.free;
  End
  Else Begin
    // Die definierenden Geraden sind Paralell => Keine Modifikation notwendig
  End;
End;

--
Just try it

Antworten