Удалите белый фон с изображения и сделайте его прозрачным - PullRequest
80 голосов
/ 07 ноября 2011

Мы пытаемся сделать следующее в Mathematica:
RMagick удаляет белый фон с изображения и делает его прозрачным

Но на реальных фотографиях это выглядит паршиво (какореол вокруг изображения).

Вот что мы уже попробовали:

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

Вот пример того, что это делает.

Исходное изображение:

original image

Изображение с замененным белым фоном без фона (или, для демонстрационных целей, с розовым фоном):

image with transparent background -- actually a pink background here, to make the halo problem obvious

Есть идеи, как избавиться от этого ореола?Настраивая такие вещи, как LevelPenalty, я могу заставить гало исчезнуть только за счет потери части изображения.

РЕДАКТИРОВАТЬ: чтобы я мог сравнить решения для награды, пожалуйста, структурируйте ваше решение, как указано выше,Автономная функция с именем unground-нечто, которая берет изображение и возвращает изображение с прозрачным фоном.Большое спасибо всем!

Ответы [ 9 ]

48 голосов
/ 11 ноября 2011

Эта функция реализует обратную смесь, описанную Марком Рэнсомом, для дополнительного небольшого, но видимого улучшения:

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}]
  ]

Это функция удаления фона. Параметр threshold используется для начальной бинаризации изображения, minSizeCorrection - для настройки ограничения размера небольших нежелательных компонентов, которые должны быть удалены после бинаризации.

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

Проверка функции:

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]

Sample

Краткое объяснение того, как это работает:

  1. Выберите ваш любимый метод бинаризации, который дает относительно точные острые края

  2. Примените его к увеличенному изображению, затем уменьшите полученное mask до исходного размера. Это дает нам сглаживание. Большая часть работы выполнена.

  3. Для небольшого улучшения смешайте изображение с фоном, используя яркость его негатива как альфа, затем смешайте полученное изображение с оригиналом в тонкой области по краям (edgemask), чтобы уменьшить видимость белых пикселей по краям. Рассчитывается альфа-канал, соответствующий этим операциям (несколько загадочное выражение ImageMultiply/Add).

  4. Теперь у нас есть оценка альфа-канала, поэтому мы можем сделать обратное смешивание.

Шаги 3 и 4 не так сильно улучшаются, но разница видна.

44 голосов
/ 08 ноября 2011

Возможно, в зависимости от требуемого качества кромки:

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]

enter image description here

Редактировать

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}}]

enter image description here

Нажмите для увеличения

Редактировать 2

Просто чтобы получить представление о степени гало и несовершенствах фона на изображении:

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

enter image description here

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

enter image description here

22 голосов
/ 08 ноября 2011

Я собираюсь говорить в общем, а не конкретно в отношении Mathematica. Я понятия не имею, являются ли эти операции сложными или тривиальными.

Первым шагом является оценка альфа (прозрачности) уровня для пикселей на краю изображения. Прямо сейчас вы используете строгий порог, поэтому альфа либо 0% полностью прозрачна, либо 100% полностью непрозрачна. Вы должны определить диапазон между общим белым цветом фона и цветами, которые являются неотъемлемой частью изображения, и установить соответствующую пропорцию - если он ближе по цвету к фону, то это низкий альфа, и если он ближе к более темному срезу, то это высокая альфа. После этого вы можете вносить корректировки на основе окружающих альфа-значений - чем больше пиксель окружен прозрачностью, тем больше вероятность того, что он сам станет прозрачным.

Как только у вас есть альфа-значения, вам нужно сделать обратное смешивание, чтобы получить правильный цвет. Когда изображение отображается поверх фона, оно смешивается в соответствии с альфа-значением, используя формулу c = bc*(1-a)+fc*a, где bc - это цвет фона, а fc - это цвет переднего плана. В вашем случае фон белый (255 255 255), а цвет переднего плана неизвестен, поэтому мы обращаемся к формуле: fc = (c - bc*(1-a))/a. Когда a=0 формула требует деления на ноль, но цвет в любом случае не имеет значения, просто используйте черный или белый.

11 голосов
/ 11 ноября 2011

Вот попытка реализации подхода Марка Рэнсома с помощью генерации маски Велисария:

Найдите границу объекта:

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]

figure edge

Установите значения альфа:

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

Обратная цветовая смесь:

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]

pink background

Обратите внимание, что на некоторых из краев есть белый пух? Сравните это с красным контуром на первом изображении. Нам нужен лучший детектор краев. Увеличение количества эрозии помогает с пушком, но тогда другие стороны становятся слишком прозрачными, так что есть компромисс по ширине маски края. Это довольно хорошо, хотя, учитывая, что по сути нет операции размытия.

Было бы поучительно запустить алгоритм на различных изображениях, чтобы проверить его устойчивость и посмотреть, насколько он автоматический.

10 голосов
/ 10 ноября 2011

Просто играть новичком - просто удивительно, сколько инструментов доступно.

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

9 голосов
/ 08 ноября 2011

Я совершенно новичок в обработке изображений, но вот что я получаю после игры с новыми морфологическими функциями обработки изображений версии 8:

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

image

6 голосов
/ 07 ноября 2011

Я рекомендую использовать Photoshop для этого и сохранить как PNG.

5 голосов
/ 07 ноября 2011

Возможные шаги:

  • расширить маску
  • размытие
  • используя маску, установите прозрачность на расстоянии от белого
  • используя маску, отрегулируйте насыщенность таким образом, чтобы ранее более белые цвета были более насыщенными.
3 голосов
/ 08 ноября 2011

Просто замените любой пиксель, «почти близкий к белому», пикселем того же цвета RGB и сигмоидальным градиентом на канале прозрачности.Вы можете применить линейный переход от твердого к прозрачному, но синусоида, сигмоид или танх выглядят более естественно, в зависимости от резкости искомого края, они быстро уходят от носителя к твердому или прозрачному, но не в пошаговом / двоичном видеЭто то, что у вас сейчас есть.

Подумайте об этом так:

Скажем, R, G, B каждый равен 0,0-1,0, тогда давайте представим белый как одно число как R+ G + B = 1,0 * 3 = 3,0.

Извлечение небольшого количества каждого цвета делает его немного "не совсем белым", но если брать немного из всех 3, это отнимает намного больше, чемнемного от любого.Допустим, вы разрешаете 10% -ное снижение на любом одном канале: 1,0 * .10 = .1. Теперь распределите эту потерю по всем трем и свяжите ее между 0 и 1 для альфа-канала, если она меньше, чем 0,1, так, чтобы (потеря = 0,9) => 0 и (потеря = 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];

Для справки:

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

Единственная опасность (или выгода?), которую вы имеете в этомЭто то, что это не заботится о белых, которые на самом деле являются частью фотографии.Удаляет все белые.Так что, если у вас есть изображение белого автомобиля, в нем будут прозрачные пятна.Но из вашего примера это, кажется, желаемый эффект.

...