前端黑科技 浏览器本地 AI 抠图 无需服务器全程隐私保护

5 阅读2分钟

前端黑科技 浏览器本地 AI 抠图 无需服务器全程隐私保护

发现一个超厉害的工具 aicut.online 不用依赖服务器 直接在浏览器里就能完成图片背景移除 所有处理都在本地进行 隐私安全拉满 研究后发现它是靠 u2net 模型加 onnxruntime-web 实现的 原理其实没那么复杂。

核心技术拆解

先搞懂三个关键技术 才能明白本地抠图怎么实现。

  • WebAssembly 简称 Wasm 一种二进制指令格式 能在浏览器里以接近原生的速度运行 还支持 C C++ Rust 等多种语言编译 专门解决 JS 处理密集型任务性能不足的问题。
  • onnxruntime-web 浏览器端的机器学习模型运行引擎 能加载 ONNX 格式的模型文件 支持 Wasm WebGL 等多种执行后端 让模型在本地高效推理。
  • u2net 专门做目标分割的神经网络 嵌套 U 型结构能精准捕捉图像细节 就像智能剪刀手 能快速把前景主体从背景中分离出来 抠图效果超棒。

技术架构层层解析

整个工具的架构很清晰 分五层协作。

  1. 用户层 就是我们看到的网页界面 负责接收图片输入 调用 API 执行抠图 最后展示结果。
  2. 模型服务层 核心是 onnxruntime-web 库 加载 u2net 模型文件 管理内存中的张量数据 给上层提供简洁的 JS API。
  3. 执行引擎层 首选 Wasm 后端保障高性能 可选 WebGL 利用 GPU 加速 还有实验性的 WebNN 调用硬件加速。
  4. 模型层 存储 u2net 的 ONNX 格式文件 包含训练好的网络结构和权重参数 专门处理抠图任务。
  5. 资源层 提供模型文件的存储和加载环境 浏览器的 Wasm 引擎 WebGL API 还有 CPU GPU 等硬件资源都在这里。

核心代码直击

核心代码关键流程就三步。

第一步 加载模型 用 IndexedDB 缓存 二次使用不用重新下载

useEffect(() => {
  const loadModel = async () => {
    try {
      setError(null);
      const db = await openDB();
      let modelData = await getModelFromDB(db);
      if (!modelData) {
        const response = await fetch('./u2net.onnx');
        if (!response.ok) throw new Error(`网络请求模型失败 ${response.status} ${response.statusText}`);
        modelData = await response.arrayBuffer();
        await storeModelInDB(db, modelData);
      }
      const newSession = await ort.InferenceSession.create(modelData, {
        executionProviders: ['wasm'],
        graphOptimizationLevel: 'all',
      });
      setSession(newSession);
    } catch (e) {
      console.error('ONNX模型加载或初始化失败', e);
      setError(`模型处理失败 ${e.message}`);
    }
  };
  loadModel();
}, []);

第二步 图片预处理 转换成模型需要的张量格式

const preprocess = async (imgElement) => {
  const canvas = document.createElement('canvas');
  const modelWidth = 320;
  const modelHeight = 320;
  canvas.width = modelWidth;
  canvas.height = modelHeight;
  const ctx = canvas.getContext('2d');
  ctx.drawImage(imgElement, 0, 0, modelWidth, modelHeight);
  const imageData = ctx.getImageData(0, 0, modelWidth, modelHeight);
  const data = imageData.data;

  const float32Data = new Float32Array(1 * 3 * modelHeight * modelWidth);
  const mean = [0.485, 0.456, 0.406];
  const std = [0.229, 0.224, 0.225];

  for (let i = 0; i < modelHeight * modelWidth; i++) {
    float32Data[i] = (data[i * 4] / 255 - mean[0]) / std[0];
    float32Data[i + modelHeight * modelWidth] = (data[i * 4 + 1] / 255 - mean[1]) / std[1];
    float32Data[i + 2 * modelHeight * modelWidth] = (data[i * 4 + 2] / 255 - mean[2]) / std[2];
  }
  return new ort.Tensor('float32', float32Data, [1, 3, modelHeight, modelWidth]);
};

第三步 运行模型推理 后处理生成透明背景图

const runSegmentation = async () => {
  if (!image || !session) {
    setError('请先上传图片并等待模型加载完成');
    return;
  }
  setError(null);
  setOutputImage(null);
  try {
    const imgElement = imageRef.current;
    if (!imgElement) throw new Error('图片元素未找到');
    if (!imgElement.complete) await new Promise(resolve => { imgElement.onload = resolve; });
    const inputTensor = await preprocess(imgElement);
    const feeds = { 'input.1': inputTensor };
    const results = await session.run(feeds);
    const outputTensor = results[session.outputNames[0]];
    const outputDataURL = postprocess(outputTensor, imgElement);
    setOutputImage(outputDataURL);
  } catch (e) {
    console.error('抠图失败', e);
    setError(`抠图处理失败 ${e.message}`);
  }
};

后处理部分主要是将模型输出的掩码图 与原图合成带透明通道的图片 代码就不贴全了 核心是处理 alpha 通道并缩放回原图尺寸。

海云前端丨前端开发丨简历面试辅导丨求职陪跑