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

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

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

Вот что мы пробовали до сих пор:

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

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

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

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

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

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

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

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


person dreeves    schedule 07.11.2011    source источник
comment
Большое спасибо за помощь до сих пор, всем! За это придет большая награда, как только stackoverflow позволит мне добавить его. И, в соответствии с духом stackoverflow, сформулированным основателями, вы можете свободно воровать друг у друга, чтобы сделать свой ответ окончательным!   -  person dreeves    schedule 09.11.2011
comment
Сначала 500 наград, а затем я призываю вас всех свободно брать взаймы друг у друга, чтобы улучшить его, если это возможно! -- Вы хотите собачью драку, не так ли?   -  person Mr.Wizard    schedule 10.11.2011
comment
@Mr.Wizard, :) Однако я не выдумываю, что основатели (Джефф и Джоэл) с самого начала сказали, что это поощряется. Идея состоит в том, чтобы лучший ответ был действительно полным и окончательным. (И, очевидно, у меня есть скрытые мотивы в этом случае!)   -  person dreeves    schedule 10.11.2011
comment
Как вы планируете это использовать? Я спрашиваю, потому что вы показываете изображение на красном/розовом фоне. Если вы собираетесь поместить его на небелый фон, очень важен целевой цвет фона (темный или светлый). В случае с реальными фотографиями неизбежно какое-то смешивание краев объектов переднего плана с цветом фона (из-за несовершенной фокусировки камеры, дрожания, нечеткости объекта и т. д.). Как бы идеально мы ни вырезали объект из фотографии , останется след фона.   -  person Szabolcs    schedule 11.11.2011
comment
Если мы заранее знаем, что объект будет отображаться на однородном темном фоне (или на светлом), мы можем это компенсировать и сделать его намного лучше. Или, по крайней мере, если у нас будет больше гибкости в компоновке нового фона с передним планом, чем полагаться на простой альфа-канал.   -  person Szabolcs    schedule 11.11.2011
comment
@dreeves Пожалуйста, ознакомьтесь с изменениями в моем ответе.   -  person Szabolcs    schedule 11.11.2011
comment
@Szabolcs, это как вы предположили на своем примере размещения продукта с Amazon в джунглях (эй, я только что получил подразумеваемый каламбур!). А именно брать товары из каталога и накладывать их на произвольные картинки, например своей квартиры. Но удаление фона должно выполняться в автономном режиме, без знания изображения, на которое оно будет наложено.   -  person dreeves    schedule 12.11.2011
comment
Для особо любопытных: компьютерная рабочая станция ФРЕДРИК от ИКЕА: ikea.com/ мы/ru/каталог/продукты/60111123   -  person Arnoud Buzing    schedule 13.11.2011
comment
Правильный! Но теперь мне любопытно, @Arnoud, ты только что узнал его или само изображение каким-то образом раскрывает его источник?   -  person dreeves    schedule 13.11.2011
comment
dreeves, я еще не успел опубликовать решение, но я намеревался использовать Manipulate для настройки результата. Это противоречит вашим новым правилам?   -  person Mr.Wizard    schedule 13.11.2011
comment
@dreeves, я использовал tineye.com.   -  person Arnoud Buzing    schedule 14.11.2011
comment
@Mr.Wizard, я так не думаю. Вы имеете в виду, что если решение хорошо настроено для этого конкретного изображения, оно не может быть хорошим общим решением?   -  person dreeves    schedule 14.11.2011
comment
Нет, я имею в виду, что я думаю, что необходимо будет иметь регулируемые параметры, чтобы получить хороший результат с разными изображениями. Это неприемлемо, если вы собираетесь обрабатывать большое количество изображений в пакетном режиме.   -  person Mr.Wizard    schedule 14.11.2011
comment
@Mr.Wizard, ах, да, конечно. Мое намерение состояло в том, чтобы обработать большое количество изображений в пакетном режиме, но я начинаю опасаться, что это на самом деле невозможно. Иногда на блестящих объектах есть пятна белого блика, а на других объектах есть множество маленьких отверстий, через которые должен просвечивать фон. Начинает казаться, что нужно быть человеком, чтобы заметить разницу. Другими словами, то, что мы надеялись сделать, может оказаться нереалистичным, и может потребоваться вмешательство человека. Что сделало бы решение с участием Manipulate возможно идеальным.   -  person dreeves    schedule 14.11.2011
comment
Я не вижу, чтобы это было предложено, но я бы попробовал алгоритм роста региона в грубой попытке. Набор уровней, например, должен отлично сработать.   -  person hpixel    schedule 17.11.2011


Ответы (9)


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

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

enter image description here

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

Изменить 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]

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

person Dr. belisarius    schedule 08.11.2011
comment
К сожалению, на моей машине ваш код не дает такого качества результата. Было ли img изображением 500x500, как указано в вопросе? Если да, возможно, дело в Mac/Windows... - person Matthias Odisio; 10.11.2011
comment
@Matthias Да, img - это копия/вставка из оригинала. Мма 8.01 на виндовс. - person Dr. belisarius; 10.11.2011
comment
О... возможно, оптимизатор выдает другой результат из-за крошечного арифметического шума. В любом случае, я рад, что у вас все работает с этим набором параметров. - person Matthias Odisio; 10.11.2011
comment
Это не похоже, что это работает. Это просто размытие краев. - person user541686; 11.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]

Образец

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

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

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

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

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

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

person Community    schedule 11.11.2011
comment
@belisarius это не про английский язык, я знаю, что мое имя для большинства выглядит очень необычно :-) - person Szabolcs; 11.11.2011
comment
Выглядит как симпатичный стд. Венгерская фамилия для меня :) - person Dr. belisarius; 11.11.2011
comment
@belisarius На самом деле это имя, а точнее имя, так как в венгерском языке фамилия идет первой, а имя последним. - person Szabolcs; 11.11.2011
comment
Тень корпуса еще присутствует на 2-м рисунке в виде сероватой полосы внизу... - person Sjoerd C. de Vries; 11.11.2011
comment
@ SjoerdC.deVries Это правда, но я думаю, что для этой задачи так и должно быть ... нет никакого способа сказать, что это тень, а не часть объекта. На большинстве картинок на Amazon либо были тени, либо они были скучно тривиальными, поэтому я выбрал эту. - person Szabolcs; 12.11.2011
comment
Большое спасибо за всю эту работу! Одна вещь, чтобы сохранить его в спецификации: можно ли вывести изображение с прозрачным фоном? Т.е. компоновка его с фоном должна быть отдельным этапом. - person dreeves; 12.11.2011
comment
@dreeves Да, похоже, это эквивалентно использованию альфа-канала, дайте мне минуту, чтобы вычислить его. - person Szabolcs; 12.11.2011
comment
@dreeves Смотрите мою правку. Код делает то же самое, за исключением того, что он вычисляет окончательный альфа-канал, который затем можно использовать для смешивания с любым фоном. Сделано немного наспех, надеюсь правильно (визуально так и есть). - person Szabolcs; 12.11.2011
comment
@Szabolcs, я официально поражен! :) Как вы думаете, вы могли бы удалить все, что вы окончательно улучшили, и оставить только решения, которые, по вашему мнению, являются кандидатами на то, что мы действительно должны использовать? Может быть, назовем их как-то вроде unground1[], unground2[] и т. д.? (Обратите внимание, что StackOverflow хранит всю вашу историю редактирования, поэтому нет опасности что-либо потерять при удалении ответов.) - person dreeves; 13.11.2011
comment
(Может быть, даже лучше было бы сделать каждый вариант решения отдельным ответом. И еще раз спасибо за вашу работу над этим; безумно ценно!) - person dreeves; 13.11.2011
comment
Я знаю, что это старый пост, просто хочу знать, какой язык кода используется в верхнем примере ?? - person JSB; 06.11.2015

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

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

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

person Mark Ransom    schedule 07.11.2011
comment
Отличный ответ. Альфа-оценка на самом деле является целой областью исследований, например. ai.stanford.edu/~ruzon/alpha - person mpenkov; 08.11.2011
comment
Согласен, отличный ответ; спасибо Марк! За вознаграждение (когда stackoverflow позволит мне добавить его), хотя я планирую использовать любое полностью реализованное решение, которое выглядит лучше всего. Думаю, пока Велисария. - person dreeves; 09.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]

край фигуры

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

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]

розовый фон

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

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

person JxB    schedule 11.11.2011
comment
Хм, мне img2 выглядит лучше (см. нижнюю часть поверхности таблицы), чем img3. Может быть, обратное смешение цветов не нужно? - person JxB; 11.11.2011

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

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

person cormullion    schedule 10.11.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]]

изображение

person Alexey Popkov    schedule 08.11.2011
comment
Я думаю, дривс пытается избавиться от этих неровных линий по краям. - person Dr. belisarius; 08.11.2011
comment
Да, это хорошо помогает уменьшить этот ореол, но зубчатость может стать препятствием для сделки. @belisarius, твоя версия выглядит потрясающе! - person dreeves; 08.11.2011
comment
@dreeves Я думаю, что края можно улучшить (в моей версии), используя преобразование расстояния после размытия, но это уже было отмечено мистером Визом, поэтому я оставляю эксперимент ему. - person Dr. belisarius; 08.11.2011
comment
Что делает Method -> "Convex"? Это не задокументировано. - person Szabolcs; 11.11.2011
comment
Мне жаль! Я понимаю, что перепутал MorphologicalComponents и MorphologicalBinarize, которые на самом деле не связаны между собой! - person Szabolcs; 11.11.2011

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

person angelfilm entertainment    schedule 07.11.2011
comment
Хороший вопрос, но какой алгоритм использует фотошоп, чтобы сделать это так хорошо? (И, конечно же, мы хотим автоматизировать это, а не щелкать волшебной палочкой в ​​фотошопе по каждому изображению.) - person dreeves; 08.11.2011
comment
Между прочим, я думаю, что это полезно отметить (я вполне мог быть настолько большим ботаником Mathematica, что фотошоп мог не прийти мне в голову!). И оказывается, что это даже можно написать в фотошопе, так что это может быть даже лучшим возможным ответом в этом смысле, если фотошоп делает что-то действительно умное, что нельзя воспроизвести с помощью небольшой математической программы. - person dreeves; 09.11.2011
comment
Есть причина, по которой Adobe может взимать 500 смакеров за свое программное обеспечение ;-). - person Timo; 10.11.2011
comment
Возможно, вы могли бы опубликовать версию изображения, созданного скриптом PhotoShop (без ручного вмешательства :-) для справки - мы бы знали, что нам нужно побить... - person cormullion; 11.11.2011

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

  • расширить маску
  • размыть это
  • с помощью маски установить прозрачность по расстоянию от белого
  • с помощью маски отрегулируйте насыщенность так, чтобы ранее более белые цвета были более насыщенными.
person Mr.Wizard    schedule 07.11.2011
comment
Хорошие мысли; Спасибо! Хотелось бы получить код общего назначения для этого. Вероятно, через пару дней мы дадим большую награду (когда stackoverflow позволит нам), если вы захотите вернуться тогда. На самом деле, я обязуюсь сделать это, если есть соблазн погрузиться в это. :) - person dreeves; 08.11.2011
comment
@dreeves Звучит хорошо для меня; У меня сейчас нет времени, но я постараюсь вернуться к этому. - person Mr.Wizard; 08.11.2011

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

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

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

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

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

person Gregory Klopper    schedule 07.11.2011
comment
Я думаю, что идея ChanVeseBinarize состоит в том, чтобы подойти к этому с умом и не делать белые пиксели прозрачными, если только они не являются частью большей области белого цвета, т. е. очень вероятно, что они являются частью фона. - person dreeves; 08.11.2011
comment
Проблема с большей площадью в том, что она может быть важной, а маленькая может быть неважной. На белой машине вся сторона будет важна, но будет помечена как большое белое пятно. Пространство между двумя людьми на белом фоне было бы маленьким и со сложными краями, но оно должно исчезнуть. Вам нужно было бы, чтобы ИИ в стиле машины Больцмана распознавал общие формы и видел, является ли белый цвет пространством или частью объекта, но мы еще этого не сделали. - person Gregory Klopper; 06.12.2011
comment
Вы также можете взять 2 изображения с немного разных точек зрения, а затем использовать вычет размерности из стереоизображения, чтобы узнать, какие пиксели являются фоном в зависимости от того, где возникают окклюзии. - person Gregory Klopper; 06.12.2011