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

Posted

技术标签:

【中文标题】从图像中删除白色背景并使其透明【英文标题】:Remove White Background from an Image and Make It Transparent 【发布时间】:2011-12-23 21:51:33 【问题描述】:

我们正在尝试在 Mathematica 中执行以下操作 - RMagick remove white background from image and make it transparent。

但是对于实际照片,它最终看起来很糟糕(比如图像周围有一个光环)。

这是我们迄今为止尝试过的:

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

这是一个例子。

原图:

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

有摆脱那个光环的想法吗?调整 LevelPenalty 之类的东西,我只能以丢失一些图像为代价让光环消失。

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

【问题讨论】:

非常感谢大家到目前为止的帮助!一旦***让我添加一个,就会有大赏金。而且,根据创始人所阐述的 *** 精神,您应该随意互相窃取以使您的答案成为最终答案! 前 500 个赏金,然后“我鼓励你们大家互相借用,以尽可能改进它!” ——你想要一场狗斗,不是吗? @Mr.Wizard,:) 我不是在编造,创始人(杰夫和乔尔)从一开始就说过这是鼓励的。这个想法是让最佳答案成为一个真正完整和明确的答案。 (显然我在这种情况下也别有用心!) 对于过度好奇的人,这是宜家的“FREDRIK”电脑工作站:ikea.com/us/en/catalog/products/60111123 @dreeves,我用过tineye.com。 【参考方案1】:

此函数实现了 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]

其工作原理的简要说明:

    选择你最喜欢的产生相对精确锐边的二值化方法

    将其应用于放大的图像,然后将获得的mask缩小到原始大小。这给了我们抗锯齿。大部分工作已经完成。

    为了进行小幅改进,使用负片的亮度作为 alpha 将图像混合到背景上,然后在边缘周围的薄区域 (edgemask) 将获得的图像混合到原始图像上以降低白色像素的可见性在边缘。计算对应于这些操作的 alpha 通道(有点神秘的ImageMultiply/Add 表达式)。

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

第 3 步和第 4 步没有太大改善,但差异是显而易见的。

【讨论】:

@belisarius 这与英语无关,我知道我的名字对大多数人来说看起来很不寻常:-) 看起来很漂亮。我的匈牙利姓氏:) @belisarius 实际上它是一个名字,或者更准确地说是一个名字,因为在匈牙利语中,姓在前,名字在后。 第二个图中,箱子的阴影仍然存在,在底部呈灰带状...... @SjoerdC.deVries 确实如此,但我认为对于这项任务,它应该是这样的......无法判断它是阴影而不是对象的一部分。亚马逊上的大多数图片要么有阴影,要么很无聊,所以我选择了这张。【参考方案2】:

也许,取决于您需要的边缘质量:

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]

Click to enlarge

编辑 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 是原件的复制/粘贴。 Windows 上的 Mma 8.01。 哦...由于微小的算术噪声,优化器可能会产生不同的结果。无论如何,我很高兴使用这组参数对您来说效果很好。 这看起来行不通。它只是模糊了边缘。【参考方案3】:

我将笼统地说,而不是专门针对 Mathematica。我不知道这些操作是困难的还是微不足道的。

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

一旦有了 alpha 值,您就需要进行反向混合以获得正确的颜色。当图像显示在背景上时,它使用公式 c = bc*(1-a)+fc*a 根据 alpha 值进行混合,其中 bc 是背景色,fc 是前景色。在您的情况下,背景是白色 (255,255,255),前景色是未知的,所以我们反转公式:fc = (c - bc*(1-a))/a。当a=0 公式要求除以零时,颜色并不重要,所以只需使用黑色或白色即可。

【讨论】:

很好的答案。 Alpha 估计实际上是一个完整的研究领域,例如ai.stanford.edu/~ruzon/alpha 同意,很好的答案;谢谢马克!对于赏金(当 *** 允许我添加一个),尽管我计划使用看起来最好的完全实施的解决方案。到目前为止,贝利撒留的,我在想。【参考方案4】:

在贝利撒留的面具生成器的帮助下,尝试实施 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 更好(见表格底部)。也许不需要反向颜色混合?【参考方案5】:

只是作为初学者玩 - 有多少可用的工具令人惊讶。

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

【讨论】:

【参考方案6】:

我对图像处理完全陌生,但这是我在玩过版本 8 的新形态图像处理功能后得到的结果:

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

【讨论】:

我认为 dreeves 正试图摆脱边缘处的锯齿状线条。 没错,这在减少光环方面做得很好,但锯齿状可能会破坏交易。 @belisarius,你的版本看起来非常棒! @dreeves 我认为可以通过在模糊后使用距离变换来改善边缘(在我的版本中),但是 Wiz 先生已经注意到了这一点,所以我把实验留给了他。 Method -> "Convex" 是做什么的?它没有记录在案。 对不起!我意识到我混淆了 MorphologicalComponents 和 MorphologicalBinarize 实际上是不相关的函数!【参考方案7】:

我建议为此使用 Photoshop 并保存为 PNG。

【讨论】:

好点,但是 Photoshop 使用什么算法来做到这一点? (当然,我们希望自动执行此操作,而不是使用 Photoshop 中的魔杖点击每张图片。) 顺便说一句,我认为指出这一点很有帮助(我很容易成为一个数学书呆子,以至于我可能没有想到 Photoshop!)。事实证明,它甚至可以在 Photoshop 中编写脚本,所以如果 Photoshop 正在做一些无法用小型数学程序复制的非常聪明的事情,这甚至可能是最好的答案。 Adobe 可以为其软件收取 500 smakeroos 的费用是有原因的 ;-)。 也许您可以发布一个由 PhotoShop 脚本生成的图像版本(无需人工干预 :-) 以供参考 - 我们会知道我们必须打败什么......【参考方案8】:

您可以采取的可能步骤:

扩张蒙版 模糊它 使用蒙版,根据与白色的距离设置透明度 使用蒙版调整饱和度,使之前较白的颜色更加饱和。

【讨论】:

好主意;谢谢!很想为此获得一些通用代码。如果您想回来的话,我们可能会在几天后(当 *** 允许我们)提供大笔赏金。事实上,我在此承诺这样做,如果这有什么诱惑力的话。:) @dreeves 对我来说听起来不错;我现在没有时间,但我会努力回去的。【参考方案9】:

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

这样想:

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

从每种颜色中取一点会使它有点“灰白色”,但是从所有 3 种颜色中取一点比从任何一种颜色中取一点要多得多。假设您允许在任何一个通道上减少 10%:1.0*.10 = .1,现在将此损失分布在所有三个通道上,并将其绑定在 0 和 1 之间以用于 alpha 通道,如果它小于 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]

你在这方面唯一的危险(或好处?)是它不关心实际上是照片一部分的白人。它会去除所有的白人。所以如果你有一张白色汽车的照片,它最终会在里面有透明的补丁。但从你的例子来看,这似乎是一种预期的效果。

【讨论】:

我认为 ChanVeseBinarize 的想法是明智的,不会将白色像素变成透明的,除非它们是更大的白色区域的一部分,即很可能是背景的一部分。 “大面积”的问题可能很重要,而小面积可能不重要。在一辆白色汽车上,整个侧面都很重要,但会被标记为一大片白色。白色背景下的两个人之间的空间会很小并且边缘复杂,但它需要消失。你必须让玻尔兹曼机器式的人工智能识别常见的形状,看看白色是空间还是物体的一部分,但我们还没有。 您也可以从稍微不同的角度拍摄 2 张​​图像,然后使用立体成像中的降维,根据遮挡发生的位置找出哪些像素是背景。

以上是关于从图像中删除白色背景并使其透明的主要内容,如果未能解决你的问题,请参考以下文章

如何将背景转换为透明? [关闭]

我想使用终端中的转换命令从彩色像素之外删除白色背景和图像

从透明表格上的图像中删除轮廓

叠加在图像上时,使文本显示为白色,具有半透明的黑色背景

Java - 跟踪抽象形状的轮廓并使外部像素透明

如何让 ScrollView 半透明并使其背后的内容具有交互性?