跳到主要内容

Mask 背景抠除

Mask 做的是最朴素的抠图:猜出背景色是什么, 然后把所有接近这个颜色的像素变成透明。

node.cache();
node.filters([Konva.Filters.Mask]);
node.threshold(10); // RGB 距离容差,默认 0.5

它怎么猜背景色:采样四个角​

源码里的逻辑很直接——取图片四个角的像素:

const no = pixelAt(idata, 0, 0); // 左上
const ne = pixelAt(idata, idata.width - 1, 0); // 右上
const so = pixelAt(idata, 0, idata.height - 1); // 左下
const se = pixelAt(idata, idata.width - 1, idata.height - 1); // 右下

然后检查相邻的四对角是否两两接近(RGB 距离小于 threshold)。 只有全部通过,才取四者的平均值作为背景色, 再把所有与之距离小于 threshold 的像素的 alpha 设为 0。

这个前置检查决定了这个滤镜的全部脾气。

四角颜色不一致时,它什么也不做​

如果四对角的距离检查有任何一对没通过,函数直接返回 undefined, 滤镜不做任何修改——不抛错、不警告、没有任何提示。

表现就是"加了 Mask 但图片纹丝不动"。

本站用三张示例图实测过(threshold 设为 10):

图片四角颜色Mask 后透明像素变化
yoda.jpg164,221,104 / 194,162,113 / 4,0,0 / 177,148,1060 → 0(毫无变化)
darth-vader.jpg165,172,182 / 189,203,212 / 183,187,196 / 155,135,1240 → 0(毫无变化)

两张照片的四个角各不相同,检查全部失败。这就是为什么 Mask 对真实照片基本无效——照片的四个角极少恰好是同一种颜色。

作为对照,把同一张图合成到纯绿背景上(四角完全相同)之后:

threshold透明像素占比
未加滤镜0.0%
555.8%
1056.3%
4057.7%
8059.0%

背景被干净地抠掉了。上面的演示用的就是这个合成图。

常见问题​

为什么加了 Mask 没反应?​

按顺序查这三条:

  1. 四个角颜色不一致。 这是最常见的原因。 用 getImageData 读一下四个角,肉眼比对一下。
  2. threshold 太小。 即使背景是"纯色", JPEG 压缩也会让四个角有几个数值的差异。默认的 0.5 对 JPEG 几乎肯定不够, 从 10 开始试。
  3. 忘了 cache()。 所有滤镜的通病。

threshold 该设多少?​

它同时控制两件事,这一点容易被忽略: 既是四角相似性检查的阈值,也是逐像素抠除的容差。

调大它会让四角检查更容易通过,但也会让更多像素被判定为背景而抠掉。 所以不存在"先调大让它生效、再调小做精细控制"这种两步走—— 两者是同一个旋钮。

实践上:纯色背景的 PNG 从 10 开始; 有压缩噪点的 JPEG 从 30 开始; 超过 80 通常会开始吃掉主体。

主体碰到边缘怎么办?​

抠不干净,而且会连主体一起抠掉—— 因为角上的像素就是主体的颜色,算出的"背景色"是错的。

Mask 没有任何办法处理这种情况。 只能先给图片加一圈纯色边距(在离屏 canvas 上画一个更大的纯色矩形, 再把原图居中画上去),保证四个角落在背景上。

抠完边缘有毛刺?​

Mask 是硬判定:距离小于阈值就完全透明,否则完全不透明, 没有羽化。半透明的抗锯齿边缘会被判成"不是背景"而保留, 形成一圈背景色的描边(绿幕抠图里典型的"绿边")。

Konva 内置的 Mask 没有去边功能。要处理只能自己写 自定义滤镜, 在抠除之后对边缘像素做一次去饱和或颜色替换。

与其他方案的取舍​

Mask 的定位是"纯色背景、要求不高"的场景, 比如把设计师给的白底产品图放到彩色卡片上。 它免费、同步、不依赖任何外部服务,这是它全部的优势。

它的局限是结构性的,不是调参能解决的:只看四个角、 只按颜色距离判定、没有羽化、没有边缘处理。

需要更好的效果时:

背景复杂或要求精细 → 让设计师直接提供带透明通道的 PNG。 这听起来像废话,但在可控的项目里这是成本最低的方案—— 前端抠图永远不如源头就做对。

用户上传的任意图片 → 走服务端。rembg、各家云厂商的抠图 API 都是基于分割模型的,效果和 Mask 不在一个量级。 放到服务端还有一个好处:抠一次存起来,不用每个访客的浏览器都算一遍。

只是要一个视觉上的"去背"效果 → 考虑用 裁剪或 混合模式绕过抠图。 比如 globalCompositeOperation: 'multiply' 能让白底在浅色背景上"消失", 不需要真的处理像素。