从图像中删除白色背景并使其透明


82

我们试图在Mathematica中执行以下操作-RMagick从图像中删除白色背景并使其透明

但是对于实际照片,它最终看起来很糟糕(就像图像周围有光晕)。

到目前为止,我们已经尝试过以下方法:

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

这是一个例子。

原始图片:

原始图像

带有白色背景的图像被替换为没有背景(或者,出于演示目的,此处为粉红色背景):

具有透明背景的图像-实际上是粉红色的背景,使光晕问题显而易见

有什么想法摆脱这种光环吗?调整诸如LevelPenalty之类的东西时,我只能以失去一些图像为代价来消除光环。

编辑:因此,我可以比较赏金的解决方案,请像上面那样构造您的解决方案,即一个名为unground-something的自包含函数,该函数获取图像并返回具有透明背景的图像。


1
到目前为止,非常感谢大家的帮助!一旦stackoverflow让我添加一个,就会有很大的赏金。而且,按照创始人所表达的stackoverflow精神,您应该相互窃取,使自己的答案是确定的!
dreeves

3
首先是500英镑的赏金,然后是“我鼓励大家自由地互相借鉴,以在可能的情况下改进它!” -你想打狗吗?
Wizard先生

@Wizard先生,:)不过,我并没有弥补,创始人(Jeff和Joel)从一开始就表示这是值得鼓舞的。这个想法是让最高答案是一个真正完整和确定的答案。(显然,在这种情况下,我也有别有用心!)
dreeves

2
对于过于好奇的人,这是宜家的“ FREDRIK”计算机工作站:ikea.com/us/en/catalog/products/60111123
Arnoud Buzing 2011年

1
@dreeves,我使用了tineye.com
Arnoud Buzing

Answers:


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 Odisio 2011年

@Matthias是的,img是原始文件的副本/粘贴。Windows上的Mma 8.01。
belisarius博士2011年

哦...由于微小的算术噪声,优化器可能会产生不同的结果。无论如何,我很高兴使用这组参数对您来说效果很好。
Matthias Odisio 2011年

看起来不起作用。这只是模糊边缘。
2011年

48

此功能实现了Mark Ransom描述的反向混合,这是一个小的但明显的改进:

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. 为了进行较小的改进,请使用负片的亮度作为Alpha将图像混合到背景上,然后在边缘附近的薄区域(edgemask)中将获得的图像混合在原始图像上,以减少边缘上白色像素的可见度。计算与这些操作相对应的Alpha通道(有点神秘的ImageMultiply/Add表达)。

  4. 现在我们有了一个Alpha通道的估计值,因此我们可以进行反向混合。

第3步和第4步的改进并不多,但区别显而易见。


@belisarius这不是英文,我知道我的名字对于大多数人来说看起来很不寻常:-)
Szabolcs

看起来很漂亮。我的匈牙利姓氏:)
belisarius博士2011年

@belisarius实际上,这是一个姓氏,或更确切地说是一个给定的名称,例如在匈牙利,姓氏排在最前面,给定名称在最后。
Szabolcs

2
案件的阴影仍然出现在第二个图中,底部是一个灰色的带子……
Sjoerd C. de Vries

@ SjoerdC.deVries没错,但是我认为对于此任务应该采用这种方式……没有办法说出它是阴影而不是对象的一部分。亚马逊上的大多数照片都带有阴影或无聊的琐碎,所以我选择了这张照片。
Szabolcs

22

我将一般性地讲,而不是专门针对Mathematica。我不知道这些操作是困难的还是琐碎的。

第一步是为图像边缘上的像素估计alpha(透明度)级别。现在,您使用的是严格的阈值,因此Alpha要么是0%完全透明,要么100%完全不透明。您应该在背景的总白色和毫无疑问地属于图像的颜色之间定义一个范围,并设置适当的比例-如果颜色更接近于背景,则它是低alpha值;如果颜色更接近于深色截止,则它是高alpha。之后,您可以根据周围的Alpha值进行调整-透明度包围的像素越多,像素本身就越可能变得透明。

一旦有了alpha值,就需要进行反向混合以获得正确的颜色。当图像显示在背景上时,将使用以下公式根据Alpha值将其混合:c = bc*(1-a)+fc*a其中bc,背景颜色fc为前景色。在您的情况下,背景为白色(255,255,255),前景色为未知色,因此我们推翻公式:fc = (c - bc*(1-a))/a。当a=0公式要求除以零,但颜色无论如何都没有关系时,只需使用黑色或白色即可。


3
好答案。Alpha估算实际上是整个研究领域,例如,ai.stanford.edu
〜ruzon /

2
同意,很好的答案;谢谢马克!对于赏金(当stackoverflow让我添加一个时),尽管我计划使用哪种方案最合适。到目前为止,我在想belisarius的。
dreeves

11

在belisarius的遮罩生成的一些帮助下,这是尝试实现Mark Ransom的方法的尝试:

找到对象的边界:

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]

图形边缘

设置Alpha值:

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]

粉红色的背景

请注意,有些边缘有白色绒毛吗?将其与第一张图像中的红色轮廓进行比较。我们需要一个更好的边缘检测器。增加腐蚀量有助于起毛,但其他面变得太透明,因此需要权衡边缘蒙版的宽度。不过,考虑到本身没有模糊操作,这非常好。

在各种图像上运行该算法以测试其鲁棒性,以查看其自动化程度,将是有益的。


嗯,对我来说,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正在尝试消除边缘的那些锯齿状线。
belisarius博士,

1
没错,这在减少光晕方面做得很好,但参差不齐可能会破坏交易。@belisarius,您的版本看起来很棒!
dreeves

@dreeves我认为可以通过在模糊之后使用距离变换来改善边缘(在我的版本中),但是Wiz先生已经指出了这一点,因此我将实验留给他。
belisarius博士

怎么Method -> "Convex"办?没有记录。
Szabolcs

对不起!我意识到我混淆了MorphologicalComponents和MorphologicalBinarize实际上是不相关的功能!
Szabolcs

6

我建议为此使用Photoshop并将其另存为PNG。


5
好点了,但是photoshop做到这一点的算法是什么?(当然,我们要自动执行此操作,而不是在photoshop中用魔术棒在每个图像上单击。)
dreeves

3
顺便说一句,我认为这是一件很有帮助的事情(我很容易成为像Mathematica这样的书呆子,而我却可能没有photoshop!)。事实证明,它甚至可以在photoshop中编写脚本,因此,即使photoshop所做的事情确实非常聪明,而小型的mathematica程序无法复制,这甚至可能是最佳的答案。
dreeves

5
有一个原因可以使Adobe对其软件收取500瑞典克朗;-)。
Timo

7
也许您可以发布由PhotoShop脚本生成的图像版本(无需人工干预:-)以供参考-我们将知道我们必须击败...
cormullion 2011年

5

您可以采取的可能步骤:

  • 扩张面膜
  • 模糊它
  • 使用遮罩,根据与白色的距离设置透明度
  • 使用遮罩,调整饱和度,使以前更白的颜色更饱和。

好主意;心存善念;睿智哲思; 谢谢!很想为此获得一些通用代码。如果您想再回来的话,我们可能会在几天之内(当stackoverflow允许我们的时候)提出很大的悬赏。实际上,如果愿意的话,我在此承诺。:)
dreeves

@dreeves对我来说听起来不错;我现在没有时间,但是我会尽力而为。
Wizard先生2011年

3

只需将任何“几乎接近白色”的像素替换为具有相同RGB颜色和透明通道上的Sigmoid渐变的像素即可。您可以应用从实体到透明的线性过渡,但是正弦波或Sigmoid或Tanh看起来更自然,具体取决于您要寻找的边缘的锐度,它们会迅速从介质移到实体或透明,但不能逐步/二值化方式,这就是您现在所拥有的。

这样想:

假设R,G,B分别为0.0-1.0,那么我们将白色表示为单个数字,因为R + G + B = 1.0 * 3 = 3.0。

取出每种颜色的一点会使它有些“灰白色”,但是全部使用3种颜色会使它的减少多于任何一种颜色。假设您允许在任何一个通道上减少10%:1.0 * .10 = .1,现在将这一损失分布在所有三个通道上,并将其限制在alpha通道的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的想法很聪明,除非白色像素是较大的白色区域的一部分(即很有可能是背景的一部分),否则不要将其变为透明。
2011年

“大面积”的问题是,这可能很重要,而小面积可能并不重要。在白色汽车上,整个侧面都很重要,但会被标记为一大片白色。在白色背景下,两个人之间的空间很小,边缘复杂,但是需要走。您将必须拥有Boltzman机器风格的AI来识别常见的形状,并查看白色是空间还是物体的一部分,但是我们还没有。
格雷戈里·克洛珀

1
您还可以从略有不同的角度拍摄2张图像,然后使用立体成像的维数推导根据发生遮挡的位置找出哪些像素是背景。
格雷戈里·克洛珀
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.