遮罩(Mask)、形状抠图与条件区域处理
在图像处理中,“遮罩(Mask)”本质上是一张单通道(1-band)8-bit 灰度图:值为 255(白色)代表完全选中或完全不透明,值为 0(黑色)代表完全未选中或完全透明,1..254 则代表平滑过渡的羽化边缘。
在 libvips 生态中,实现遮罩抠图与局部区域处理主要有三种模式:
- 将灰度遮罩直接附加为图像的 Alpha 通道(
BandJoin/joinChannel); - 使用 Porter-Duff
dest-in混合模式按形状裁切底图(BlendModeDestIn); - 根据遮罩在“前景图”与“背景图”之间逐像素条件混合(
ifthenelse或 Alpha 合成)。


1. 将单通道灰度遮罩附加为 Alpha 通道(BandJoin)
Section titled “1. 将单通道灰度遮罩附加为 Alpha 通道(BandJoin)”如果你有一张 3 通道 RGB 照片,以及一张同尺寸的 1 通道灰度蒙版(例如由 SVG 光栅化、文字渲染或分割模型生成),可以直接用 BandJoin 把灰度图拼作第 4 个通道(Alpha):
// 确保原图为 3 通道 RGB(若已有 Alpha 则先提取前 3 个通道)if baseImg.Bands() > 3 { if err := baseImg.ExtractBand(0, 3); err != nil { return err }}
// 确保 maskImg 为 1 通道灰度图(若为多通道则提取第 0 通道)if maskImg.Bands() > 1 { if err := maskImg.ExtractBand(0, 1); err != nil { return err }}
// 将 1 通道遮罩追加为第 4 通道 (RGBA)if err := baseImg.BandJoin(maskImg); err != nil { return err}import sharp from 'sharp';
// 先确保底图无旧 Alpha,再用 joinChannel 追加灰度遮罩作为第 4 通道await sharp('input.jpg') .removeAlpha() .joinChannel('mask-gray.png') .png() .toFile('masked-cutout.png');import pyvips
base = pyvips.Image.new_from_file("input.jpg")[0:3]mask = pyvips.Image.new_from_file("mask-gray.png")[0]
rgba = base.bandjoin(mask)rgba.write_to_file("masked-cutout.png")vips bandjoin "input.jpg mask-gray.png" masked-cutout.png2. 使用 SVG 形状与 dest-in 混合模式做圆角或图形抠图
Section titled “2. 使用 SVG 形状与 dest-in 混合模式做圆角或图形抠图”当遮罩本身是一张带透明通道的 SVG(例如圆角矩形、圆形头像框或复杂矢量徽章)时,最简捷的做法是:先将原图确保为 4 通道 RGBA,然后把 SVG 蒙版以 BlendModeDestIn(dest-in) 合成上去——dest-in 会保留底图的颜色,但将其 Alpha 乘上蒙版的 Alpha:
// 构造一个 500×500 的圆角矩形 SVG 蒙版svgMask := []byte(`<svg width="500" height="500" xmlns="http://www.w3.org/2000/svg"> <rect width="500" height="500" rx="64" ry="64" fill="#ffffff"/></svg>`)
mask, err := vips.NewImageFromBuffer(svgMask)if err != nil { return err}defer mask.Close()
if !cropImg.HasAlpha() { if err := cropImg.AddAlpha(); err != nil { return err }}
// BlendModeDestIn:仅保留与 SVG 不透明区域重叠的底图像素if err := cropImg.Composite(mask, vips.BlendModeDestIn, 0, 0); err != nil { return err}import sharp from 'sharp';
const roundedMask = Buffer.from( '<svg width="500" height="500"><rect width="500" height="500" rx="64" ry="64" fill="#fff"/></svg>',);
await sharp('crop-500.webp') .ensureAlpha() .composite([{ input: roundedMask, blend: 'dest-in' }]) .png() .toFile('rounded-avatar.png');import pyvips
svg_mask = b'<svg width="500" height="500"><rect width="500" height="500" rx="64" ry="64" fill="#fff"/></svg>'mask = pyvips.Image.new_from_buffer(svg_mask, "")
crop = pyvips.Image.new_from_file("crop-500.webp")if not crop.hasalpha(): crop = crop.addalpha()
rounded = crop.composite2(mask, "dest-in")rounded.write_to_file("rounded-avatar.png")vips composite2 crop-500.png mask.svg rounded-avatar.png dest-in3. 局部区域滤镜与条件混合(ifthenelse vs Alpha 合成)
Section titled “3. 局部区域滤镜与条件混合(ifthenelse vs Alpha 合成)”假设你要实现“背景虚化”或“局部打码”:中心主体保持清晰,其余区域应用高斯模糊。
- 在
pyvips与vips CLI中,可以直接使用三元条件算子mask.ifthenelse(sharp_img, blurred_img, blend=True):遮罩值越接近 255 越取sharp_img,越接近 0 越取blurred_img,blend=True会按0..255线性插值过渡。 - 在
govips与sharp中,虽然没有公开导出独立的Ifthenelse方法,但将单通道遮罩附加给前景图(BandJoin/joinChannel),再以BlendModeOver合成到模糊背景图上,数学效果与ifthenelse(..., blend=True)完全一致。
// govips:将灰度遮罩附加为清晰前景的 Alpha,再以 Over 混合到模糊背景图上if err := foreground.BandJoin(maskGray); err != nil { return err}if err := blurredBg.Composite(foreground, vips.BlendModeOver, 0, 0); err != nil { return err}// sharp:先用 joinChannel 将灰度遮罩附加给前景,再 composite 到模糊底图上const maskedFg = await sharp('foreground.jpg') .removeAlpha() .joinChannel('mask-gray.png') .png() .toBuffer();
await sharp('input.jpg') .blur(8) .composite([{ input: maskedFg, blend: 'over' }]) .webp({ quality: 82 }) .toFile('selective-blur.webp');import pyvips
fg = pyvips.Image.new_from_file("input.jpg")bg = fg.gaussblur(8.0)mask = pyvips.Image.new_from_file("mask-gray.png")[0]
# blend=True 根据 0..255 遮罩强度平滑混合 fg 与 bgresult = mask.ifthenelse(fg, bg, blend=True)result.write_to_file("selective-blur.webp", Q=82)vips gaussblur input.jpg bg-blur.v 8.0vips ifthenelse mask-gray.png input.jpg bg-blur.v 'selective-blur.webp[Q=82]' --blend