libincao
← ARTICLES
2026.09.03 · 10 MIN · 中文 · AI

从一个 Chrome 插件到自己的桌面 app:四个 bug 全都跟钱有关

一个 Chrome 插件让我在网页上直接把军装穿到身上,我顺着它挖到背后的实时扩散模型,又用一个下午做了个自己的 macOS 版本,全程花了 $0.56。但真正值钱的不是那个 app,是它真跑起来之后冒出的四个 bug:共同点不是难,而是只有真跑才撞得见,并且全部导致多花钱。在按秒计费的产品里,UI 本身就是成本控制的一部分。

时间:2026-08-07 成本:$0.56(约 56 credits,含全部调试和实测) 产出virtual-tryon-desktop

起因很随便:我在一个军装 prompt 资料库网站上闲逛,发现浏览器里多了个东西。


结论速查

结论
插件叫什么 Anywear,Chrome Web Store 上架,背后是 Decart.AI
技术是什么 实时逐帧扩散(Live-Stream Diffusion),不是把衣服 P 上去
能不能本地跑 M2 上不行,差约 200 倍算力,差距不是优化能补的
自己做要多久 一个下午,Python + PySide6,官方 SDK 明说不需要浏览器
花了多少钱 全程 $0.56,免费额度是 $10
最值钱的收获 四个 bug,全部只有真跑起来才会撞见,且全部跟计费有关

一、先是插件

那天我在看 uniform.wingzero.tw 的军装 prompt 库,页面上每张服装图的左上角都多了一个 ✦ Virtual Try-On 的小标。点下去,右下角弹出一个悬浮窗,里面是我的摄像头画面,只不过我身上那件衬衫已经变成了一套军装礼服,连大盘帽上的帽徽都在。

我转头,它跟着转。我抬手,袖子的褶皱跟着变。

浏览网页时插件在每张服装图上注入的 Virtual Try-On 入口,以及右下角的实时试穿悬浮窗
配图 1 · 浏览网页时插件在每张服装图上注入的 Virtual Try-On 入口,以及右下角的实时试穿悬浮窗

这东西的体验密度很高:你不需要离开正在逛的页面。看到一件衣服,点一下,它就在你身上了。这比"打开某个试衣 app、上传照片、等 30 秒"要顺畅一个数量级。

实时试穿的效果特写
配图 2 · 实时试穿的效果特写

二、然后是挖背后是谁

体验完的第一反应是:这算力谁出的?不可能在我的 MacBook 上跑。

顺着三条线索对上了:

  1. Chrome Web Store 的发布者信息:Anywear: Live Virtual Try-On,发布者 Decart.AI, Inc.,联系邮箱 dev@decart.ai,注册地 Delaware。
  2. 产品站是 anywear.decart.ai,decart.ai 的子域名,页脚写着 Powered by decart,隐私政策和服务条款全部托管在 docs.platform.decart.ai
  3. 底层模型在他们自己的 API 文档里公开卖lucy-vton,Realtime 分类下。

所以这不是"某个团队做了个插件",而是"一家做实时视频模型的公司,用一个插件给自己的 API 做了个 demo"。插件是橱窗,API 才是货。

技术上它凭什么能实时

这是我最想搞明白的部分。普通的图片试穿模型(CatVTON、IDM-VTON 这些)一张图要跑 20–50 步去噪,根本追不上视频帧率。

Decart 的做法来自他们的 MirageLSD(Live-Stream Diffusion)路线,把问题的形状换掉了:

代价是分辨率和细节。这一点后面会反复出现。

关键认知:它不是把真实的那件衣服变形贴到你身上,而是以那件衣服为条件,重新生成你的每一帧。所以版型、垂坠、光影都是推理出来的,不是测量出来的。判断"这个廓形适不适合我"够用,判断"袖子会不会太长"完全没用。


三、三条路的账,我算完选了最贵的那条单价

想自己做,摆在面前三条路。

路线 A:本地跑

我的机器是 Apple M2 / 16GB。开源对标是 StreamDiffusionV2(causal DiT + rolling KV cache,基于 CausVid 和 Self-Forcing),报的数字是 1.3B 模型 61fps、14B 模型 31fps。

但那是数据中心 GPU 的数字。M2 的 GPU 大约 3.6 TFLOPS fp16,H100 约 1000。差距约 200 倍,不是 2 倍。 模型能塞进内存,但会变成"几秒一帧"而不是"每秒几十帧"。这个差距没有任何优化能补。

本地能做的是 CatVTON 那种静态试穿:899M 参数、8GB 显存以内、MPS 能跑,一张图几十秒。功能有了,"实时"没了。

路线 B:租 H100

$1.49–2.99/小时(Vast.ai / RunPod / Lambda)。

这条路有个容易被忽略的杀手:网络往返。帧预算是 40ms,如果 GPU 在美东而我在亚洲,光 RTT 就 150–250ms。模型再快也没用,你看到的是四分之一秒延迟的自己,手眼反馈已经断了。真要租得租东京或新加坡。

路线 C:直接用 API

$0.02/秒,按实际生成时间计费。

换算成小时是 $72/小时,比租 H100 贵将近 30 倍。但盈亏平衡点在"每小时串流 2 分钟":低于这个用量,API 更便宜;高于这个,自建更划算。

个人自用一天用几分钟,API 完胜。而且它一次性消掉了 GPU、区域、冷启动、WebRTC 服务端四件事。

选 C。

顺带看了条款


四、做成桌面端,而不是浏览器插件

我想要一个本机 app,不套浏览器壳。

一开始以为要绕路,结果查下来是官方支持的:Decart 有 Python realtime SDK,文档明说不需要浏览器pip install decart[realtime],底层是 LiveKit 的 Python 客户端)。另外还有 Swift SDK,支持 macOS 12+。

于是技术栈定了:

选择
GUI PySide6
实时传输 decart[realtime] → LiveKit
摄像头 OpenCV VideoCapture

架构上唯一有难度的地方:SDK 是 asyncio,Qt 不是。解法是让会话独占一个线程跑私有事件循环,进来的数据走 Qt signals,出去的调用走 call_soon_threadsafe / run_coroutine_threadsafe

界面就三块:左边传服装图和写描述,右边看实时画面,底下一条状态栏放计时器和累计花费。

自己做的 macOS app,左侧上传服装图,右侧实时试穿画面
配图 3 · 自己做的 macOS app,左侧上传服装图,右侧实时试穿画面

一个上来就纠正的错误认知

我最初按网上 MirageLSD 的介绍写文档,说模型是 768×432 / 20fps。实际查 SDK 是 1088×624 / 30fps。旧文章的数字过期了。

这件事的教训很直接:能 introspect 就别读二手资料。我后来所有的参数都是直接从安装好的包里问出来的,不是从博客里抄的。


五、四个 bug,全部跟钱有关

这是整件事里最值得写的部分。

我在写代码的时候,验证了模块导入、配置加载、帧格式转换、以及跟 Decart 的完整连接收流。全过。但 GUI 本身、摄像头、试穿效果,一次都没真跑过——那需要摄像头权限和显示器。

真跑起来之后,四个 bug 挨个冒出来。它们的共同点不是"难",而是"只有真跑才会撞见",以及"全部导致多花钱"。

Bug 1:Connect 在没有任何输入时是可点的

启动 app,没选服装、描述框是空的,ClearApply outfit 都正确置灰了,只有 Connect 亮着。点下去就开一个计费会话,但没有任何东西可以应用。纯烧钱。

原因很蠢:刷新按钮状态的函数只在用户操作时触发,__init__ 里从来没调用过,所以启动时按钮停留在 Qt 的默认 enabled 状态。

一行修复,但只有把 app 打开盯着看才会发现。

Bug 2:镜像做在输出端,把水印翻转了

试穿画面右上角有 Decart 烧录的 AI Generated 水印。我第一版跑出来,它显示成 detareneG IA

因为我把「镜像」做在了收到画面之后,整帧水平翻转,连水印一起翻了。

这不只是难看。Decart 的 AUP 原文禁止 "remove, obscure, disable or circumvent any watermark, label, provenance information",一个反着的水印很难说不算 obscure

正确做法是在输入端镜像:翻转送出去的摄像头帧,回传画面本来就是镜像的,而水印是生成之后才打上去的,方向自然是对的。Decart 的 JS SDK 有个 mirror: "auto" 参数,做的就是这件事。我绕了一圈才想明白。

Bug 3:Retina 上先缩小再放大,丢掉一半细节

跑起来第一感觉是画面有点糊。我一开始怀疑是模型分辨率不够、或者摄像头输入被劣化了。

查下来两条都不是:摄像头稳定给 1280×720,我的裁剪缩放用的是 INTER_AREA,高质量降采样,没有损失。

真正的原因在显示端。我写的是:

self._pixmap.scaled(self.size(), ...)

self.size()逻辑点,不是物理像素。在 2x 的 Retina 屏上实际发生的是:

  1. 1088px 的原图被缩小到约 670 点
  2. Qt 再把这 670 点渲染到 1340 个物理像素上,放大 2 倍

等于一张 670px 的图被拉到 1340px。先扔掉一半信息,再插值补回来——补不回来的。

改成按物理像素缩放之后只重采样一次,画面尺寸不变,清晰度肉眼可见地提升。修复后的截图里,超人胸口那个 S 能看到布料的编织纹理。

修复清晰度和水印方向之后的效果:右侧水印方向正确,布料纹理清晰
配图 4 · 修复清晰度和水印方向之后的效果:右侧水印方向正确,布料纹理清晰

Bug 4:一张大图搞死控制通道,而 app 浑然不觉还在计费

传了一张超人剧照,点 Apply,弹出:

Could not apply outfit: Control channel disconnected

翻 SDK 源码,这句话来自 WebSocket 接收循环退出时finally 块。注意它跟另一条 Control channel not connected(第 429 行)不是一回事:我遇到的不是"还没连上",而是通道中途死了

再往下看:set_image 把整个图片文件 base64 之后直接推进这个 WebSocket,SDK 里既没缩放也没大小上限ws_connect(ws_url) 也没设 max_msg_size。一张桌面壁纸尺寸的图,base64 之后再涨 33%,很可能就是这么把通道撑死的。

加了一层预处理,长边压到 1024、转 JPEG。实测一张 4032×3024 的图:

4032x3024 35787KB  →  1024x768 353KB

但比这个 bug 更糟的是它的两个后果:

后果一:会话死了还在计费。 控制通道断了之后,视频流还在回传,所以界面显示 Live、画面照动,实际上任何指令都执行不了,而钱一直在扣。光看画面无法判断会话是否健康。 修复是每 250ms 查一次 get_connection_state(),遇到 disconnected / reconnecting 直接停。

后果二:报错用的是模态弹窗。 弹窗等你点 OK 的整段时间,$0.02/秒照收。你去泡杯咖啡回来就是另一个数字了。会话类错误现在只写状态栏和 stderr,不拦路;只有摄像头打不开这种发生在计费之前的致命错误才保留弹窗。


六、计费的真相:我的估算高了整整一倍

整个过程我在报告花费时一直按"连接开始到断开"的墙钟时间乘 $0.02。

三次测试下来我报了 $0.61。翻 dashboard,实际是 32 credits = $0.32

会话 墙钟 按墙钟推算 实际扣费
1 + 2 21 秒 $0.42 22 credits($0.22)
3 9 秒 $0.19 10 credits($0.10)
合计 $0.61 32 credits($0.32)

差在两处:连接握手的 2.7–3.8 秒不计费,以及回传帧率约 17fps 而标称 30fps,所以一秒墙钟对应的生成视频不足一秒。

我把这个偏差保留在了 app 的费用表里,并在 README 写明——宁可高估,让消费上限提前触发而不是延后触发。费用表是天花板,不是账单。


七、几点心得

1. 二手资料会过期,能问代码就问代码。 模型分辨率、SetInput 的字段、连接参数的默认值、set_image 到底怎么发的,全部是 introspect 安装好的包和读源码得到的,没有一条是从文章里抄的。那篇写 768×432 的介绍并没有错,只是过期了。

2. "验证过"要说清楚验证到哪一层。 我在交付时说过"跟 API 的完整连接和收流已验证",同时明确说"GUI 本身、摄像头、试穿效果没验证过"。后面四个 bug 全部落在没验证的那一层。这个边界如果含糊,就会变成"我以为它能用"。

3. 按秒计费的产品,UI 本身就是成本控制的一部分。 四个 bug 里有三个的真正危害是多花钱,不是功能不对。一个可点的按钮、一个模态弹窗、一个显示 Live 的死会话,在 $0.02/秒的语境下都是漏水的口子。写这类应用,得把"哪些界面状态会导致计费继续"当成一条独立的检查项。

4. 实时不总是更好。 1088×624、17fps、带水印的实时流,判断"这个廓形适不适合我"够用;但如果只是想看清一件衣服的细节,一张高清静态图更有用,而且便宜约两个数量级(Lucy Image 2 是 $0.01–0.02 一张)。实时的价值在于"动起来看版型",不在于清晰度。


附:如果你也想试

只想玩:装 Anywear 插件,免费,两分钟。

想自己做:注册 platform.decart.ai 送 1000 credits($10,约合 8 分钟生成时间,按上面那个 2 倍关系,够十几次完整试穿)。我的实现在 ribincao/virtual-tryon-desktop,MIT,make run 会自己建 venv,从零开始只需要填一个 API key。

README 里记了全部踩坑:摄像头 TCC 权限归属、RGB24 与 BGR、LiveKit 缓冲区复用、以及上面那四个 bug 的完整前因后果。