Patcher l'image

114

Dans un logiciel d'édition d'images très répandu, les correctifs (le terme utilisé dans le traitement d'images correspond à celui indiqué par @ mınxomaτ.) Correspondent à une zone sélectionnée d'une image, en fonction des informations extérieures à ce correctif. Et il fait un très bon travail, considérant que ce n'est qu'un programme. En tant qu'être humain, vous pouvez parfois voir que quelque chose ne va pas, mais si vous serrez les yeux ou regardez simplement un coup d'œil, le patch semble combler assez bien le vide .

exemple par le logiciel d'édition d'image populaire

Défi

Etant donné qu'une image et un masque spécifiant une zone rectangulaire de l'image doivent être corrigés (également en tant qu'image ou tout autre format préféré), votre programme doit essayer de remplir la zone spécifiée avec un correctif qui tente de se fondre dans le reste de l'image. l'image. Le programme ne peut pas utiliser les informations de l'image d'origine qui se trouvait dans la zone spécifiée.

Vous pouvez supposer que le patch est toujours au moins de largeur par rapport aux côtés et de hauteur par rapport au haut et au bas de l'image. Cela signifie que la surface maximale d'un patch est de 1/9 de l'image entière.

Veuillez ajouter une brève description du fonctionnement de votre algorithme.

Vote

Les électeurs sont invités à juger de l’efficacité des algorithmes et à voter en conséquence.

Quelques suggestions sur la façon de juger: (Encore une fois, merci @ mınxomaτ pour quelques critères supplémentaires.)

  • Si vous plissez les yeux et que la photo est belle?
  • Pouvez-vous dire exactement où se trouve le patch?
  • Dans quelle mesure les structures et les textures de l'arrière-plan de l'image et de la zone environnante sont-elles bien conservées?
  • Combien de pixels en fausses couleurs parasites contient la zone modifiée?
  • Y a-t-il des blobs / blocs de couleur uniforme dans la zone qui ne semblent pas y appartenir?
  • La zone modifiée présente-t-elle des décalages drastiques de couleur / contraste ou de luminosité par rapport au reste de l'image?

Critère de validité

Pour qu'une soumission soit valide, l'image de sortie doit correspondre exactement à l'image d'entrée en dehors de la zone spécifiée.

Cas de test

A gauche l'image source, à droite le masque correspondant:

flawr
la source
1
Pouvons-nous accepter la saisie de masque comme arguments de texte (par exemple inpaint.exe left top width height img.jpg)?
mınxomaτ
1
Bien sûr, le format des entrées / sorties n’est pas vraiment important, car c’est un concours de popularité dans lequel, en premier lieu, les performances de votre algorithme sont importantes.
Flawr
24
C'est un défi très pratique. Il est possible que les résultats soient meilleurs que les algorithmes existants utilisés dans GIMP et d'autres logiciels d'édition d'images open source. La fortune, la gloire et la gloire pourraient être à vous!
Sparr le
6
Les filigranes @Sparr et enfin les laids peuvent être supprimés des fichiers téléchargés;)
Andras Deak
2
Les bâtis sont tout à fait acceptables, mais je doute qu'ils soient très populaires.
mardi

Réponses:

142

AutoIt , VB

introduction

Ceci est une implémentation de l' algorithme de suppression d'objet par exemple basé sur des exemples développé par A. Criminisi, P. Perez (Cambridge Microsoft Research Ltd.) et K. Toyama (Microsoft) [X] . Cet algorithme vise les images à haute information (et les images vidéo) et vise à établir un équilibre entre reconstruction structurelle et reconstruction organique. Les paragraphes de cette réponse contiennent des citations de texte intégral tirées du document d'origine (puisqu'il n'est plus disponible officiellement) afin de rendre cette réponse plus autonome.

L'algorithme

Objectif : remplacer un élément sélectionné ( masquée ) (de préférence un objet de premier plan séparé visuellement) par un arrière-plan plausible visuellement.

Dans des travaux antérieurs, plusieurs chercheurs ont considéré la synthèse de texture comme un moyen de remplir de grandes régions d’image avec des textures "pures" - des motifs texturaux bidimensionnels répétitifs à stochasticité modérée. Ceci est basé sur un grand nombre de recherches sur la synthèse de texture, qui cherchent à reproduire la texture à l' infini , à partir d'un petit échantillon source de texture pure [1] [8] [9] [10] [11] [12] [14]. [15] [16] [19] [22] .

Aussi efficaces que ces techniques permettent de reproduire des textures cohérentes, elles ont du mal à combler les trous dans les photographies de scènes du monde réel, souvent composées de structures linéaires et de textures composites - de multiples textures en interaction spatiale [23] . Le problème principal est que les frontières entre les régions de l’image sont un produit complexe des influences mutuelles entre différentes textures. Contrairement à la nature bidimensionnelle des textures pures, ces limites forment ce que l’on pourrait considérer comme des structures d’image unidimensionnelles ou linéaires.

Les techniques d' inpainting d'image comblent les trous dans les images en propageant des structures linéaires (appelées isophotes dans la littérature d' inpainting ) dans la région cible par diffusion. Ils s’inspirent des équations aux dérivées partielles du flux de chaleur physique et fonctionnent de manière convaincante en tant qu’algorithmes de restauration. Leur inconvénient est que le processus de diffusion introduit un certain flou, ce qui est perceptible.

figure.  2

La région à remplir, c'est-à-dire que la région cible est indiquée par Ω, et son contour est noté δΩ. Le contour évolue vers l'intérieur à mesure que l'algorithme progresse. Nous l'appelons donc le «front de remplissage». La région source Φ, qui reste fixe tout au long de l'algorithme, fournit des échantillons utilisés dans le processus de remplissage. Nous nous concentrons maintenant sur une seule itération de l'algorithme pour montrer comment la structure et la texture sont gérées de manière adéquate par une synthèse basée sur des exemples. Supposons que le gabarit carré Ψp Ω centré au point p (fig. 2b) soit rempli. Le meilleur échantillon de la région source provient du patch patchqˆ ∈ Φ, qui ressemble le plus aux parties déjà remplies dans p. Dans l'exemple de la fig. 2b, on voit que si p se trouve sur la suite du bord d’une image, les meilleures combinaisons les plus probables se situeront le long du même bord (ou d’un bord de même couleur) (par exemple, Ψq 'et Ψq' 'sur la figure 2c). Tout ce qui est nécessaire pour propager l'isophote vers l'intérieur est un simple transfert du motif à partir du patch source le plus proche (fig. 2d). Notez que l'orientation isophote est automatiquement préservée. Sur la figure, malgré le fait que le bord d'origine n'est pas orthogonal au contour cible δΩ, la structure propagée a conservé la même orientation que dans la région source.

Détails d'implémentation et d'algorithme

La fonctionnalité de cette implémentation est encapsulée dans une DLL COM ActiveX qui est supprimée du programme hôte sous forme binaire, puis appelée à la volée en appelant l'inpainter par IID. Dans ce cas spécifique, l'API est écrite en VisualBasic et peut être appelée à partir de n'importe quel langage activé par COM. La section suivante du code supprime le binaire:

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

La bibliothèque est ensuite instanciée à l'aide du CLSID et de l'IID:

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

La bibliothèque accepte un descripteur GDIOBJECT, en particulier une section DIB de n’importe quel bitmap GDI / + (fichiers, flux, etc.). Le fichier image spécifié est chargé et dessiné sur un bitmap vide construit à partir deScan0 partir des dimensions de l'image en entrée.

Le fichier d'entrée pour cette implémentation est tout format de fichier compatible GDI / + contenant des données d'image masquées. Le (s) masque (s) sont une ou plusieurs régions uniformément colorées dans l'image d'entrée. L'utilisateur fournit une valeur de couleur RVB pour le masque, seuls les pixels ayant exactement cette valeur de couleur seront appariés. La couleur de masquage par défaut est le vert (0, 255, 0). Toutes les régions masquées représentent ensemble la région cible Ω à supprimer et à remplir. La région source, Φ, est définie comme l’ensemble de l’image moins la région cible (Φ = I Ω).

Ensuite, comme pour toute synthèse de texture basée sur des exemples [10] , la taille de la fenêtre de modèle (ou " rayon de balayage ") doit être spécifiée. Cette implémentation fournit une taille de fenêtre par défaut de 6² pixels, mais impose en pratique à l'utilisateur de la définir pour qu'elle soit légèrement plus grande que le plus grand élément de texture pouvant être distingué, ou «texel», dans la région source. Une modification supplémentaire de l'algorithme d'origine est la " taille de bloc " définissable par l'utilisateur, qui détermine la zone de pixels à remplacer par une nouvelle couleur uniforme. Cela augmente la vitesse et diminue la qualité. Les blocs dont la taille est supérieure à 1px sont conçus pour être utilisés avec des zones extrêmement uniformes (eau, sable, fourrure, etc.). Toutefois, doit être maintenu à une valeur maximale. .5x la taille du bloc (ce qui peut être impossible en fonction du masque).

Pour ne pas bloquer l'algorithme sur les images 1 bit, chaque fois qu'une image de moins de 5 couleurs est reçue, la taille de la fenêtre est amplifiée de 10x.

Une fois que ces paramètres sont déterminés, le reste du processus de remplissage de région est complètement automatique. Dans notre algorithme, chaque pixel conserve une valeur de couleur (ou «vide», si le pixel est vide) et une valeur de confiance, qui reflète notre confiance dans la valeur de pixel, et qui est gelée une fois qu'un pixel est rempli. Au cours de l'algorithme, les correctifs situés le long du front de remplissage reçoivent également une valeur de priorité temporaire, qui détermine leur ordre de remplissage. Ensuite, notre algorithme itère les trois étapes suivantes jusqu'à ce que tous les pixels soient remplis.

Étape 1: Priorités des correctifs informatiques

L'ordre de remplissage est crucial pour la synthèse de texture non paramétrique [1] [6] [10] [13] . Jusqu'ici, le favori par défaut a été la méthode de «pelure d'oignon», dans laquelle la région cible est synthétisée de l'extérieur vers l'intérieur, en couches concentriques. Notre algorithme effectue cette tâche par le biais d'un algorithme de remplissage optimal, qui dépend entièrement des valeurs de priorité attribuées à chaque patch sur le front de remplissage. Le calcul de la priorité est orienté vers les patchs situés dans le prolongement d'arêtes fortes et qui sont entourés de pixels de confiance élevée. Ces pixels sont la limite, marquée par la valeur -2. Le code suivant recalcule les priorités:

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

Soit un patch centeredp centré au point p pour certains p ∈ δΩ (voir fig. 3), sa priorité P (p) est définie comme le produit de la confiance calculée ( ComputeConfidence, ou C (p) ) et du terme de données ( ComputeData, ou D (p) ), où

, où

| |p | est l'aire de Ψp, α est un facteur de normalisation (par exemple, α = 255 pour une image en niveaux de gris typique) et np est un vecteur unitaire orthogonal au front δΩ au point p. La priorité est calculée pour chaque patch de bordure, avec des patchs distincts pour chaque pixel situé à la limite de la région cible.

Mis en œuvre comme

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

Le terme de confiance C (p) peut être considéré comme une mesure de la quantité d'informations fiables entourant le pixel p. L’intention est de commencer par remplir les patchs qui ont déjà plus de pixels, avec une préférence supplémentaire pour les pixels qui ont été remplis très tôt (ou qui n’ont jamais fait partie de la région cible).

Ceci incorpore automatiquement la préférence vers certaines formes le long du front de remplissage. Par exemple, les correctifs qui incluent des coins et des vrilles minces de la région cible auront tendance à être remplis en premier, car ils sont entourés de plusieurs pixels de l'image d'origine. Ces correctifs fournissent des informations plus fiables avec lesquelles faire correspondre. Inversement, les zones situées à l'extrémité des «péninsules» de pixels remplis faisant saillie dans la région cible tendent à être mises de côté jusqu'à ce que davantage de pixels environnants soient remplis. À un niveau grossier, le terme C (p) de (1) applique l'ordre de remplissage concentrique souhaitable.

Au fur et à mesure du remplissage, les pixels des couches extérieures de la région cible auront tendance à se caractériser par des valeurs de confiance plus grandes et seront donc remplis plus tôt; les pixels au centre de la région cible auront des valeurs de confiance moins grandes. Le terme de données D (p) est fonction de la force des isophotes frappant le front δΩ à chaque itération. Ce terme augmente la priorité d'un patch dans lequel un "flux" isophote. Ce facteur revêt une importance fondamentale dans notre algorithme car il encourage la synthèse préalable des structures linéaires, puis leur propagation de manière sécurisée dans la région cible. Les lignes brisées ont tendance à se connecter, réalisant ainsi le "principe de connectivité" de la psychologie de la vision [7] [17] .

L’ordre de remplissage dépend des propriétés de l’image, ce qui aboutit à un processus de synthèse organique qui élimine le risque d’artefacts «à structure cassée» et réduit également les artefacts encombrants sans étape coûteuse de découpe de patch [9] ou de fusion induisant un flou [19]. ] .

Étape 2: Propagation des informations de texture et de structure

Une fois que toutes les priorités sur le front de remplissage ( limite ) ont été calculées, le patch Ψpˆ avec la priorité la plus élevée est trouvé. Nous le remplissons ensuite avec les données extraites de la région source. Nous propagons la texture de l'image par échantillonnage direct de la région source. Semblable à [10] , nous cherchons dans la région source le patch qui ressemble le plus à pˆ. Officiellement,

, où

la distance d (Ψa, Ψb) entre deux correctifs génériques a et Ψb est simplement définie comme la somme des différences au carré (SSD) des pixels déjà remplis dans les deux correctifs. Aucune analyse ou manipulation supplémentaire (en particulier aucun flou ) n'est effectuée à cette étape. Ce calcul s'exécute dans la boucle de cycle principale et est mis en œuvre comme suit:

Obtenir la priorité maximale:

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

Trouver le patch le plus similaire:

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

Étape 3: Mise à jour des valeurs de confiance

Une fois que le patch Ψpˆ a été rempli avec de nouvelles valeurs de pixel, la confiance C (p) est mise à jour dans la zone délimitée par pˆ comme suit:

Cette règle de mise à jour simple nous permet de mesurer la confiance relative des correctifs sur le front de remplissage, sans paramètres spécifiques à une image. Au fur et à mesure du remplissage, les valeurs de confiance diminuent, indiquant que nous sommes moins sûrs des valeurs de couleur des pixels proches du centre de la région cible. Mis en œuvre ici (avec toutes les autres mises à jour nécessaires):

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 complet

Voici le code exécutable, avec le code source des bibliothèques sous forme de commentaires.

Le code est appelé par

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

Des exemples sont inclus sous la forme de

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

Décommentez simplement l'exemple que vous voulez utiliser avec CTRL+ Q.

Fichiers de test officiels

Cet algorithme est fait pour être ajusté pour chaque image. Par conséquent, les valeurs par défaut (ainsi que les masques par défaut) sont complètement sous-optimaux. Les valeurs par défaut sont choisies pour que chaque échantillon puisse être traité dans un délai raisonnable. Je recommande fortement de jouer avec des masques de forme irrégulière et de meilleures tailles de fenêtres. Cliquez sur les images pour les agrandir!

Damier

gothique americain

Labyrinthe

Mona Lisa

(masque terrible)

Crier

Étoilé

Exemples concrets

Ceux-ci utilisent tous des masques personnalisés dessinés à la main.

Si vous avez d'autres images intéressantes que vous aimeriez voir incluses, laissez un commentaire.

Améliorations EBII

Il existe de nombreuses variantes d'EBII, créées par divers chercheurs. AnkurKumar Patel a attiré mon attention avec sa collection de documents [24] sur diverses améliorations d'EBII.

Plus précisément, le document " Algorithme amélioré amélioré pour la mise en image par exemple " [25] mentionne deux améliorations concernant la pondération des valeurs de priorité.

L'amélioration

La modification effective se trouve à l'étape 1 (voir ci-dessus) de l'algorithme et étend l'effet C (p) et D (p) sur la cote de priorité de ce pixel en utilisant ceci:

Dans la formule pour C et D donnée ci-dessus, et sont respectivement le facteur de normalisation (par exemple, α = 255), le vecteur isophote et le vecteur unitaire orthogonal au front du point p.

Plus loin,

La fonction de priorité est définie comme la somme en poids du terme de confiance régularisé C (p) et du nouveau terme de données D (p) . Où α est le coefficient d’ajustement, on définit comme suit la satisfaction de 0Rp (p):

Où α et β sont respectivement les pondérations composantes des termes de confiance et de données. Notez que α + β = 1 .

Notation objective

Ce qui est vraiment intéressant, c’est que ce document contient une méthode proposée (et simple!) Pour évaluer les performances des algorithmes EBII. Prenez ceci avec un grain de sel, car c’est une méthode choisie par les auteurs du papier eux-mêmes pour vérifier l’efficacité de la méthode de variance proposée et l’amélioration de plusieurs images.

L'évaluation du résultat est effectuée en comparant le PSNR (le rapport signal / bruit de pointe [26] ) entre l'image restaurée et l'image d'origine. En règle générale, plus la valeur PSNR est élevée, plus la similarité de l'image réparée avec l'image d'origine est grande. L'équation pour calculer le PSNR est la suivante:

Voici deux images stupéfiantes (deux!) Du monde réel qu'ils ont utilisées:

La conclusion est aussi décevante que la qualité du papier lui-même. Cela montre très peu d'amélioration. La chose principale ici est une méthode de notation d'objet possible pour ce type de défi (et d'autres défis de réparation d'image):

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

Meh

Recherche à faire

(Spécifique à EBII)

a) pré-traitement

Tout se résume au principe "d'effacement magique" selon lequel l'algorithme devrait "fonctionner" pour tout. Ma solution naïve pour cela est une amplification basée sur la couleur (voir ci-dessus), mais il existe de meilleures méthodes. Je songe à reconnaître la moyenne géométrique de tous les texels traçables pour ajuster automatiquement la taille de la fenêtre et faire en sorte que la taille du tampon (ainsi que mon amélioration) dépende de la résolution de texel et de l'image entière. La recherche doit être faite ici.

b) Post-traitement

Les auteurs originaux ont déjà fait un excellent travail en supprimant tous les filtres de post-traitement qui nous viennent à l’esprit. Aujourd'hui, j'ai essayé quelque chose d'autre, inspiré par la toujours mystérieuse Mona Lisa (merci undergroundmonorail). Si vous ne prenez que la région non peinte et appliquez un nouveau masque à tous les blocs de couleur étranges et que vous l'introduisez dans un algorithme de suppression du filtrage, vous obtenez un résultat presque parfait. Je pourrai explorer cela quelque temps dans le futur.


[X] - Suppression d'objet par une peinture à l'aide d'un modèle, par A. Criminisi, P. Perez, K. Toyama
[1] - M. Ashikhmin. Synthétiser des textures naturelles. Dans Proc. ACM Symp. sur les graphiques interactifs 3D, p. 217-226, Research Triangle Park, Caroline du Nord, mars 2001.
[5] - M. Bertalmio, L. Vese, G. Sapiro et S. Osher. Application simultanée d'images de structure et de texture. à paraître, 2002
[6] - R. Bornard, E. Lecan, L. Laborelli et JH. Chenot. Correction des données manquantes dans les images fixes et les séquences d'images. Dans ACM Multimedia, France, décembre 2002.
[7] - TF Chan et J. Shen. Inpainting non texturé par diffusions induites par la courbure (CDD). J. Visual Comm. Image Rep., 4 (12), 2001.
[8] - JS de Bonet. Procédure d'échantillonnage multirésolution pour l'analyse et la synthèse d'images de texture. Dans Proc. ACM Conf. Comp. Graphics (SIGGRAPH), volume 31, pages 361–368, 1997.
[9] - A. Efros et WT Freeman. Quilting d'image pour la synthèse et le transfert de texture. Dans Proc. ACM Conf. Comp. Graphics (SIGGRAPH), p. 341–346, Eugene Fiume, août 2001.
[10] - A. Efros et T. Leung. Synthèse de texture par échantillonnage non paramétrique. Dans Proc. ICCV, p. 1033-1038, Kerkyra, Grèce, septembre 1999.
[11] - WT Freeman, EC Pasztor et OT Carmichael. Apprendre la vision de bas niveau. Int. J. Computer Vision, 40 (1): 25–47, 2000.
[12] - D. Garber. Modèles de calcul pour l'analyse et la synthèse de texture. Thèse de doctorat, Univ. de Californie du Sud, USA, 1981.
[13] - P. Harrison. Une procédure non hiérarchique pour la resynthèse de texture complexe. Dans Proc. Int. Conf. Europe centrale Comp. Graphiques, Visua. et comp. Vision, Plzen, République tchèque, février 2001.
[14] - DJ Heeger et JR Bergen. Analyse / synthèse de texture à base de pyramide. Dans Proc. ACM Conf. Comp. Graphics (SIGGRAPH), volume 29, pages 229-233, Los Angeles, CA, 1995.
[15] - A. Hertzmann, C. Jacobs, N. Oliver, B. Curless et D. Salesin. Analogies d'image. Dans Proc. ACM Conf. Comp. Graphics (SIGGRAPH), Eugene Fiume, août 2001.
[16] - H. Igehy et L. Pereira. Remplacement d'image par synthèse de texture. Dans Proc. Int. Conf. Image Processing, pp. III: 186-190, 1997.
[17] - G. Kanizsa. Organisation en vision. Praeger, New York, 1979.
[19] - L. Liang, C. Liu, Y.-Q. Xu, B. Guo et H.-Y. Shum. Synthèse de texture en temps réel par échantillonnage par patch. Dans ACM Transactions on Graphics, 2001.
[22] - L.-W. Wey et M. Levoy. Synthèse de texture rapide par quantification vectorielle structurée en arborescence. Dans Proc. ACM Conf. Comp. Graphics (SIGGRAPH), 2000.
[23] - A. Zalesny, V. Ferrari, G. Caenen et L. van Gool. Synthèse de texture composite parallèle. Dans l'atelier Texture 2002 - (en liaison avec ECCV02), Copenhague, Danemark, juin 2002.
[24] - AkurKumar Patel, Université technologique du Gujarat, Informatique et ingénierie
[25] - Algorithme amélioré pour la modélisation d'images à l'aide d'exemplaires
[26] - Wikipedia, rapport signal sur bruit

mınxomaτ
la source
30
C'est génial . La nuit étoilée est tellement bonne. Pourtant, cette Mona Lisa ...
Hannes Karppila le
8
Permettez-moi d’abord de dire «oh mon dieu, c’est incroyable». Deuxièmement: j'ai déjà commenté "que Mona Lisa est une merde de SCP" sur une autre question, mais que cette chouette ressemble en réalité à quelque chose qui pourrait apparaître sur le wiki de SCP.
undergroundmonorail
3
Les paragraphes cités que vous mentionnez pourraient-ils être des blocs de citation?
Trichoplax
1
@trichoplax Il y a des modifications mineures dans presque chaque phrase, ce ne sont pas des citations exactes. Considérez la description de l'algorithme comme identique à l'org. papier sauf quand il est dit modification ou code. Je ne veux plus encombrer le formatage :)
lundi
2
Lorsque j'essaie de regarder très attentivement quelque chose dans mes rêves, parfois, les choses se passent exactement de la sorte.
Jimmy23013
45

Matlab

C'est une approche d'interpolation simple. L'idée est d'abord de refléter ce qui est de chaque côté du patch. Ensuite, ces pixels d’image miroir sont interpolés en fonction de leur distance par rapport au bord correspondant:

La partie la plus délicate consistait à trouver de bons poids d’interpolation. Après quelques jeux, je suis arrivé à une fonction rationnelle nulle à tous les bords sauf celui sur lequel nous avons réfléchi. Ceci est ensuite transformé par un polynôme du troisième degré pour un lissage:

Cette approche simple fait étonnamment bien sur les images "naturelles", mais dès que vous êtes confronté à des contours nets, le jeu est terminé. Dans l' exemple gothique américain, les pointes de la fourche à foin s'alignent bien sur la grille de pixels, ce qui lui donne une belle apparence, mais cela aurait été bien pire autrement.

Alors voici les résultats:

Et enfin, le 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']);
flawr
la source
10
Mona Lisa me fait peur.
Andras Deak
46
O̢̎̓̀ǹ̰͎̣͙a̤̩̖̞̝ͧ̈ͤͤ ̣̖̠̮̘̹̠̾̇ͣL͉̻̭͌i̛̥͕̱͋͌ş̠͔̏̋̀ạ̫͕͎ͨͮͪ̐͡ͅ
mınxomaτ
8
Cette Mona Lisa est une merde de SCP
undergroundmonorail
1
L'image en damier a l'air vraiment cool IMHO.
ETHproductions
1
Je ne serais pas surpris si vous remportiez votre propre défi avec cela. C'est une très bonne solution.
Alex A.
25

Mathematica

Ceci utilise la Inpaintfonction de Mathematica . Parce que Mathematica lui-même fait le gros du travail, il s'agit d'un wiki de communauté.

inPaint(ci-dessous) est une simple adaptation de Inpaint. Pour les peintures / photos en couleur, il utilise le paramètre par défaut, "TextureSynthesis". S'il détecte que l'image est en noir et blanc (parce que les données d'image de l'image sont identiques à celles de la forme binaire de l'image), il binarise alors l'image et applique le patch "Variation totale". La Ifclause s'applique Binarizeou Identityà l'image. (La Identityfonction retourne son argument sous forme inchangée.)

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

L'image et le masque sont entrés en arguments inPaint. Partitionet Gridsont simplement à des fins de formatage.

contribution

Les sorties ont été corrigées. Il n'y a pas eu de retouche des images après inPaint.

sortie

DavidC
la source
4
C'est peut-être une coïncidence, mais je suis émerveillé par la performance du labyrinthe!
mardi
1
@flawr Je voudrais ajouter quelque chose comme ceci juste pour déconner avec cette solution;) (Qui sait? Ces noirs et blancs sont vraiment déroutants.)
Andras Deak le
17
Je ne pense pas que cela devrait être un wiki de communauté.
Dennis
lawr, Oui, Inpaintsemble rechercher des symétries sur l’ensemble de l’image en noir et blanc. - DavidC Il y a 9 heures
DavidC
Êtes-vous sûr que l'algorithme en noir et blanc n'implique pas de sacrifier des chèvres où que ce soit? Comment --- sur Terre --- diable devine-t-il la structure centrale de l'image, si tout cela est masqué ??
Andras Deak
18

Python 2 et PIL

Ce programme mélange des copies des régions Nord, Sud, Est et Ouest pour créer des pixels de remplacement utilisant des couleurs, des textures et des ombres issues de la région d'image locale.

Les exemples de sortie:

Le code trouve d'abord le cadre de sélection du correctif. Ensuite, pour chaque pixel à générer, il calcule la couleur de chaque canal (RVB) sur la base de la somme pondérée des 4 régions environnantes.

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)
Logic Knight
la source
3
Cette Mona Lisa est aussi terrifiante! Est-ce que toutes les Mona Lisas de ce défi sont condamnées à faire peur?
undergroundmonorail
@undergroundmonorail Je suppose que les visages accidentels générés par ordinateur proviennent des profondeurs de l' étonnante vallée .
Andras Deak
D'où obtenez-vous la PIL?
Elliot A.
@ ElliotA. D'après ce que je comprends, la PIL proprement dite est morte, mais c'était une source ouverte et elle subsiste donc sous le nom de "Pillow". Si vous recherchez un "oreiller en python" sur Google, vous devriez le trouver.
undergroundmonorail
13

Python 3, PIL

Ce programme utilise l'opérateur sobel et dessine des lignes sur l'image en fonction de cela.

L'opérateur sobel trouve l'angle de chaque arête, ainsi toutes les arêtes extrudées dans la zone inconnue doivent continuer.

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

En attendant, voici les exemples d’images.

entrez la description de l'image ici

entrez la description de l'image ici

entrez la description de l'image ici

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

entrez la description de l'image ici La zone de l'image ci-dessus est aussi lisse qu'un cactus

Ce n'est pas très bon avec une couleur constante.

entrez la description de l'image ici

entrez la description de l'image ici

Magenta
la source
1
Oh, et pourriez-vous s'il vous plaît ajouter les cas de test N / B?
Flawr
2
Ça a l'air vraiment bien sur celui de Starry Night.
SuperJedi224
1
Wow, cela semble incroyable maintenant! Les patchs sont encore visibles mais une bonne nouvelle idée! Mon préféré à ce jour =)
flawr
8
+1 pour "Mona Lisa Mona Lisa oǹ̰͎̣a Lisa o̢̎̓̀ǹ̰͎̣aͧ̈ͤ Lisa o̢̎̓̀ǹ̰͎̣͙a̤̩̖̞̝ͧ̈ͤͤ L͉̻̭͌i̛̥͕̱͋͌ş̠͔̏̋̀ạ̫͕͎ͨͮͪ̐͡ͅ"
mbomb007
2
Vous gagnez le concours "Le plus effrayant Mona Lisa", IMO. 0_o
DLosc
8

Python 2

Un script python simple qui crée un patch en utilisant des valeurs de pixels juste en dehors de gap. Il prend les valeurs de couleur à partir de la fin de la ligne et de la colonne de pixels et calcule la moyenne pondérée en utilisant la distance par rapport à ces pixels.

La sortie n'est pas si jolie, mais c'est de l' art .

img1 img2 img3 img4 img5 img6

Et ensuite, 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")
Hannes Karppila
la source
Au moment où vous voyez ces bandes horizontales / verticales, peut-être que vous pouvez l’améliorer en incluant d’autres directions!
mardi
En fait, j'ai essayé, mais je ne pouvais pas obtenir de bons résultats, alors j'ai simplement décidé de flouter l'image: D
Hannes Karppila
19
Enfin, une Mona Lisa qui ne me fait pas peur, mais ressemble plutôt à un meurtrier arrêté.
Andras Deak
6

Mathematica

Inpaint

Il se trouve que Mathematica a une fonction intégrée qui effectue exactement cette tâche, et je veux dire exactement :

Inpaint[image, region]

  • retouches des parties de ce imagequi correspondent à des éléments non nuls dans region.

Par défaut, il utilise une "méthode de synthèse de texture la mieux adaptée utilisant un échantillonnage aléatoire" qui produit de bons résultats sur les peintures, mais médiocre pour le labyrinthe et le damier:

entrez la description de l'image ici entrez la description de l'image ici entrez la description de l'image ici entrez la description de l'image ici entrez la description de l'image ici entrez la description de l'image ici

Jouer avec les réglages ne m'a pas apporté une augmentation de la qualité de toutes les images, alors j'ai simplement utilisé les valeurs par défaut (pour économiser des octets, c'est codegolf.seaprès tout!).

2012rcampion
la source
23
" Il se trouve que Mathematica a une fonction intégrée " ... surprise, surprise;)
Andras Deak
Pour le labyrinthe et le damier, il est préférable d’utiliser la méthode "TotalVariation" avec Binarize(pour éliminer les taches grises). Essayez ceci: methods = {"TextureSynthesis", "Diffusion", "FastMarching", "NavierStokes", "TotalVariation"};g[pic_, mask_] := Join[{Labeled[Framed@pic, "Original"]}, Labeled[ Binarize@Inpaint[pic, mask, Method -> #], #] & /@ methods]
DavidC
@ David C J'ai essayé les autres méthodes, mais cela ne TextureSynthesisfait que paraître beau sur les peintures; et je ne pense pas que nous soyons autorisés à ajuster nos paramètres pour chaque cas de test individuel. (Si nous le pouvions, nous pourrions alors trivialement fournir la partie manquante sous la forme d'un "paramètre".)
2012rcampion le
Les résultats du labyrinthe et du damier m’interpellent beaucoup. Pourquoi la reconstruction par Mathematica de la région manquante est-elle si irrégulière et asymétrique?
David Zhang
Ceci détectera automatiquement si une image est en noir et blanc et effectuera les ajustements appropriés (fichiers binaires et méthode de "Variation totale"). inPaint[picture_, mask_] := If[bw = ImageData@Rasterize[Binarize[picture]] == ImageData[picture], Binarize, Identity]@ Inpaint[picture, mask, Method -> If[bw, "TotalVariation", "TextureSynthesis"]]
DavidC
5

Python3

Cette réponse implémente l'idée dans l'article "Deep Image Prior" d'Ulyanov et al. (CVPR 2018) Dans cet article, ils ont exploré l’idée que la façon dont les réseaux de neurones performants sont conçus pour le traitement d’images reflète étroitement notre idée de ce à quoi une image naturelle devrait ressembler (la distribution "antérieure").

Ils ont proposé une méthode pouvant être utilisée pour l’inpainting, ainsi que pour la suppression du bruit et des artefacts, qui utilise simplement l’image donnée sans aucune formation sur d’autres données. Le concept actuel est assez simple: le réseau est entraîné à produire l'image souhaitée (pour certains parasites aléatoires fixes en entrée) en ne pénalisant que les erreurs qui se trouvent en dehors d'un masque donné. Si vous souhaitez supprimer le bruit, vous n'avez simplement pas besoin de masquer quoi que ce soit, mais arrêtez-vous tôt dans la formation.

Pour inpainting, vous masquez la partie que vous souhaitez peindre et former jusqu'à la convergence. Ce n’est certes pas à la pointe de la technologie, mais j’avais quand même envie de le poster et de le poster ici en raison de la simplicité de l’idée et de la performance encore remarquable. Dans mes expériences, l’inpainting de plus gros patchs n’a pas donné les mêmes résultats, mais pour des segments plus petits, les résultats peuvent être beaucoup plus convaincants.

J'ai implémenté cela en utilisant l' architecture U-Net de jaxony sur github . Le code de formation et de traitement des images est disponible ci-dessous.

Entraînement

Ceci est une visualisation du processus de formation. Chaque cadre est l’état d’un certain nombre d’itérations:

Exemples

Code

Notez que sur un processeur cela peut prendre des heures pour une seule image, alors qu'un bon gpu activé par cuda peut prendre beaucoup moins de temps.

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))
flawr
la source
Deep Image Prior utilise-t-il quelque chose de différent des U-Nets? On dirait qu'ils obtiendraient de meilleurs résultats
ASCII seulement
Aussi, avez-vous essayé avec le code Deep Image Prior
ASCII uniquement
@ ASCII-only Ils indiquent dans le papier qu'ils utilisent effectivement principalement U-Net, mais je ne trouve pas les paramètres exacts qu'ils ont utilisés. Ils auraient pu utiliser un filet avec une plus grande capacité. Je n'avais qu'un ordinateur avec une quantité de puissance très limitée. J'ai donc dû choisir des paramètres qui restent dans la mémoire et qui n'ont pas pris trop de temps pour m'entraîner. Je ne sais pas combien de temps cela a pris exactement, mais sur l'ordinateur que j'ai utilisé (uniquement avec un processeur), ces images prennent plusieurs jours. (Si vous avez un GPU activé avec cuda, faites le moi savoir :)
mardi
Je soupçonne également que, du fait de la conception du réseau, l'utilisation de masques rectangulaires n'est pas idéale (et que les masques plus petits auraient probablement aussi une meilleure apparence), si vous comparez par exemple les premières images aux deux dernières (n'utilisant pas de masques rectangulaires) .
mardi
4

Python avec OpenCV

OpenCV a une fonction appelée inpaint. Il existe deux types d’inpainting, je vais utiliser la méthode de marche rapide. Selon la documentation, l'algorithme fonctionne comme suit:

Considérez une région de l'image à peindre. L'algorithme commence à partir de la limite de cette région et va à l'intérieur de la région en remplissant progressivement tout ce qui se trouve dans la limite. Il faut un petit quartier autour du pixel sur le quartier pour être peint. Ce pixel est remplacé par la somme pondérée normalisée de tous les pixels connus du voisinage. La sélection des poids est une question importante. Une plus grande pondération est accordée aux pixels situés près du point, à la normale de la limite et à ceux situés sur les contours de la limite. Une fois qu'un pixel est peint, il passe au pixel suivant à l'aide de la méthode de défilement rapide. FMM veille à ce que les pixels proches des pixels connus soient peints en premier, afin que cela fonctionne comme une opération heuristique manuelle.

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

Notez comment je convertis le BGR en RVB pour des raisons de traçage. Aussi, je le tourne. Voici les résultats:

gothique

nuit étoilée crier un autre mona lisa effrayant!

Mona Lisa revient!

ligne 1

vérificateur

Comme vous pouvez le constater, ce n’est pas le meilleur avec les deux couleurs.

TanMath
la source
Mona Lisa fait peau neuve
Conor O'Brien
3

Java

Une approche de la moyenne des couleurs. Peut probablement être amélioré.

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

Résultats:

entrez la description de l'image ici entrez la description de l'image ici entrez la description de l'image ici entrez la description de l'image ici entrez la description de l'image ici entrez la description de l'image ici

SuperJedi224
la source
2
Pourquoi avez-vous toujours des lignes dans cet angle particulier? Les coins en haut à gauche semblent correspondre assez bien, tandis que la partie en bas à droite ne correspond pas du tout.
Flawr
Je pense que cela a à voir avec la manière dont je parcoure la région. Je vais probablement changer cela par la suite.
SuperJedi224
On dirait toujours qu'il y a des trapèzes.
ericw31415
@ ericw31415 C'est un artefact de l'ordre des itérations.
SuperJedi224