Удалите белый фон с изображения и сделайте его прозрачным

82

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

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

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

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

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

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

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

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

изображение с прозрачным фоном - на самом деле здесь розовый фон, чтобы проблема ореола была очевидна

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

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

Dreeves
источник
1
Большое спасибо за помощь, всем! Большая награда за это, как только stackoverflow позволит мне добавить его. И в соответствии с духом stackoverflow, сформулированным основателями, вы можете свободно воровать друг у друга, чтобы сделать свой ответ окончательным!
dreeves 08
3
Сначала награда 500, а затем «Я призываю вас всех щедро брать взаймы друг у друга, чтобы улучшить его, если возможно!» - Вы хотите собачьей драки, не так ли?
Мистер Волшебник
@ Mr.Wizard, :) Я не придумываю, что основатели (Джефф и Джоэл) с самого начала сказали, что это поощряется. Идея состоит в том, чтобы главный ответ был действительно полным и окончательным. (И, очевидно, в этом случае у меня также есть скрытые мотивы!)
dreeves
2
Для чрезмерно любопытных, это компьютерная рабочая станция IKEA "FREDRIK": ikea.com/us/en/catalog/products/60111123
Arnoud Buzing
1
@dreeves, я использовал tineye.com .
Arnoud Buzing

Ответы:

45

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

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]

введите описание изображения здесь

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

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

введите описание изображения здесь

нажмите, чтобы увеличить

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

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

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

введите описание изображения здесь

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

введите описание изображения здесь

Доктор велизарий
источник
К сожалению, на моей машине ваш код не дает такого же качества результата. Было ли изображение img размером 500x500, как указано в вопросе? Если да, то, возможно, дело в Mac / Windows ...
Маттиас Одисио
@Matthias Да, img - это копия / вставка оригинала. ММА 8.01 на windows.
Доктор Велизарий
Ой ... возможно, оптимизатор выдаст другой результат из-за крошечного арифметического шума. В любом случае, я рад, что этот набор параметров хорошо работает для вас.
Маттиас Одисио
Похоже, это не работает. Это просто размытие краев.
user541686
48

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

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]

Образец

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

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

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

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

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

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

Szabolcs
источник
@belisarius, дело не в английском, я знаю, что для большинства мое имя выглядит очень необычно :-)
Szabolcs
Похоже на довольно стандартное. Венгерская фамилия для меня :)
Dr. belisarius
@belisarius На самом деле это имя или, точнее, имя, поскольку в венгерском языке фамилия идет первой, а имя - последней.
Szabolcs
2
Тень от дела все еще присутствует на 2-м рисунке в виде сероватой полосы внизу ...
Сьорд К. де Врис
@ SjoerdC.deVries Это правда, но я думаю, что для этой задачи так и должно быть ... нет способа сказать, что это тень, а не часть объекта. Большинство изображений на Amazon либо были с тенями, либо были до утомления тривиальными, поэтому я выбрал эту.
Szabolcs
22

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

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

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

Марк Рэнсом
источник
3
Отличный ответ. Альфа-оценка - это фактически целая область исследований, например ai.stanford.edu/~ruzon/alpha
mpenkov
2
Согласен, отличный ответ; спасибо Марк! За вознаграждение (когда stackoverflow позволяет мне добавить его), хотя я планирую использовать любое полностью реализованное решение, которое выглядит лучше всего. Думаю, пока что Велисария.
dreeves 08
11

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

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

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]

край фигуры

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

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]

розовый фон

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

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

JxB
источник
Хммм, мне img2 выглядит лучше (см. Нижнюю часть поверхности таблицы), чем img3. Может быть, обратное смешение цветов не нужно?
JxB
10

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

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

кормильон
источник
9

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

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

образ

Алексей Попков
источник
3
Думаю, Dreeves пытается избавиться от неровных линий по краям.
Доктор Велизарий
1
Да, это неплохо помогает уменьшить этот ореол, но неровности могут помешать сделке. @belisarius, ваша версия выглядит потрясающе!
dreeves 08
@dreeves Я думаю, что края можно улучшить (в моей версии), используя дистанционное преобразование после размытия, но это уже было отмечено мистером Визом, поэтому я оставляю эксперимент ему.
Доктор Велизарий
Что Method -> "Convex"делать? Это не задокументировано.
Szabolcs
Мне жаль! Я понимаю, что перепутал MorphologicalComponents и MorphologicalBinarize, которые на самом деле не связаны между собой!
Сабольч
6

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

ангелфильм развлечения
источник
5
Хорошая мысль, но какой алгоритм использует Photoshop, чтобы сделать это так хорошо? (И, конечно, мы хотим автоматизировать это, а не щелкать волшебной палочкой в ​​фотошопе для каждого изображения.)
dreeves
3
Кстати, я думаю, что это полезно отметить (я легко мог бы быть таким большим ботаником в области Mathematica, что фотошоп мог бы мне не прийти в голову!). И оказывается, что это даже скрипт в фотошопе, так что это может быть даже лучший ответ в этом смысле, если фотошоп делает что-то действительно умное, что не может быть воспроизведено с помощью небольшой математической программы.
dreeves 08
5
Есть причина, по которой Adobe может взимать 500 долларов за свое программное обеспечение ;-).
Timo
7
Возможно, вы могли бы опубликовать версию изображения, созданного скриптом PhotoShop (без ручного вмешательства :-), для справки - мы бы знали, что нам нужно победить ...
cormullion
5

Возможные шаги, которые вы могли бы предпринять:

  • расширить маску
  • размыть это
  • с помощью маски установите прозрачность по расстоянию от белого
  • используя маску, настройте насыщенность так, чтобы ранее более белые цвета были более насыщенными.
Мистер волшебник
источник
Хорошие мысли; благодарю вас! Хотелось бы получить для этого какой-нибудь универсальный код. Мы, вероятно, назначим большую награду через пару дней (когда нам позволит stackoverflow), если вы захотите вернуться тогда. Фактически, я обязуюсь сделать это, если это какой-то соблазн, чтобы погрузиться в это. :)
dreeves
@dreeves Мне нравится; У меня сейчас нет времени, но я постараюсь вернуться к этому.
Мистер Волшебник
3

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

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

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

Если убрать немного каждого цвета, он станет немного «не совсем белым», но если убрать все три цвета, то получится гораздо больше, чем немного от любого другого. Допустим, вы допускаете снижение на 10% для любого канала: 1.0 * .10 = .1. Теперь распределите эту потерю по всем трем и привяжите ее между 0 и 1 для альфа-канала, если он меньше, чем .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}]

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

Грегори Клоппер
источник
Я думаю, что идея ChanVeseBinarize состоит в том, чтобы быть умным в этом и не делать белые пиксели прозрачными, если они не являются частью большей области белого, то есть с большой вероятностью не являются частью фона.
dreeves 08
Проблема с «большей площадью» в том, что она может быть важной, а небольшая - неважной. На белой машине важна вся сторона, но она будет помечена как большое белое пятно. Пространство между двумя людьми на белом фоне будет небольшим и со сложными краями, но его нужно убрать. Вам нужно, чтобы ИИ в стиле машины Больцмана распознавал общие формы и смотрел, является ли белый цвет пространством или частью объекта, но мы еще не достигли этого.
Грегори Клоппер
1
Вы также можете сделать 2 изображения с немного разных точек зрения, а затем использовать вычитание размерности из стереоизображения, чтобы определить, какие пиксели являются фоном, в зависимости от того, где происходят окклюзии.
Грегори Клоппер