首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >纯浏览器实现实物描边:NanoDet + MagicTouch 本地推理实践

纯浏览器实现实物描边:NanoDet + MagicTouch 本地推理实践

原创
作者头像
用户5557817
发布2026-07-30 15:05:03
发布2026-07-30 15:05:03
1420
举报

最近,我在 Timeline Studio 中实现了实物描边能力:识别画面中的商品、鞋子、玩具等物体,并在浏览器本地生成可编辑的描边效果。

这项功能的核心并不是“找出画面边缘”,而是稳定地回答三个问题:

  1. 用户想处理的是哪个物体?
  2. 如何获得这个物体干净的 Alpha 蒙版?
  3. 视频前后帧如何保持为同一个物体?

检测和分割为什么要拆开

目标检测擅长回答“物体在哪里”,但只能得到矩形框;提示分割可以生成像素级蒙版,却需要一个可靠的目标提示。

因此,整个流程拆成了两步:

代码语言:plaintext
复制
NanoDet 检测候选物体
        ↓
选择主要编辑对象
        ↓
根据目标中心生成提示点
        ↓
MagicTouch 生成物体蒙版
        ↓
质量检查
        ↓
生成并缓存 Alpha

检测放在独立 Worker 中运行,避免阻塞编辑器界面。推理时优先使用 WebGPU,不支持时回退到 WASM:

代码语言:js
复制
async function createSession(model) {
  if (navigator.gpu) {
    try {
      return await ort.InferenceSession.create(model, {
        executionProviders: ["webgpu"],
        graphOptimizationLevel: "all",
      });
    } catch (error) {
      console.warn("WebGPU 不可用,回退 WASM", error);
    }
  }

  return ort.InferenceSession.create(model, {
    executionProviders: ["wasm"],
    graphOptimizationLevel: "all",
  });
}

如何选择真正的编辑对象

一张图中可能同时存在多个物体,因此不能简单选择置信度最高的检测框。

项目会综合置信度、面积、画面中心距离,以及与上一帧目标的重叠程度进行排序:

代码语言:js
复制
function rankObject(item, previousSubject) {
  const area = getBoxArea(item.box);
  const centered = getCenterScore(item.box);
  const previousIoU = boxIoU(item.box, previousSubject?.box);

  return (
    item.score * 0.55 +
    Math.min(0.25, area) +
    centered * 0.2 +
    previousIoU * 1.2
  );
}

视频处理中,上一帧目标的 IoU 权重更高。这样即使画面中出现新的物体,也不会轻易切换编辑对象。

提示式物体分割

检测到目标后,将目标中心转换为归一化提示点,交给提示式分割模型:

代码语言:js
复制
const result = segmenter.segment(image, {
  keypoint: {
    x: Math.max(0, Math.min(1, point.x)),
    y: Math.max(0, Math.min(1, point.y)),
  },
});

const probabilities = result.confidenceMasks[1]
  .getAsFloat32Array();

const alpha = new Uint8ClampedArray(probabilities.length);

for (let i = 0; i < probabilities.length; i += 1) {
  const value = probabilities[i];
  alpha[i] = value >= threshold
    ? Math.round(value * 255)
    : 0;
}

这里不会直接使用模型输出,而是继续检查蒙版面积、边界、连通区域和目标框关系。

如果蒙版包含背景、其他物体或发生异常面积跳变,该帧会被拒绝。宁可暂时不显示效果,也不传播错误的物体蒙版。

视频不需要每帧都运行完整模型

如果每一帧都执行目标检测和提示分割,浏览器推理成本会很高。

项目采用“稀疏锚点+光流传播”的方式:

代码语言:js
复制
const needsAnchor =
  frameIndex === 0 ||
  frameIndex - lastAnchorIndex >= anchorInterval ||
  !trackedMaskBox;

if (needsAnchor) {
  currentAlpha = await analyzeObjectAnchor(frame);
  lastAnchorIndex = frameIndex;
} else {
  currentAlpha = await propagateWithOpticalFlow(
    previousFrame,
    currentFrame,
    currentAlpha,
  );
}

稳定追踪期间,通过光流将上一帧 Alpha 传播到当前帧;达到锚点间隔、目标丢失或质量检查失败时,再执行完整检测和分割。

如果传播后的蒙版面积变化过大,就会立即失效:

代码语言:js
复制
const areaRatio = nextArea / previousArea;

if (!nextBox || areaRatio < 0.35 || areaRatio > 2.8) {
  currentAlpha = null;
  trackedMaskBox = null;
}

AI 分析和描边样式解耦

目标分析是比较耗时的步骤,而颜色、宽度、阴影和材质调整应该即时响应。

因此,项目会缓存分析生成的 Alpha,描边参数单独保存在片段状态中:

代码语言:js
复制
const effect = {
  enabled: true,
  targetKind: "object",
  outline: {
    color: "#f3efe4",
    width: 12,
    opacity: 1,
    softness: 0,
    glow: 0.35,
  },
  material: {
    id: "paper",
    edgeDensity: 0.5,
    shadowDepth: 0.32,
  },
};

用户调整样式时只重新合成画面,不重新运行模型。预览和导出也使用同一套 Alpha 与效果状态,避免最终导出和编辑器显示不一致。

完整项目和代码可以在 GitHub 查看:

https://github.com/MartinDelophy/ai-video-editor

总结

实物描边的关键不只是模型精度,而是把检测、分割、质量门槛、目标身份和渲染状态组合成一条可靠的浏览器处理链路。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 检测和分割为什么要拆开
  • 如何选择真正的编辑对象
  • 提示式物体分割
  • 视频不需要每帧都运行完整模型
  • AI 分析和描边样式解耦
  • 总结
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档