save code
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
This is a FastAPI-based virtual try-on service that integrates with ComfyUI for AI-powered clothing try-on. The service accepts base64-encoded images (model photo, shirt, pants) and returns a generated try-on result.
|
||||
|
||||
## Running the Service
|
||||
|
||||
Start the server:
|
||||
```bash
|
||||
python service2.py
|
||||
```
|
||||
|
||||
The server runs on `http://0.0.0.0:47698` by default.
|
||||
|
||||
## Dependencies
|
||||
|
||||
Required Python packages (install via pip):
|
||||
- fastapi
|
||||
- uvicorn
|
||||
- httpx
|
||||
- pydantic
|
||||
|
||||
## Architecture
|
||||
|
||||
### Request Flow
|
||||
1. Client sends POST to `/try-on` with base64 images (model_image, shirt_image, pants_image)
|
||||
2. Server decodes images and uploads to ComfyUI at `http://127.0.0.1:8188`
|
||||
3. Server modifies workflow JSON (change2_1_ex.json) with uploaded image filenames
|
||||
4. Workflow submitted to ComfyUI queue
|
||||
5. Server polls ComfyUI history endpoint until completion (max 300s timeout)
|
||||
6. Result image downloaded from ComfyUI and returned as base64
|
||||
|
||||
### Key Components
|
||||
|
||||
**service2.py**: Main FastAPI application
|
||||
- `/try-on`: Main endpoint for virtual try-on
|
||||
- `/health`: Health check endpoint
|
||||
- `/`: Serves static frontend (index.html)
|
||||
|
||||
**change2_1_ex.json**: ComfyUI workflow configuration
|
||||
- Node "11": Model image input (LoadImage)
|
||||
- Node "10": Shirt image input (LoadImage)
|
||||
- Node "33": Output image (SaveImage)
|
||||
- Node "31": Text prompt for image editing
|
||||
|
||||
**static/index.html**: Frontend interface for testing
|
||||
|
||||
### ComfyUI Integration
|
||||
|
||||
The service expects ComfyUI to be running at `http://127.0.0.1:8188` with:
|
||||
- FireRed-Image-Edit model loaded
|
||||
- Qwen 2.5 VL CLIP model
|
||||
- Required custom nodes for QwenEditConfigPreparer, QwenEditOutputExtractor
|
||||
|
||||
### Workflow Modification
|
||||
|
||||
When processing requests, the code dynamically updates these workflow nodes:
|
||||
- `workflow["11"]["inputs"]["image"]`: Model photo filename
|
||||
- `workflow["10"]["inputs"]["image"]`: Shirt photo filename
|
||||
- `workflow["31"]["inputs"]["value"]`: Text prompt (currently hardcoded in Chinese)
|
||||
|
||||
## Important Notes
|
||||
|
||||
- Images are uploaded with unique filenames (UUID prefix) to avoid caching issues
|
||||
- The pants_image parameter is currently unused (commented out in code)
|
||||
- Workflow uses 8 sampling steps with euler sampler and beta scheduler
|
||||
- Output resolution is 768x1024 pixels
|
||||
- CORS is enabled for all origins
|
||||
+3
-9
@@ -44,7 +44,6 @@ app.add_middleware(
|
||||
class TryOnRequest(BaseModel):
|
||||
model_image: str # base64 模特图片
|
||||
shirt_image: str # base64 上衣图片
|
||||
pants_image: str # base64 裤子图片
|
||||
|
||||
|
||||
class TryOnResponse(BaseModel):
|
||||
@@ -113,7 +112,6 @@ async def try_on(req: TryOnRequest):
|
||||
换装接口
|
||||
- model_image: 模特图片 base64
|
||||
- shirt_image: 上衣图片 base64
|
||||
- pants_image: 裤子图片 base64
|
||||
返回 result_image: 换装结果 base64
|
||||
"""
|
||||
|
||||
@@ -124,11 +122,10 @@ async def try_on(req: TryOnRequest):
|
||||
|
||||
|
||||
async with httpx.AsyncClient(timeout=60.0) as client:
|
||||
# 解码三张图片
|
||||
# 解码两张图片
|
||||
try:
|
||||
model_bytes = decode_base64_image(req.model_image)
|
||||
shirt_bytes = decode_base64_image(req.shirt_image)
|
||||
# pants_bytes = decode_base64_image(req.pants_image)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=f"图片解码失败: {e}")
|
||||
|
||||
@@ -136,14 +133,12 @@ async def try_on(req: TryOnRequest):
|
||||
uid = uuid.uuid4().hex[:8]
|
||||
model_fname = f"model_{uid}.jpg"
|
||||
shirt_fname = f"shirt_{uid}.jpg"
|
||||
# pants_fname = f"pants_{uid}.jpg"
|
||||
|
||||
# 并发上传三张图片
|
||||
# 并发上传两张图片
|
||||
try:
|
||||
model_name, shirt_name, pants_name = await asyncio.gather(
|
||||
model_name, shirt_name = await asyncio.gather(
|
||||
upload_image(client, model_bytes, model_fname),
|
||||
upload_image(client, shirt_bytes, shirt_fname),
|
||||
# upload_image(client, pants_bytes, pants_fname),
|
||||
)
|
||||
except httpx.HTTPError as e:
|
||||
raise HTTPException(status_code=502, detail=f"上传图片到 ComfyUI 失败: {e}")
|
||||
@@ -151,7 +146,6 @@ async def try_on(req: TryOnRequest):
|
||||
# 修改工作流节点的图片输入
|
||||
workflow[NODE_MODEL]["inputs"]["image"] = model_name
|
||||
workflow[NODE_SHIRT]["inputs"]["image"] = shirt_name
|
||||
# workflow[NODE_PANTS]["inputs"]["image"] = pants_name
|
||||
|
||||
# 提交工作流
|
||||
try:
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
.upload-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 20px;
|
||||
width: 100%;
|
||||
max-width: 900px;
|
||||
@@ -204,7 +204,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<h1>AI 换装系统</h1>
|
||||
<p class="subtitle">上传模特、上衣、裤子三张图片,AI 自动完成换装</p>
|
||||
<p class="subtitle">上传模特和上衣两张图片,AI 自动完成换装</p>
|
||||
|
||||
<div class="upload-grid">
|
||||
<!-- 模特 -->
|
||||
@@ -224,15 +224,6 @@
|
||||
<div class="upload-hint">节点 10 · 上身服装</div>
|
||||
<img class="preview-img" id="preview-shirt" alt="上衣预览" />
|
||||
</div>
|
||||
|
||||
<!-- 裤子 -->
|
||||
<div class="upload-card" id="card-pants">
|
||||
<input type="file" accept="image/*" id="input-pants" />
|
||||
<div class="upload-icon">👖</div>
|
||||
<div class="upload-label">裤子图片</div>
|
||||
<div class="upload-hint">节点 4 · 下身服装</div>
|
||||
<img class="preview-img" id="preview-pants" alt="裤子预览" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn-run" id="btn-run" disabled>开始换装</button>
|
||||
@@ -245,7 +236,7 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const images = { model: null, shirt: null, pants: null };
|
||||
const images = { model: null, shirt: null };
|
||||
|
||||
function setupUpload(inputId, previewId, cardId, key) {
|
||||
const input = document.getElementById(inputId);
|
||||
@@ -269,11 +260,10 @@
|
||||
|
||||
setupUpload('input-model', 'preview-model', 'card-model', 'model');
|
||||
setupUpload('input-shirt', 'preview-shirt', 'card-shirt', 'shirt');
|
||||
setupUpload('input-pants', 'preview-pants', 'card-pants', 'pants');
|
||||
|
||||
function updateRunButton() {
|
||||
const btn = document.getElementById('btn-run');
|
||||
btn.disabled = !(images.model && images.shirt && images.pants);
|
||||
btn.disabled = !(images.model && images.shirt);
|
||||
}
|
||||
|
||||
function setStatus(msg, cls = '') {
|
||||
@@ -308,7 +298,6 @@
|
||||
body: JSON.stringify({
|
||||
model_image: images.model,
|
||||
shirt_image: images.shirt,
|
||||
// pants_image: images.pants,
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user