Łatka obrazu


114

W popularnym oprogramowaniu do edycji obrazów dostępna jest funkcja, która łata (termin używany w przetwarzaniu obrazu jest malowaniem, jak wskazał @ mınxomaτ.) Wybrany obszar obrazu, na podstawie informacji poza tą łatką. I robi całkiem dobrą robotę, biorąc pod uwagę, że to tylko program. Jako człowiek czasami widzisz, że coś jest nie tak, ale jeśli ściśniesz oczy lub po prostu rzucisz okiem, łatka wydaje się całkiem dobrze wypełniać lukę .

przykład popularnego oprogramowania do edycji obrazu

Wyzwanie

Biorąc pod uwagę obraz i maskę określającą prostokątny obszar obrazu, należy go załatać (również jako obraz lub inny preferowany format), twój program powinien spróbować wypełnić określony obszar łatką, która próbuje wtopić się w resztę Obraz. Program nie może użyć informacji o oryginalnym obrazie, który był w określonym obszarze.

Możesz założyć, że łatka zawsze ma przynajmniej szerokość od boków i wysokość od góry i dołu obrazu. Oznacza to, że maksymalny obszar łatki wynosi 1/9 całego obrazu.

Dodaj krótki opis działania algorytmu.

Głosowanie

Głosujący proszeni są o ocenę skuteczności algorytmów i głosowanie odpowiednio.

Kilka sugestii dotyczących sposobu oceniania: (Jeszcze raz dziękuję @ mınxomaτ za więcej kryteriów).

  • Jeśli zmrużysz oczy, a obraz wygląda dobrze?
  • Czy możesz dokładnie powiedzieć, gdzie jest łatka?
  • Jak dobrze kontynuowane są struktury i tekstury z tła obrazu i okolic?
  • Ile pikseli bezpańskich fałszywych kolorów zawiera edytowany obszar?
  • Czy są jakieś jednolicie kolorowe plamy / bloki w obszarze, które wydają się tam nie należeć?
  • Czy edytowany obszar ma jakieś drastyczne zmiany koloru / kontrastu lub jasności w porównaniu z resztą obrazu?

Kryterium ważności

Aby przesłanie było prawidłowe, obraz wyjściowy musi dokładnie pasować do obrazu wejściowego poza określonym obszarem.

Przypadek testowy

Po lewej stronie obraz źródłowy, po prawej odpowiednia maska:


1
Czy możemy zaakceptować wprowadzanie maski jako argumenty tekstowe (np. inpaint.exe left top width height img.jpg)?
mınxomaτ

1
Jasne, format wejściowy / wyjściowy nie jest tak ważny, ponieważ jest to konkurs popularności, w którym ważna jest przede wszystkim wydajność Twojego algorytmu .
flawr

24
To bardzo praktyczne wyzwanie. Możliwe, że wyniki będą lepsze niż istniejące algorytmy stosowane w GIMP i innym oprogramowaniu do edycji obrazów open source. Fortuna, sława i chwała mogą być twoje!
Sparr

6
@Sparr i wreszcie brzydkie znaki wodne można usunąć z pobranych mediów;)
Andras Deak,

2
Wbudowane są w porządku, wątpię jednak, czy będą bardzo popularne.
flawr

Odpowiedzi:


142

AutoIt , VB

Wprowadzenie

Jest to implementacja algorytmu Removal Object by Exemplar-Based Inpainting opracowanego przez A. Criminisi, P. Pereza (Cambridge Microsoft Research Ltd.) i K. Toyama (Microsoft) [X] . Algorytm ten jest ukierunkowany na obrazy informacyjne (i klatki wideo) i ma na celu zachowanie równowagi między rekonstrukcją strukturalną a rekonstrukcją organiczną. Akapity tej odpowiedzi zawierają pełne cytaty z oryginalnego artykułu (ponieważ nie jest już oficjalnie dostępny), aby uczynić tę odpowiedź bardziej samodzielną.

Algorytm

Cel : Zamień wybrane ( zamaskowane ) obszar (najlepiej wizualnie oddzielony obiekt na pierwszym planie) na wizualnie wiarygodne tła.

W poprzedniej pracy kilku badaczy rozważało syntezę tekstur jako sposób na wypełnienie dużych obszarów obrazu „czystymi” teksturami - powtarzającymi się dwuwymiarowymi wzorami tekstur o umiarkowanej stochastyczności. Opiera się to na dużej liczbie badań syntezy tekstur, które mają na celu odtworzenie tekstury ad infinitum , biorąc pod uwagę małą próbkę źródłową czystej tekstury [1] [8] [9] [10] [11] [12] [14] [15] [16] [19] [22] .

Choć techniki te są skuteczne w odtwarzaniu spójnej tekstury, mają trudności z wypełnianiem dziur w fotografiach scen z prawdziwego świata, które często składają się ze struktur liniowych i tekstur kompozytowych - wiele tekstur oddziałuje przestrzennie [23] . Głównym problemem jest to, że granice między regionami obrazu są złożonym produktem wzajemnych wpływów między różnymi teksturami. W przeciwieństwie do dwuwymiarowej natury czystych tekstur, granice te tworzą coś, co można by uznać za bardziej jednowymiarowe lub liniowe struktury obrazu.

Obraz inpainting techniki wypełnić otwory obrazów propagacja struktur liniowych (zwane izoluksom w inpainting literatura) do obszaru docelowego przez dyfuzję. Są inspirowane częściowymi równaniami różniczkowymi fizycznego przepływu ciepła i działają przekonująco jako algorytmy przywracania. Ich wadą jest to, że proces dyfuzji wprowadza pewne rozmycie, co jest zauważalne.

Figa.  2)

Obszar do wypełnienia, tj. Obszar docelowy jest oznaczony przez Ω, a jego kontur jest oznaczony δΩ. Kontur ewoluuje do wewnątrz wraz z postępem algorytmu, dlatego też nazywamy go „frontem wypełnienia”. Region źródłowy Φ, który pozostaje stały w całym algorytmie, zapewnia próbki używane w procesie napełniania. Skupiamy się teraz na jednej iteracji algorytmu, aby pokazać, w jaki sposób struktura i tekstura są odpowiednio obsługiwane przez syntezę opartą na przykładach. Załóżmy, że kwadratowy szablon Ψp ∈ Ω wyśrodkowany w punkcie p (rys. 2b) ma zostać wypełniony. Próbka najlepiej pasująca z regionu źródłowego pochodzi z łatki Ψqˆ ∈ Φ, która jest najbardziej podobna do tych części, które są już wypełnione Ψp. W przykładzie na ryc. 2b, widzimy, że jeśli liesp leży na kontynuacji krawędzi obrazu, najprawdopodobniej najlepsze dopasowania będą znajdować się wzdłuż tej samej (lub w podobnym kolorze) krawędzi (np. Ψq 'i Ψq' 'na ryc. 2c). Wszystko, co jest potrzebne do rozpropagowania izotopu do wewnątrz, to proste przeniesienie wzoru z najlepiej dopasowanej łatki źródłowej (ryc. 2d). Zauważ, że orientacja izofotyczna jest automatycznie zachowywana. Na rysunku, pomimo faktu, że pierwotna krawędź nie jest prostopadła do konturu docelowego δΩ, propagowana struktura zachowała taką samą orientację jak w obszarze źródłowym.

Szczegóły implementacji i algorytmu

Funkcjonalność tej implementacji jest zawarta w bibliotece DLL ActiveX COM, która jest usuwana z programu hosta jako plik binarny, a następnie wywoływana w locie przez wywołanie inpainter przez IID. W tym konkretnym przypadku interfejs API jest napisany w języku VisualBasic i można go wywoływać z dowolnego języka obsługującego COM. Następująca sekcja kodu upuszcza plik binarny:

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

Biblioteka jest później tworzona za pomocą CLSID i IID:

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

Biblioteka akceptuje uchwyt GDIOBJECT, w szczególności DIBSection dowolnej bitmapy GDI / + (pliki, strumienie itp.). Podany plik obrazu jest ładowany i rysowany na pustej bitmapie zbudowanej zScan0 wymiarów obrazu wejściowego.

Plikem wejściowym dla tej implementacji jest dowolny format pliku zgodny z GDI / + zawierający zamaskowane dane obrazu. Maski (a) stanowi jeden lub więcej kolorowych obszarów równomiernie na obraz wejściowej. Użytkownik podaje wartość koloru RGB maski, tylko piksele o dokładnie takiej wartości koloru zostaną dopasowane. Domyślnym kolorem maskowania jest zielony (0, 255, 0). Wszystkie zamaskowane regiony razem reprezentują region docelowy Ω, który należy usunąć i wypełnić. Region źródłowy Φ jest zdefiniowany jako cały obraz minus region docelowy (Φ = I Ω).

Następnie, podobnie jak w przypadku wszystkich przykładowych syntez tekstur [10] , należy określić rozmiar okna szablonu Ψ (inaczej „ promień skanu ”). Ta implementacja zapewnia domyślny rozmiar okna wynoszący 6² pikseli, ale w praktyce wymaga od użytkownika, aby był nieco większy niż największy rozpoznawalny element tekstury lub „texel” w obszarze źródłowym. Dodatkową modyfikacją oryginalnego algorytmu jest definiowany przez użytkownika „ rozmiar bloku ”, który określa obszar pikseli do zastąpienia nowym jednolitym kolorem. Zwiększa to prędkość i obniża jakość. Bloki o rozmiarach większych niż 1px są przeznaczone do stosowania z bardzo jednolitymi obszarami (woda, piasek, futro itp.), Jednak Ψ należy utrzymywać na maks. .5x rozmiar bloku (co może być niemożliwe w zależności od maski).

Aby nie blokować algorytmu na obrazach 1-bitowych, za każdym razem, gdy odbierany jest obraz o mniej niż 5 kolorach, rozmiar okna jest powiększany 10-krotnie.

Po określeniu tych parametrów pozostała część procesu wypełniania regionu jest całkowicie automatyczna. W naszym algorytmie każdy piksel zachowuje wartość koloru (lub „pustą”, jeśli piksel jest niewypełniony) oraz wartość pewności, która odzwierciedla naszą pewność co do wartości piksela i która jest zamrożona po wypełnieniu piksela. W trakcie algorytmu łatki wzdłuż czoła wypełnienia otrzymują również tymczasową wartość priorytetu, która określa kolejność ich wypełniania. Następnie nasz algorytm iteruje następujące trzy kroki, aż wszystkie piksele zostaną wypełnione.

Krok 1: Obliczanie priorytetów łatek

Kolejność wypełniania ma kluczowe znaczenie dla nieparametrycznej syntezy tekstur [1] [6] [10] [13] . Do tej pory domyślnym faworytem była metoda „skórki cebuli”, w której region docelowy jest syntetyzowany od zewnątrz do wewnątrz, w koncentrycznych warstwach. Nasz algorytm wykonuje to zadanie za pomocą algorytmu najlepszego wypełniania, który zależy całkowicie od wartości priorytetów przypisanych do każdej łaty na froncie wypełnienia. Obliczenia priorytetów są ukierunkowane na te łatki, które są kontynuacją mocnych krawędzi i które są otoczone pikselami o wysokiej ufności, piksele te są granicą, oznaczoną wartością -2. Poniższy kod ponownie oblicza priorytety:

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

Biorąc pod uwagę łatkę Ψp wyśrodkowaną w punkcie p dla niektórych p ∈ δΩ (patrz rys. 3), jej priorytet P (p) jest zdefiniowany jako iloczyn obliczonej ufności ( ComputeConfidencelub C (p) ) i terminu danych ( ComputeData, lub D (p) ), gdzie

, gdzie

| Ψp | jest obszarem Ψp, α jest współczynnikiem normalizacji (np. α = 255 dla typowego obrazu poziomu szarości), a np jest wektorem jednostkowym prostopadłym do przodu frontΩ w punkcie p. Priorytet jest obliczany dla każdej łatki granicznej, z odrębnymi łatami dla każdego piksela na granicy regionu docelowego.

Wdrożony jako

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

Określenie ufności C (p) może być traktowane jako miara ilości wiarygodnych informacji otaczających piksel p. Chodzi o to, aby najpierw wypełnić te łaty, w których więcej ich pikseli jest już wypełnionych, z dodatkową preferencją dla pikseli, które zostały wcześniej wypełnione (lub które nigdy nie były częścią regionu docelowego).

To automatycznie uwzględnia preferencje względem określonych kształtów wzdłuż frontu wypełnienia. Na przykład łatki zawierające narożniki i cienkie wąsy regionu docelowego będą zwykle wypełnione jako pierwsze, ponieważ są otoczone większą liczbą pikseli od oryginalnego obrazu. Te łatki zapewniają bardziej wiarygodne informacje, z którymi można się równać. I odwrotnie, łaty na końcu „półwyspu” wypełnionych pikseli wystających w obszar docelowy będą miały tendencję do odkładania na bok, dopóki więcej otaczających pikseli nie zostanie wypełnionych. Na grubszym poziomie termin C (p) z (1) w przybliżeniu wymusza pożądaną koncentryczną kolejność napełniania.

W miarę wypełniania piksele w zewnętrznych warstwach obszaru docelowego będą miały tendencję do charakteryzowania się większymi wartościami ufności, a zatem będą wypełniane wcześniej; piksele w środku regionu docelowego będą miały mniejsze wartości ufności. Termin danych D (p) jest funkcją siły izotopów uderzających w przednią δΩ przy każdej iteracji. Termin ten zwiększa priorytet łatki, do której „wpływa” izofhota. Ten czynnik ma fundamentalne znaczenie w naszym algorytmie, ponieważ zachęca najpierw do syntezy struktur liniowych, a zatem do bezpiecznego propagowania w regionie docelowym. Linie przerywane mają tendencję do łączenia się, realizując w ten sposób „zasadę łączności” psychologii wzroku [7] [17] .

Kolejność wypełniania zależy od właściwości obrazu, co skutkuje procesem syntezy organicznej, który eliminuje ryzyko artefaktów „zepsutej struktury”, a także zmniejsza artefakty blokowe bez kosztownego etapu wycinania łaty [9] lub etapu mieszania wywołującego rozmycie [19 ] .

Krok 2: Propagowanie informacji o teksturze i strukturze

Po obliczeniu wszystkich priorytetów na froncie wypełnienia ( granicy ) znajduje się łatka Ψpˆ o najwyższym priorytecie. Następnie wypełniamy je danymi wyodrębnionymi z regionu źródłowego Φ. Propagujemy teksturę obrazu przez bezpośrednie próbkowanie regionu źródłowego. Podobnie do [10] , szukamy w regionie źródłowym tej łatki, która jest najbardziej podobna do Ψpˆ. Formalnie,

, gdzie

odległość d (Ψa, Ψb) między dwoma rodzajowymi łatami Ψa i Ψb jest po prostu zdefiniowana jako suma kwadratów różnic (SSD) już wypełnionych pikseli w dwóch łatach. W tym kroku nie są wykonywane dalsze analizy ani manipulacje ( szczególnie brak rozmycia ). Obliczenia są wykonywane w głównej pętli cyklu i są realizowane w następujący sposób:

Uzyskiwanie maksymalnego priorytetu:

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

Znajdowanie najbardziej podobnej łatki:

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

Krok 3: Aktualizacja wartości ufności

Po wypełnieniu łatki Ψpˆ nowymi wartościami pikseli, pewność C (p) jest aktualizowana w obszarze wyznaczonym przez Ψpˆ w następujący sposób:

Ta prosta reguła aktualizacji pozwala nam zmierzyć względną pewność łatek na froncie wypełnienia, bez parametrów specyficznych dla obrazu. W miarę postępu wypełniania zanikają wartości ufności, wskazując, że jesteśmy mniej pewni wartości kolorów pikseli w pobliżu środka regionu docelowego. Zaimplementowano tutaj (wraz ze wszystkimi innymi niezbędnymi aktualizacjami):

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

Kompletny kod

Oto działający kod wraz z kodem źródłowym bibliotek jako komentarzem.

Kod jest wywoływany przez

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

Przykłady są zawarte w postaci

;~ 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")

po prostu odkomentuj przykład, który chcesz uruchomić za pomocą CTRL+ Q.

Oficjalne pliki testowe

Ten algorytm jest wykonany być dostosowane do każdego obrazu. Dlatego wartości domyślne (a także maski domyślne) są całkowicie nieoptymalne. Wartości domyślne są wybierane, aby każda próbka mogła zostać przetworzona w rozsądnym czasie. Bardzo polecam bawić się maskami o nieregularnych kształtach i lepszymi rozmiarami okien. Kliknij obrazy, aby powiększyć!

Szachownica

amerykański gotyk

Labirynt

Mona Lisa

(straszna maska)

Krzyk

Gwiaździsty

Przykłady ze świata rzeczywistego

Wszystkie używają niestandardowych ręcznie rysowanych masek.

Jeśli masz inne ciekawe zdjęcia, które chcesz zobaczyć, zostaw komentarz.

Ulepszenia EBII

Istnieje wiele wariantów EBII, stworzonych przez różnych badaczy. AnkurKumar Patel zwrócił moją uwagę swoją kolekcją dokumentów [24] na temat różnych ulepszeń EBII.

W szczególności w dokumencie „ Improved Solid Solid Algorytm dla przykładowego obrazu opartego na malowaniu[25] wspomniano o dwóch ulepszeniach dotyczących ważenia wartości priorytetów.

Poprawa

Skuteczna modyfikacja znajduje się w kroku 1 (patrz wyżej) algorytmu i rozszerza efekt C (p) i D (p) na ocenę priorytetu dla tego piksela za pomocą:

We wzorze na C i D podanym powyżej i są odpowiednio współczynnikiem normalizacji (np. Α = 255), wektorem izofotycznym i wektorem jednostkowym prostopadłym do przodu w punkcie p.

Dalej,

Funkcja priorytetu jest zdefiniowana jako suma wagi znormalizowanego składnika ufności C (p) i nowego składnika danych D (p) . Gdzie α jest współczynnikiem korygującym, definiuje się spełnienie 0Rp (p):

Gdzie α i β są odpowiednio składowymi wagami ufności i warunków danych. Zauważ, że α + β = 1 .

Punktacja obiektywna

Najbardziej interesujące jest to, że ten artykuł zawiera proponowaną (i prostą!) Metodę oceny wydajności, jeśli algorytmy EBII. Weź to jednak z odrobiną soli, ponieważ jest to metoda wybrana przez samych autorów artykułu w celu zweryfikowania skuteczności proponowanego podejścia do wariancji i poprawy na kilku obrazach.

Oceny wyników dokonuje się przez porównanie PSNR (szczytowy stosunek sygnału do szumu [26] ) między przywróconym obrazem a obrazem oryginalnym. Zasadniczo im wyższa wartość PSNR, tym większe podobieństwo naprawionego obrazu do oryginału. Równanie do obliczenia PSNR jest następujące:

Są to oszałamiające 2 (dwa!) Zdjęcia testowe, które wykorzystali:

Wniosek jest równie rozczarowujący jak jakość samego papieru. Pokazuje bardzo małą poprawę. Najważniejsze tutaj jest możliwa metoda punktacji obiektów dla tego rodzaju wyzwań (i innych wyzwań związanych z naprawą obrazu):

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

Meh

Badania do zrobienia

(Specyficzny dla EBII)

a) Przetwarzanie wstępne

Wszystko sprowadza się do zasady „Magic Erase”, zgodnie z którą algorytm „po prostu działa” na wszystko. Moim naiwnym rozwiązaniem jest wzmocnienie oparte na kolorach (patrz wyżej), ale są lepsze sposoby. Zastanawiam się nad rozpoznaniem średniej geometrycznej wszystkich identyfikowalnych tekstur, aby automatycznie dostosować rozmiar okna i uzależnić rozmiar znaczka (także moją poprawę) od texela i całego obrazu. Należy tu przeprowadzić badania.

b) Przetwarzanie końcowe

Oryginalni autorzy wykonali już świetną robotę, obalając wszystkie filtry przetwarzania końcowego, które przychodzą na myśl. Dzisiaj spróbowałem czegoś innego, zainspirowanego zawsze niesamowitą Moną Lisą (dzięki podziemnej kolejce). Jeśli weźmiesz tylko pomalowany region i nałożysz nową maskę na wszystkie dziwne bloki koloru i wprowadzisz ją do algorytmu usuwania śladów, otrzymasz prawie idealny wynik. Mogę to zbadać w przyszłości.


[X] - Usuwanie obiektów przez malowanie oparte na przykładach A. Criminisi, P. Perez, K. Toyama
[1] - M. Ashikhmin. Syntezowanie naturalnych tekstur. W Proc. ACM Symp. on Interactive 3D Graphics, s. 217–226, Research Triangle Park, NC, marzec 2001.
[5] - M. Bertalmio, L. Vese, G. Sapiro i S. Osher. Jednoczesne malowanie struktur i tekstur. pojawi się, 2002
[6] - R. Bornard, E. Lecan, L. Laborelli i JH. Chenot. Brak korekcji danych w zdjęciach i sekwencjach obrazów. W ACM Multimedia, Francja, grudzień 2002.
[7] - TF Chan i J. Shen. Malowanie bez tekstur przez dyfuzje kierowane krzywizną (CDD). J. Visual Comm. Image Rep., 4 (12), 2001.
[8] - JS de Bonet. Procedura próbkowania w wielu rozdzielczościach do analizy i syntezy obrazów tekstur. W Proc. ACM Conf. Komp. Grafika (SIGGRAPH), tom 31, str. 361–368, 1997.
[9] - A. Efros i WT Freeman. Pikowanie obrazu do syntezy i przesyłania tekstur. W Proc. ACM Conf. Komp. Graphics (SIGGRAPH), s. 341–346, Eugene Fiume, sierpień 2001.
[10] - A. Efros i T. Leung. Synteza tekstury przez próbkowanie nieparametryczne. W Proc. ICCV, ss. 1033–1038, Kerkyra, Grecja, wrzesień 1999.
[11] - WT Freeman, EC Pasztor i OT Carmichael. Uczenie się wizji niskiego poziomu. Int. J. Computer Vision, 40 (1): 25–47, 2000.
[12] - D. Garber. Modele obliczeniowe do analizy i syntezy tekstur. Praca doktorska, Univ. z Południowej Kalifornii, USA, 1981.
[13] - P. Harrison. Niehierarchiczna procedura ponownej syntezy złożonej tekstury. W Proc. Int. Konf. Central Europe Comp. Grafika, Visua. i komp. Vision, Pilzno, Republika Czeska, luty 2001.
[14] - DJ Heeger i JR Bergen. Analiza / synteza tekstur oparta na piramidzie. W Proc. ACM Conf. Komp. Graphics (SIGGRAPH), tom 29, str. 229–233, Los Angeles, Kalifornia, 1995.
[15] - A. Hertzmann, C. Jacobs, N. Oliver, B. Curless i D. Salesin. Analogie obrazu. W Proc. ACM Conf. Komp. Grafika (SIGGRAPH), Eugene Fiume, sierpień 2001.
[16] - H. Igehy i L. Pereira. Zastępowanie obrazu poprzez syntezę tekstur. W Proc. Int. Konf. Image Processing, s. III: 186–190, 1997.
[17] - G. Kanizsa. Organizacja w wizji. Praeger, Nowy Jork, 1979.
[19] - L. Liang, C. Liu, Y.-Q. Xu, B. Guo i H.-Y. Shum. Synteza tekstur w czasie rzeczywistym przez próbkowanie na podstawie łatek. W ACM Transactions on Graphics, 2001.
[22] - L.-W. Wey i M. Levoy. Szybka synteza tekstur za pomocą kwantowania wektorów o strukturze drzewa. W Proc. ACM Conf. Komp. Graphics (SIGGRAPH), 2000.
[23] - A. Zalesny, V. Ferrari, G. Caenen i L. van Gool. Równoległa synteza tekstur kompozytowych. W warsztacie Texture 2002 - (w połączeniu z ECCV02), Kopenhaga, Dania, czerwiec 2002.
[24] - AkurKumar Patel, Uniwersytet Technologiczny w Gujarat, Informatyka i Inżynieria
[25] - Udoskonalony solidny algorytm do przykładowego malowania obrazów
[26] - Wikipedia, stosunek szczytowego sygnału do szumu


30
To jest niesamowite . Gwiaździsta noc jest taka dobra. Mimo to, Mona Lisa ...
Hannes Karppila

8
Najpierw pozwól mi powiedzieć „o mój Boże, to jest niesamowite”. Po drugie: skomentowałem już „To Mona Lisa to gówno SCP” w innym pytaniu tutaj, ale ta sowa faktycznie wygląda jak coś, co mogłoby pojawić się na wiki SCP.
undergroundmonorail

3
Czy cytowane akapity mogą być blokami cytatów?
trichoplax

1
@trichoplax Istnieją niewielkie modyfikacje w prawie każdym zdaniu, nie są to dokładne cytaty. Rozważ opis algorytmu taki sam jak organizacji. papier, z wyjątkiem sytuacji, gdy mówi modyfikacja lub kod Nie chcę już zaśmiecać formatowania :)
mınxomaτ

2
Kiedy próbowałem spojrzeć na coś bardzo uważnie w moich snach, czasami rzeczy wyglądają dokładnie tak.
jimmy23013

45

Matlab

Jest to proste podejście interpolacyjne. Chodzi o to, aby najpierw wykonać kopię lustrzaną tego, co jest po każdej stronie łatki. Następnie te lustrzane odbicie pikseli są interpolowane według odległości od odpowiedniej krawędzi:

Trudną częścią było znalezienie ładnych wag interpolacyjnych. Po krótkiej zabawie wymyśliłem racjonalną funkcję, która jest zerowa na wszystkich krawędziach oprócz tej, w której dublowaliśmy się. Jest to następnie przekształcane przez wielomian trzeciego stopnia w celu wygładzenia:

To proste podejście zaskakująco dobrze sprawdza się na „naturalnych” obrazach, ale gdy tylko natrafisz na ostre krawędzie, gra się kończy. W amerykańskim gotyckim przykładzie kolce widelca siana ładnie układają się z siatką pikseli, co sprawia, że ​​wygląda całkiem ładnie, ale inaczej byłoby znacznie gorzej.

Oto wyniki:

I na koniec kod:

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 mnie przeraża.
Andras Deak

46
Ḿ̳̜͇͓͠o̢̎̓̀ǹ̰͎̣͙a̤̩̖̞̝ͧ̈ͤͤ ̣̖̠̮̘̹̠̾̇ͣL͉̻̭͌i̛̥͕̱͋͌ş̠͔̏̋̀ạ̫͕͎ͨͮͪ̐͡ͅ
mınxomaτ

8
To, że Mona Lisa jest jakimś gównem SCP
podziemny

1
Obraz w kratkę wygląda naprawdę fajnie IMHO.
ETHproductions

1
Nie zdziwiłbym się, gdybyś wygrał z tym własne wyzwanie. To naprawdę miłe rozwiązanie.
Alex A.

25

Matematyka

To używa Inpaintfunkcji Mathematica . Ponieważ sama Mathematica wykonuje ciężkie prace, jest to wiki społeczności.

inPaint(poniżej) to prosta adaptacja Inpaint. W przypadku kolorowych obrazów / zdjęć używa domyślnego ustawienia „TextureSynthesis”. Jeśli wykryje, że obraz jest czarno-biały (ponieważ dane obrazu są takie same jak dane obrazu w postaci binarnej obrazu), binaryzuje obraz i zastosuje łatkę „TotalVariation”. IfKlauzula albo zastosowanie Binarizelub Identitydo obrazu. ( IdentityFunkcja zwraca argument bez zmian.)

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

Obraz i maska ​​są wprowadzane jako argumenty do inPaint. Partitioni Gridsłużą wyłącznie do formatowania.

Wejście

Wyjścia zostały załatane. Po tym nie było retuszu zdjęć inPaint.

wynik


4
To może być zbieg okoliczności, ale jestem zdumiony wydajnością labiryntu!
flawr

1
@flawr Wrzuciłbym coś takiego, aby zepsuć to rozwiązanie;) (Kto wie? Ci czarnoskórzy naprawdę się dziwią.)
Andras Deak

17
Nie sądzę, że powinna to być wiki społeczności.
Dennis

Lawr, Tak, Inpaintwydaje się szukać symetrii na całym czarno-białym obrazie. - DavidC 9 godzin temu
DavidC

Czy jesteś pewien, że czarno-biały algorytm nigdzie nie wymaga poświęcenia kóz? Jak --- na Ziemi --- do diabła, zgaduje centralną strukturę obrazu, jeśli wszystko jest zamaskowane?
Andras Deak

18

Python 2 i PIL

Ten program łączy kopie regionów Północ, Południe, Wschód i Zachód w celu utworzenia pikseli zastępczych, które używają kolorów, tekstur i cieniowania z lokalnego regionu obrazu.

Przykładowe dane wyjściowe:

Kod najpierw znajduje obwiednię dla łatki. Następnie dla każdego generowanego piksela oblicza kolor każdego kanału (RGB) na podstawie sumy ważonej 4 otaczających obszarów.

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
Ta Mona Lisa jest również przerażająca! Czy wszyscy Mona Lisas w tym wyzwaniu są skazani na przerażenie?
metro

@undergroundmonorail Wydaje mi się, że przypadkowe twarze wygenerowane komputerowo pochodzą prosto z głębi niesamowitej doliny .
Andras Deak

Skąd bierzesz PIL?
Elliot A.

@ElliotA. Rozumiem, że PIL właściwy jest martwy, ale był open source i dlatego żyje pod nazwą „Poduszka”. Jeśli wyszukujesz w Google „poduszkę python”, powinieneś ją znaleźć.
undergroundmonorail

13

Python 3, PIL

Ten program używa operatora sobel i na tej podstawie rysuje linie na obrazie.

Operator sobel znajduje kąt każdej krawędzi, więc wszelkie krawędzie wytłaczające w nieznany obszar powinny być kontynuowane.

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')

Tymczasem oto przykładowe obrazy.

wprowadź opis zdjęcia tutaj

wprowadź opis zdjęcia tutaj

wprowadź opis zdjęcia tutaj

Mona Lisa Mona Lisa Ḿ͠oǹ̰͎̣a ̾̇Lisa Ḿ͠o̢̎̓̀ǹ̰͎̣aͧ̈ͤ ̣̖̠̮̘̹̠̾̇ͣLisa Ḿ̳̜͇͓͠o̢̎̓̀ǹ̰͎̣͙a̤̩̖̞̝ͧ̈ͤͤ ̣̖̠̮̘̹̠̾̇ͣL͉̻̭͌i̛̥͕̱͋͌ş̠͔̏̋̀ạ̫͕͎ͨͮͪ̐͡ͅ

wprowadź opis zdjęcia tutaj Obszar na powyższym zdjęciu jest gładki jak kaktus

Nie jest bardzo dobry ze stałym kolorem.

wprowadź opis zdjęcia tutaj

wprowadź opis zdjęcia tutaj


1
Aha, czy możesz dodać przypadki testowe B / W?
flawr

2
Wygląda naprawdę dobrze w Starry Night.
SuperJedi224

1
Wow, teraz wygląda to niesamowicie! Łatki są nadal zauważalne, ale świetny nowy pomysł! Mój ulubiony jak dotąd =)
flawr

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

2
Wygrywasz konkurs „najstraszniejsza Mona Lisa”, IMO. 0_o
DLosc

8

Python 2

Prosty skrypt Pythona, który tworzy łatkę przy użyciu wartości z pikseli znajdujących się poza luką. Pobiera wartości kolorów z końca wiersza i kolumny pikseli i oblicza średnią ważoną na podstawie odległości od tych pikseli.

Produkcja nie jest taka ładna, ale to sztuka .

img1 img2 img3 img4 img5 img6

A następnie kod:

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")

W tej chwili widzisz te poziome / pionowe paski, być może możesz to poprawić, włączając inne kierunki!
flawr

Właściwie to próbowałem, ale nie byłem w stanie uzyskać dobrych wyników, więc po prostu postanowiłem po prostu rozmazać obraz: D
Hannes Karppila

19
Wreszcie Mona Lisa, która nie przeraża mnie na śmierć, ale wygląda jak aresztowany morderca.
Andras Deak

6

Matematyka

Inpaint

Tak się składa, że Mathematica ma wbudowaną funkcję, która wykonuje dokładnie to zadanie, a mam na myśli dokładnie :

Inpaint[image, region]

  • retuszuje części imageodpowiadające niezerowym elementom w region.

Domyślnie używa „najlepiej dopasowanej metody syntezy tekstur przy użyciu losowego próbkowania”, która daje dobre wyniki na obrazach, ale słabe wyniki dla labiryntu i szachownicy:

wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj

Zabawa z ustawieniami nie przyniosła mi wzrostu jakości na wszystkich obrazach, więc po prostu użyłem wartości domyślnych (aby zaoszczędzić bajty - w codegolf.sekońcu to jest !).


23
Tak się składa, że ​​Mathematica ma wbudowaną funkcję ” ... niespodzianka, niespodzianka;)
Andras Deak

W przypadku labiryntu i planszy lepiej zastosować metodę „TotalVariation” wraz z Binarize(aby wyeliminować szare smugi). Spróbuj tego: methods = {"TextureSynthesis", "Diffusion", "FastMarching", "NavierStokes", "TotalVariation"};g[pic_, mask_] := Join[{Labeled[Framed@pic, "Original"]}, Labeled[ Binarize@Inpaint[pic, mask, Method -> #], #] & /@ methods]
DavidC

@DavidC Próbowałem innych metod, ale TextureSynthesisdobrze wygląda tylko na obrazach; i nie sądzę, abyśmy mogli dostroić nasze ustawienia dla każdego indywidualnego przypadku testowego. (Gdybyśmy mogli, moglibyśmy trywialnie dostarczyć brakującą porcję jako „ustawienie”.)
2012rcampion 30.01.2016

Labirynt i wyniki szachownicy są dla mnie naprawdę zagadkowe. Dlaczego rekonstrukcja brakującego regionu przez Mathematica jest tak nieregularna i asymetryczna?
David Zhang

To automatycznie wykryje, czy obraz jest czarno-biały i wprowadzi odpowiednie poprawki (pliki binarne i metoda „TotalVariation”). inPaint[picture_, mask_] := If[bw = ImageData@Rasterize[Binarize[picture]] == ImageData[picture], Binarize, Identity]@ Inpaint[picture, mask, Method -> If[bw, "TotalVariation", "TextureSynthesis"]]
DavidC

5

Python3

Ta odpowiedź implementuje ten pomysł w artykule „Deep Image Prior” autorstwa Uljanowa i in. (CVPR 2018) W tym artykule badali ideę, że sposób, w jaki zaprojektowane są dobrze działające sieci neuronowe do przetwarzania obrazu, ściśle odzwierciedla nasze wyobrażenie o tym, jak powinien wyglądać naturalny obraz („wcześniejsza” dystrybucja).

Zaproponowali metodę, która może być używana do malowania, a także usuwania szumów i artefaktów, która wykorzystuje tylko dany obraz bez szkolenia innych danych. Rzeczywista koncepcja jest dość prosta: sieć jest wyszkolona do wyświetlania pożądanego obrazu (dla niektórych ustalonych losowych szumów jako danych wejściowych) poprzez karanie tylko za błędy poza pewną maską. Jeśli chcesz usunąć hałas, po prostu nie musisz niczego maskować, ale po prostu przestań wcześnie na treningu.

W celu malowania zamaskuj część, którą chcesz pomalować i trenuj, aż do zbieżności. Z pewnością nie jest to najnowocześniejsze, ale nadal chciałem opublikować wypróbować i opublikować tutaj tutaj ze względu na prostotę pomysłu i wciąż niezwykłą wydajność. W moich eksperymentach malowanie większych łat nie wypadło tak dobrze, ale w przypadku mniejszych segmentów wyniki mogą być znacznie bardziej przekonujące.

Zaimplementowałem to za pomocą popularnej architektury U-Net z Jaxony na github . Kod do szkolenia i przetwarzania obrazów można znaleźć poniżej.

Trening

Jest to wizualizacja procesu szkolenia. Każda ramka jest stanem pewnej liczby iteracji:

Przykłady

Kod

Zauważ, że na jednostce centralnej uruchomienie pojedynczego obrazu może potrwać kilka godzin, a dobre GPU z włączoną funkcją cuda może zająć znacznie mniej czasu.

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))

czy Deep Image Prior używa czegoś innego niż sieci U? wygląda na to, że uzyskaliby lepsze wyniki
tylko ASCII

Wypróbowałeś także kod Deep Image Prior
tylko ASCII,

@ Tylko ASCII W artykule stwierdzają, że rzeczywiście używają głównie sieci U-Net, ale nie mogę znaleźć dokładnych parametrów, których używali. Mogli użyć sieci o większej pojemności. Miałem tylko komputer o bardzo ograniczonej mocy. Musiałem więc wybrać parametry, które nadal pasują do pamięci i których trenowanie nie trwało długo. Nie jestem pewien, ile dokładnie to zajęło, ale na komputerze, z którego korzystałem (tylko z procesorem), te zdjęcia trwają wiele dni. (Jeśli masz zapasowy procesor graficzny z włączoną obsługą
cuda,

Podejrzewam również, że ze względu na konstrukcję sieci posiadanie prostokątnych masek również nie jest idealne (a mniejsze maski prawdopodobnie również wyglądałyby lepiej), jeśli porównasz na przykład kilka pierwszych obrazów z dwoma ostatnimi (które nie używają prostokątnych masek) .
flawr

4

Python z OpenCV

OpenCV ma funkcję o nazwie inpaint. Istnieją dwa rodzaje malowania, użyję metody szybkiego marszu. Zgodnie z dokumentacją algorytm działa w następujący sposób:

Rozważ region na obrazie, który ma zostać pomalowany. Algorytm zaczyna się od granicy tego regionu i przechodzi do regionu, stopniowo wypełniając najpierw wszystko na granicy. Malowanie zajmuje niewielką okolicę wokół piksela w sąsiedztwie. Piksel ten jest zastępowany znormalizowaną ważoną sumą wszystkich znanych pikseli w sąsiedztwie. Wybór wag jest ważną kwestią. Większy ciężar przypisuje się pikselom leżącym blisko punktu, w pobliżu normalnej granicy i tym, które leżą na konturach granicy. Po pomalowaniu piksel przesuwa się do następnego najbliższego piksela przy użyciu metody szybkiego marszu. FMM zapewnia, że ​​piksele znajdujące się w pobliżu znanych pikseli są najpierw pomalowane, dzięki czemu działa jak ręczna operacja heurystyczna.

Oto kod *:

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)

Zwróć uwagę, jak przekonwertowałem BGR na RGB z powodów drukowania. Obracam to. Oto wyniki:

gotyk

Gwieździsta noc krzyk kolejna przerażająca Mona Lisa!

Mona Lisa powraca!

linia 1

szachownica

Jak widać, nie jest najlepszy z tymi dwoma kolorami.


Mona Lisa dostała liftingu
Conor O'Brien

3

Jawa

Metoda uśredniania kolorów. Prawdopodobnie można to poprawić.

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"));
    }
}

Wyniki:

wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj


2
Dlaczego zawsze otrzymujesz linie pod tym konkretnym kątem? Lewe górne rogi wydają się pasować stosunkowo dobrze, podczas gdy dolna prawa część w ogóle nie pasuje.
flawr

Myślę, że ma to związek ze sposobem, w jaki iteruję po regionie. Prawdopodobnie w końcu to zmienię.
SuperJedi224

Zawsze wygląda na to, że są trapezoidy.
ericw31415

@ ericw31415 To artefakt kolejności iteracji.
SuperJedi224
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.