Patchen Sie das Image


114

In einem beliebten Bildbearbeitungssoftware gibt es eine Funktion, dass Patches (Der Begriff in der Bildverarbeitung verwendet wird Inpaintingvorgang als @ mınxomaτ hingewiesen.) Einen ausgewählten Bereich eines Bildes auf der Grundlage der Informationen außerhalb des Patch. Und es macht einen ziemlich guten Job, wenn man bedenkt, dass es nur ein Programm ist. Als Mensch kann man manchmal sehen, dass etwas nicht stimmt, aber wenn man die Augen zusammenpresst oder nur einen kurzen Blick darauf wirft, scheint der Fleck die Lücke ziemlich gut zu füllen .

beispielsweise durch gängige Bildbearbeitungssoftware

Herausforderung

Wenn ein Bild und eine Maske, die einen rechteckigen Bereich des Bildes angibt, gepatcht werden sollen (auch als Bild oder in einem anderen bevorzugten Format), sollte Ihr Programm versuchen, den angegebenen Bereich mit einem Patch zu füllen, der sich dem Rest des Bildes anpasst das Bild. Das Programm kann nicht die Informationen des Originalbilds verwenden, die sich innerhalb des angegebenen Bereichs befanden.

Sie können davon ausgehen, dass der Patch immer mindestens die Breite von den Seiten und die Höhe von der Ober- und Unterseite des Bildes entfernt ist. Das bedeutet, dass die maximale Fläche eines Patches 1/9 des gesamten Bildes beträgt.

Bitte fügen Sie eine kurze Beschreibung der Funktionsweise Ihres Algorithmus hinzu.

Wählen

Die Wähler werden gebeten, zu beurteilen, wie gut die Algorithmen funktionieren, und entsprechend abzustimmen.

Einige Vorschläge zur Beurteilung: (Nochmals vielen Dank an @ mınxomaτ für die weiteren Kriterien.)

  • Wenn Sie die Augen zusammenkniffen und das Bild gut aussieht?
  • Kannst du genau sagen, wo sich der Patch befindet?
  • Wie gut setzen sich Strukturen und Texturen aus Bildhintergrund und Umgebung fort?
  • Wie viele falsche Farbpixel enthält der bearbeitete Bereich?
  • Gibt es in dem Bereich gleichmäßig gefärbte Kleckse / Blöcke, die nicht dorthin zu gehören scheinen?
  • Weist der bearbeitete Bereich im Vergleich zum Rest des Bildes drastische Farb- / Kontrast- oder Helligkeitsverschiebungen auf?

Gültigkeitskriterium

Damit eine Übermittlung gültig ist, muss das Ausgabebild genau mit dem Eingabebild außerhalb des angegebenen Bereichs übereinstimmen.

Testfall

Links das Quellbild, rechts die entsprechende Maske:


1
Können wir die Maskeneingabe als Textargumente (zB inpaint.exe left top width height img.jpg) akzeptieren ?
Mittwoch,

1
Sicher, das Eingabe- / Ausgabeformat ist nicht wirklich wichtig, da es ein Beliebtheitswettbewerb ist, bei dem vor allem die Leistung Ihres Algorithmus wichtig ist.
Fehler

24
Dies ist eine sehr praktische Herausforderung. Möglicherweise sind die Ergebnisse besser als die in GIMP und anderen Open-Source-Bildbearbeitungsprogrammen verwendeten Algorithmen. Vermögen, Ruhm und Ehre könnten dir gehören!
Sparr

6
@Sparr und schließlich hässliche Wasserzeichen können von heruntergeladenen Medien entfernt werden;)
Andras Deak

2
Builtins sind vollkommen in Ordnung, ich bezweifle jedoch, dass sie sehr beliebt sein werden.
Fehler

Antworten:


142

AutoIt , VB

Einführung

Dies ist eine Implementierung des von A. Criminisi, P. Perez (Cambridge Microsoft Research Ltd.) und K. Toyama (Microsoft) [X] entwickelten Algorithmus zum Entfernen von Objekten durch exemplarbasiertes Inpainting . Dieser Algorithmus zielt auf Bilder (und Videobilder) mit hohem Informationsgehalt ab und soll das Gleichgewicht zwischen struktureller Rekonstruktion und organischer Rekonstruktion herstellen. Die Absätze dieser Antwort enthalten Volltextzitate aus dem Originalpapier (da es nicht mehr offiziell verfügbar ist), um diese Antwort in sich geschlossener zu machen.

Der Algorithmus

Ziel : Ersetzen Sie einen ausgewählten ( maskierten ) Bereich (vorzugsweise ein visuell getrenntes Vordergrundobjekt) durch einen visuell plausiblen Hintergrund.

In früheren Arbeiten haben mehrere Forscher die Textur-Synthese als einen Weg betrachtet, große Bildbereiche mit "reinen" Texturen zu füllen - sich wiederholenden zweidimensionalen Texturmustern mit mäßiger Stochastizität. Dies basiert auf einer Vielzahl von Untersuchungen zur Textur-Synthese, die versuchen, die Textur ad infinitum zu replizieren , wenn eine kleine Quellprobe reiner Textur vorliegt. [1] [8] [9] [10] [11] [12] [14] [15] [16] [19] [22] .

So effektiv diese Techniken bei der Replikation konsistenter Texturen sind, so schwierig ist es, Löcher in Fotografien realer Szenen zu füllen, die häufig aus linearen Strukturen und zusammengesetzten Texturen bestehen - mehreren räumlich wechselwirkenden Texturen [23] . Das Hauptproblem besteht darin, dass Grenzen zwischen Bildbereichen ein komplexes Produkt gegenseitiger Einflüsse zwischen verschiedenen Texturen sind. Im Gegensatz zur zweidimensionalen Natur reiner Texturen bilden diese Grenzen eher eindimensionale oder lineare Bildstrukturen.

Bild-Inpainting- Techniken füllen Löcher in Bildern, indem sie lineare Strukturen ( in der Inpainting- Literatur als Isophoten bezeichnet ) durch Diffusion in die Zielregion ausbreiten . Sie sind von den partiellen Differentialgleichungen des physikalischen Wärmeflusses inspiriert und überzeugen als Wiederherstellungsalgorithmen. Ihr Nachteil ist, dass der Diffusionsprozess einige Unschärfen hervorruft, die sich bemerkbar machen.

Feige.  2

Der zu füllende Bereich, dh der Zielbereich, ist mit Ω bezeichnet, und seine Kontur ist mit δΩ bezeichnet. Die Kontur entwickelt sich im Verlauf des Algorithmus nach innen und wird daher auch als "Füllfront" bezeichnet. Der Source-Bereich Φ, der während des gesamten Algorithmus fest bleibt, liefert Proben, die beim Füllvorgang verwendet werden. Wir konzentrieren uns nun auf eine einzelne Iteration des Algorithmus, um zu zeigen, wie Struktur und Textur durch beispielbasierte Synthese angemessen gehandhabt werden. Angenommen, die quadratische Schablone Ψp ∈ Ω, die am Punkt p zentriert ist (Abb. 2b), soll ausgefüllt werden. Das am besten passende Sample aus der Quellregion stammt aus dem Patch ΨqΨ ∈ ∈, das den bereits ausgefüllten Teilen von filledp am ähnlichsten ist. Im Beispiel in Abb. 2b sehen wir, dass, wenn Ψp auf der Fortsetzung einer Bildkante liegt, Die wahrscheinlich besten Übereinstimmungen liegen entlang derselben (oder einer ähnlich gefärbten) Kante (z. B. Ψq 'und Ψq' 'in Abb. 2c). Alles, was erforderlich ist, um die Isophote nach innen zu verbreiten, ist eine einfache Übertragung des Musters vom Quell-Patch mit der besten Übereinstimmung (Abb. 2d). Beachten Sie, dass die Ausrichtung der Isophoten automatisch beibehalten wird. In der Figur hat die ausgebreitete Struktur trotz der Tatsache, dass die ursprüngliche Kante nicht orthogonal zur Zielkontur & dgr; & OHgr; ist, die gleiche Orientierung wie im Quellbereich beibehalten.

Implementierungs- und Algorithmusdetails

Die Funktionalität dieser Implementierung ist in einer ActiveX-COM-DLL gekapselt, die als Binärdatei aus dem Host-Programm entfernt und dann sofort durch Aufrufen des Inpainter durch IID aufgerufen wird. In diesem speziellen Fall ist die API in VisualBasic geschrieben und kann aus jeder COM-fähigen Sprache aufgerufen werden. Im folgenden Codeabschnitt wird die Binärdatei gelöscht:

Func deflate($e=DllStructCreate,$f=@ScriptDir&"\inpaint.dll")
    If FileExists($f) Then Return
    !! BINARY CODE OMITTED FOR SIZE REASONS !!
    $a=$e("byte a[13015]")
    DllCall("Crypt32.dll","bool","CryptStringToBinaryA","str",$_,"int",0,"int",1,"struct*",$a,"int*",13015,"ptr",0,"ptr",0)
    $_=$a.a
    $b=$e('byte a[13015]')
    $b.a=$_
    $c=$e("byte a[14848]")
    DllCall("ntdll.dll","int","RtlDecompressBuffer","int",2,"struct*",$c,"int",14848,"struct*",$b,"int",13015,"int*",0)
    $d=FileOpen(@ScriptDir&"\inpaint.dll",18)
    FileWrite($d,Binary($c.a))
    FileClose($d)
EndFunc

Die Bibliothek wird später mithilfe von CLSID und IID instanziiert:

Local $hInpaintLib = DllOpen("inpaint.dll")
Local $oInpaintLib = ObjCreate("{3D0C8F8D-D246-41D6-BC18-3CF18F283429}", "{2B0D9752-15E8-4B52-9569-F64A0B12FFC5}", $hInpaintLib)

Die Bibliothek akzeptiert ein GDIOBJECT-Handle, insbesondere einen DIBSection einer beliebigen GDI / + -Bitmap (Dateien, Streams usw.). Die angegebene Bilddatei wird geladen und auf eine leere Bitmap gezeichnet, die Scan0aus den eingegebenen Bildabmessungen erstellt wurde.

Die Eingabedatei für diese Implementierung ist ein beliebiges GDI / + -kompatibles Dateiformat, das maskierte Bilddaten enthält. Die Maske (n) ist (sind) ein oder mehrere gleichmäßig gefärbte Bereiche im Eingabebild. Der Benutzer gibt einen RGB-Farbwert für die Maske an. Nur Pixel mit genau diesem Farbwert werden abgeglichen. Die Standardmaskierungsfarbe ist grün (0, 255, 0). Alle maskierten Bereiche zusammen repräsentieren den zu entfernenden und zu füllenden Zielbereich Ω. Der Quellbereich Φ ist definiert als das gesamte Bild abzüglich des Zielbereichs (Φ = I - Ω).

Als nächstes muss, wie bei allen beispielbasierten Textursynthesen [10] , die Größe des Vorlagenfensters Ψ (auch " Scanradius " genannt) angegeben werden. Diese Implementierung bietet eine Standardfenstergröße von 6² Pixeln. In der Praxis muss der Benutzer jedoch festlegen, dass sie geringfügig größer ist als das größte unterscheidbare Texturelement oder „Texel“ in der Quellregion. Eine zusätzliche Modifikation des ursprünglichen Algorithmus ist die benutzerdefinierte " Blockgröße ", die den Bereich der Pixel bestimmt, die durch eine neue einheitliche Farbe ersetzt werden sollen. Dies erhöht die Geschwindigkeit und verringert die Qualität. Blockgrößen größer als 1px sind für extrem gleichmäßige Bereiche (Wasser, Sand, Fell usw.) vorgesehen. Ψ sollte jedoch bei max. .5x die Blockgröße (was je nach Maske unmöglich sein kann).

Damit der Algorithmus für 1-Bit-Bilder nicht blockiert wird, wird die Fenstergröße jedes Mal, wenn ein Bild mit weniger als 5 Farben empfangen wird, um das 10-fache vergrößert.

Sobald diese Parameter bestimmt sind, erfolgt der Rest des Bereichsfüllvorgangs vollständig automatisch. In unserem Algorithmus behält jedes Pixel einen Farbwert (oder "leer", wenn das Pixel nicht gefüllt ist) und einen Vertrauenswert bei, der unser Vertrauen in den Pixelwert widerspiegelt und der eingefroren wird, sobald ein Pixel gefüllt wurde. Im Verlauf des Algorithmus wird Patches entlang der Füllfront auch ein temporärer Prioritätswert zugewiesen, der die Reihenfolge bestimmt, in der sie gefüllt werden. Dann durchläuft unser Algorithmus die folgenden drei Schritte, bis alle Pixel gefüllt sind.

Schritt 1: Patch-Prioritäten berechnen

Die Füllreihenfolge ist entscheidend für die nichtparametrische Textur-Synthese [1] [6] [10] [13] . Bisher war der Standardfavorit die Zwiebelschälmethode, bei der die Zielregion von außen nach innen in konzentrischen Schichten synthetisiert wird. Unser Algorithmus führt diese Aufgabe durch einen Best-First-Füllungsalgorithmus aus, der vollständig von den Prioritätswerten abhängt, die jedem Patch auf der Füllungsfront zugewiesen sind. Die Prioritätsberechnung ist auf diejenigen Flecken ausgerichtet, die sich auf der Fortsetzung starker Kanten befinden und von Pixeln mit hoher Vertrauenswürdigkeit umgeben sind, wobei diese Pixel die Grenze sind, die durch den Wert -2 markiert ist. Der folgende Code berechnet die Prioritäten neu:

For j = m_top To m_bottom: Y = j * m_width: For i = m_left To m_right
    If m_mark(Y + i) = -2 Then m_pri(Y + i) = ComputeConfidence(i, j) * ComputeData(i, j)
Next i: Next j

Wenn ein Patch Ψp am Punkt p für einige p ∈ δΩ zentriert ist (siehe Abb. 3), ist seine Priorität P (p) definiert als das Produkt aus dem berechneten Vertrauen ( ComputeConfidence, oder C (p) ) und dem Datenterm ( ComputeData, oder D (p) ), wobei

, wo

| Ψp | ist die Fläche von Ψp, α ist ein Normalisierungsfaktor (z. B. α = 255 für ein typisches Graustufenbild), und np ist ein Einheitsvektor senkrecht zur Vorderseite δΩ im Punkt p. Die Priorität wird für jedes Randfeld mit unterschiedlichen Feldern für jedes Pixel an der Grenze der Zielregion berechnet.

Implementiert als

Private Function ComputeConfidence(ByVal i As Long, ByVal j As Long) As Double
    Dim confidence As Double
    Dim X, Y As Long

    For Y = IIf(j - Winsize > 0, j - Winsize, 0) To IIf(j + Winsize < m_height - 1, j + Winsize, m_height - 1): For X = IIf(i - Winsize > 0, i - Winsize, 0) To IIf(i + Winsize < m_width - 1, i + Winsize, m_width - 1)
        confidence = confidence + m_confid(Y * m_width + X)
    Next X: Next Y

    ComputeConfidence = confidence / ((Winsize * 2 + 1) * (Winsize * 2 + 1))
End Function

Private Function ComputeData(ByVal i As Long, ByVal j As Long) As Double
    Dim grad As CPOINT
    Dim temp As CPOINT
    Dim grad_T As CPOINT
    Dim result As Double
    Dim magnitude As Double
    Dim max As Double
    Dim X As Long
    Dim Y As Long
    Dim nn As CPOINT
    Dim Found As Boolean
    Dim Count, num As Long
    Dim neighbor_x(8) As Long
    Dim neighbor_y(8) As Long
    Dim record(8) As Long
    Dim n_x As Long
    Dim n_y As Long
    Dim tempL As Long
    Dim square As Double

    For Y = IIf(j - Winsize > 0, j - Winsize, 0) To IIf(j + Winsize < m_height - 1, j + Winsize, m_height - 1): For X = IIf(i - Winsize > 0, i - Winsize, 0) To IIf(i + Winsize < m_width - 1, i + Winsize, m_width - 1)
        If m_mark(Y * m_width + X) >= 0 Then
            Found = False
            Found = m_mark(Y * m_width + X + 1) < 0 Or m_mark(Y * m_width + X - 1) < 0 Or m_mark((Y + 1) * m_width + X) < 0 Or m_mark((Y - 1) * m_width + X) < 0
            If Found = False Then
                temp.X = IIf(X = 0, m_gray(Y * m_width + X + 1) - m_gray(Y * m_width + X), IIf(X = m_width - 1, m_gray(Y * m_width + X) - m_gray(Y * m_width + X - 1), (m_gray(Y * m_width + X + 1) - m_gray(Y * m_width + X - 1)) / 2#))
                temp.Y = IIf(Y = 0, m_gray((Y + 1) * m_width + X) - m_gray(Y * m_width + X), IIf(Y = m_height - 1, m_gray(Y * m_width + X) - m_gray((Y - 1) * m_width + X), (m_gray((Y + 1) * m_width + X) - m_gray((Y - 1) * m_width + X)) / 2#))
                magnitude = temp.X ^ 2 + temp.Y ^ 2
                If magnitude > max Then
                    grad.X = temp.X
                    grad.Y = temp.Y
                    max = magnitude
                End If
            End If
        End If
    Next X: Next Y

    grad_T.X = grad.Y
    grad_T.Y = -grad.X

    For Y = IIf(j - 1 > 0, j - 1, 0) To IIf(j + 1 < m_height - 1, j + 1, m_height - 1): For X = IIf(i - 1 > 0, i - 1, 0) To IIf(i + 1 < m_width - 1, i + 1, m_width - 1): Count = Count + 1
        If X <> i Or Y <> j Then
            If m_mark(Y * m_width + X) = -2 Then
                num = num + 1
                neighbor_x(num) = X
                neighbor_y(num) = Y
                record(num) = Count
            End If
        End If
    Next X: Next Y

    If num = 0 Or num = 1 Then
        ComputeData = Abs((0.6 * grad_T.X + 0.8 * grad_T.Y) / 255)
    Else
        n_x = neighbor_y(2) - neighbor_y(1)
        n_y = neighbor_x(2) - neighbor_x(1)
        square = CDbl(n_x ^ 2 + n_y ^ 2) ^ 0.5
        ComputeData = Abs((IIf(n_x = 0, 0, n_x / square) * grad_T.X + IIf(n_y = 0, 0, n_y / square) * grad_T.Y) / 255)
    End If
End Function

Der Konfidenzterm C (p) kann als Maß für die Menge an zuverlässiger Information angesehen werden, die das Pixel p umgibt. Die Absicht ist es, zuerst die Patches zu füllen, bei denen mehr Pixel bereits gefüllt sind, wobei Pixel bevorzugt werden, die zu einem frühen Zeitpunkt gefüllt wurden (oder die niemals Teil des Zielbereichs waren).

Dies beinhaltet automatisch die Bevorzugung bestimmter Formen entlang der Füllfront. Beispielsweise werden Flecken, die Ecken und dünne Ranken des Zielbereichs enthalten, in der Regel zuerst gefüllt, da sie von mehr Pixeln aus dem Originalbild umgeben sind. Diese Patches bieten zuverlässigere Informationen, mit denen verglichen werden kann. Umgekehrt werden Flecken an der Spitze von "Halbinseln" von gefüllten Pixeln, die in den Zielbereich hineinragen, eher beiseite gelegt, bis mehr der umgebenden Pixel ausgefüllt sind Erzwingt die gewünschte konzentrische Füllreihenfolge.

Mit fortschreitendem Füllen werden Pixel in den äußeren Schichten des Zielbereichs tendenziell durch höhere Vertrauenswertewerte gekennzeichnet und daher früher gefüllt; Pixel in der Mitte des Zielbereichs weisen geringere Konfidenzwerte auf. Der Datenterm D (p) ist eine Funktion der Stärke der Isophoten, die bei jeder Iteration auf die Front δΩ treffen. Dieser Begriff erhöht die Priorität eines Patches, in das ein Isophot "fließt". Dieser Faktor ist für unseren Algorithmus von grundlegender Bedeutung, da er dazu anregt, zunächst lineare Strukturen zu synthetisieren und sich daher sicher in der Zielregion auszubreiten. Gebrochene Linien verbinden sich und verwirklichen so das "Konnektivitätsprinzip" der Sehpsychologie [7] [17] .

Die Füllreihenfolge hängt von den Bildeigenschaften ab. Dies führt zu einem organischen Syntheseverfahren, bei dem das Risiko von Artefakten mit „gebrochener Struktur“ beseitigt wird und blockartige Artefakte ohne teuren Patch-Schneideschritt [9] oder verwacklungsinduzierenden Mischschritt [19 ] reduziert werden ] .

Schritt 2: Weitergabe von Textur- und Strukturinformationen

Nachdem alle Prioritäten an der Füllfront ( Grenze ) berechnet wurden, wird das Patch ΨpΨ mit der höchsten Priorität gefunden. Wir füllen es dann mit Daten, die aus der Quellregion Φ extrahiert wurden. Wir verbreiten die Bildtextur durch direktes Abtasten der Quellregion. Ähnlich wie bei [10] suchen wir in der Quellregion nach dem Patch, der am ähnlichsten zu ΨpΨ ist. Formal,

, wo

Der Abstand d (Ψa, Ψb) zwischen zwei generischen Feldern Ψa und Ψb wird einfach als die Summe der quadratischen Differenzen (SSD) der bereits gefüllten Pixel in den beiden Feldern definiert. In diesem Schritt wird keine weitere Analyse oder Manipulation ( insbesondere keine Unschärfe ) durchgeführt. Diese Berechnung läuft in der Hauptzyklusschleife und wird wie folgt implementiert:

Maximale Priorität erhalten:

For j = m_top To m_bottom: Jidx = j * m_width: For i = m_left To m_right
    If m_mark(Jidx + i) = -2 And m_pri(Jidx + i) > max_pri Then
        pri_x = i
        pri_y = j
        max_pri = m_pri(Jidx + i)
    End If
Next i: Next j

Den ähnlichsten Patch finden:

min = 99999999

For j = PatchT To PatchB: Jidx = j * m_width: For i = PatchL To PatchR
    If m_source(Jidx + i) Then
        sum = 0
        For iter_y = -Winsize To Winsize: target_y = pri_y + iter_y
            If target_y > 0 And target_y < m_height Then
                target_y = target_y * m_width: For iter_x = -Winsize To Winsize: target_x = pri_x + iter_x
                    If target_x > 0 And target_x < m_width Then
                        Tidx = target_y + target_x
                        If m_mark(Tidx) >= 0 Then
                            source_x = i + iter_x
                            source_y = j + iter_y
                            Sidx = source_y * m_width + source_x
                            temp_r = m_r(Tidx) - m_r(Sidx)
                            temp_g = m_g(Tidx) - m_g(Sidx)
                            temp_b = m_b(Tidx) - m_b(Sidx)
                            sum = sum + temp_r * temp_r + temp_g * temp_g + temp_b * temp_b
                        End If
                    End If
                Next iter_x
            End If
        Next iter_y

        If sum < min Then: min = sum: patch_x = i: patch_y = j
    End If
Next i: Next j

Schritt 3: Aktualisieren der Konfidenzwerte

Nachdem das Patch Ψpˆ mit neuen Pixelwerten gefüllt wurde, wird die Konfidenz C (p) in dem durch ΨpΨ begrenzten Bereich wie folgt aktualisiert:

Mit dieser einfachen Aktualisierungsregel können wir die relative Zuverlässigkeit von Patches auf der Füllfront ohne bildspezifische Parameter messen. Mit fortschreitender Füllung nehmen die Konfidenzwerte ab, was darauf hinweist, dass die Farbwerte von Pixeln nahe der Mitte des Zielbereichs unsicherer sind. Hier implementiert (zusammen mit allen anderen notwendigen Updates):

x0 = -Winsize
For iter_y = -Winsize To Winsize: For iter_x = -Winsize To Winsize
    x0 = patch_x + iter_x
    y0 = patch_y + iter_y
    x1 = pri_x + iter_x
    y1 = pri_y + iter_y
    X1idx = y1 * m_width + x1
    If m_mark(X1idx) < 0 Then
        X0idx = y0 * m_width + x0
        PicAr1(x1, y1) = m_color(X0idx)
        m_color(X1idx) = m_color(X0idx)
        m_r(X1idx) = m_r(X0idx)
        m_g(X1idx) = m_g(X0idx)
        m_b(X1idx) = m_b(X0idx)
        m_gray(X1idx) = CDbl((m_r(X0idx) * 3735 + m_g(X0idx) * 19267 + m_b(X0idx) * 9765) / 32767)
        m_confid(X1idx) = ComputeConfidence(pri_x, pri_y)
    End If
Next iter_x: Next iter_y

For Y = IIf(pri_y - Winsize - 2 > 0, pri_y - Winsize - 2, 0) To IIf(pri_y + Winsize + 2 < m_height - 1, pri_y + Winsize + 2, m_height - 1): Yidx = Y * m_width: For X = IIf(pri_x - Winsize - 2 > 0, pri_x - Winsize - 2, 0) To IIf(pri_x + Winsize + 2 < m_width - 1, pri_x + Winsize + 2, m_width - 1)
    m_mark(Yidx + X) = IIf(PicAr1(X, Y).rgbRed = MaskRed And PicAr1(X, Y).rgbgreen = MaskGreen And PicAr1(X, Y).rgbBlue = MaskBlue, -1, Source)
Next X: Next Y

For Y = IIf(pri_y - Winsize - 2 > 0, pri_y - Winsize - 2, 0) To IIf(pri_y + Winsize + 2 < m_height - 1, pri_y + Winsize + 2, m_height - 1): Yidx = Y * m_width: For X = IIf(pri_x - Winsize - 2 > 0, pri_x - Winsize - 2, 0) To IIf(pri_x + Winsize + 2 < m_width - 1, pri_x + Winsize + 2, m_width - 1)
    If m_mark(Yidx + X) = -1 Then
        Found = (Y = m_height - 1 Or Y = 0 Or X = 0 Or X = m_width - 1) Or m_mark(Yidx + X - 1) = Source Or m_mark(Yidx + X + 1) = Source Or m_mark((Y - 1) * m_width + X) = Source Or m_mark((Y + 1) * m_width + X) = Source
        If Found Then: Found = False: m_mark(Yidx + X) = -2
    End If
Next X: Next Y

For i = IIf(pri_y - Winsize - 3 > 0, pri_y - Winsize - 3, 0) To IIf(pri_y + Winsize + 3 < m_height - 1, pri_y + Winsize + 3, m_height - 1): Yidx = i * m_width: For j = IIf(pri_x - Winsize - 3 > 0, pri_x - Winsize - 3, 0) To IIf(pri_x + Winsize + 3 < m_width - 1, pri_x + Winsize + 3, m_width - 1)
    If m_mark(Yidx + j) = -2 Then m_pri(Yidx + j) = ComputeConfidence(j, i) * ComputeData(j, i)
Next j: Next i

Code vervollständigen

Hier ist der ausführbare Code, zusammen mit dem Quellcode der Bibliotheken als Kommentar.

Der Code wird von aufgerufen

inpaint(infile, outfile, blocksize, windowsize, r, g, b)

Beispiele sind in der Form von enthalten

;~ inpaint("gothic_in.png", "gothic_out.png")
;~ inpaint("starry_in.png", "starry_out.png")
;~ inpaint("scream_in.png", "scream_out.png")
;~ inpaint("mona_in.png", "mona_out.png")
;~ inpaint("maze_in.png", "maze_out.png")
;~ inpaint("checker_in.png", "checker_out.png")

Kommentieren Sie einfach das Beispiel aus, das Sie mit CTRL+ ausführen möchten Q.

Offizielle Testdateien

Dieser Algorithmus ist gemacht für jedes Bild angepasst werden. Daher sind die Standardwerte (und auch die Standardmasken) vollständig suboptimal. Die Standardwerte werden so gewählt, dass jede Probe in angemessener Zeit verarbeitet werden kann. Ich empfehle dringend, mit unregelmäßig geformten Masken und besseren Fenstergrößen zu spielen. Klicken Sie auf die Bilder, um sie zu vergrößern!

Schachbrett

Amerikanische Gotik

Matze

Mona Lisa

(Schreckliche Maske)

Schrei

Sternenhimmel

Beispiele aus der Praxis

Diese verwenden alle benutzerdefinierte handgezeichnete Masken.

Wenn Sie weitere interessante Bilder haben, die Sie gerne sehen würden, hinterlassen Sie einen Kommentar.

EBII-Verbesserungen

Es gibt mehrere Varianten von EBII, die von verschiedenen Forschern entwickelt wurden. AnkurKumar Patel machte mich mit seiner Sammlung von Artikeln [24] zu verschiedenen EBII-Verbesserungen auf sich aufmerksam.

Insbesondere in der Veröffentlichung " Verbesserter robuster Algorithmus für exemplarisches Inpainting von Bildern " [25] werden zwei Verbesserungen beim Abwägen der Prioritätswerte erwähnt.

Die Verbesserung

Die effektive Änderung befindet sich in Schritt 1 (siehe oben) des Algorithmus und erweitert den C (p) - und D (p) -Effekt auf die Prioritätsbewertung für dieses Pixel wie folgt:

In der oben angegebenen Formel für C und D sind und der Normalisierungsfaktor (z. B. α = 255), der Isophotenvektor und der Einheitsvektor orthogonal zur Front im Punkt p.

Des Weiteren,

Die Prioritätsfunktion ist definiert als die Gewichtungssumme des regulierten Vertrauensausdrucks C (p) und des neuen Datenausdrucks D (p) . Wenn α der Anpassungskoeffizient ist, ist die Erfüllung von 0Rp (p) wie folgt definiert:

Wobei α und β jeweils die Komponentengewichte des Vertrauens und der Datenausdrücke sind. Man beachte, dass α + β = 1 ist .

Objektive Wertung

Was jedoch wirklich interessant ist, ist, dass dieses Papier eine vorgeschlagene (und einfache!) Methode zur Bewertung der Leistung von EBII-Algorithmen enthält. Nehmen Sie dies jedoch mit einem Körnchen Salz, da dies eine Methode ist, die von den Autoren des Papiers selbst gewählt wurde, um die Wirksamkeit des vorgeschlagenen Varianzansatzes und die Verbesserung mehrerer Bilder zu überprüfen.

Die Ergebnisbewertung erfolgt durch Vergleichen des PSNR (Peak Signal to Noise Ratio [26] ) zwischen dem wiederhergestellten Bild und dem Originalbild. Im Allgemeinen ist die Ähnlichkeit des reparierten Bildes mit dem Original umso größer, je höher der PSNR-Wert ist. Die Gleichung zur Berechnung des PSNR lautet wie folgt:

Dies sind die atemberaubenden 2 (zwei!) Realen Testbilder, die sie verwendet haben:

Das Fazit ist so enttäuschend wie die Qualität des Papiers. Es zeigt sehr wenig Besserung. Die Hauptsache hier ist eine mögliche Objektbewertungsmethode für diese Art von Herausforderung (und andere Bildreparaturherausforderungen):

+-------+---------------+----------+
| Image | EBII Original | Improved |
+-------+---------------+----------+
|     1 |       52.9556 |  53.7890 |
|     2 |       53.9098 |  53.8989 |
+-------+---------------+----------+

Meh.

Forschung zu tun

(EBII-spezifisch)

a) Vorverarbeitung

Alles läuft auf das "Magic Erase" -Prinzip hinaus, dass der Algorithmus für alles "nur funktionieren" soll. Meine naive Lösung hierfür ist eine farbbasierte Verstärkung (siehe oben), aber es gibt bessere Möglichkeiten. Ich denke daran, das geometrische Mittel aller verfolgbaren Texel zu erkennen, um die Fenstergröße automatisch anzupassen und die Stempelgröße (auch meine Verbesserung) von der Texel- und Gesamtbildauflösung abhängig zu machen . Hier muss geforscht werden.

b) Nachbearbeitung

Die ursprünglichen Autoren haben bereits großartige Arbeit geleistet, um alle in Betracht kommenden Nachbearbeitungsfilter zu entlarven. Heute habe ich etwas anderes ausprobiert, inspiriert von der immer unheimlichen Mona Lisa (danke undergroundmonorail). Wenn Sie nur den unbemalten Bereich nehmen und eine neue Maske auf alle seltsamen Farbblöcke anwenden und diese in einen Despeckling-Algorithmus einspeisen, erhalten Sie ein nahezu perfektes Ergebnis. Ich werde das vielleicht irgendwann in Zukunft untersuchen.


[X] - Objektentfernung durch exemplarisches Bemalen von A. Criminisi, P. Perez, K. Toyama
[1] - M. Ashikhmin. Natürliche Texturen synthetisieren. In Proc. ACM Symp. on Interactive 3D Graphics, S. 217–226, Research Triangle Park, NC, März 2001.
[5] - M. Bertalmio, L. Vese, G. Sapiro und S. Osher. Gleichzeitiges Struktur- und Texturbild-Inpainting. Erscheinen, 2002
[6] - R. Bornard, E. Lecan, L. Laborelli und JH. Chenot. Fehlende Datenkorrektur in Standbildern und Bildsequenzen. In ACM Multimedia, Frankreich, Dezember 2002.
[7] - TF Chan und J. Shen. Nicht-Textur-Inpainting durch krümmungsgetriebene Diffusionen (CDD). J. Visual Comm. Image Rep., 4 (12), 2001.
[8] - JS de Bonet. Multiresolution-Sampling-Verfahren zur Analyse und Synthese von Texturbildern. In Proc. ACM Conf. Comp. Graphics (SIGGRAPH), Band 31, S. 361–368, 1997.
[9] - A. Efros und WT Freeman. Bildquilten zur Textur Synthese und Übertragung. In Proc. ACM Conf. Comp. Graphics (SIGGRAPH), S. 341–346, Eugene Fiume, August 2001.
[10] - A. Efros und T. Leung. Textursynthese durch nicht parametrisches Sampling. In Proc. ICCV, S. 1033–1038, Kerkyra, Griechenland, September 1999.
[11] - WT Freeman, EC Pasztor und OT Carmichael. Low Level Vision lernen. Int. J. Computer Vision, 40 (1): 25–47, 2000.
[12] - D. Garber. Computermodelle für die Texturanalyse und -synthese. Doktorarbeit, Univ. von Südkalifornien, USA, 1981.
[13] - P. Harrison. Ein nicht hierarchisches Verfahren zur Neusynthese komplexer Texturen. In Proc. Int. Conf. Central Europe Comp. Grafik, Visua. und Comp. Vision, Pilsen, Tschechische Republik, Februar 2001.
[14] - DJ Heeger und JR Bergen. Pyramidenbasierte Texturanalyse / -synthese. In Proc. ACM Conf. Comp. Graphics (SIGGRAPH), Band 29, S. 229–233, Los Angeles, CA, 1995.
[15] - A. Hertzmann, C. Jacobs, N. Oliver, B. Curless und D. Salesin. Bildanalogien. In Proc. ACM Conf. Comp. Graphics (SIGGRAPH), Eugene Fiume, August 2001.
[16] - H. Igehy und L. Pereira. Bildersetzung durch Textur-Synthese. In Proc. Int. Conf. Image Processing, S. III: 186–190, 1997.
[17] - G. Kanizsa. Organisation in Vision. Praeger, New York, 1979.
[19] - L. Liang, C. Liu, Y.-Q. Xu, B. Guo und H.-Y. Shum. Echtzeit-Textur-Synthese durch patchbasiertes Sampling. In ACM Transactions on Graphics, 2001.
[22] - L.-W. Wey und M. Levoy. Schnelle Textursynthese mit baumstrukturierter Vektorquantisierung. In Proc. ACM Conf. Comp. Graphics (SIGGRAPH), 2000.
[23] - A. Zalesny, V. Ferrari, G. Caenen und L. van Gool. Parallele Komposittextursynthese. In Texture 2002 Workshop - (in Verbindung mit ECCV02), Kopenhagen, Dänemark, Juni 2002.
[24] - AkurKumar Patel, Gujarat Technological University, Informatik und Ingenieurwesen
[25] - Verbesserter robuster Algorithmus für exemplarisches Inpainting von Bildern
[26] - Wikipedia, Spitzen-Signal-Rausch-Verhältnis


30
Das ist großartig . Die Sternennacht ist so gut. Trotzdem, diese Mona Lisa ...
Hannes Karppila

8
Lassen Sie mich zuerst sagen "Oh mein Gott, das ist unglaublich". Zweitens: Ich habe bereits zu einer anderen Frage gesagt, dass "Mona Lisa ein bisschen SCP-Scheiße ist", aber diese Eule sieht tatsächlich so aus, als könnte sie im SCP-Wiki erscheinen.
Undergroundmonorail

3
Könnten die zitierten Absätze, die Sie erwähnen, zu Anführungszeichen gemacht werden?
Trichoplax

1
@trichoplax Es gibt geringfügige Änderungen in fast jedem Satz, es handelt sich nicht um exakte Anführungszeichen. Betrachten Sie die Beschreibung des Algorithmus genauso wie die der Organisation. Papier, es sei denn, es steht Modifikation oder Code. Ich möchte die Formatierung nicht mehr
überladen

2
Wenn ich versuchte, etwas in meinen Träumen sehr genau zu betrachten, werden die Dinge manchmal genau so.
Jimmy23013

45

Matlab

Dies ist ein einfacher Interpolationsansatz. Die Idee besteht darin, zunächst zu spiegeln, was sich auf jeder Seite des Patches befindet. Dann werden diese Spiegelbildpixel interpoliert, um wie weit sie von der entsprechenden Kante entfernt sind:

Der knifflige Teil bestand darin, schöne Interpolationsgewichte zu finden. Nach einigem Herumspielen habe ich eine rationale Funktion gefunden, die an allen Kanten Null ist, mit Ausnahme derjenigen, an der wir gespiegelt haben. Dies wird dann zur Glättung durch ein Polynom dritten Grades transformiert:

Dieser einfache Ansatz eignet sich überraschend gut für "natürliche" Bilder, aber sobald Sie mit scharfen Kanten konfrontiert werden, ist das Spiel vorbei. Im Beispiel der amerikanischen Gotik stimmen die Spitzen der Heugabel gut mit dem Pixelraster überein, wodurch es recht schön aussieht, aber sonst wäre es viel schlimmer gewesen.

Also hier die Ergebnisse:

Und zum Schluss der Code:

imgfile= 'filename.png';
maskfile = [imgfile(1:end-4),'_mask.png'];
img = double(imread(imgfile));
mask = rgb2gray(imread(maskfile));
%% read mask
xmin = find(sum(mask,1),1,'first');
xmax = find(sum(mask,1),1,'last');
ymin = find(sum(mask,2),1,'first');
ymax = find(sum(mask,2),1,'last');
%% weight transformation functiosn
third = @(x)-2* x.^3 + 3* x.^2;
f=@(x)third(x);
w=@(x,y)y.*(x-1).*(y-1)./( (x+y).*(x+1-y));

for x=xmin:xmax
    for y=ymin:ymax
        %Left Right Up Down;
        P = [img(y,xmin-(x-xmin)-1,:);img(y,xmax+(xmax-x)+1,:);img(ymin-(y-ymin)-1,x,:);img(ymax+(ymax-y)+1,x,:)];
        % normalize coordinates
        rx = (x-xmin)/(xmax-xmin); 
        ry = (y-ymin)/(ymax-ymin);
        % calculate the weights
        W = [w(rx,ry),w(1-rx,ry),w(ry,rx),w(1-ry,rx)]';
        W = f(W);
        W(isnan(W))=1;
        img(y,x,:) = sum(bsxfun(@times,P,W),1)/sum(W); 
    end
end
imshow(img/255);
imwrite(img/255,[imgfile(1:end-4),'_out.png']);

10
Mona Lisa macht mich fertig.
Andras Deak

46
Ḿ̳̜͇͓͠o̢̓ǹ̰͎̣͙a̤̩̖̞̝ͧ̈ͤͤ lisa
mınxomaτ

8
Diese Mona Lisa ist eine SCP-Scheiße
Undergroundmonorail

1
Das karierte Bild sieht meiner Meinung nach wirklich cool aus.
ETHproductions

1
Es würde mich nicht wundern, wenn Sie damit Ihre eigene Herausforderung gewinnen würden. Das ist eine wirklich schöne Lösung.
Alex A.

25

Mathematica

Dies nutzt die InpaintFunktion von Mathematica . Da Mathematica selbst das ganze schwere Heben übernimmt, ist dies ein Community-Wiki.

inPaint(unten) ist eine einfache Anpassung von Inpaint. Für farbige Gemälde / Fotos wird die Standardeinstellung "TextureSynthesis" verwendet. Wenn festgestellt wird, dass das Bild schwarzweiß ist (da die Bilddaten des Bildes mit den Bilddaten der Binärform des Bildes identisch sind), wird das Bild binarisiert und der Patch "TotalVariation" angewendet. Die IfKlausel gilt entweder für das Bild Binarizeoder Identityfür das Bild. (Die IdentityFunktion gibt ihr Argument unverändert zurück.)

inPaint[picture_, mask_] :=  
 If[bw = ImageData@Rasterize[Binarize[picture]] == ImageData[picture], Binarize, Identity]@
  Inpaint[picture, mask, Method -> If[bw, "TotalVariation", "TextureSynthesis"]]

Das Bild und die Maske werden als Argumente für eingegeben inPaint. Partitionund Griddienen lediglich Formatierungszwecken.

Eingang

Die Ausgänge wurden gepatcht. Es gab keine Retusche der Bilder danach inPaint.

Ausgabe


4
Es mag ein Zufall sein, aber ich bin erstaunt über die Leistung des Labyrinths!
Fehler

1
@flawr Ich würde so etwas einwerfen, um mich mit dieser Lösung herumzuschlagen ;) (Wer weiß? Diese Schwarz-Weißen sind wirklich verblüffend.)
Andras Deak

17
Ich denke nicht, dass dies ein Community-Wiki sein sollte.
Dennis

Ja, Lawr Inpaintscheint auf Symmetrien über das gesamte Schwarz-Weiß-Bild zu achten. - DavidC vor 9 Stunden
DavidC

Sind Sie sicher, dass der Schwarz-Weiß-Algorithmus nirgendwo Ziegen opfert? Wie - zum Teufel - errät es die zentrale Struktur des Bildes, wenn alles maskiert ist?
Andras Deak

18

Python 2 und PIL

Mit diesem Programm werden Kopien der Regionen Nord, Süd, Ost und West gemischt, um Ersatzpixel zu erstellen, die Farben, Texturen und Schattierungen aus der lokalen Bildregion verwenden.

Das Beispiel gibt Folgendes aus:

Der Code findet zuerst den Begrenzungsrahmen für das Patch. Dann berechnet es für jedes zu erzeugende Pixel die Farbe jedes Kanals (RGB) basierend auf der gewichteten Summe der 4 umgebenden Regionen.

import sys
from PIL import Image

infile, maskfile, outfile = sys.argv[1:4]
imageobj = Image.open(infile)
maskobj = Image.open(maskfile)
image = imageobj.load()
mask = maskobj.load()

assert imageobj.size == maskobj.size
W, H = imageobj.size
pixels = [(x,y) for x in range(W) for y in range(H)]
whitepart = [xy for xy in pixels if sum(mask[xy]) > 230*3]
xmin = min(x for x,y in whitepart)
xmax = max(x for x,y in whitepart)
ymin = min(y for x,y in whitepart)
ymax = max(y for x,y in whitepart)
xspan = xmax - xmin + 1
yspan = ymax - ymin + 1

def mkcolor(channel):
    value = image[(xmin-dx, y)][channel] * 0.5*(xspan - dx)/xspan
    value += image[(xmax+1 + xspan - dx, y)][channel] * 0.5*dx/xspan
    value += image[(x, ymin-dy)][channel] * 0.5*(yspan - dy)/yspan
    value += image[(x, ymax+1 + yspan - dy)][channel] * 0.5*dy/yspan
    return int(value)

for dx in range(xspan):
    for dy in range(yspan):
        x = xmin + dx
        y = ymin + dy
        image[(x, y)] = (mkcolor(0), mkcolor(1), mkcolor(2))

imageobj.save(outfile)

3
Diese Mona Lisa ist auch erschreckend! Sind alle Mona Lisas in dieser Herausforderung dazu verdammt, beängstigend zu sein?
Undergroundmonorail

@undergroundmonorail Ich denke, computergenerierte zufällige Gesichter kommen direkt aus den Tiefen des unheimlichen Tals .
Andras Deak

Woher bekommen Sie PIL?
Elliot A.

@ElliotA. Ich verstehe, dass der eigentliche PIL tot ist, aber es war Open Source und so lebt er unter dem Namen "Pillow" weiter. Wenn Sie "Python Kissen" googeln, sollten Sie es finden.
Undergroundmonorail

13

Python 3, PIL

Dieses Programm verwendet den Sobel-Operator und zeichnet darauf basierend Linien auf das Bild.

Der Sobel-Operator ermittelt den Winkel jeder Kante, sodass alle Kanten, die in den unbekannten Bereich hineinragen, fortgesetzt werden sollten.

from PIL import Image, ImageFilter, ImageDraw
import time
im=Image.open('2.png')
im1=Image.open('2 map.png')
a=list(im.getdata())
b=list(im1.getdata())
size=list(im.size)
'''
def dist(a,b):
    d=0
    for x in range(0,3):
        d+=(a[x]-b[x])**2
    return(d**0.5)
#'''
C=[]
d=[]
y=[]
for x in range(0,len(a)):
    if(b[x][0]==255):
        C.append((0,0,0))
    else:
        y=(a[x][0],a[x][1],a[x][2])
        C.append(y)
im1.putdata(C)
k=(-1,0,1,-2,0,2,-1,0,1)
k1=(-1,-2,-1,0,0,0,1,2,1)
ix=im.filter(ImageFilter.Kernel((3,3),k,1,128))
iy=im.filter(ImageFilter.Kernel((3,3),k1,1,128))
ix1=list(ix.getdata())
iy1=list(iy.getdata())
d=[]
im2=Image.new('RGB',size)
draw=ImageDraw.Draw(im2)
c=list(C)
Length=0
for L in range(100,0,-10):
    for x in range(0,size[0]):
        for y in range(0,size[1]):
            n=x+(size[0]*y)
            if(c[n]!=(0,0,0)):
                w=(((iy1[n][0]+iy1[n][1]+iy1[n][2])//3)-128)
                z=(((ix1[n][0]+ix1[n][1]+ix1[n][2])//3)-128)
                Length=(w**2+z**2)**0.5
                if Length==0:
                    w+=1
                    z+=1
                Length=(w**2+z**2)**0.5
                w/=(Length/L)
                z/=(Length/L)
                w=int(w)
                z=int(z)
                draw.line(((x,y,w+x,z+y)),c[n])

d=list(im2.getdata())
S=[]
d1=[]
A=d[0]
for x in range(0,size[0]):
    for y in range(0,size[1]):
        n=y+(size[1]*x)
        nx=y+(size[1]*x)-1
        ny=y+(size[1]*x)-size[0]
        if d[n]==(0,0,0):
            S=[0,0,0]
            for z in range(0,3):
                S[z]=(d[nx][z]+d[ny][z])//2
            #print(S)
            d1.append(tuple(S))
        else:
            d1.append(tuple(d[n]))
d=list(d1)
im2.putdata(d)
#im2=im2.filter(ImageFilter.GaussianBlur(radius=0.5))
d=im2.getdata()
f=[]
#'''
for v in range(0,len(a)):
    if(b[v][0]*b[v][1]*b[v][2]!=0):
        f.append(d[v])
    else:
        f.append(C[v])
#'''
im1.putdata(f)
im1.save('pic.png')

In der Zwischenzeit sind hier die Beispielbilder.

Bildbeschreibung hier eingeben

Bildbeschreibung hier eingeben

Bildbeschreibung hier eingeben

Mona Lisa Mona Lisa Ḿ͠oḾ͠a ̾̇Lisa Ḿ͠o̢̎̓̀ǹ̰͎̣a̢̎̓̀ǹ̰͎̣ ̣̖̠̮̘̹̠̾̇ͣLisa Ḿ̳̜͇͓͠oḾ̳̜͇͓͠a̢̎̓̀ǹ̰͎̣͙ ̣̖̠̮̘̹̠̾̇ͣḶ̖̠̮̘̹̠̾̇ͣi͉̻̭͌ş̠͔̏̋̀ạ̫͕͎ͨͮͪ̐͡ͅ

Bildbeschreibung hier eingeben Der Bereich im obigen Bild ist glatt wie ein Kaktus

Es ist nicht sehr gut mit konstanter Farbe.

Bildbeschreibung hier eingeben

Bildbeschreibung hier eingeben


1
Oh, und könnten Sie bitte die S / W-Testfälle hinzufügen?
Fehler

2
Sieht in der Sternennacht wirklich gut aus.
SuperJedi224

1
Wow, das sieht jetzt unglaublich aus! Die Patches sind immer noch auffällig, aber eine großartige neue Idee! Mein bisheriger Favorit =)
flawr

8
+1 für "Mona Lisa Mona Lisa Ḿ͠oḾ͠a ̾̇Lisa Ḿ͠o̢̎̓̀ǹ̰͎̣a̢̎̓̀ǹ̰͎̣ ̣̖̠̮̘̹̠̾̇ͣLisa Ḿ̳̜͇͓͠oḾ̳̜͇͓͠a̤̩̖̞̝ͧ̈ͤͤ ̣̖̠̮̘̹̠̾̇ͣḶ̖̠̮̘̹̠̾̇ͣi͉̻̭͌ş̠͔̏̋̀ạ̫͕͎ͨͮͪ̐͡ͅ"
mbomb007

2
Sie gewinnen den "gruseligsten Mona Lisa" -Wettbewerb, IMO. 0_o
DLosc

8

Python 2

Einfaches Python-Skript, das Patches mithilfe von Werten aus Pixeln außerhalb der Lücke erstellt. Es nimmt Farbwerte vom Ende der Pixelzeile und -spalte und berechnet den gewichteten Durchschnitt anhand des Abstands von diesen Pixeln.

Output ist nicht so hübsch, aber es ist Kunst .

img1 img2 img3 img4 img5 img6

Und dann Code:

IMGN = "6"

IMGFILE = "images/img%s.png" % (IMGN,)
MASKFILE = "images/img%s_mask.png" % (IMGN,)

BLUR = 5


def getp(img,pos):
    return img.get_at(pos)[:3]
def setp(img,pos,color):
    img.set_at(pos, map(int, color))

def pixelavg(L):
    return map(int, [sum([i[q] for i in L])/float(len(L)) for q in [0,1,2]])
def pixelavg_weighted(L, WL):   # note: "inverse" weights. More weight => less weight
    # colors [sum, max]
    color_data = [[0, 0], [0, 0], [0, 0]]
    for color,weight in zip(L, WL):
        for i in [0, 1, 2]: # r,g,b
            color_data[i][0] += inv_w_approx(weight) * color[i]
            color_data[i][1] += inv_w_approx(weight) * 255
    return [255*(float(s)/m) for s,m in color_data]
def inv_w_approx(x):
    return (1.0/(x+1e-10))

import pygame
image = pygame.image.load(IMGFILE)
mask = pygame.image.load(MASKFILE)

size = image.get_size()
assert(size == mask.get_size())

# get square from mask
min_pos = None
max_pos = [0, 0]
for x in range(size[0]):
    for y in range(size[1]):
        if getp(mask, [x, y]) == (255, 255, 255):
            if min_pos == None:
                min_pos = [x, y]
            max_pos = [x, y]
if not min_pos:
    exit("Error: no mask found.")
# patch area info
patch_position = min_pos[:]
patch_size = [max_pos[0]-min_pos[0], max_pos[1]-min_pos[1]]

# remove pixels from orginal image (fill black)
for dx in range(patch_size[0]):
    for dy in range(patch_size[1]):
        setp(image, [patch_position[0]+dx, patch_position[1]+dy], [0, 0, 0])

# create patch
patch = pygame.Surface(patch_size)

# take pixels around the patch
top = [getp(image, [patch_position[0]+dx, patch_position[1]-1]) for dx in range(patch_size[0])]
bottom = [getp(image, [patch_position[0]+dx, patch_position[1]+patch_size[1]+1]) for dx in range(patch_size[0])]
left = [getp(image, [patch_position[0]-1, patch_position[1]+dy]) for dy in range(patch_size[1])]
right = [getp(image, [patch_position[0]+patch_size[0]+1, patch_position[1]+dy]) for dy in range(patch_size[1])]

cpixels = top+left+right+bottom

# set area to average color around it
average = [sum([q[i] for q in cpixels])/float(len(cpixels)) for i in [0, 1, 2]]

for dx in range(patch_size[0]):
    for dy in range(patch_size[1]):
        setp(patch, [dx, dy], average)

# create new pixels
for dx in range(patch_size[0]):
    for dy in range(patch_size[1]):
        setp(patch, [dx, dy], pixelavg_weighted([top[dx], bottom[dx], left[dy], right[dy]], [dy, patch_size[1]-dy, dx, patch_size[0]-dx]))

# apply patch
for dx in range(patch_size[0]):
    for dy in range(patch_size[1]):
        setp(image, [patch_position[0]+dx, patch_position[1]+dy], getp(patch, [dx, dy]))

# blur patch?
for r in range(BLUR):
    for dx in range(patch_size[0]):
        for dy in range(patch_size[1]):
            around = []
            for ddx in [-1,0,1]:
                for ddy in [-1,0,1]:
                    around.append(getp(image, [patch_position[0]+dx+ddx, patch_position[1]+dy+ddy]))
            setp(patch, [dx, dy], pixelavg(around))

    # apply blurred patch
    for dx in range(patch_size[0]):
        for dy in range(patch_size[1]):
            setp(image, [patch_position[0]+dx, patch_position[1]+dy], getp(patch, [dx, dy]))

# save result
pygame.image.save(image, "result.png")

Im Moment sehen Sie diese horizontalen / vertikalen Streifen, vielleicht können Sie sie verbessern, indem Sie andere Richtungen einbeziehen!
Fehler

Eigentlich habe ich es versucht, aber ich konnte keine guten Ergebnisse
erzielen

19
Schließlich eine Mona Lisa, die mich nicht zu Tode erschreckt, sondern stattdessen wie ein verhafteter Mörder aussieht.
Andras Deak

6

Mathematica

Inpaint

Es ist einfach so, dass Mathematica eine eingebaute Funktion hat, die genau diese Aufgabe erfüllt, und ich meine genau :

Inpaint[image, region]

  • retuschiert Teile davon image, die Nicht-Null-Elementen in entsprechen region.

Standardmäßig wird eine "Best-Fit-Textur-Synthesemethode unter Verwendung von Zufallsstichproben" verwendet, die auf den Gemälden gute Ergebnisse liefert, für das Labyrinth und das Schachbrett jedoch schlechte Ergebnisse:

Bildbeschreibung hier eingeben Bildbeschreibung hier eingeben Bildbeschreibung hier eingeben Bildbeschreibung hier eingeben Bildbeschreibung hier eingeben Bildbeschreibung hier eingeben

Das Herumspielen mit den Einstellungen hat mir nicht bei allen Bildern zu einer Qualitätssteigerung geführt, daher habe ich nur die Standardeinstellungen verwendet (um Bytes zu sparen - das ist codegolf.seimmerhin!).


23
" Es ist einfach so, dass Mathematica eine eingebaute Funktion hat " ... Überraschung, Überraschung;)
Andras Deak

Für das Labyrinth und das Schachbrett ist es besser, die "TotalVariation" -Methode zusammen mit Binarize(um graue Flecken zu beseitigen) zu verwenden. Versuchen Sie methods = {"TextureSynthesis", "Diffusion", "FastMarching", "NavierStokes", "TotalVariation"};g[pic_, mask_] := Join[{Labeled[Framed@pic, "Original"]}, Labeled[ Binarize@Inpaint[pic, mask, Method -> #], #] & /@ methods]
Folgendes

@DavidC Ich habe die anderen Methoden ausprobiert, TextureSynthesissieht aber nur auf den Gemälden gut aus. und ich glaube nicht, dass wir unsere Einstellungen für jeden einzelnen Testfall anpassen dürfen. (Wenn wir könnten, dann könnten wir den fehlenden Teil trivial als "Einstellung"
liefern

Die Labyrinth- und Schachbrett-Ergebnisse sind für mich wirklich rätselhaft. Warum ist Mathematicas Rekonstruktion der fehlenden Region so unregelmäßig und asymmetrisch?
David Zhang

Dadurch wird automatisch erkannt, ob ein Bild schwarzweiß ist, und es werden die entsprechenden Anpassungen vorgenommen (Binärdateien und "TotalVariation" -Methode). inPaint[picture_, mask_] := If[bw = ImageData@Rasterize[Binarize[picture]] == ImageData[picture], Binarize, Identity]@ Inpaint[picture, mask, Method -> If[bw, "TotalVariation", "TextureSynthesis"]]
DavidC

5

Python3

Diese Antwort setzt die Idee in der Arbeit "Deep Image Prior" um von Ulyanov et al. (CVPR 2018) In diesem Artikel untersuchten sie die Idee, dass die Art und Weise, in der gut funktionierende neuronale Netze für die Bildverarbeitung entworfen werden, genau unserer Vorstellung entspricht, wie ein natürliches Bild aussehen sollte (die "vorherige" Verteilung).

Sie schlugen eine Methode vor, die zum Aufmalen sowie zum Entfernen von Rauschen und Artefakten verwendet werden kann und bei der nur das angegebene Bild ohne Training für andere Daten verwendet wird. Das eigentliche Konzept ist recht einfach: Das Netz wird darauf trainiert, das gewünschte Bild (für ein bestimmtes zufälliges Rauschen als Eingabe) auszugeben, indem nur die Fehler außerhalb einer bestimmten Maske bestraft werden. Wenn Sie Störgeräusche entfernen möchten, brauchen Sie nichts zu maskieren, sondern müssen nur zu Beginn des Trainings aufhören.

Zum Übermalen maskieren Sie den Teil, den Sie übermalen und trainieren möchten, bis zur Konvergenz. Es ist sicherlich nicht auf dem neuesten Stand der Technik, aber ich wollte es dennoch veröffentlichen, um es zu versuchen und hier zu veröffentlichen, aufgrund der Einfachheit der Idee und der immer noch bemerkenswerten Leistung. In meinen Experimenten hat sich das Inpainting größerer Patches nicht so gut bewährt, aber für kleinere Segmente können die Ergebnisse viel überzeugender sein.

Ich habe dies mit der beliebten U-Net-Architektur von jaxony on github implementiert . Den Code zum Trainieren und Verarbeiten der Bilder finden Sie unten.

Ausbildung

Dies ist eine Visualisierung des Trainingsprozesses. Jeder Frame enthält eine bestimmte Anzahl von Iterationen:

Beispiele

Code

Beachten Sie, dass es auf einer CPU Stunden dauern kann, bis nur ein einzelnes Image ausgeführt wird, während eine gute cuda-fähige GPU viel weniger Zeit in Anspruch nimmt.

import torch
import numpy as np
unet = __import__('unet-pytorch')
import PIL.ImageOps
#specify device (cpu/cuda)
device = "cpu"
#specify file and size
file = 'mona'
size = 512 #pad to this size (no smaller than original image), must be divisible by 2^5
img_pil = PIL.Image.open(file +'.png').convert('RGB')
mask_pil = PIL.Image.open(file +'-mask.png').convert('RGB')

net = unet.UNet(num_classes=3, in_channels=32, depth=6, start_filts=64).to(device)
h,w = img_pil.size
pad = (0, 0, size - h, size - w)
img = PIL.ImageOps.expand(img_pil, border=pad)
img = torch.Tensor(np.array(img).transpose([2, 0, 1])[None, :, :, :].astype(np.double)).to(device)
mask = PIL.ImageOps.expand(mask_pil, border=pad)
mask = torch.Tensor((np.array(mask)==0).transpose([2, 0, 1])[None, 0:3, :, :].astype(np.double)).to(device)
mean = img.mean()
std = img.std()
img = (img - mean)/std
optimizer = torch.optim.Adam(net.parameters(), lr=0.0001)
criterion = torch.nn.MSELoss()
input = torch.rand((1, 32, size, size)).to(device)
for it in range(5000):
    if it == 1000:
        optimizer.param_groups[0]['lr'] = 0.00003
    out = net(input)
    loss = criterion(out * mask, img * mask)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
out = out.detach().cpu().numpy()[0].transpose([1,2,0])*std.item() + mean.item()
out = np.clip(out, 0, 255).astype(np.uint8)[0:w, 0:h, :]
mask_np = (np.array(mask_pil) > 0).astype(np.uint8)
img_np = np.array(img_pil)
inpaint = (img_np * (1-mask_np) + mask_np * out).astype(np.uint8)
PIL.Image.fromarray(inpaint).save('./{}_inpainted.png'.format(file))

verwendet Deep Image Prior etwas anderes als U-Netze? scheint , wie würden sie bessere Ergebnisse erzielen
ASCII-only

Haben Sie es auch mit dem Code von Deep Image Prior versucht
ASCII

@ Nur ASCII Sie geben in dem Papier an, dass sie zwar hauptsächlich das U-Net verwenden, aber ich kann die genauen Parameter, die sie verwendet haben, nicht finden. Sie könnten ein Netz mit einer größeren Kapazität verwendet haben. Ich hatte nur einen Computer mit sehr begrenzter Leistung. Also musste ich Parameter auswählen, die immer noch in den Speicher passen und die nicht zu lange zum Trainieren brauchten. Ich bin nicht sicher, wie lange es genau gedauert hat, aber auf dem Computer, den ich verwendet habe (nur mit einer CPU), dauern diese Bilder mehrere Tage. (Wenn Sie eine Ersatz-Cuda-fähige GPU haben, lassen Sie es mich wissen :)
31.

Ich vermute auch, dass aufgrund des Designs des Netzwerks rechteckige Masken auch nicht ideal sind (und kleinere Masken würden wahrscheinlich auch besser aussehen), wenn man zum Beispiel die ersten paar Bilder mit den letzten beiden vergleicht (die keine rechteckigen Masken verwenden) .
31.

4

Python mit OpenCV

OpenCV hat eine Funktion namens Inpaint. Es gibt zwei Arten von Inpainting, ich werde die Fast Marching-Methode verwenden. Der Algorithmus funktioniert laut Dokumentation folgendermaßen:

Stellen Sie sich einen Bereich im Bild vor, der unbemalt sein soll. Der Algorithmus beginnt an der Grenze dieser Region und geht in die Region hinein, wobei zuerst alles in die Grenze gefüllt wird. Es braucht eine kleine Nachbarschaft um das Pixel in der Nachbarschaft, um übermalt zu werden. Dieses Pixel wird durch die normalisierte gewichtete Summe aller bekannten Pixel in der Nachbarschaft ersetzt. Die Auswahl der Gewichte ist eine wichtige Angelegenheit. Pixel, die in der Nähe des Punktes, in der Nähe der Normalen der Grenze und auf den Grenzkonturen liegen, werden stärker gewichtet. Sobald ein Pixel gestrichen ist, bewegt es sich mit der Fast Marching-Methode zum nächsten Pixel. FMM stellt sicher, dass diese Pixel in der Nähe der bekannten Pixel zuerst übermalt werden, so dass es wie eine manuelle heuristische Operation funktioniert.

Hier ist der Code *:

import numpy as np
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('gothic.jpg')
b,g,r = cv2.split(img)
img2 = cv2.merge([r,g,b])
mask = cv2.imread('mask.jpg',0)
dst = cv2.inpaint(img2,mask,3,cv2.INPAINT_TELEA)
(h, w) = dst.shape[:2]
center = (w / 2, h / 2)
# rotate the image by 180 degrees
M = cv2.getRotationMatrix2D(center, 180, 1.0)
rotated = cv2.warpAffine(dst, M, (w, h))
plt.imshow(rotated)

Beachten Sie, wie ich die BGR aus Plotgründen in RGB konvertiere. Auch ich drehe es. Hier sind die Ergebnisse:

gotisch

sternenklare Nacht Schrei noch eine gruselige mona lisa!

Mona Lisa kehrt zurück!

Linie 1

Kontrolleur

Wie Sie sehen können, ist es nicht das beste mit den zwei Farben.


Mona Lisa bekam ein Facelift
Conor O'Brien

3

Java

Ein Ansatz zur Farbmittelung. Kann wohl verbessert werden.

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Scanner;

import javax.imageio.ImageIO;


public class ImagePatcher{
    public static void main(String[]args) throws Exception{
        Scanner in=new Scanner(System.in);
        int white=Color.WHITE.getRGB();
        int black=Color.BLACK.getRGB();
        BufferedImage image=ImageIO.read(new File(in.nextLine())),mask=ImageIO.read(new File(in.nextLine()));
        assert(image.getWidth()==mask.getWidth()&&image.getHeight()==mask.getHeight());
        boolean bool=true;
        while(bool){
            bool=false;
        for(int x=0;x<image.getWidth();x+=2){
            for(int y=0;y<image.getHeight();y+=2){
                if(mask.getRGB(x,y)!=white)continue;
                int r=0,g=0,b=0,n=0;
                for(int dx=-1;dx<=1;dx++){
                    if(x+dx<0)continue;
                    if(x+dx>=image.getWidth())continue;
                    for(int dy=-1;dy<=1;dy++){
                        if(y+dy<0)continue;
                        if(y+dy>=image.getHeight())continue;
                        if(mask.getRGB(x+dx,y+dy)==white)continue;
                        Color c=new Color(image.getRGB(x+dx,y+dy));
                        r+=c.getRed();
                        g+=c.getGreen();
                        b+=c.getBlue();
                        n++;
                    }
                }
                if(n==0){bool=true;continue;}
                Color c=n>0?new Color(r/n,g/n,b/n):new Color(100,100,100);
                image.setRGB(x,y,c.getRGB());
                mask.setRGB(x, y, black);
            }           
        }
        for(int x=0;x<image.getWidth();x+=2){
            for(int y=1;y<image.getHeight();y+=2){
                if(mask.getRGB(x,y)!=white)continue;
                int r=0,g=0,b=0,n=0;
                for(int dx=-1;dx<=1;dx++){
                    if(x+dx<0)continue;
                    if(x+dx>=image.getWidth())continue;
                    for(int dy=-1;dy<=1;dy++){
                        if(y+dy<0)continue;
                        if(y+dy>=image.getHeight())continue;
                        if(mask.getRGB(x+dx,y+dy)==white)continue;
                        Color c=new Color(image.getRGB(x+dx,y+dy));
                        r+=c.getRed();
                        g+=c.getGreen();
                        b+=c.getBlue();
                        n++;
                    }
                }
                if(n==0){bool=true;continue;}
                Color c=n>0?new Color(r/n,g/n,b/n):new Color(100,100,100);
                image.setRGB(x,y,c.getRGB());
                mask.setRGB(x, y, black);
            }
        }
        for(int x=1;x<image.getWidth();x+=2){
            for(int y=0;y<image.getHeight();y+=2){
                if(mask.getRGB(x,y)!=white)continue;
                int r=0,g=0,b=0,n=0;
                for(int dx=-1;dx<=1;dx++){
                    if(x+dx<0)continue;
                    if(x+dx>=image.getWidth())continue;
                    for(int dy=-1;dy<=1;dy++){
                        if(y+dy<0)continue;
                        if(y+dy>=image.getHeight())continue;
                        if(mask.getRGB(x+dx,y+dy)==white)continue;
                        Color c=new Color(image.getRGB(x+dx,y+dy));
                        r+=c.getRed();
                        g+=c.getGreen();
                        b+=c.getBlue();
                        n++;
                    }
                }
                if(n==0){bool=true;continue;}
                Color c=n>0?new Color(r/n,g/n,b/n):new Color(100,100,100);
                image.setRGB(x,y,c.getRGB());
                mask.setRGB(x, y, black);
            }           
        }
        for(int x=1;x<image.getWidth();x+=2){
            for(int y=1;y<image.getHeight();y+=2){
                if(mask.getRGB(x,y)!=white)continue;
                int r=0,g=0,b=0,n=0;
                for(int dx=-1;dx<=1;dx++){
                    if(x+dx<0)continue;
                    if(x+dx>=image.getWidth())continue;
                    for(int dy=-1;dy<=1;dy++){
                        if(y+dy<0)continue;
                        if(y+dy>=image.getHeight())continue;
                        if(mask.getRGB(x+dx,y+dy)==white)continue;
                        Color c=new Color(image.getRGB(x+dx,y+dy));
                        r+=c.getRed();
                        g+=c.getGreen();
                        b+=c.getBlue();
                        n++;
                    }
                }
                if(n==0){bool=true;continue;}
                Color c=n>0?new Color(r/n,g/n,b/n):new Color(100,100,100);
                image.setRGB(x,y,c.getRGB());
                mask.setRGB(x, y, black);
            }
        }
        };
        ImageIO.write(image, "png", new File("output.png"));
    }
}

Ergebnisse:

Bildbeschreibung hier eingeben Bildbeschreibung hier eingeben Bildbeschreibung hier eingeben Bildbeschreibung hier eingeben Bildbeschreibung hier eingeben Bildbeschreibung hier eingeben


2
Warum bekommen Sie immer Linien in diesem bestimmten Winkel? Die oberen linken Ecken scheinen relativ gut zu passen, während der untere rechte Teil überhaupt nicht passt.
Fehler

Ich denke, das hat mit der Art und Weise zu tun, in der ich mich durch die Region bewege. Ich werde das wahrscheinlich irgendwann ändern.
SuperJedi224

Es sieht immer so aus, als gäbe es Trapezoide.
ericw31415

@ ericw31415 Es ist ein Artefakt der Iterationsreihenfolge.
SuperJedi224
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.