智慧衣橱虚拟试穿实践:从占位 UI到 Sophnet 双图图生图的落地思考

 一、写在前面:

做智慧衣橱时,产品页面上最早写的是AI 试穿 / 图生图 · 接入中。很长一段时间里,我把试穿和小衣对话助手混在一起想——以为在聊天框里发一张衣服图,大模型说几句搭配建议,就算AI 试衣了。

后来明白:虚拟试穿本质上是图像问题,不是文本问题。

- 对话大模型:输入文字 → 输出文字(或附带描述)
- 虚拟试穿:输入 人物图 + 服装图 → 输出 一张新的合成图

这在技术分类上属于 图生图(Image-to-Image) 或 条件图像编辑:以人物照为底,用服装图和 Prompt 作为条件,让扩散模型生成穿上这件衣服的效果图。

专用 VTON(Virtual Try-On)扩散模型(如 IDM-VTON)效果最好,但需要 GPU 或海外 API。我们团队当时 只有 Sophnet 平台的 API Key,没有阿里云 DashScope 原厂 Key,也没有 Replicate 账号。

于是做了一个务实选择:用 Sophnet 上的 `Qwen-Image-Edit-2509` 做双图 + Prompt的近似试穿——不是严格意义上的 VTON,但在工程上能跑通、能演示、能写进论文,而且和现有 Key 兼容。

二、近似试穿

 2.1 三种路线,我们选了中间那条

路线 做法 难度 效果

A. 专用 VTON

IDM-VTON 等双图模型

高(GPU/海外 API)

最好

B. 图像编辑 / 图生图

双图 +「穿上这件衣服」Prompt

够用,不稳定

C. 纯文生图

只描述衣服,不喂服装图

衣服对不上

我们走的是 B:在 Sophnet 文档里,`Qwen-Image-Edit-2509` 支持 1~3 张参考图 + 编辑指令,任务类型标注为 I2I(图生图),这和让人物穿上某件衣服的需求是对齐的。

 2.2 和专用 VTON差在哪里(心里要有数)

图生图近似试穿 不保证:

- 服装纹理 1:1 还原(Logo、条纹可能糊)
- 人脸 100% 不变(偶发换脸感)
- 复杂姿态下的遮挡关系

但它 能保证:

- 端到端链路:App 选图 → 后端 → 云 API → 结果 URL → 展示
- 和衣橱数据打通:服装图可直接来自已录入单品
-基于图生图条件编辑的近似虚拟试穿,后续可升级为专用 VTON

三、整体架构:Key 绝不进 App

个人理解里,移动端 AI 接入的第一原则是:API Key 只放服务端。

```
Android VirtualTryOnScreen
    │  Multipart: person_image + garment_image
    │  Header: Authorization: Bearer <登录 JWT>
    ▼
FastAPI  /api/v1/ai/tryon
    │  鉴权 → 存图 → tryon_service.run_tryon()
    ▼
Sophnet  Qwen-Image-Edit-2509
    │  异步任务:创建 → 轮询 → 取 results[0].url
    ▼
backend/uploads/tryon/  →  App 用 Coil 加载 result_url
```

令试穿单独一个页面:

1. 图生图慢(常 30~120 秒),不适合在聊天气泡里干等  
2. 两张图 + 轮询,交互更像工具而不是对话  
3. 以后换 VTON 模型,只改 `tryon_service.py`,App 协议不变  

四、后端实现

4.1 接口设计:简单、可演示、可扩展

FastAPI 路由(节选):

```python
@app.post("/api/v1/ai/tryon", response_model=TryOnResponse)
async def ai_tryon(
    user_id: Annotated[int, Depends(current_user_id)],
    person_image: UploadFile = File(...),
    garment_image: UploadFile = File(...),
) -> TryOnResponse:
    # 校验 image/ → 落临时文件 → run_tryon()
    ...
    return TryOnResponse(payload)
```

响应里我刻意加了 `mode` 字段:

- `provider`:Sophnet 图生图成功  
- `demo`:未配置 Key,或调用失败时回显人物图  

这是个人很看重的一点:没有 Key 的日子也要能答辩演示 UI。Demo 不是造假,而是工程上的 降级策略(Graceful Degradation)。

4.2 核心:双图顺序 + Prompt 设计

Sophnet 文档写得很清楚:`input.images` 支持 1~3 张图,多图时按数组顺序,且 输出宽高比以最后一张为准。

因此我们的顺序是:

```python
"images": [garment_data_uri, person_data_uri]
# 图1 = 服装,图2 = 人物 → 输出比例跟人物走
```

Prompt 是反复试出来的(可放 `.env` 覆盖):

```python
DEFAULT_TRYON_PROMPT = (
    "参考图1中的服装单品,让图2中的人物自然穿上该服装。"
    "保持图2的人脸、发型、体态、背景不变,服装贴合身体,写实摄影风格,高清。"
)

DEFAULT_NEGATIVE_PROMPT = (
    "模糊,低质量,变形的脸,多余的手指,文字水印,卡通,插画风格,"
    "服装错位,头部替换,背景突变"
)
```

思考: Prompt 写图1 / 图2是和数组顺序绑定的;若以后改成人物在前、服装在后,Prompt 必须一起改,否则模型会懵。

4.3 异步任务:创建 + 轮询

Sophnet 图片接口是 异步 的:POST 返回 `taskId`,再 GET 查状态直到 `SUCCEEDED`。

创建任务(节选):

```python
payload = {
    "model": "Qwen-Image-Edit-2509",
    "input": {
        "prompt": prompt,
        "negative_prompt": negative,
        "images": [garment_data_uri, person_data_uri],
    },
    "parameters": {
        "size": "12801280",
        "watermark": False,
        "prompt_extend": True,
        "save_to_jpeg": True,
    },
}
r = client.post(SOPHNET_IMAGE_TASK_URL, headers={...}, json=payload)
task_id = output["taskId"]
```

轮询逻辑(个人实现要点):

```python
while time.time() < deadline:
    r = client.get(f".../task/{task_id}", headers={...})
    status = output.get("taskStatus", "").upper()
    if status == "SUCCEEDED":
        return output["results"][0]["url"]
    if status in ("FAILED", "CANCELED"):
        raise RuntimeError(...)
    time.sleep(2)  # 默认 2 秒一轮,最多 180 秒
```

响应体有时包在 `output` 里,有时在 `result` 里,我写了 `_unwrap_output()` 做兼容,避免联调时明明成功却解析失败。

图生图失败时不向 App 抛 500,而是 `print` 日志 + 降级 demo。用户至少能看到人物回显和错误提示,而不是整页崩溃。

4.4 环境变量(`.env`)

```env
TRYON_PROVIDER=sophnet
SOPHNET_API_KEY=你的Key
TRYON_MODEL=Qwen-Image-Edit-2509
TRYON_API_BASE_URL=https://www.sophnet.com/api/open-apis/projects/easyllms/imagegenerator/task
TRYON_POLL_MAX_SEC=180
```

Sophnet 文档:[视觉模型](https://www.sophnet.com/docs/component/vision_model.html)

五、Android 端实现与思考

5.1 页面:VirtualTryOnScreen

交互我设计成三步,尽量像工具不像聊天:

1. 选人物照(相册)  
2. 选服装图(相册 或 衣橱横滑选单品)  
3. 点生成试穿效果→ 展示 `result_url`  

实现的衣橱联动:服装图不必重新拍,直接用 DataStore 里已有 `imageUri`,形成我的衣橱 → 试穿预览闭环。

5.2 上传:Retrofit Multipart

```kotlin
@Multipart
@POST("api/v1/ai/tryon")
suspend fun tryOn(
    @Part personImage: MultipartBody.Part,
    @Part garmentImage: MultipartBody.Part,
): TryOnResponseDto
```

`AiRepository` 里从 `content://` 读字节,和上传头像同一套路:

```kotlin
suspend fun virtualTryOn(personUri: Uri, garmentUri: Uri): TryOnResponseDto {
    val personPart = uriToPart(personUri, "person_image", "person.jpg")
    val garmentPart = uriToPart(garmentUri, "garment_image", "garment.jpg")
    return api.tryOn(personPart, garmentPart)
}
```

5.3 超时:图生图 ≠ 登录接口

普通接口 readTimeout 20s;图生图单独 `aiApi()` 拉到 120s。这是实际联调时得到的经验——异步轮询在后端做,但整次 HTTP 仍可能接近一分钟。

5.4 登录与游客

试穿接口复用 JWT。游客点击生成会引导登录——既是安全(防刷 API),也是产品逻辑(试穿算深度功能)。

六、联调步骤

1. `cd backend && pip install httpx && uvicorn main:app --host 0.0.0.0 --port 8080`  
2. `.env` 填 `SOPHNET_API_KEY`  
3. Android `local.properties`:`WARDROBE_API_BASE_URL=http://10.0.2.2:8080/`(模拟器)  
4. App 登录 → 搭配 → AI 实验室 → AI 试穿 / 虚拟上身
5. 人物照 + 服装图 → 生成  
6. 成功:`mode=provider`;未配 Key:`mode=demo` 回显人物图  

七、效果与局限:

效果:

- 认清试穿 = 图生图,没有硬塞进 LLM 聊天  
- 服务端代理 Key,JWT 鉴权  
- 演示降级 + 真实模式用 `mode` 区分  
- Sophnet 双图 I2I 与现有 Key 兼容,零 GPU  

局限:

- 效果随 Prompt、照片质量波动大  
- 专用 VTON 在衣服还原度上仍明显更好  
- 大图 Base64 上传,尚未做压缩/去背景预处理  
- 轮询在后端同步阻塞,高并发时要改 task 队列  

若继续迭代,我会按这个顺序:

1. 上传前缩放到短边 768、服装图去背景  
2. 后端改异步:POST 返回 task_id,App 轮询  
3. 有余力再换 Replicate IDM-VTON 或万相试衣专用接口  

八、总结

本项目的虚拟试穿模块,是在智慧衣橱场景下对图生图(Image-to-Image)技术的一次工程实践:客户端采集人物照与服装单品图,经 FastAPI 服务端调用 Sophnet 平台的  Qwen-Image-Edit-2509 图像编辑模型,通过双图输入与自然语言编辑指令生成近似上身效果。该方案属于 条件图像编辑式近似试穿,与专用 VTON 扩散模型相比实现成本更低,便于在课程项目中完成端到端验证;API 密钥部署于服务端,移动端仅传输业务图片与 JWT,兼顾安全与可扩展性。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐