Eliminar el fondo blanco de una imagen y hacerla transparente

82

Estamos tratando de hacer lo siguiente en Mathematica: RMagick eliminar el fondo blanco de la imagen y hacerla transparente .

Pero con fotos reales termina luciendo horrible (como si tuviera un halo alrededor de la imagen).

Esto es lo que hemos probado hasta ahora:

unground0[img_] := With[{mask = ChanVeseBinarize[img, TargetColor->{1.,1.,1.}]},
  Rasterize[SetAlphaChannel[img, ImageApply[1-#&, mask]], Background->None]]]

Aquí hay un ejemplo de lo que hace.

Imagen original:

imagen original

Imagen con el fondo blanco reemplazado sin fondo (o, para fines de demostración aquí, un fondo rosa):

imagen con fondo transparente - en realidad, un fondo rosa aquí, para que el problema del halo sea obvio

¿Alguna idea para deshacerse de ese halo? Al ajustar cosas como LevelPenalty, solo puedo hacer que el halo desaparezca a expensas de perder parte de la imagen.

EDITAR: Para que pueda comparar soluciones para la recompensa, por favor, estructure su solución como la anterior, es decir, una función autónoma llamada unground, algo que toma una imagen y devuelve una imagen con fondo transparente.

dreeves
fuente
1
¡Muchas gracias a todos por la ayuda hasta ahora! Una gran recompensa por esto tan pronto como stackoverflow me permita agregar uno. Y, según el espíritu de stackoverflow articulado por los fundadores, ¡deben sentirse libres de robarse unos a otros para hacer que su respuesta sea la definitiva!
dreeves el
3
Primero 500 recompensas y luego "¡Les animo a todos a que se presten generosamente para mejorarlo si es posible!" - quieres una pelea de perros, ¿no?
Mr.Wizard
@ Mr.Wizard, :) Sin embargo, no me lo estoy inventando, que los fundadores (Jeff y Joel) han dicho desde el principio que eso es alentador. La idea es que la respuesta principal sea realmente completa y definitiva. (¡Y obviamente también tengo motivos ocultos en este caso!)
dreeves
2
Para los demasiado curiosos, esta es la estación de trabajo con computadora "FREDRIK" de IKEA: ikea.com/us/en/catalog/products/60111123
Arnoud Buzing
1
@dreeves, usé tineye.com .
Arnoud Buzing

Respuestas:

45

Quizás, dependiendo de la calidad del borde que necesite:

img = Import@"http://i.stack.imgur.com/k7E1F.png";
mask = ChanVeseBinarize[img, TargetColor -> {1., 1., 1.}, "LengthPenalty" -> 10]
mask1 = Blur[Erosion[ColorNegate[mask], 2], 5]
Rasterize[SetAlphaChannel[img, mask1], Background -> None]

ingrese la descripción de la imagen aquí

Editar

Stealing a bit from @Szabolcs

img2 = Import@"http://i.stack.imgur.com/k7E1F.png";
(*key point:scale up image to smooth the edges*)
img = ImageResize[img2, 4 ImageDimensions[img2]];
mask = ChanVeseBinarize[img, TargetColor -> {1., 1., 1.}, "LengthPenalty" -> 10];
mask1 = Blur[Erosion[ColorNegate[mask], 8], 10];
f[col_] := Rasterize[SetAlphaChannel[img, mask1], Background -> col, 
                     ImageSize -> ImageDimensions@img2]
GraphicsGrid[{{f@Red, f@Blue, f@Green}}]

ingrese la descripción de la imagen aquí

Click para agrandar

Editar 2

Solo para tener una idea del alcance del halo y las imperfecciones del fondo en la imagen:

img = Import@"http://i.stack.imgur.com/k7E1F.png";
Join[{img}, MapThread[Binarize, {ColorSeparate[img, "HSB"], {.01, .01, .99}}]]

ingrese la descripción de la imagen aquí

ColorNegate@ImageAdd[EntropyFilter[img, 1] // ImageAdjust, ColorNegate@img]

ingrese la descripción de la imagen aquí

Dr. belisario
fuente
Lamentablemente, en mi máquina su código no produce en absoluto la misma calidad de resultado. ¿Fue img la imagen de 500x500 que se publicó en la pregunta? Si es así, tal vez una cosa de mac / windows ...
Matthias Odisio
@Matthias Sí, el img es una copia / pegado del original. Mma 8.01 en Windows.
Dr. belisarius
Oh ... quizás el optimizador produce un resultado diferente debido a un pequeño ruido aritmético. De todos modos, me alegro de que esté funcionando bien con este conjunto de parámetros.
Matthias Odisio
Eso no parece que funcione. Solo está difuminando los bordes.
user541686
48

Esta función implementa la combinación inversa descrita por Mark Ransom, para una mejora adicional pequeña pero visible:

reverseBlend[img_Image, alpha_Image, bgcolor_] :=
 With[
  {c = ImageData[img], 
   a = ImageData[alpha] + 0.0001, (* this is to minimize ComplexInfinitys and considerably improve performance *)
   bc = bgcolor},

  ImageClip@
   Image[Quiet[(c - bc (1 - a))/a, {Power::infy, 
       Infinity::indet}] /. {ComplexInfinity -> 0, Indeterminate -> 0}]
  ]

Esta es la función de eliminación de fondo. El thresholdparámetro se utiliza para la binarización inicial de la imagen, el minSizeCorrectiones para ajustar el límite de tamaño de los pequeños componentes basura que se eliminarán después de la binarización.

removeWhiteBackground[img_, threshold_: 0.05, minSizeCorrection_: 1] :=
  Module[
  {dim, bigmask, mask, edgemask, alpha},
  dim = ImageDimensions[img];
  bigmask = 
   DeleteSmallComponents[
    ColorNegate@
     MorphologicalBinarize[ColorNegate@ImageResize[img, 4 dim], threshold], 
    Round[minSizeCorrection Times @@ dim/5]];
  mask = ColorNegate@
    ImageResize[ColorConvert[bigmask, "GrayScale"], dim];
  edgemask = 
   ImageResize[
    ImageAdjust@DistanceTransform@Dilation[EdgeDetect[bigmask, 2], 6],
     dim];
  alpha = 
   ImageAdd[
    ImageSubtract[
     ImageMultiply[ColorNegate@ColorConvert[img, "GrayScale"], 
      edgemask], ImageMultiply[mask, edgemask]], mask];
  SetAlphaChannel[reverseBlend[img, alpha, 1], alpha]
  ]

Probando la función:

img = Import["http://i.stack.imgur.com/k7E1F.png"];

background = 
  ImageCrop[
   Import["http://cdn.zmescience.com/wp-content/uploads/2011/06/\
forest2.jpg"], ImageDimensions[img]];

result = removeWhiteBackground[img]

ImageCompose[background, result]
Rasterize[result, Background -> Red]
Rasterize[result, Background -> Black]

Muestra

Breve explicación de cómo funciona:

  1. Elija su método de binariaización favorito que produzca bordes afilados relativamente precisos

  2. Aplíquelo a una imagen mejorada, luego reduzca la escala obtenida maskal tamaño original. Esto nos da antialiasing. La mayor parte del trabajo está hecho.

  3. Para una pequeña mejora, combine la imagen con el fondo usando el brillo de su negativo como alfa, luego mezcle la imagen obtenida sobre el original en una región delgada alrededor de los bordes ( edgemask) para reducir la visibilidad de los píxeles blancos en los bordes. Se calcula el canal alfa correspondiente a estas operaciones (la ImageMultiply/Addexpresión algo críptica ).

  4. Ahora tenemos una estimación del canal alfa para que podamos hacer una mezcla inversa.

Los pasos 3 y 4 no mejoran mucho, pero la diferencia es visible.

Szabolcs
fuente
@belisarius no se trata de inglés, sé que mi nombre se ve muy inusual para la mayoría :-)
Szabolcs
Parece una bonita enfermedad. Apellido húngaro para mí :)
Dr. belisarius
@belisarius En realidad, es un nombre, o más precisamente un nombre de pila, ya que en húngaro el apellido va primero y el nombre de pila al final.
Szabolcs
2
La sombra del caso todavía está allí en la segunda figura como una banda grisácea en la parte inferior ...
Sjoerd C. de Vries
@ SjoerdC.deVries Eso es cierto, pero creo que para esta tarea debería ser así ... no hay forma de saber que es una sombra y no parte del objeto. La mayoría de las imágenes en Amazon tenían sombras o eran aburridas y triviales, así que elegí esta.
Szabolcs
22

Voy a hablar de manera genérica, no específicamente en referencia a Mathematica. No tengo idea de si estas operaciones son difíciles o triviales.

El primer paso es estimar un nivel alfa (transparencia) para los píxeles en el borde de la imagen. En este momento, estás usando un umbral estricto, por lo que el alfa es 0% totalmente transparente o 100% totalmente opaco. Debe definir un rango entre el blanco total del fondo y los colores que son indiscutiblemente parte de la imagen, y establecer una proporción adecuada: si el color está más cerca del fondo, es alfa bajo, y si está más cerca del corte más oscuro, entonces es un alfa alto. Después de eso, puede realizar ajustes basados ​​en los valores alfa circundantes: cuanto más un píxel esté rodeado de transparencia, más probable será que sea transparente.

Una vez que tenga los valores alfa, debe hacer una mezcla inversa para obtener el color adecuado. Cuando una imagen se muestra sobre un fondo, se combina de acuerdo con el valor alfa utilizando la fórmula c = bc*(1-a)+fc*adonde bces el color de fondo y fcel color de primer plano. En su caso, el fondo es blanco (255,255,255) y el color de primer plano es lo desconocido, por lo que invertimos la fórmula: fc = (c - bc*(1-a))/a. Cuando a=0la fórmula pide una división por cero, pero el color no importa de todos modos, así que use blanco o negro.

Mark Ransom
fuente
3
Gran respuesta. La estimación alfa es en realidad un campo de investigación completo, por ejemplo, ai.stanford.edu/~ruzon/alpha
mpenkov
2
De acuerdo, gran respuesta; gracias Mark! Por la recompensa (cuando stackoverflow me permite agregar uno), aunque planeo ir con la solución completamente implementada que se vea mejor. Hasta ahora, de Belisario, estoy pensando.
dreeves el
11

Aquí hay un intento de implementar el enfoque de Mark Ransom, con la ayuda de la generación de máscaras de belisarius:

Localice el límite del objeto:

img1 = SetAlphaChannel[img, 1];
erosionamount=2;
mb = ColorNegate@ChanVeseBinarize[img, TargetColor -> {1., 1., 1}, 
      "LengthPenalty" -> 10];
edge = ImageSubtract[Dilation[mb, 2], Erosion[mb, erosionamount]];

ImageApply[{1, 0, 0} &, img, Masking ->edge]

borde de la figura

Establezca los valores alfa:

edgealpha = ImageMultiply[ImageFilter[(1 - Mean[Flatten[#]]^5) &, 
   ColorConvert[img, "GrayScale"], 2, Masking -> edge], edge];
imagealpha = ImageAdd[edgealpha, Erosion[mb, erosionamount]];
img2 = SetAlphaChannel[img, imagealpha];

Mezcla de color inverso:

img3 = ImageApply[Module[{c, \[Alpha], bc, fc},
   bc = {1, 1, 1};
   c = {#[[1]], #[[2]], #[[3]]};
   \[Alpha] = #[[4]];
   If[\[Alpha] > 0, Flatten[{(c - bc (1 - \[Alpha]))/\[Alpha], \[Alpha]}], {0., 0., 
   0., 0}]] &, img2];

Show[img3, Background -> Pink]

fondo rosa

¿Observa cómo algunos de los bordes tienen pelusa blanca? Compare eso con el contorno rojo de la primera imagen. Necesitamos un mejor detector de bordes. El aumento de la cantidad de erosión ayuda con la pelusa, pero luego los otros lados se vuelven demasiado transparentes, por lo que hay una compensación en el ancho de la máscara de borde. Sin embargo, es bastante bueno, considerando que no existe una operación de desenfoque per se.

Sería instructivo ejecutar el algoritmo en una variedad de imágenes para probar su robustez, para ver qué tan automático es.

JxB
fuente
Hmmm, para mí img2 se ve mejor (ver la parte inferior de la superficie de la mesa) que img3. ¿Quizás la combinación de colores inversa es innecesaria?
JxB
10

Simplemente jugando como principiante, es increíble la cantidad de herramientas disponibles.

b = ColorNegate[
    GaussianFilter[MorphologicalBinarize[i, {0.96, 0.999}], 6]];
c = SetAlphaChannel[i, b];
Show[Graphics[Rectangle[], Background -> Orange, 
     PlotRangePadding -> None], c]

cormullion
fuente
9

Soy completamente nuevo en el procesamiento de imágenes, pero esto es lo que obtengo después de jugar con las nuevas funciones de procesamiento de imágenes morfológicas de la versión 8:

mask = DeleteSmallComponents[
   ColorNegate@
    Image[MorphologicalComponents[ColorNegate@img, .062, 
      Method -> "Convex"], "Bit"], 10000];
Show[Graphics[Rectangle[], Background -> Red, 
  PlotRangePadding -> None], SetAlphaChannel[img, ColorNegate@mask]]

imagen

Alexey Popkov
fuente
3
Creo que Dreeves está tratando de deshacerse de esas líneas irregulares en los bordes.
Dr. belisarius
1
Es cierto que esto hace un buen trabajo al reducir ese halo, pero la irregularidad podría ser un factor decisivo. @belisarius, ¡tu versión se ve increíble!
dreeves el
@dreeves Creo que los bordes se pueden mejorar (en mi versión) usando una transformación de distancia después del desenfoque, pero eso ya lo notó el Sr. Wiz, así que le dejo el experimento.
Dr. belisarius
¿Qué hace Method -> "Convex"? No esta documentado.
Szabolcs
¡Lo siento! Me doy cuenta de que confundí MorphologicalComponents y MorphologicalBinarize, que de hecho son funciones no relacionadas.
Szabolcs
6

Recomiendo usar Photoshop para esto y guardarlo como PNG.

entretenimiento angelfilm
fuente
5
Buen punto, pero ¿cuál es el algoritmo que usa Photoshop para hacer esto tan bien? (Y, por supuesto, queremos automatizar esto, no hacer clic con la varita mágica en Photoshop para cada imagen.)
dreeves
3
Por cierto, creo que es útil señalar esto (¡fácilmente podría haber sido un nerd de Mathematica tan grande que el photoshop no se me habría ocurrido!). Y resulta que incluso se puede programar en Photoshop, por lo que esta puede ser la mejor respuesta posible en ese sentido, si Photoshop está haciendo algo realmente inteligente que no se puede duplicar con un pequeño programa matemático.
dreeves el
5
Hay una razón por la que Adobe puede cobrar 500 smakeroos por su software ;-).
Timo
7
Quizás podría publicar una versión de la imagen generada por un script de PhotoShop (sin intervención manual :-) como referencia - sabríamos lo que tenemos que vencer ...
cormullion
5

Posibles pasos que podría tomar:

  • dilatar la mascarilla
  • difuminarlo
  • usando la máscara, establezca la transparencia por distancia del blanco
  • con la máscara, ajuste la saturación de modo que los colores que antes eran más blancos estén más saturados.
Señor mago
fuente
Buenos pensamientos; ¡gracias! Me encantaría obtener un código de propósito general para esto. Probablemente pondremos una gran recompensa en un par de días (cuando stackoverflow nos lo permita) si quieres volver entonces. De hecho, por la presente me comprometo a hacerlo, si eso es un incentivo para sumergirse. :)
dreeves
@dreeves Me parece bien; Ahora no tengo tiempo, pero intentaré volver a hacerlo.
Mr.Wizard
3

Simplemente reemplace cualquier píxel que esté "casi cerca del blanco" con un píxel del mismo color RGB y un degradado sigmoide en el canal de transparencia. Puede aplicar una transición lineal de sólido a transparente, pero Sinusoide, Sigmoide o Tanh se ven más naturales, dependiendo de la nitidez del borde que esté buscando, se mueven rápidamente del medio a sólido o transparente, pero no en pasos / binarios. manera, que es lo que tienes ahora.

Piénsalo de esta manera:

Digamos que R, G, B son cada uno 0.0-1.0, luego representemos el blanco como un solo número como R + G + B = 1.0 * 3 = 3.0.

Quitar un poco de cada color lo hace un poco "blanquecino", pero quitar un poco de los 3 es quitarlo mucho más que un poco de cualquiera. Digamos que permite una reducción del 10% en cualquier canal: 1.0 * .10 = .1. Ahora distribuya esta pérdida entre los tres y únala entre 0 y 1 para el canal alfa, si es menor que .1, de modo que ( pérdida = 0.9) => 0 y (pérdida = 1.0) => 1:

threshold=.10;
maxLoss=1.0*threshold;
loss=3.0-(R+G+B);
alpha=If[loss>maxLoss,0,loss/maxLoss];
(* linear scaling is used above *)
(* or use 1/(1 + Exp[-10(loss - 0.5maxLoss)/maxLoss]) to set sigmoid alpha *)
(* Log decay: Log[maxLoss]/Log[loss]
      (for loss and maxLoss <1, when using RGB 0-255, divide by 255 to use this one *)

setNewPixel[R,G,B,alpha];

Para referencia:

maxLoss = .1;
Plot[{ 1/(1 + Exp[-10(loss - 0.5maxLoss)/maxLoss]),
       Log[maxLoss]/Log[loss],
       loss/maxLoss
     }, {loss, 0, maxLoss}]

El único peligro (¿o beneficio?) Que tienes en esto, es que a esto no le importan los blancos que en realidad SON parte de la foto. Elimina todos los blancos. De modo que si tiene una imagen de un automóvil blanco, terminará teniendo parches transparentes. Pero a partir de su ejemplo, ese parece ser un efecto deseado.

Gregory Klopper
fuente
Creo que la idea de ChanVeseBinarize es ser inteligente al respecto y no convertir los píxeles blancos en transparentes a menos que sean parte de un área más grande de blanco, es decir, es muy probable que formen parte del fondo.
dreeves
El problema con el "área más grande" es que podría ser importante, mientras que el área pequeña podría no ser importante. En un automóvil blanco, todo el costado sería importante, pero se etiquetaría como una gran mancha blanca. El espacio entre dos personas sobre un fondo blanco sería pequeño y con bordes complejos, pero debe desaparecer. Tendría que tener una IA al estilo de la máquina Boltzman que reconozca formas comunes y vea si el blanco es el espacio o parte del objeto, pero aún no hemos llegado allí.
Gregory Klopper
1
También puede tomar 2 imágenes desde perspectivas ligeramente diferentes y luego usar la deducción de dimensionalidad de las imágenes estéreo para averiguar qué píxeles son el fondo según el lugar donde ocurren las oclusiones.
Gregory Klopper