You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何利用Segment Anything生成的mask数据在Canvas中绘制掩码图像

实现Segment Anything掩码的Web端Canvas绘制

一、Masks数据核心可用字段说明

通过SamAutomaticMaskGenerator.generate()生成的masks是字典列表,每个字典中和绘制相关的核心字段:

  • segmentation: 二维布尔数组(np.ndarray类型),对应原图每个像素是否属于当前掩码区域(True代表在掩码范围内)
  • bbox: 掩码的边界框,格式为[x, y, width, height],可用于快速定位掩码在原图中的位置
  • area: 掩码覆盖的像素面积,可用来过滤过小或过大的无效掩码

二、后端处理:将Masks转换为前端可接收的格式

直接传递numpy数组给前端不可行,这里提供两种实用转换方案:

方案1:转换为Base64图片(快速落地)

把每个掩码转成带透明通道的PNG,再编码为Base64,方便前端直接加载:

from segment_anything import SamAutomaticMaskGenerator, sam_model_registry
import numpy as np
from PIL import Image
import base64
from io import BytesIO

sam = sam_model_registry["vit_b"](checkpoint="/Users/jiangxingshang/Downloads/sam_vit_b_01ec64.pth")
sam.to(device='cuda')
mask_generator = SamAutomaticMaskGenerator(sam)

img = Image.open("/Users/jiangxingshang/Pictures/8435e5dde71190ef699eced5cc1b9d16fdfa6056.jpg")
imgnp = np.array(img)
masks = mask_generator.generate(imgnp)

# 处理每个掩码为Base64格式
processed_masks = []
for mask in masks:
    # 布尔数组转8位灰度图(255为掩码区域,0为背景)
    mask_img = (mask["segmentation"] * 255).astype(np.uint8)
    # 转为带透明通道的PIL图片
    pil_mask = Image.fromarray(mask_img).convert("L")
    # 编码为Base64
    buffer = BytesIO()
    pil_mask.save(buffer, format="PNG")
    base64_str = base64.b64encode(buffer.getvalue()).decode("utf-8")
    # 打包掩码数据
    processed_masks.append({
        "base64": f"data:image/png;base64,{base64_str}",
        "bbox": mask["bbox"]
    })

# 后续可通过FastAPI/Flask等框架将processed_masks返回给前端

方案2:使用RLE编码(高效传输)

SAM生成的掩码默认包含RLE编码(字典格式,含counts和size字段),直接提取传输能大幅减小数据体积:

# 提取RLE及辅助字段
processed_masks = []
for mask in masks:
    processed_masks.append({
        "rle": mask["rle"],
        "bbox": mask["bbox"],
        "color": [np.random.randint(0,255), np.random.randint(0,255), np.random.randint(0,255)]
        # 提前生成随机颜色,前端直接复用
    })

# 通过接口返回给前端即可

三、前端Canvas绘制实现

假设前端已获取后端返回的processed_masks和原图尺寸,以下是两种方案的绘制代码:

方案1:基于Base64图片的绘制

<canvas id="maskCanvas"></canvas>
<script>
const canvas = document.getElementById('maskCanvas');
const ctx = canvas.getContext('2d');

// 假设已通过接口获取:masksList(processed_masks)、imageWidth、imageHeight
async function drawMasks() {
    // 匹配Canvas尺寸与原图
    canvas.width = imageWidth;
    canvas.height = imageHeight;

    // 先绘制原图(可选)
    const img = new Image();
    img.src = "原图的Base64或URL";
    await img.decode();
    ctx.drawImage(img, 0, 0);

    // 逐个绘制掩码,添加半透明效果
    for (const mask of masksList) {
        const maskImg = new Image();
        maskImg.src = mask.base64;
        await maskImg.decode();
        
        ctx.globalAlpha = 0.5;
        // 随机给掩码添加色调区分
        ctx.filter = `hue-rotate(${Math.random()*360}deg)`;
        // 绘制到对应位置
        ctx.drawImage(maskImg, mask.bbox[0], mask.bbox[1], mask.bbox[2], mask.bbox[3]);
        ctx.globalAlpha = 1;
        ctx.filter = 'none';
    }
}

drawMasks();
</script>

方案2:基于RLE编码的绘制(无图片传输,更高效)

<canvas id="maskCanvas"></canvas>
<script>
const canvas = document.getElementById('maskCanvas');
const ctx = canvas.getContext('2d');

// RLE解码为ImageData的工具函数
function rleToImageData(rle, width, height) {
    const imageData = ctx.createImageData(width, height);
    const data = imageData.data;
    let idx = 0;
    let currentPixel = 0;
    const counts = rle.counts;
    
    for (let i = 0; i < counts.length; i++) {
        const count = counts[i];
        // 偶数索引为背景(透明),奇数为掩码区域(半透明)
        const isMask = i % 2 === 1;
        for (let j = 0; j < count; j++) {
            if (currentPixel >= width * height) break;
            data[idx] = isMask ? 255 : 0;
            data[idx+1] = isMask ? 255 : 0;
            data[idx+2] = isMask ? 255 : 0;
            data[idx+3] = isMask ? 127 : 0; // 半透明
            idx += 4;
            currentPixel++;
        }
    }
    return imageData;
}

async function drawMasks() {
    canvas.width = imageWidth;
    canvas.height = imageHeight;

    // 绘制原图(可选)
    const img = new Image();
    img.src = "原图的Base64或URL";
    await img.decode();
    ctx.drawImage(img, 0, 0);

    // 逐个解码RLE并绘制掩码
    for (const mask of masksList) {
        const maskHeight = mask.rle.size[0];
        const maskWidth = mask.rle.size[1];
        const imageData = rleToImageData(mask.rle, maskWidth, maskHeight);
        
        // 创建临时Canvas绘制掩码
        const tempCanvas = document.createElement('canvas');
        tempCanvas.width = maskWidth;
        tempCanvas.height = maskHeight;
        const tempCtx = tempCanvas.getContext('2d');
        tempCtx.putImageData(imageData, 0, 0);
        
        // 叠加带颜色的掩码到主画布
        ctx.globalAlpha = 0.5;
        ctx.fillStyle = `rgb(${mask.color[0]}, ${mask.color[1]}, ${mask.color[2]})`;
        ctx.drawImage(tempCanvas, mask.bbox[0], mask.bbox[1]);
        ctx.globalCompositeOperation = 'source-atop';
        ctx.fillRect(mask.bbox[0], mask.bbox[1], mask.bbox[2], mask.bbox[3]);
        ctx.globalCompositeOperation = 'source-over';
        ctx.globalAlpha = 1;
    }
}

drawMasks();
</script>

内容的提问来源于stack exchange,提问作者user1434702

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 00:14:52