Corrigir a imagem


114

Em um software popular de edição de imagens, há um recurso, que os patches (o termo usado no processamento de imagens é pintado como @ mınxomaτ apontado.) Em uma área selecionada de uma imagem, com base nas informações externas desse patch. E faz um bom trabalho, considerando que é apenas um programa. Como humano, às vezes você pode ver que algo está errado, mas se você apertar os olhos ou apenas dar uma rápida olhada, o adesivo parece preencher a lacuna muito bem.

exemplo pelo popular software de edição de imagens

Desafio

Dada uma imagem e uma máscara que especifica uma área retangular da imagem devem ser corrigidas (também como imagem ou qualquer outro formato preferido), seu programa deve tentar preencher a área especificada com um patch que tente se misturar com o restante da imagem. a imagem. O programa não pode usar as informações da imagem original que estavam dentro da área especificada.

Você pode supor que o patch esteja sempre a pelo menos a largura dos lados e a altura da parte superior e inferior da imagem. Isso significa que a área máxima de um patch é 1/9 da imagem inteira.

Por favor, adicione uma breve descrição de como seu algoritmo funciona.

Votando

Pede-se aos eleitores que julguem o desempenho dos algoritmos e votem de acordo.

Algumas sugestões sobre como julgar: (Mais uma vez, obrigado @ mınxomaτ pelos mais alguns critérios.)

  • Se você apertar os olhos e a imagem parecer boa?
  • Você pode dizer exatamente onde está o patch?
  • Até que ponto as estruturas e texturas do fundo da imagem e da área circundante continuam?
  • Quantos pixels dispersos de cores falsas a área editada contém?
  • Existem blobs / blocos de cores uniformes na área que parecem não pertencer a isso?
  • A área editada possui mudanças drásticas de cor / contraste ou brilho em comparação com o restante da imagem?

Critério de validade

Para que um envio seja válido, a imagem de saída deve corresponder exatamente à imagem de entrada fora da área especificada.

Caso de teste

À esquerda, a imagem de origem, à direita, a máscara correspondente:


1
Podemos aceitar a entrada da máscara como argumentos de texto (por exemplo inpaint.exe left top width height img.jpg)?
mınxomaτ

1
Certamente, o formato de entrada / saída não é realmente tão importante, pois é um concurso de popularidade onde, antes de tudo, o desempenho do seu algoritmo é importante.
flawr

24
Este é um desafio muito prático. É possível que os resultados sejam melhores que os algoritmos existentes usados ​​no GIMP e em outros softwares de edição de imagens de código aberto. Fortuna, fama e glória podem ser suas!
Sparr

6
@Sparr e, finalmente, marcas d'água feias podem ser removidas da mídia baixada;)
Andras Deak

2
Builtins são perfeitamente ok, duvido que sejam muito populares.
flawr

Respostas:


142

AutoIt , VB

Introdução

Esta é uma implementação do algoritmo Remoção de Objetos por Inpainting Baseada em Exemplos desenvolvido por A. Criminisi, P. Perez (Cambridge Microsoft Research Ltd.) e K. Toyama (Microsoft) [X] . Esse algoritmo é direcionado a imagens de alta informação (e quadros de vídeo) e visa ser o equilíbrio entre reconstrução estrutural e reconstrução orgânica. Os parágrafos desta resposta contêm citações de texto completo do artigo original (uma vez que não está mais disponível oficialmente) para tornar essa resposta mais independente.

O Algoritmo

Objetivo : Substituir uma área selecionada ( mascarada ) (de preferência um objeto em primeiro plano visualmente separado) por fundos visualmente plausíveis.

Em trabalhos anteriores, vários pesquisadores consideraram a síntese de textura como uma maneira de preencher grandes regiões de imagem com texturas "puras" - padrões repetitivos de textura bidimensional com estocástica moderada. Isso se baseia em um amplo corpo de pesquisa de síntese de textura, que busca replicar a textura ad infinitum , dada uma pequena amostra de fonte de textura pura [1] [8] [9] [10] [11] [12] [14] [15] [16] [19] [22] .

Por mais eficazes que sejam essas técnicas na replicação de textura consistente, elas têm dificuldade em preencher buracos nas fotografias de cenas do mundo real, que geralmente consistem em estruturas lineares e texturas compostas - várias texturas interagindo espacialmente [23] . O principal problema é que os limites entre as regiões da imagem são um produto complexo de influências mútuas entre diferentes texturas. Em contraste com a natureza bidimensional das texturas puras, esses limites formam o que pode ser considerado mais estruturas de imagem unidimensionais ou lineares.

Imagem inpainting técnicas encher furos em imagens por propagação de estruturas lineares (chamados isofotas na inpainting literatura) para a região alvo através de difusão. Eles são inspirados pelas equações diferenciais parciais do fluxo de calor físico e funcionam de maneira convincente como algoritmos de restauração. Sua desvantagem é que o processo de difusão introduz um pouco de desfoque, o que é perceptível.

FIG.  2

A região a ser preenchida, ou seja, a região alvo é indicada por Ω e seu contorno é denotado δΩ. O contorno evolui para dentro à medida que o algoritmo progride, e também o chamamos de "frente de preenchimento". A região de origem, Φ, que permanece fixa em todo o algoritmo, fornece amostras usadas no processo de preenchimento. Agora, focamos em uma única iteração do algoritmo para mostrar como a estrutura e a textura são tratadas adequadamente por síntese baseada em exemplos. Suponha que o modelo quadrado Ψp ∈ Ω centrado no ponto p (fig. 2b) seja preenchido. A amostra de melhor correspondência da região de origem vem do patch Ψqˆ ∈ Φ, que é mais semelhante às partes que já foram preenchidas Ψp. No exemplo da fig. 2b, vemos que se liesp está na continuação de uma borda da imagem, as melhores correspondências provavelmente ocorrerão na mesma borda (ou de uma cor similar) (por exemplo, Ψq 'e Ψq' 'na figura 2c). Tudo o que é necessário para propagar a isofote para dentro é uma simples transferência do padrão do patch de melhor correspondência de origem (fig. 2d). Observe que a orientação isophote é preservada automaticamente. Na figura, apesar de a aresta original não ser ortogonal ao contorno alvo δΩ, a estrutura propagada manteve a mesma orientação da região de origem.

Detalhes de implementação e algoritmo

A funcionalidade desta implementação é encapsulada em uma DLL COM do ActiveX, que é descartada do programa host como um binário e, em seguida, invocada rapidamente, chamando o inpainter pelo IID. Nesse caso específico, a API é escrita em VisualBasic e pode ser chamada de qualquer idioma habilitado para COM. A seção a seguir do código descarta o binário:

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

A biblioteca é instanciada posteriormente usando o CLSID e o IID:

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

A biblioteca aceita um identificador GDIOBJECT, especificamente uma DIBSection de qualquer bitmap GDI / + (arquivos, fluxos etc.). O arquivo de imagem especificado é carregado e desenhado em um bitmap vazio construído a partir Scan0das dimensões da imagem de entrada.

O arquivo de entrada para esta implementação é qualquer formato de arquivo compatível com GDI / + que contém dados de imagem mascarada. As máscaras são uma ou mais regiões uniformemente coloridas na imagem de entrada. O usuário fornece um valor de cor RGB para a máscara, apenas pixels com exatamente esse valor de cor serão correspondidos. A cor de máscara padrão é verde (0, 255, 0). Todas as regiões mascaradas juntas representam a região de destino, Ω, a ser removida e preenchida. A região de origem, is, é definida como a imagem inteira menos a região de destino (Φ = I-Ω).

Em seguida, como em toda síntese de textura baseada em exemplos [10] , o tamanho da janela do modelo Ψ (também conhecido como " raio de varredura ") deve ser especificado. Essa implementação fornece um tamanho de janela padrão de 6² pixels, mas, na prática, exige que o usuário defina um pouco maior que o maior elemento de textura distinguível, ou "texel", na região de origem. Uma modificação adicional no algoritmo original é o " tamanho do bloco " definível pelo usuário, que determina a área de pixels a serem substituídos por uma nova cor uniforme. Isso aumenta a velocidade e diminui a qualidade. Os tamanhos de bloco maiores que 1px devem ser usados ​​em áreas extremamente uniformes (água, areia, pele etc.), no entanto, Ψ deve ser mantido no máximo. .5x o tamanho do bloco (que pode ser impossível dependendo da máscara).

Para não travar o algoritmo em imagens de 1 bits, toda vez que uma imagem com menos de 5 cores é recebida, o tamanho da janela é amplificado em 10x.

Uma vez determinados esses parâmetros, o restante do processo de preenchimento da região é completamente automático. Em nosso algoritmo, cada pixel mantém um valor de cor (ou "vazio", se o pixel não estiver preenchido) e um valor de confiança, que reflete nossa confiança no valor do pixel e é congelado quando o pixel é preenchido. Durante o curso do algoritmo, os patches ao longo da frente de preenchimento também recebem um valor de prioridade temporário, que determina a ordem em que eles são preenchidos. Em seguida, nosso algoritmo itera as três etapas a seguir até que todos os pixels tenham sido preenchidos.

Etapa 1: Computando as prioridades do patch

A ordem de preenchimento é crucial para a síntese não-paramétrica de texturas [1] [6] [10] [13] . Até agora, o favorito padrão tem sido o método "casca de cebola", onde a região de destino é sintetizada de fora para dentro, em camadas concêntricas. Nosso algoritmo executa essa tarefa por meio do melhor algoritmo de preenchimento que depende inteiramente dos valores de prioridade atribuídos a cada amostra na frente de preenchimento. O cálculo da prioridade é enviesado em direção àquelas amostras que estão na continuação de arestas fortes e cercadas por pixels de alta confiança; esses pixels são o limite, marcado pelo valor -2. O código a seguir recalcula as prioridades:

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

Dado um patch Ψp centrado no ponto p para alguns p ∈ δΩ (ver fig. 3), sua prioridade P (p) é definida como o produto da confiança calculada ( ComputeConfidenceou C (p) ) e o termo de dados ( ComputeData, ou D (p) ), onde

, Onde

| Ψp | é a área de Ψp, α é um fator de normalização (por exemplo, α = 255 para uma imagem típica de nível de cinza) e np é um vetor unitário ortogonal à frente δΩ no ponto p. A prioridade é calculada para cada patch de borda, com patches distintos para cada pixel no limite da região de destino.

Implementado como

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

O termo de confiança C (p) pode ser pensado como uma medida da quantidade de informações confiáveis ​​em torno do pixel p. A intenção é preencher primeiro os patches que já tenham mais pixels preenchidos, com preferência adicional aos pixels que foram preenchidos desde o início (ou que nunca fizeram parte da região de destino).

Isso incorpora automaticamente a preferência para determinadas formas ao longo da frente de preenchimento. Por exemplo, os patches que incluem cantos e gavinhas finas da região de destino tendem a ser preenchidos primeiro, pois são cercados por mais pixels da imagem original. Esses patches fornecem informações mais confiáveis ​​com as quais comparar. Por outro lado, as manchas na ponta das “penínsulas” de pixels preenchidos que se projetam na região de destino tendem a ser deixadas de lado até que mais pixels adjacentes sejam preenchidos. Em um nível aproximado, o termo C (p) de (1) aproximadamente impõe a ordem de preenchimento concêntrico desejável.

À medida que o preenchimento prossegue, os pixels nas camadas externas da região de destino tendem a ser caracterizados por maiores valores de confiança e, portanto, são preenchidos mais cedo; pixels no centro da região de destino terão valores de confiança menores. O termo de dados D (p) é uma função da força das isofotes atingindo a frente δΩ a cada iteração. Esse termo aumenta a prioridade de um patch para o qual uma isofote "flui". Esse fator é de fundamental importância em nosso algoritmo, pois estimula estruturas lineares a serem sintetizadas primeiro e, portanto, propagadas com segurança na região de destino. Linhas quebradas tendem a se conectar, realizando o "Princípio da Conectividade" da psicologia da visão [7] [17] .

A ordem de preenchimento depende das propriedades da imagem, resultando em um processo de síntese orgânica que elimina o risco de artefatos de “estrutura quebrada” e também reduz artefatos em blocos sem uma etapa cara de corte de adesivo [9] ou uma etapa de mistura indutora de desfoque [19 ] .

Etapa 2: Propagando informações de textura e estrutura

Depois de calculadas todas as prioridades na frente de preenchimento ( limite ), o patch Ψpˆ com maior prioridade é encontrado. Em seguida, preenchemos com dados extraídos da região de origem Φ. Propagamos a textura da imagem por amostragem direta da região de origem. Semelhante a [10] , procuramos na região de origem o patch mais semelhante ao topˆ. Formalmente,

, Onde

a distância d (Ψa, Ψb) entre dois patches genéricos anda e Ψb é simplesmente definida como a soma das diferenças quadráticas (SSD) dos pixels já preenchidos nos dois patches. Nenhuma análise ou manipulação adicional ( especialmente sem desfoque ) é feita nesta etapa. Este cálculo é executado no ciclo principal do ciclo e é implementado da seguinte maneira:

Obtendo a prioridade máxima:

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

Encontrando o patch mais semelhante:

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

Etapa 3: Atualizando Valores de Confiança

Após o patch Ψpˆ ter sido preenchido com novos valores de pixel, a confiança C (p) é atualizada na área delimitada por ˆpˆ da seguinte maneira:

Essa regra de atualização simples permite medir a confiança relativa dos patches na frente de preenchimento, sem parâmetros específicos da imagem. À medida que o preenchimento prossegue, os valores de confiança diminuem, indicando que temos menos certeza dos valores de cores dos pixels próximos ao centro da região de destino. Implementado aqui (junto com todas as outras atualizações necessárias):

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

Código completo

Aqui está o código executável, completo com o código fonte das bibliotecas como comentários.

O código é chamado por

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

Exemplos são incluídos na forma 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")

apenas remova o comentário do exemplo que você deseja executar usando CTRL+ Q.

Arquivos oficiais de teste

Este algoritmo é feito para ser ajustado para cada imagem. Portanto, os valores padrão (e também as máscaras padrão) são completamente abaixo do ideal. Os valores padrão são escolhidos para que cada amostra possa ser processada em um período de tempo razoável. Eu recomendo jogar com máscaras de forma irregular e melhores tamanhos de janelas. Clique nas imagens para aumentá-las!

Tabuleiro de damas

gótico americano

Labirinto

Monalisa

(máscara terrível)

Grito

Estrelado

Exemplos do mundo real

Todos eles usam máscaras personalizadas desenhadas à mão.

Se você tiver outras imagens interessantes que gostaria de ver incluídas, deixe um comentário.

Melhorias no EBII

Existem várias variantes do EBII por aí, criadas por vários pesquisadores. AnkurKumar Patel chamou minha atenção com sua coleção de artigos [24] sobre várias melhorias no EBII.

Especificamente, o artigo " Algoritmo Robusto Aprimorado para Pintura de Imagem Baseada em Exemplos " [25] menciona duas melhorias na pesagem dos valores de prioridade.

A melhoria

A modificação efetiva está na Etapa 1 (veja acima) do algoritmo e estende o efeito C (p) e D (p) na classificação de prioridade para esse pixel usando o seguinte:

Na fórmula para C e D dada acima, e são respectivamente o fator de normalização (por exemplo, α = 255), o vetor isofoto e o vetor unitário ortogonal à frente no ponto p.

Mais distante,

A função de prioridade é definida como a soma de peso do termo de confiança regularizado C (p) e o novo termo de dados D (p) . Onde α é o coeficiente de ajuste, é definido o seguinte 0Rp (p):

Onde α e β são respectivamente os pesos dos componentes da confiança e os termos dos dados. Note que α + β = 1 .

Pontuação objetiva

O que é realmente interessante é que este artigo contém um método proposto (e simples!) Para pontuar o desempenho se houver algoritmos EBII. Leve isso com um pouco de sal, pois esse é um método escolhido pelos próprios autores do artigo para verificar a eficácia da abordagem de variância proposta e a melhoria em várias imagens.

A avaliação do resultado é realizada através da comparação do PSNR (relação sinal de pico / ruído [26] ) entre a imagem restaurada e a imagem original. Geralmente, quanto maior o valor do PSNR, maior a semelhança da imagem reparada com a original. A equação para calcular o PSNR é a seguinte:

Estas são as impressionantes 2 (duas!) Imagens de teste do mundo real que eles usaram:

A conclusão é tão decepcionante quanto a qualidade do papel em si. Isso mostra muito pouca melhoria. O principal aqui é um possível método de pontuação de objetos para esse tipo de desafio (e outros desafios de reparo de imagem):

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

Meh.

Pesquisa a ser realizada

(Específico para EBII)

a) Pré-processamento

Tudo se resume ao princípio "Apagamento mágico" de que o algoritmo deve "apenas funcionar" para tudo. Minha solução ingênua para isso é uma amplificação baseada em cores (veja acima), mas existem maneiras melhores. Estou pensando em reconhecer a média geométrica de todos os texels rastreáveis ​​para ajustar automaticamente o tamanho da janela e tornar o tamanho do carimbo (também minha melhoria) dependente da resolução de texel e de imagem inteira. A pesquisa tem que ser feita aqui.

b) Pós-processamento

Os autores originais já fizeram um ótimo trabalho ao desmembrar todos os filtros de pós-processamento que vêm à mente. Hoje, tentei outra coisa, inspirada na sempre misteriosa Mona Lisa (graças ao undergroundmonorail). Se você pegar apenas a região não pintada e aplicar uma nova máscara a todos os estranhos blocos de cores e inseri-los em um algoritmo de despeckling, ficará com um resultado quase perfeito. Eu posso explorar isso em algum momento no futuro.


[X] - Remoção de Objetos por Pintura com Base em Exemplos por A. Criminisi, P. Perez, K. Toyama
[1] - M. Ashikhmin. Sintetizando texturas naturais. Em Proc. ACM Symp. em Interactive 3D Graphics, pp. 217–226, Research Triangle Park, NC, março de 2001.
[5] - M. Bertalmio, L. Vese, G. Sapiro e S. Osher. Pintura simultânea de estrutura e imagem de textura. para aparecer, 2002
[6] - R. Bornard, E. Lecan, L. Laborelli e JH. Chenot. Correção de dados ausentes em imagens estáticas e seqüências de imagens. Em ACM Multimedia, França, dezembro de 2002.
[7] - TF Chan e J. Shen. Pintura sem textura por difusões orientadas por curvatura (CDD). J. Visual Comm. Representante da Imagem, 4 (12), 2001.
[8] - JS de Bonet. Procedimento de amostragem multirresolução para análise e síntese de imagens de textura. Em Proc. ACM Conf. Comp. Graphics (SIGGRAPH), volume 31, pp. 361–368, 1997.
[9] - A. Efros e WT Freeman. Acolchoado de imagens para síntese e transferência de texturas. Em Proc. ACM Conf. Comp. Graphics (SIGGRAPH), pp. 341-346, Eugene Fiume, agosto de 2001.
[10] - A. Efros e T. Leung. Síntese de textura por amostragem não paramétrica. Em Proc. ICCV, pp. 1033-1038, Kerkyra, Grécia, setembro de 1999.
[11] - WT Freeman, EC Pasztor e OT Carmichael. Aprendendo visão de baixo nível. Int. J. Computer Vision, 40 (1): 25-47, 2000.
[12] - D. Garber. Modelos computacionais para análise e síntese de texturas. Tese de doutorado, Univ. do sul da Califórnia, EUA, 1981.
[13] - P. Harrison. Um procedimento não hierárquico para re-síntese de textura complexa. Em Proc. Int. Conf. Europa Central Comp. Gráficos, Visua. e Comp. Vision, Plzen, República Tcheca, fevereiro de 2001.
[14] - DJ Heeger e JR Bergen. Análise / síntese de textura baseada em pirâmide. Em Proc. ACM Conf. Comp. Graphics (SIGGRAPH), volume 29, pp. 229-233, Los Angeles, CA, 1995.
[15] - A. Hertzmann, C. Jacobs, N. Oliver, B. Curless e D. Salesin. Analogias de imagem. Em Proc. ACM Conf. Comp. Graphics (SIGGRAPH), Eugene Fiume, agosto de 2001.
[16] - H. Igehy e L. Pereira. Substituição de imagem através da síntese de textura. Em Proc. Int. Conf. Image Processing, pp. III: 186–190, 1997.
[17] - G. Kanizsa. Organização em visão. Praeger, Nova Iorque, 1979.
[19] - L. Liang, C. Liu, Y.-Q. Xu, B. Guo e H.-Y. Shum. Síntese de textura em tempo real por amostragem baseada em patch. Em ACM Transactions on Graphics, 2001.
[22] - L.-W. Wey e M. Levoy. Síntese rápida de textura usando quantização vetorial estruturada em árvore. Em Proc. ACM Conf. Comp. Graphics (SIGGRAPH), 2000.
[23] - A. Zalesny, V. Ferrari, G. Caenen e L. van Gool. Síntese de textura composta paralela. Oficina In Texture 2002 - (em conjunto com ECCV02), Copenhague, Dinamarca, junho de 2002.
[24] - AkurKumar Patel, Universidade Tecnológica de Gujarat, Ciência da Computação e Engenharia
[25] - Algoritmo Robusto Aprimorado para Pintura de Imagem Baseada em Exemplos
[26] - Wikipedia, Relação Pico-Sinal-Ruído


30
Isso é demais . A noite estrelada é tão boa. Ainda assim, isso Mona Lisa ...
Hannes Karppila

8
Primeiro, deixe-me dizer "oh meu Deus, isso é incrível". Segundo: eu já comentei "Que Mona Lisa é uma merda do SCP" em outra pergunta aqui, mas essa coruja realmente se parece com algo que poderia aparecer no wiki do SCP.
Undergroundmonorail

3
Os parágrafos citados mencionados podem ser feitos com blocos de cotação?
Trichoplax

1
@trichoplax Há pequenas modificações em quase todas as frases, não são citações exatas. Considere a descrição do algoritmo da mesma forma que a organização. papel, exceto quando diz modificação ou código. Eu não quero para atravancar a formatação mais :)
mınxomaτ

2
Quando tentei olhar para algo com muito cuidado nos meus sonhos, às vezes as coisas se tornam exatamente assim.
precisa saber é o seguinte

45

Matlab

Essa é uma abordagem simples de interpolação. A idéia é espelhar primeiro o que está em cada lado do patch. Em seguida, esses pixels da imagem espelhada são interpolados pela proximidade com a borda correspondente:

A parte complicada foi encontrar um bom peso de interpolação. Depois de algumas brincadeiras, criei uma função racional que é zero em todas as extremidades, exceto aquela em que espelhamos. Isso é transformado por um polinômio de terceiro grau para uma certa suavização:

Essa abordagem simples funciona surpreendentemente bem em imagens "naturais", mas assim que você se depara com arestas vivas, o jogo acaba. No exemplo gótico americano, os picos do garfo de feno alinham-se muito bem com a grade de pixels, o que faz com que pareça bastante agradável, mas teria sido muito pior caso contrário.

Então aqui estão os resultados:

E, finalmente, o código:

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 me assusta.
Andras Deak

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

8
Que Mona Lisa é uma merda SCP
undergroundmonorail

1
A imagem quadriculada parece muito legal IMHO.
ETHproductions

1
Eu não ficaria surpreso se você vencesse seu próprio desafio com isso. Esta é uma solução muito boa.
Alex A.

25

Mathematica

Isso usa a Inpaintfunção do Mathematica . Como o próprio Mathematica faz todo o trabalho pesado, este é um wiki da comunidade.

inPaint(abaixo) é uma adaptação simples de Inpaint. Para pinturas / fotos coloridas, ele usa a configuração padrão "TextureSynthesis". Se detectar que a imagem está em preto e branco (porque os dados da imagem são os mesmos da imagem binária da imagem), ela binariza a imagem e aplica o patch "TotalVariation". A Ifcláusula se aplica Binarizeou Identityà imagem. (A Identityfunção retorna seu argumento inalterado.)

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

A imagem e a máscara são inseridas como argumentos para inPaint. Partitione Gridsão apenas para fins de formatação.

entrada

As saídas foram corrigidas. Não houve retoque das imagens depois inPaint.

resultado


4
Pode ser uma coincidência, mas estou impressionado com o desempenho do labirinto!
flawr

1
@flawr eu jogaria em algo como isto só para mexer com esta solução;) (Quem sabe Esses preto-e-brancos são realmente desconcertante)?.
Andras Deak

17
Eu não acho que isso deva ser um wiki da comunidade.
Dennis

lawr, Sim, Inpaintparece procurar simetrias em toda a imagem em preto e branco. - DavidC 9 hours ago
DavidC 31/01

Você tem certeza de que o algoritmo em preto e branco não envolve sacrificar cabras em lugar algum? Como --- na Terra --- que diabos adivinha a estrutura central da imagem, se tudo está mascarado ??
Andras Deak

18

Python 2 e PIL

Este programa combina cópias das regiões Norte, Sul, Leste e Oeste para criar pixels de substituição que usam cores, texturas e sombras da região da imagem local.

O exemplo gera:

O código encontra primeiro a caixa delimitadora do patch. Em seguida, para cada pixel a ser gerado, ele calcula a cor de cada canal (RGB) com base na soma ponderada das 4 regiões circundantes.

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
Essa Mona Lisa também é aterrorizante! Todas as Mona Lisas neste desafio estão condenadas a ser assustadoras ??
undergroundmonorail

@undergroundmonorail Eu acho que rostos acidentais gerados por computador vêm diretamente das profundezas do vale misterioso .
Andras Deak

De onde você tira o PIL?
Elliot A.

@ElliotA. Meu entendimento é que o PIL propriamente dito está morto, mas era de código aberto e, portanto, vive sob o nome "Pillow". Se você pesquisar "travesseiro python" no Google, deverá encontrá-lo.
Undergroundmonorail

13

Python 3, PIL

Este programa usa o operador sobel e desenha linhas na imagem com base nisso.

O operador sobel encontra o ângulo de cada aresta; portanto, todas as arestas que extrudam para a área desconhecida devem continuar.

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

Enquanto isso, aqui estão as imagens de amostra.

insira a descrição da imagem aqui

insira a descrição da imagem aqui

insira a descrição da imagem aqui

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

insira a descrição da imagem aqui A área na imagem acima é tão lisa quanto um cacto

Não é muito bom com cores constantes.

insira a descrição da imagem aqui

insira a descrição da imagem aqui


1
Ah, e você poderia adicionar os casos de teste em preto e branco?
flawr

2
Parece realmente bom no Starry Night.
SuperJedi224

1
Uau, isso parece incrível agora! Os patches ainda são perceptíveis, mas uma ótima idéia nova! O meu favorito até agora =)
flawr

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

2
Você vence o concurso "mais assustadora da Mona Lisa", IMO. 0_o
DLosc 20/01

8

Python 2

Script python simples que cria um patch usando valores de pixels apenas fora da lacuna. Ele pega os valores de cores do final da linha e coluna dos pixels e calcula a média ponderada usando a distância desses pixels.

A produção não é tão bonita, mas é arte .

img1 img2 img3 img4 img5 img6

E então, codifique:

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

No momento em que você vê essas faixas horizontais / verticais, talvez você possa melhorá-lo incluindo outras direções!
flawr

Na verdade, eu tentei, mas não consegui obter bons resultados, então apenas decidi desfocar a imagem: D
Hannes Karppila

19
Finalmente, uma Mona Lisa que não me assusta até a morte, mas parece um assassino preso.
Andras Deak

6

Mathematica

Inpaint

Acontece que o Mathematica tem uma função interna que executa exatamente essa tarefa, e eu quero dizer exatamente :

Inpaint[image, region]

  • retoca partes imagecorrespondentes a elementos diferentes de zero em region.

Por padrão, ele usa um "método de síntese de textura mais adequado usando amostragem aleatória", que produz bons resultados nas pinturas, mas maus resultados para o labirinto e o tabuleiro de damas:

insira a descrição da imagem aqui insira a descrição da imagem aqui insira a descrição da imagem aqui insira a descrição da imagem aqui insira a descrição da imagem aqui insira a descrição da imagem aqui

Brincar com as configurações não me proporcionou um aumento na qualidade em todas as imagens, então eu apenas usei os padrões (para salvar bytes - codegolf.seafinal de contas!).


23
" Acontece que Mathematica tem uma função built-in " ... surpresa, surpresa;)
Andras Deak

Para o labirinto e o tabuleiro de damas, é melhor usar o método "TotalVariation" junto com Binarize(para eliminar manchas cinzentas). Tente isto: methods = {"TextureSynthesis", "Diffusion", "FastMarching", "NavierStokes", "TotalVariation"};g[pic_, mask_] := Join[{Labeled[Framed@pic, "Original"]}, Labeled[ Binarize@Inpaint[pic, mask, Method -> #], #] & /@ methods]
DavidC 30/01

@ DavidC Tentei os outros métodos, mas só TextureSynthesisfica bem nas pinturas; e acho que não podemos ajustar nossas configurações para cada caso de teste individual. (Se pudéssemos, então poderíamos trivialmente suprir a parte ausente como um 'ajuste'.)
2012rcampion

Os resultados do labirinto e do tabuleiro de damas são realmente intrigantes para mim. Por que a reconstrução da região desaparecida pelo Mathematica é tão irregular e assimétrica?
David Zhang

Isso detectará automaticamente se uma imagem é preto e branco e fará os ajustes apropriados (binários e método "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

Esta resposta implementa a idéia no artigo "Deep Image Prior" de Ulyanov et al. (CVPR 2018) Neste artigo, eles exploraram a ideia de que a maneira como as redes neurais de bom desempenho para o processamento de imagens são projetadas reflete de perto nossa idéia de como deve ser uma imagem natural (a distribuição "anterior").

Eles propuseram um método que pode ser usado para pintar, além de remover ruídos e artefatos, que apenas usa a imagem fornecida sem treinamento em nenhum outro dado. O conceito atual é bastante simples: a rede é treinada para produzir a imagem desejada (para algum ruído aleatório fixo como entrada) penalizando apenas os erros fora de uma determinada máscara. Se você deseja remover o ruído, não precisa mascarar nada, mas basta parar no início do treinamento.

Para pintar, você mascara a peça que deseja pintar e treina até a convergência. Certamente não é o estado da arte, mas eu ainda queria publicá-lo e publicá-lo aqui devido à simplicidade da ideia e ao desempenho ainda notável. Nas minhas experiências, a pintura de remendos maiores não resultou tão bem, mas para segmentos menores os resultados podem ser muito mais convincentes.

Eu implementei isso usando a popular arquitetura U-Net da jaxony no github . O código para treinar e processar as imagens pode ser encontrado abaixo.

Treinamento

Esta é uma visualização do processo de treinamento. Cada quadro é o estado de um certo número de iterações:

Exemplos

Código

Observe que em uma CPU, isso pode levar horas para executar apenas uma imagem, enquanto uma boa GPU habilitada para cuda pode levar muito menos tempo.

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

o Deep Image Prior usa algo diferente das U-Nets? parece que eles obteriam melhores resultados
somente ASCII

Além disso, você já tentou com o código do Deep Image Prior
somente ASCII

@ Somente ASCII Eles declaram no artigo que realmente usam principalmente a U-Net, mas não consigo encontrar os parâmetros exatos que eles usaram. Eles podem ter usado uma rede com maior capacidade. Eu só tinha um computador com uma quantidade muito limitada de energia. Então eu tive que escolher parâmetros que ainda se encaixam na memória e que não demoraram muito para serem treinados. Não sei exatamente quanto tempo levou, mas no computador que usei (apenas com uma CPU) essas imagens demoram vários dias. (Se você tiver uma GPU sobressalente ativada para cuda, avise-me :)
flawr

Também suspeito que, devido ao design da rede com máscaras retangulares, também não ser o ideal (e máscaras menores provavelmente também tenham melhor aparência), se você comparar, por exemplo, as primeiras imagens com as duas últimas (que não usam máscaras retangulares) .
flawr

4

Python com OpenCV

O OpenCV possui uma função chamada inpaint. Existem dois tipos de pintura usados, vou usar o método de marcha rápida. De acordo com a documentação, o algoritmo funciona assim:

Considere uma região na imagem a ser pintada. O algoritmo começa a partir do limite desta região e entra dentro da região, preenchendo gradualmente tudo primeiro. É preciso pintar uma pequena vizinhança ao redor do pixel no bairro. Esse pixel é substituído pela soma ponderada normalizada de todos os pixels conhecidos no bairro. A seleção dos pesos é uma questão importante. É dada mais ponderação aos pixels próximos ao ponto, próximos ao normal do limite e àqueles localizados nos contornos do limite. Depois que um pixel é pintado, ele passa para o próximo pixel mais próximo usando o Método de marcha rápida. O FMM garante que os pixels próximos aos pixels conhecidos sejam pintados primeiro, para que funcione como uma operação heurística manual.

Aqui está o código *:

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)

Observe como eu converto o BGR em RGB por motivos de plotagem. Além disso, eu giro. Aqui estão os resultados:

gótico

noite estrelada grito outra assustadora mona lisa!

Mona Lisa retorna!

linha 1

verificador

Como você pode ver, não sou o melhor com as duas cores.


Mona Lisa conseguiu um facelift
Conor O'Brien

3

Java

Uma abordagem de média de cores. Provavelmente pode ser melhorado.

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

Resultados:

insira a descrição da imagem aqui insira a descrição da imagem aqui insira a descrição da imagem aqui insira a descrição da imagem aqui insira a descrição da imagem aqui insira a descrição da imagem aqui


2
Por que você sempre obtém linhas nesse ângulo específico? Os cantos superiores esquerdo parecem corresponder relativamente bem, enquanto a parte inferior direita não corresponde.
flawr

Eu acho que tem a ver com a maneira pela qual eu itero pela região. Provavelmente vou mudar isso eventualmente.
SuperJedi224

Sempre parece que existem trapézios.
ericw31415

@ ericw31415 É um artefato da ordem de iteração.
precisa saber é o seguinte
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.