Parchear la imagen


114

En un popular software de edición de imágenes hay una característica, que los parches (el término utilizado en el procesamiento de imágenes es pintar como señaló @ mınxomaτ). Un área seleccionada de una imagen, basada en la información fuera de ese parche. Y hace un trabajo bastante bueno, considerando que es solo un programa. Como humano, a veces puedes ver que algo está mal, pero si aprietas los ojos o solo echas un vistazo, el parche parece llenar el vacío bastante bien.

ejemplo por el popular software de edición de imágenes

Desafío

Dada una imagen y una máscara que especifica un área rectangular de la imagen debe ser parcheada (también como imagen, o cualquier otro formato preferido), su programa debe tratar de llenar el área especificada con un parche que intente mezclarse con el resto de la imagen. El programa no puede usar la información de la imagen original que estaba dentro del área especificada.

Puede suponer que el parche siempre está al menos a lo ancho de los lados y a una altura de la parte superior e inferior de la imagen. Eso significa que el área máxima de un parche es 1/9 de la imagen completa.

Agregue una breve descripción sobre cómo funciona su algoritmo.

Votación

Se les pide a los votantes que juzguen qué tan bien funcionan los algoritmos y voten en consecuencia.

Algunas sugerencias sobre cómo juzgar: (De nuevo, gracias @ mınxomaτ por algunos criterios más).

  • Si entrecierra los ojos y la imagen se ve bien?
  • ¿Puedes decir exactamente dónde está el parche?
  • ¿Qué tan bien continúan las estructuras y texturas del fondo de la imagen y el área circundante?
  • ¿Cuántos píxeles perdidos de color falso contiene el área editada?
  • ¿Hay manchas / bloques de colores uniformes en el área que no parecen pertenecer allí?
  • ¿El área editada tiene cambios drásticos de color / contraste o brillo en comparación con el resto de la imagen?

Criterio de validez

Para que un envío sea válido, la imagen de salida debe coincidir exactamente con la imagen de entrada fuera del área especificada.

Caso de prueba

A la izquierda la imagen de origen, a la derecha la máscara correspondiente:


1
¿Podemos aceptar la entrada de máscara como argumentos de texto (p. Ej. inpaint.exe left top width height img.jpg)?
mınxomaτ

1
Claro, el formato de entrada / salida no es realmente tan importante, ya que es un concurso de popularidad donde, en primer lugar, el rendimiento de su algoritmo es importante.
flawr

24
Este es un desafío muy práctico. Es posible que los resultados sean mejores que los algoritmos existentes utilizados en GIMP y otro software de edición de imágenes de código abierto. ¡La fortuna, la fama y la gloria podrían ser tuyas!
Sparr

66
@Sparr y, finalmente, las marcas de agua feas pueden eliminarse de los medios descargados;)
Andras Deak

2
Las incorporaciones están perfectamente bien, aunque dudo que sean muy populares.
falla

Respuestas:


142

AutoIt , VB

Introducción

Esta es una implementación de la eliminación de objetos mediante el algoritmo de pintura basado en ejemplos desarrollado por A. Criminisi, P. Perez (Cambridge Microsoft Research Ltd.) y K. Toyama (Microsoft) [X] . Este algoritmo está dirigido a imágenes de alta información (y cuadros de video) y pretende ser el equilibrio entre la reconstrucción estructural y la reconstrucción orgánica. Los párrafos de esta respuesta contienen citas de texto completo del documento original (ya que ya no está oficialmente disponible) para hacer que esta respuesta sea más autónoma.

El algoritmo

Objetivo : reemplazar un área seleccionada ( enmascarada ) (preferiblemente un objeto de primer plano visualmente separado) con fondos visualmente plausibles.

En trabajos anteriores, varios investigadores consideraron la síntesis de texturas como una forma de llenar regiones de imágenes grandes con texturas "puras", patrones texturales repetitivos bidimensionales con estocasticidad moderada. Esto se basa en un gran cuerpo de investigación de síntesis de textura, que busca replicar la textura hasta el infinito , dada una pequeña muestra de textura pura [1] [8] [9] [10] [11] [12] [14] [15] [16] [19] [22] .

Tan efectivas como estas técnicas son para replicar texturas consistentes, tienen dificultades para llenar agujeros en fotografías de escenas del mundo real, que a menudo consisten en estructuras lineales y texturas compuestas, texturas múltiples que interactúan espacialmente [23] . El principal problema es que los límites entre las regiones de imagen son un producto complejo de influencias mutuas entre diferentes texturas. En contraste con la naturaleza bidimensional de las texturas puras, estos límites forman lo que podría considerarse estructuras de imagen más unidimensionales o lineales.

Las técnicas de pintura de imágenes llenan los agujeros en las imágenes mediante la propagación de estructuras lineales (llamadas isofotos en la literatura sobre pintura ) en la región objetivo mediante difusión. Se inspiran en las ecuaciones diferenciales parciales del flujo de calor físico y funcionan de manera convincente como algoritmos de restauración. Su inconveniente es que el proceso de difusión introduce algo de desenfoque, que es notable.

higo.  2

La región a rellenar, es decir, la región objetivo se indica con Ω, y su contorno se denota con δΩ. El contorno evoluciona hacia adentro a medida que avanza el algoritmo, por lo que también nos referimos a él como el "frente de relleno". La región fuente, Φ, que permanece fija en todo el algoritmo, proporciona muestras utilizadas en el proceso de llenado. Ahora nos centramos en una única iteración del algoritmo para mostrar cómo la estructura y la textura se manejan adecuadamente mediante síntesis basada en ejemplos. Suponga que la plantilla cuadrada Ψp ∈ Ω centrada en el punto p (fig. 2b) se debe llenar. La muestra de mejor coincidencia de la región de origen proviene del parche Ψqˆ ∈ Φ, que es más similar a las partes que ya están rellenadas en Ψp. En el ejemplo de la fig. 2b, vemos que si Ψp se encuentra en la continuación del borde de una imagen, las mejores coincidencias más probables se ubicarán a lo largo del mismo borde (o de un color similar) (por ejemplo, Ψq 'y Ψq' 'en la figura 2c). Todo lo que se requiere para propagar el isófoto hacia adentro es una simple transferencia del patrón desde el parche fuente de mejor coincidencia (figura 2d). Tenga en cuenta que la orientación isophote se conserva automáticamente. En la figura, a pesar de que el borde original no es ortogonal al contorno objetivo δΩ, la estructura propagada ha mantenido la misma orientación que en la región fuente.

Implementación y detalles del algoritmo

La funcionalidad de esta implementación se encapsula en una DLL COM de ActiveX que se elimina del programa host como binario y luego se invoca sobre la marcha llamando al intruso por IID. En este caso específico, la API está escrita en VisualBasic y se puede llamar desde cualquier lenguaje habilitado para COM. La siguiente sección del código elimina el binario:

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 biblioteca luego se instancia usando el CLSID y el IID:

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

La biblioteca acepta un identificador GDIOBJECT, específicamente una sección DIB de cualquier mapa de bits GDI / + (archivos, secuencias, etc.). El archivo de imagen especificado se carga y se dibuja en un mapa de bits vacío construido a partir Scan0de las dimensiones de la imagen de entrada.

El archivo de entrada para esta implementación es cualquier formato de archivo compatible con GDI / + que contenga datos de imagen enmascarados. La (s) máscara (s) es una o más regiones de color uniforme en la imagen de entrada. El usuario proporciona un valor de color RGB para la máscara, solo coincidirán los píxeles con exactamente ese valor de color. El color de enmascaramiento predeterminado es el verde (0, 255, 0). Todas las regiones enmascaradas juntas representan la región objetivo, Ω, que se eliminará y rellenará. La región de origen, Φ, se define como la imagen completa menos la región de destino (Φ = I − Ω).

A continuación, como con todas las síntesis de textura basadas en ejemplos [10] , se debe especificar el tamaño de la ventana de plantilla Ψ (también conocido como " radio de exploración "). Esta implementación proporciona un tamaño de ventana predeterminado de 6² píxeles, pero en la práctica requiere que el usuario establezca que sea un poco más grande que el elemento de textura distinguible más grande, o "texel", en la región de origen. Una modificación adicional al algoritmo original es el " tamaño de bloque " definible por el usuario que determina el área de píxeles que se reemplazará con un nuevo color uniforme. Esto aumenta la velocidad y disminuye la calidad. Los tamaños de bloque de más de 1px deben usarse con áreas extremadamente uniformes (agua, arena, pelaje, etc.), sin embargo, Ψ debe mantenerse al máximo. .5x el tamaño del bloque (que puede ser imposible dependiendo de la máscara).

Para no detener el algoritmo en imágenes de 1 bit, cada vez que se recibe una imagen con menos de 5 colores, el tamaño de la ventana se amplifica en 10x.

Una vez que se determinan estos parámetros, el resto del proceso de llenado de la región es completamente automático. En nuestro algoritmo, cada píxel mantiene un valor de color (o "vacío", si el píxel no está lleno) y un valor de confianza, que refleja nuestra confianza en el valor del píxel, y que se congela una vez que se ha llenado un píxel. Durante el curso del algoritmo, los parches a lo largo del frente de relleno también reciben un valor de prioridad temporal, que determina el orden en que se llenan. Luego, nuestro algoritmo itera los siguientes tres pasos hasta que se hayan completado todos los píxeles.

Paso 1: calcular las prioridades del parche

El orden de llenado es crucial para la síntesis de textura no paramétrica [1] [6] [10] [13] . Hasta ahora, el favorito predeterminado ha sido el método de "pelado de cebolla", donde la región objetivo se sintetiza desde afuera hacia adentro, en capas concéntricas. Nuestro algoritmo realiza esta tarea a través del mejor algoritmo de relleno que depende completamente de los valores de prioridad asignados a cada parche en el frente de relleno. El cálculo de prioridad está sesgado hacia aquellos parches que están en la continuación de bordes fuertes y que están rodeados por píxeles de alta confianza, estos píxeles son el límite, marcado por el valor -2. El siguiente código recalcula las 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 un parche Ψp centrado en el punto p para algunos p ∈ δΩ (ver figura 3), su prioridad P (p) se define como el producto de la confianza calculada ( ComputeConfidence, o C (p) ) y el término de datos ( ComputeData, o D (p) ), donde

, dónde

| Ψp | es el área de Ψp, α es un factor de normalización (p. ej., α = 255 para una imagen típica de nivel de gris), y np es un vector unitario ortogonal al frente δΩ en el punto p. La prioridad se calcula para cada parche de borde, con parches distintos para cada píxel en el límite de la región 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

El término de confianza C (p) puede considerarse como una medida de la cantidad de información confiable que rodea al píxel p. La intención es llenar primero aquellos parches que tienen más de sus píxeles ya rellenados, con preferencia adicional a los píxeles que se rellenaron desde el principio (o que nunca fueron parte de la región objetivo).

Esto incorpora automáticamente preferencia hacia ciertas formas a lo largo del frente de relleno. Por ejemplo, los parches que incluyen esquinas y zarcillos delgados de la región de destino tenderán a llenarse primero, ya que están rodeados por más píxeles de la imagen original. Estos parches proporcionan información más confiable contra la cual comparar. Por el contrario, los parches en la punta de las "penínsulas" de píxeles rellenos que sobresalen en la región de destino tenderán a dejarse de lado hasta que se llenen más de los píxeles circundantes. En un nivel aproximado, el término C (p) de (1) aproximadamente aplica el orden de relleno concéntrico deseable.

A medida que avanza el llenado, los píxeles en las capas externas de la región objetivo tenderán a caracterizarse por mayores valores de confianza y, por lo tanto, se rellenarán antes; los píxeles en el centro de la región de destino tendrán valores de confianza menores. El término de datos D (p) es una función de la fuerza de las isófotas que golpean el δΩ frontal en cada iteración. Este término aumenta la prioridad de un parche en el que un "isófoto" fluye ". Este factor es de fundamental importancia en nuestro algoritmo porque alienta a las estructuras lineales a sintetizarse primero y, por lo tanto, a propagarse de forma segura en la región objetivo. Las líneas discontinuas tienden a conectarse, dándose cuenta del "Principio de conectividad" de la psicología de la visión [7] [17] .

El orden de relleno depende de las propiedades de la imagen, lo que resulta en un proceso de síntesis orgánica que elimina el riesgo de artefactos de "estructura rota" y también reduce los artefactos en bloque sin un costoso paso de corte de parche [9] o un paso de mezcla que induce el desenfoque [19] ] .

Paso 2: Propagar información de textura y estructura

Una vez que se han calculado todas las prioridades en el frente de relleno ( límite ), se encuentra el parche Ψpˆ con la prioridad más alta. Luego lo llenamos con datos extraídos de la región fuente Φ. Propagamos la textura de la imagen mediante muestreo directo de la región de origen. Similar a [10] , buscamos en la región fuente ese parche que es más similar a Ψpˆ. Formalmente,

, dónde

La distancia d (Ψa, Ψb) entre dos parches genéricos Ψa y Ψb se define simplemente como la suma de las diferencias al cuadrado (SSD) de los píxeles ya rellenos en los dos parches. No se realiza ningún análisis o manipulación adicional ( especialmente sin desenfoque ) en este paso. Este cálculo se ejecuta en el ciclo principal y se implementa de la siguiente manera:

Obteniendo la máxima prioridad:

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

Encontrar el parche más similar:

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

Paso 3: actualizar los valores de confianza

Después de que el parche Ψpˆ se haya llenado con nuevos valores de píxeles, la confianza C (p) se actualiza en el área delimitada por Ψpˆ de la siguiente manera:

Esta simple regla de actualización nos permite medir la confianza relativa de los parches en el frente de relleno, sin parámetros específicos de la imagen. A medida que avanza el relleno, los valores de confianza disminuyen, lo que indica que estamos menos seguros de los valores de color de los píxeles cerca del centro de la región objetivo. Implementado aquí (junto con todas las demás actualizaciones necesarias):

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

Aquí está el código ejecutable, completo con el código fuente de las bibliotecas como comentarios.

El código es invocado por

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

Se incluyen ejemplos en 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")

simplemente descomente el ejemplo que desea ejecutar con CTRL+ Q.

Archivos de prueba oficiales

Este algoritmo está hecho para ajustarse para cada imagen. Por lo tanto, los valores predeterminados (y también las máscaras predeterminadas) son completamente subóptimos. Los valores predeterminados se eligen para que cada muestra se pueda procesar en un período de tiempo razonable. Recomiendo jugar con máscaras de formas irregulares y mejores tamaños de ventana. Haga clic en las imágenes para ampliar!

Tablero de damas

gótico americano

Laberinto

Mona Lisa

(máscara terrible)

Gritar

Estrellado

Ejemplos del mundo real

Todos estos usan máscaras personalizadas dibujadas a mano.

Si tiene otras imágenes interesantes que le gustaría ver incluidas, deje un comentario.

Mejoras EBII

Existen múltiples variantes de EBII, creadas por varios investigadores. AnkurKumar Patel me llamó la atención con su colección de documentos [24] sobre varias mejoras de EBII.

Específicamente, el documento " Algoritmo robusto mejorado para la incrustación de imágenes basadas en ejemplos " [25] menciona dos mejoras en la ponderación de los valores de prioridad.

El mejoramiento

La modificación efectiva se encuentra en el Paso 1 (ver arriba) del algoritmo, y extiende el efecto C (p) y D (p) en la clasificación de prioridad para este píxel usando esto:

En la fórmula para C y D dada anteriormente, y son respectivamente el factor de normalización (p. Ej., Α = 255), el vector isófoto y el vector unitario ortogonal al frente en el punto p.

Promover, adicional,

La función de prioridad se define como la suma ponderada del término de confianza regularizado C (p) y el nuevo término de datos D (p) . Donde α es el coeficiente de ajuste, la satisfacción de 0Rp (p) se define a continuación:

Donde α y β son respectivamente los pesos componentes de la confianza y los términos de datos. Tenga en cuenta que α + β = 1 .

Puntuación objetiva

Sin embargo, lo que es realmente interesante es que este documento contiene un método propuesto (¡y simple!) Para calificar el rendimiento de los algoritmos EBII. Sin embargo, tome esto con un grano de sal, ya que este es un método elegido por los propios autores del artículo para verificar la efectividad del enfoque de variación propuesto y la mejora en varias imágenes.

La evaluación del resultado se realiza comparando el PSNR (la relación señal / ruido de pico [26] ) entre la imagen restaurada y la imagen original. Generalmente, cuanto mayor es el valor de PSNR, mayor es la similitud de la imagen reparada con el original. La ecuación para calcular el PSNR es la siguiente:

Estas son las asombrosas 2 (¡dos!) Imágenes de prueba del mundo real que utilizaron:

La conclusión es tan decepcionante como la calidad del papel en sí. Muestra muy poca mejora. Lo principal aquí es un posible método de puntuación de objetos para este tipo de desafío (y otros desafíos de reparación de imágenes):

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

Meh

Investigación por hacer

(Específico para EBII)

a) Preprocesamiento

Todo se reduce al principio de "Borrado mágico" de que el algoritmo debería "simplemente funcionar" para todo. Mi solución ingenua para esto es una amplificación basada en el color (ver arriba), pero hay mejores formas. Estoy pensando en reconocer la media geométrica de todos los texels trazables para ajustar automáticamente el tamaño de la ventana y hacer que el tamaño del sello (también mi mejora) dependa de la resolución de texel e imagen completa. La investigación tiene que hacerse aquí.

b) Postprocesamiento

Los autores originales ya hicieron un gran trabajo al desacreditar todos los filtros de procesamiento posterior que vienen en mente. Hoy, probé algo más, inspirado en la siempre extraña Mona Lisa (gracias metro subterráneo). Si toma solo la región pintada y aplica una nueva máscara a todos los bloques extraños de color y la introduce en un algoritmo de eliminación de errores, tendrá un resultado casi perfecto. Puedo explorar esto en algún momento en el futuro.


[X] - Eliminación de objetos por pintura basada en ejemplos por A. Criminisi, P. Perez, K. Toyama
[1] - M. Ashikhmin. Sintetizando texturas naturales. En proc. ACM Symp. on Interactive 3D Graphics, págs. 217–226, Research Triangle Park, Carolina del Norte, marzo de 2001.
[5] - M. Bertalmio, L. Vese, G. Sapiro y S. Osher. Estructura simultánea e imagen de textura en pintura. a aparecer, 2002
[6] - R. Bornard, E. Lecan, L. Laborelli y JH. Chenot Falta la corrección de datos en imágenes fijas y secuencias de imágenes. En ACM Multimedia, Francia, diciembre de 2002.
[7] - TF Chan y J. Shen. Pintura sin textura mediante difusiones conducidas por curvatura (CDD). J. Visual Comm. Representante de imagen, 4 (12), 2001.
[8] - JS de Bonet. Procedimiento de muestreo multirresolución para análisis y síntesis de imágenes de textura. En proc. ACM Conf. Comp. Gráficos (SIGGRAPH), volumen 31, págs. 361–368, 1997.
[9] - A. Efros y WT Freeman. Acolchado de imagen para síntesis y transferencia de textura. En proc. ACM Conf. Comp. Gráficos (SIGGRAPH), págs. 341–346, Eugene Fiume, agosto de 2001.
[10] - A. Efros y T. Leung. Síntesis de textura por muestreo no paramétrico. En proc. ICCV, págs. 1033–1038, Kerkyra, Grecia, septiembre de 1999.
[11] - WT Freeman, EC Pasztor y OT Carmichael. Aprendizaje de visión de bajo nivel. En t. J. Computer Vision, 40 (1): 25–47, 2000.
[12] - D. Garber. Modelos computacionales para análisis de textura y síntesis de textura. Tesis doctoral, Univ. del sur de California, Estados Unidos, 1981.
[13] - P. Harrison. Un procedimiento no jerárquico para la resíntesis de textura compleja. En proc. En t. Conf. Europa Central Comp. Gráficos, Visua. y Comp. Vision, Plzen, República Checa, febrero de 2001.
[14] - DJ Heeger y JR Bergen. Análisis / síntesis de textura basada en pirámides. En proc. ACM Conf. Comp. Gráficos (SIGGRAPH), volumen 29, págs. 229–233, Los Ángeles, CA, 1995.
[15] - A. Hertzmann, C. Jacobs, N. Oliver, B. Curless y D. Salesin. Analogías de imagen. En proc. ACM Conf. Comp. Gráficos (SIGGRAPH), Eugene Fiume, agosto de 2001.
[16] - H. Igehy y L. Pereira. Reemplazo de imagen mediante síntesis de textura. En proc. En t. Conf. Procesamiento de imágenes, págs. III: 186–190, 1997.
[17] - G. Kanizsa. Organización en visión. Praeger, Nueva York, 1979.
[19] - L. Liang, C. Liu, Y.-Q. Xu, B. Guo y H.-Y. Shum Síntesis de textura en tiempo real mediante muestreo basado en parches. En ACM Transactions on Graphics, 2001.
[22] - L.-W. Wey y M. Levoy. Síntesis de textura rápida usando cuantificación vectorial estructurada en árbol. En proc. ACM Conf. Comp. Gráficos (SIGGRAPH), 2000.
[23] - A. Zalesny, V. Ferrari, G. Caenen y L. van Gool. Síntesis de textura compuesta paralela. En el taller Texture 2002 - (en conjunto con ECCV02), Copenhague, Dinamarca, junio de 2002.
[24] - AkurKumar Patel, Universidad Tecnológica de Gujarat, Ciencias de la Computación e Ingeniería
[25] - Algoritmo robusto mejorado para la impresión de imágenes basadas en ejemplos
[26] - Wikipedia, relación señal-ruido-pico


30
Esto es increíble . La noche estrellada es muy buena. Aún así, esa Mona Lisa ...
Hannes Karppila

8
Primero déjame decir "oh, Dios mío, esto es increíble". Segundo: ya he comentado "Que Mona Lisa es una mierda de SCP" en otra pregunta aquí, pero esa lechuza en realidad parece algo que podría aparecer en la wiki de SCP.
undergroundmonorail

3
¿Los párrafos citados que mencionas podrían ser bloques de citas?
trichoplax

1
@trichoplax Hay modificaciones menores en casi todas las oraciones, no son citas exactas. Considere la descripción del algoritmo igual que la organización. papel excepto cuando dice modificación o código. Ya no quiero saturar el formato :)
mınxomaτ

2
Cuando traté de mirar algo con mucho cuidado en mis sueños, a veces las cosas se vuelven exactamente así.
jimmy23013

45

Matlab

Este es un enfoque de interpolación simple. La idea es primero reflejar lo que está a cada lado del parche. Luego, esos píxeles de imagen especular se interpolan por lo cerca que están del borde correspondiente:

La parte difícil fue encontrar una buena interpolación de pesos. Después de jugar un poco, se me ocurrió una función racional que es cero en todos los bordes, excepto en el que nos reflejamos. Esto se transforma luego en un polinomio de tercer grado para un poco de suavizado:

Este enfoque simple funciona sorprendentemente bien en imágenes "naturales", pero tan pronto como te enfrentas a bordes afilados, el juego termina. En el ejemplo gótico americano, los picos del tenedor de heno se alinean muy bien con la cuadrícula de píxeles, lo que lo hace ver bastante bien, pero de lo contrario hubiera sido mucho peor.

Así que aquí los resultados:

Y finalmente, el 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 asusta.
Andras Deak

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

8
Que Mona Lisa es una mierda SCP
undergroundmonorail

1
La imagen a cuadros se ve realmente genial en mi humilde opinión.
ETHproductions

1
No me sorprendería si ganaras tu propio desafío con esto. Esta es una muy buena solución.
Alex A.

25

Mathematica

Esto usa la Inpaintfunción de Mathematica . Debido a que Mathematica hace todo el trabajo pesado, este es un wiki comunitario.

inPaint(abajo) es una simple adaptación de Inpaint. Para pinturas / fotos en color, utiliza el ajuste predeterminado "TextureSynthesis". Si detecta que la imagen es en blanco y negro (debido a que los datos de la imagen son los mismos que los de la forma binaria de la imagen), binariza la imagen y aplica el parche "TotalVariation". La Ifcláusula sea aplicable Binarizeo Identityla fotografía. (La Identityfunción devuelve su argumento sin cambios).

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

La imagen y la máscara se ingresan como argumentos para inPaint. Partitiony Gridson meramente para fines de formateo.

entrada

Las salidas han sido parcheadas. No hubo retoque de las imágenes después inPaint.

salida


44
Puede ser una coincidencia, ¡pero estoy sorprendido por el rendimiento del laberinto!
falla

1
@flawr, agregaría algo como esto solo para meterse con esta solución;) (¿Quién sabe? Esos negros y blancos realmente son desconcertantes).
Andras Deak

17
No creo que esto deba ser un wiki comunitario.
Dennis

lawr, sí, Inpaintparece buscar simetrías en toda la imagen en blanco y negro. - DavidC hace 9 horas
DavidC

¿Estás seguro de que el algoritmo en blanco y negro no implica sacrificar cabras en ninguna parte? ¿Cómo --- en la Tierra --- demonios adivina la estructura central de la imagen, si todo está enmascarado?
Andras Deak

18

Python 2 y PIL

Este programa combina copias de las regiones Norte, Sur, Este y Oeste para crear píxeles de reemplazo que usan colores, texturas y sombras de la región de la imagen local.

Las salidas de ejemplo:

El código primero encuentra el cuadro delimitador para el parche. Luego, para que se genere cada píxel, calcula el color de cada canal (RGB) en función de la suma ponderada de las 4 regiones 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
¡Esta Mona Lisa también es aterradora! ¿Están todas las Mona Lisas en este desafío condenadas a dar miedo?
undergroundmonorail

@undergroundmonorail Supongo que las caras accidentales generadas por computadora provienen directamente de las profundidades del valle misterioso .
Andras Deak

¿De dónde sacas PIL?
Elliot A.

@ElliotA. Tengo entendido que PIL propiamente dicho está muerto, pero era de código abierto y por eso sigue vivo bajo el nombre de "Pillow". Si buscas en Google "almohada de pitón", deberías encontrarla.
undergroundmonorail

13

Python 3, PIL

Este programa utiliza el operador sobel y dibuja líneas en la imagen en función de eso.

El operador de sobel encuentra el ángulo de cada borde, por lo que cualquier borde que se extrusione en el área desconocida debe 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')

Mientras tanto, aquí están las imágenes de muestra.

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

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

ingrese la descripción de la imagen aquí El área en la imagen de arriba es tan suave como un cactus

No es muy bueno con color constante.

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí


1
Ah, y ¿podría agregar los casos de prueba B / W?
flawr

2
Se ve muy bien en la noche estrellada.
SuperJedi224

1
Wow, esto se ve increíble ahora! ¡Los parches aún se notan pero es una gran idea nueva! Mi favorito hasta ahora =)
flawr

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

2
Ganas el concurso "La más aterradora Mona Lisa", OMI. 0_o
DLosc

8

Python 2

Script simple de Python que crea parches utilizando valores de píxeles justo fuera del espacio. Toma valores de color desde el final de la fila y la columna de los píxeles y calcula el promedio ponderado usando la distancia desde esos píxeles.

La salida no es tan bonita, pero es arte .

img1 img2 img3 img4 img5 img6

Y luego, código:

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

En el momento en que vea esas rayas horizontales / verticales, ¡tal vez pueda mejorarlo al incluir otras direcciones!
flawr

En realidad lo intenté, pero no pude obtener buenos resultados, así que decidí difuminar la imagen: D
Hannes Karppila

19
Finalmente, una Mona Lisa que no me da miedo de muerte, sino que parece un asesino arrestado.
Andras Deak

6

Mathematica

Inpaint

Sucede que Mathematica tiene una función incorporada que realiza exactamente esta tarea, y quiero decir exactamente :

Inpaint[image, region]

  • retoca partes de imageeso que corresponden a elementos distintos de cero en region.

De forma predeterminada, utiliza un "método de síntesis de textura que mejor se ajusta mediante muestreo aleatorio" que produce buenos resultados en las pinturas, pero malos resultados para el laberinto y el tablero de ajedrez:

ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

Jugar con la configuración no me proporcionó un aumento de la calidad en todas las imágenes, por lo que solo utilicé los valores predeterminados (para guardar bytes, ¡esto es codegolf.sedespués de todo!).


23
" Da la casualidad de que Mathematica tiene una función incorporada " ... sorpresa, sorpresa;)
Andras Deak

Para el laberinto y el tablero de ajedrez es mejor usar el método "TotalVariation" junto con Binarize(para eliminar manchas grises). Pruebe esto: methods = {"TextureSynthesis", "Diffusion", "FastMarching", "NavierStokes", "TotalVariation"};g[pic_, mask_] := Join[{Labeled[Framed@pic, "Original"]}, Labeled[ Binarize@Inpaint[pic, mask, Method -> #], #] & /@ methods]
DavidC

@DavidC Probé los otros métodos, pero solo se TextureSynthesisve bien en las pinturas; y no creo que se nos permita ajustar nuestra configuración para cada caso de prueba individual. (Si pudiéramos, entonces podríamos suministrar trivialmente la porción faltante como una 'configuración'.)
2012 Arcampion

Los resultados del laberinto y el tablero son realmente desconcertantes para mí. ¿Por qué la reconstrucción de Mathematica de la región faltante es tan irregular y asimétrica?
David Zhang

Esto detectará automáticamente si una imagen es en blanco y negro y realizará los ajustes apropiados (binarios y 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 respuesta implementa la idea en el documento "Deep Image Prior" de Ulyanov et al. (CVPR 2018) En este documento exploraron la idea de que la forma en que se diseñan las redes neuronales para el procesamiento de imágenes refleja de cerca nuestra idea de cómo debería ser una imagen natural (la distribución "previa").

Propusieron un método que se puede usar para pintar, así como para eliminar el ruido y los artefactos, que solo usa la imagen dada sin capacitación sobre ningún otro dato. El concepto real es bastante simple: la red está entrenada para emitir la imagen deseada (para algo de ruido aleatorio fijo como entrada) penalizando solo los errores fuera de alguna máscara dada. Si desea eliminar el ruido, simplemente no necesita enmascarar nada, solo deténgase temprano en el entrenamiento.

Para la pintura, enmascara la parte que desea pintar y entrena hasta la convergencia. Ciertamente no es lo último en tecnología, pero todavía quería publicarlo, probarlo y publicarlo aquí debido a la simplicidad de la idea y al rendimiento aún notable. En mis experimentos, la pintura de parches más grandes no resultó tan bien, pero para segmentos más pequeños los resultados pueden ser mucho más convincentes.

Implementé esto usando la popular arquitectura U-Net de jaxony en github . El código para entrenar y procesar las imágenes se puede encontrar a continuación.

Formación

Esta es una visualización del proceso de capacitación. Cada cuadro es el estado de un cierto número de iteraciones:

Ejemplos

Código

Tenga en cuenta que en una CPU esto puede tardar horas en ejecutarse para una sola imagen, mientras que una buena GPU habilitada para Cuda puede tomar mucho menos tiempo.

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

¿Deep Image Prior utiliza algo diferente de U-Nets? parece que obtendrían mejores resultados
solo ASCII

Además, ¿has probado con el código de Deep Image Prior
Solo ASCII

@ Solo ASCII En el documento afirman que en realidad usan principalmente la U-Net, pero no puedo encontrar los parámetros exactos que usaron. Podrían haber usado una red con mayor capacidad. Solo tenía una computadora con una cantidad de energía muy limitada. Así que tuve que elegir parámetros que aún encajaran en la memoria y que no tomaran mucho tiempo entrenar. No estoy seguro de cuánto tiempo tomó exactamente, pero en la computadora que usé (solo con una CPU) estas imágenes toman varios días. (Si tiene una GPU de repuesto habilitada para cuda, hágamelo saber :)
falte

También sospecho que debido al diseño de la red, tener máscaras rectangulares tampoco es ideal (y las máscaras más pequeñas probablemente también se verían mejor), si compara, por ejemplo, las primeras imágenes con las dos últimas (que no usan máscaras rectangulares) .
defecto

4

Python con OpenCV

OpenCV tiene una función llamada inpaint. Se usan dos tipos de pintura, usaré el Método de Marcha Rápida. Según la documentación, el algoritmo funciona así:

Considere una región en la imagen para pintar. El algoritmo comienza desde el límite de esta región y va dentro de la región llenando gradualmente todo en el límite primero. Se necesita pintar un pequeño vecindario alrededor del píxel en el vecindario. Este píxel se reemplaza por la suma ponderada normalizada de todos los píxeles conocidos en el vecindario. La selección de los pesos es un asunto importante. Se da más peso a los píxeles que se encuentran cerca del punto, cerca de la normalidad del límite y aquellos que se encuentran en los contornos del límite. Una vez que se pinta un píxel, se mueve al siguiente píxel más cercano utilizando el Método de Marcha Rápida. FMM asegura que esos píxeles cercanos a los píxeles conocidos se pinten primero, de modo que simplemente funcione como una operación heurística manual.

Aquí está el 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)

Tenga en cuenta cómo convierto el BGR a RGB por motivos de trazado. Además, lo giro. Aquí están los resultados:

gótico

noche estrellada gritar otra espeluznante mona lisa!

Mona Lisa regresa!

línea 1

inspector

Como puede ver, no soy el mejor con los dos colores.


Mona Lisa tiene un lavado de cara
Conor O'Brien

3

Java

Un enfoque de promedio de color. Probablemente se puede mejorar.

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:

ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí


2
¿Por qué siempre obtienes líneas en este ángulo particular? Las esquinas superiores izquierdas parecen coincidir relativamente bien, mientras que la parte inferior derecha no coincide en absoluto.
defecto

Creo que tiene que ver con la forma en que itero por la región. Probablemente cambiaré eso eventualmente.
SuperJedi224 01 de

Siempre parece que hay trapecios.
ericw31415

@ ericw31415 Es un artefacto del orden de iteración.
SuperJedi224
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.