5 Commits
Author SHA1 Message Date
Ubuntu 9379fbb8f8 save 2026-08-02 01:27:04 +08:00
xsl c20606c003 save 2026-07-31 23:29:07 +08:00
xslandCursor 1ca033f25a feat: 接口1/6 标注层字号上调一档 + 眉心改用 9 号点定位
- annotation: 自适应字号系数 0.017→0.020(下限 8→9),标注文字更大更清晰
- measure: _brow_center 只取 FaceMesh 9 号点(眉间上点),不再与 151 取中点

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 23:02:19 +08:00
UbuntuandCursor 87ff2c15d0 chore: 精简生发提示词,去掉磨皮/美颜要求
统一改为「填充遮罩区域的头发」,涉及后端默认值、ComfyUI 工作流 JSON、
测试页、benchmark 脚本、local_test。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-29 15:57:57 +08:00
UbuntuandCursor c536e4ccb1 feat: 屏蔽接口3(B端生发)
网关层直接拦截返回 1007,不再转发到 worker 池;worker 侧路由同步标记
deprecated 并短路返回,保留原参数签名避免老客户端裸 404。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-29 15:57:46 +08:00
31 changed files with 56 additions and 150 deletions
+1 -1
View File
@@ -29,7 +29,7 @@
"60": {
"class_type": "JjkText",
"inputs": {
"text": "填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜"
"text": "填充遮罩区域的头发"
}
},
"22": {
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -410,7 +410,7 @@
},
"60": {
"inputs": {
"text": "填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜"
"text": "填充遮罩区域的头发"
},
"class_type": "JjkText",
"_meta": {
+2 -2
View File
@@ -381,7 +381,7 @@
},
"60": {
"inputs": {
"text": "充遮罩区域的头发,区域内填充满头发,不要保留皮肤,发际线下移填充头发。自然的头发生长方向,逼真的头发质感,自然发质。"
"text": "充遮罩区域的头发"
},
"class_type": "JjkText",
"_meta": {
@@ -684,7 +684,7 @@
},
"87": {
"inputs": {
"text": "去掉头发接缝的黄色痕迹,头发完美融合,保持发型不变,发色不变。其他不变。",
"text": "填充遮罩区域的头发",
"clip": [
"78",
0
+9 -33
View File
@@ -736,7 +736,7 @@ async def hair_grow(
hair_style: Optional[str] = Form(default=None, description="发型序号逗号分隔(必填),如 1,2,3。female:1-5 male:1-4"),
beauty_enabled: bool = Form(default=False, description="是否开启美颜(本期不生效)"),
use_mask: bool = Form(default=True, description="是否启用 inpaint 遮罩(测试对比用)。false 时用干净原图生成(空遮罩,不烧模板线)"),
prompt: str = Form(default="填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜", description="ComfyUI 提示词,会替换工作流节点60的文本"),
prompt: str = Form(default="填充遮罩区域的头发", description="ComfyUI 提示词,会替换工作流节点60的文本"),
):
# 1. gender 必填校验(非法/缺失 → 1004)
if gender not in ("male", "female"):
@@ -808,6 +808,7 @@ async def hair_grow_v2():
"/api/v1/hair/grow-b",
summary="接口3 B端生发(医生/操作端)",
tags=["生发"],
deprecated=True,
description="""
医生/操作端在用户照片上**手动用马克笔划线标注**目标发际线后,**只需上传这一张划线图**,返回:
- 生发后效果图(系统检测划线 → 据此生成「植发 3 个月」效果)
@@ -847,36 +848,11 @@ async def hair_grow_b(
marked_image_url: Optional[str] = Form(default=None, description="划线图片 URL"),
marked_image_base64: Optional[str] = Form(default=None, description="划线图片 base64"),
use_mask: bool = Form(default=True, description="是否画发际线(测试对比用)。false 时跳过划线检测、直接送划线图"),
prompt: str = Form(default="填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜", description="ComfyUI 提示词,会替换工作流节点60的文本"),
prompt: str = Form(default="填充遮罩区域的头发", description="ComfyUI 提示词,会替换工作流节点60的文本"),
):
# 划线图三选一取图(只需这一张)
marked_raw, e = await resolve_image_bytes(marked_image_file, marked_image_url, marked_image_base64)
if e is not None:
return e
marked = cv2.imdecode(np.frombuffer(marked_raw, np.uint8), cv2.IMREAD_COLOR)
if marked is None:
return err(1008, "图片格式不支持(仅 JPG / PNG)")
try:
from fastapi.concurrency import run_in_threadpool
from hairline.service import generate_grow_b
res = await run_in_threadpool(generate_grow_b, marked, use_mask, prompt)
if res["status"] == "no_face":
return err(1001, "无法识别人像")
if res["status"] == "no_line":
return err(1001, "未检测到发际线划线,请确认划线图额头有清晰的手绘发际线")
grown_b64 = _png_to_jpg_b64(res["grown_png"]) if res["grown_png"] else None # 生发图 JPG
data = {
"hair_growth_image_base64": grown_b64,
"hairline_type": "custom",
}
return ok(data)
except Exception as ex: # noqa: BLE001
logger.exception("接口3 处理异常")
return err(1007, f"处理失败:{ex}")
"""接口3 已屏蔽:调用直接返回错误,不再执行生发/ComfyUI 逻辑。
保留路由(含原参数签名)避免老客户端裸 404,multipart 入参仍被接受但不处理。"""
return err(1007, "接口3/api/v1/hair/grow-b)已屏蔽,暂不提供服务")
# ---------------------------------------------------------------------------
@@ -1025,7 +1001,7 @@ async def hairline_generate(
gender: Optional[str] = Form(default=None, description="性别 male/female(必填)"),
hair_style: Optional[str] = Form(default=None, description="发型序号逗号分隔(必填,如 1,2,3)。female:1-5 male:1-4"),
use_mask: bool = Form(default=True, description="生发是否启用 inpaint 遮罩(同接口2,测试对比用)"),
prompt: str = Form(default="填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜", description="ComfyUI 提示词(同接口2),会替换工作流节点60的文本"),
prompt: str = Form(default="填充遮罩区域的头发", description="ComfyUI 提示词(同接口2),会替换工作流节点60的文本"),
generate_grow_image: bool = Form(default=True, description="是否生成生发效果图(ComfyUI 生发,最耗时)。默认 true 出图;false 时跳过生发,各发型 grown_image 恒为 null,仅返回三档发际线叠图与中心点"),
):
if gender not in ("male", "female"):
@@ -1419,7 +1395,7 @@ async def hairline_grow_v2(
inpainting_fill: int = Form(default=1, description="change_hair服务端重绘填充:0=保留原图 | 1=噪声 | 2=纯色 | 3=潜变量。默认 1"),
mask_blur: int = Form(default=11, description="change_hair服务端遮罩边缘模糊像素,默认 11"),
mask_dilate_scale: float = Form(default=1.0, description="change_hair服务端遮罩膨胀缩放,默认 1.0"),
comfyui_prompt: Optional[str] = Form(default=None, description="Flux-2 重绘提示词,None 用默认「填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜"),
comfyui_prompt: Optional[str] = Form(default=None, description="Flux-2 重绘提示词,None 用默认「填充遮罩区域的头发」"),
beauty_alpha: float = Form(default=0.6, description="redraw_band 版 band 外的全脸美颜融入强度(0=band外无美颜纯用final,1≈整帧版),默认 0.6"),
band_lo_mult: float = Form(default=0.5, description="重绘带外推倍率下限(相对 hairline_push_cm,内轮廓=0×、原外推线=1.0×),默认 0.5"),
band_hi_mult: float = Form(default=1.5, description="重绘带外推倍率上限(相对 hairline_push_cm),默认 1.5"),
@@ -1585,7 +1561,7 @@ async def hairline_grow_v2_final_v2(
async def api_redraw(
image_file: UploadFile = File(..., description="人物图片(JPG/PNG"),
mask_file: UploadFile = File(..., description="遮罩图片(PNG,支持红/白/alpha 格式)"),
prompt: str = Form(default="填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜",
prompt: str = Form(default="填充遮罩区域的头发",
description="ComfyUI 提示词"),
):
image_bytes = await image_file.read()
+1 -1
View File
@@ -58,7 +58,7 @@ def call_iface2(image_path, hair_style):
with open(image_path, "rb") as f:
img_data = f.read()
fields = {"gender": "female", "hair_style": str(hair_style),
"prompt": "填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜"}
"prompt": "填充遮罩区域的头发"}
body, boundary = _multipart(
fields, {"image_file": (os.path.basename(image_path), img_data, "image/jpeg")})
req = urllib.request.Request(f"{API_BASE}/api/v1/hair/grow", data=body, method="POST")
+1 -1
View File
@@ -56,7 +56,7 @@ def call_iface2_female(image_path, hair_style):
with open(image_path, "rb") as f:
img_data = f.read()
fields = {"gender": "female", "hair_style": str(hair_style),
"prompt": "填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜"}
"prompt": "填充遮罩区域的头发"}
body, boundary = _multipart(
fields, {"image_file": (os.path.basename(image_path), img_data, "image/jpeg")})
req = urllib.request.Request(f"{API_BASE}/api/v1/hair/grow", data=body, method="POST")
+2 -2
View File
@@ -89,7 +89,7 @@ def call_api2(image_path, gender, hair_style="1"):
with open(image_path, "rb") as f:
img_data = f.read()
fields = {"gender": gender, "hair_style": hair_style, "use_mask": "0",
"prompt": "填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜"}
"prompt": "填充遮罩区域的头发"}
body, boundary = _multipart(fields, {"image_file": (os.path.basename(image_path), img_data, "image/jpeg")})
req = urllib.request.Request(f"{API_BASE}/api/v1/hair/grow", data=body, method="POST")
req.add_header("Content-Type", f"multipart/form-data; boundary={boundary}")
@@ -107,7 +107,7 @@ def call_api3(image_path):
"""接口3B端生发(use_mask=False,直接送图)"""
with open(image_path, "rb") as f:
img_data = f.read()
fields = {"use_mask": "true", "prompt": "填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜"}
fields = {"use_mask": "true", "prompt": "填充遮罩区域的头发"}
body, boundary = _multipart(fields, {"marked_image_file": (os.path.basename(image_path), img_data, "image/jpeg")})
req = urllib.request.Request(f"{API_BASE}/api/v1/hair/grow-b", data=body, method="POST")
req.add_header("Content-Type", f"multipart/form-data; boundary={boundary}")
+2 -2
View File
@@ -85,7 +85,7 @@
### 接口4 用户特征 `/api/v1/face/features`**网关本机**
- **做什么**:照片 → 几十项面部特征(脸型/眉形/肤色/三庭五眼/四季色彩季型/量感/基因风格/性别…)。`data.features` 是 JSON 字符串。
- **怎么实现**`gateway/`,逻辑参考 worker `face_features.py` / `/home/xsl/fuyan`):调**火山方舟 豆包视觉模型**
`doubao-seed-1-6-vision`(OpenAI 兼容,base64 data URI 喂图),解析眉形/年龄/动静/性别/基因风格 5 项。
`doubao-seed-2-0-lite-260428`(OpenAI 兼容,base64 data URI 喂图),解析眉形/年龄/动静/性别/基因风格 5 项。
**`face_shape`(脸型)改为本机 `face/face_shape_classifier.py`(MediaPipe)计算并覆盖豆包结果**。
无人脸→1001。网关需可达 `ark.cn-beijing.volces.com`API Key 走网关配置(不入 git)。
⚠️ 因此**网关机不再是纯轻量代理**,需额外安装 `mediapipe`/`opencv-python`/`numpy<2`(见 `requirements.txt`)。
@@ -115,7 +115,7 @@
(不再是"网关不装 torch/mediapipe/opencv",只是仍不需要 torch/transformers/scikit-image 等重依赖)。
- 配置 `gateway/config.json`(不入 git)`workers` 列表、`shared_password`
`ark` 的 api_key/base_url/model、`public_base_url`、超时(**生发接口慢,`request_timeout_seconds` 调大 ≥120s**)。
- 托管 `/static/annotations/`(落盘的图)定期清理。
- 托管 `/static/annotations/`(落盘的图)永久保留,不自动清理。
---
+1 -1
View File
@@ -352,7 +352,7 @@
## 接口 4:用户特征接口
**说明**:输入用户照片,由**火山方舟 豆包视觉模型**(`doubao-seed-1-6-vision`)分析,输出一大批面部特征。
**说明**:输入用户照片,由**火山方舟 豆包视觉模型**(`doubao-seed-2-0-lite-260428`)分析,输出一大批面部特征。
**请求**`POST /api/v1/face/features`
+1 -1
View File
@@ -203,7 +203,7 @@ def create_annotated_image(image_bgr, measure_result, ear_mask=None, hair_mask=N
# --- 自适应尺寸:字号/线宽/虚线/箭头按短边缩放 ---
s = min(w, h)
font_size = max(11, round(s * 0.026)) # 字体更小
font_size = max(12, round(s * 0.030)) # 字号上调一档
line_w = max(1, round(s * 0.0022))
dash_len = max(4, round(s * 0.008))
gap_len = max(2, round(dash_len * 0.7)) # 虚线更稠密(间隙<划线)
+1 -1
View File
@@ -1107,7 +1107,7 @@ def generate_hairline_redraw(image_bgr, hairline_id, is_hr=False, seg_model="seg
"hairline_id": hairline_id,
"blend_method": blend_method,
"hairline_push_cm": round(float(hairline_push_cm), 2),
"comfyui_prompt": comfyui_prompt or "填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜",
"comfyui_prompt": comfyui_prompt or "填充遮罩区域的头发",
"beauty_alpha": beauty_alpha,
"px_per_cm": round(float(px_per_cm), 4),
"mask_pixels": mask_viz["mask_pixels"],
+3 -5
View File
@@ -12,7 +12,7 @@ from face_analysis.calibration import (
estimate_scale_factor, normalized_to_pixel, pixel_distance, _lm_list,
)
from face_analysis.face_mesh_landmarks import (
GLABELLA_9, GLABELLA_151, NOSE_BOTTOM, CHIN_TIP,
GLABELLA_9, NOSE_BOTTOM, CHIN_TIP,
LEFT_EYE_OUTER, LEFT_EYE_INNER, RIGHT_EYE_INNER, RIGHT_EYE_OUTER,
LEFT_CHEEK, RIGHT_CHEEK, LEFT_POSITION, RIGHT_POSITION,
)
@@ -24,10 +24,8 @@ _TOP_RATIO = 0.22 / 0.28 # 顶庭 ÷ 中庭(≈ 0.786)
def _brow_center(lm, w, h):
"""眉心 = 索引 9 / 151 中点"""
g9 = normalized_to_pixel(lm[GLABELLA_9], w, h)
g151 = normalized_to_pixel(lm[GLABELLA_151], w, h)
return (g9[0] + g151[0]) / 2, (g9[1] + g151[1]) / 2
"""眉心 = 索引 9(眉间上点)"""
return normalized_to_pixel(lm[GLABELLA_9], w, h)
def estimate_vertical_landmarks(landmarks, image_width, image_height):
+1 -1
View File
@@ -17,7 +17,7 @@ import os
logger = logging.getLogger("hair.worker")
ARK_BASE_URL = os.getenv("ARK_BASE_URL", "https://ark.cn-beijing.volces.com/api/v3")
ARK_MODEL = os.getenv("ARK_MODEL", "doubao-seed-1-6-vision-250815")
ARK_MODEL = os.getenv("ARK_MODEL", "doubao-seed-2-0-lite-260428")
# doubao 中文键 → 接口4 英文字段(脸型不走豆包,见 _local_face_shape
_KEY_MAP = {
+10 -70
View File
@@ -4,13 +4,10 @@
不跑任何算法 torch/mediapipe/opencv 依赖
"""
import asyncio
import base64
import json
import logging
import time
from contextlib import asynccontextmanager
from io import BytesIO
from pathlib import Path
from typing import Optional
@@ -67,32 +64,15 @@ async def lifespan(app: FastAPI):
else:
app.state._pool_shutdown = None
# 确保标注图目录存在
# 确保标注图目录存在(永久保留,不做定期清理)
static_dir = Path(cfg["static_dir"])
static_dir.mkdir(parents=True, exist_ok=True)
logger.info("标注图目录: %s", static_dir)
# 启动定期清理任务(阶段四)
cleanup_shutdown = asyncio.Event()
cleanup_task = asyncio.create_task(
_cleanup_loop(static_dir, cfg, cleanup_shutdown)
)
app.state._cleanup_shutdown = cleanup_shutdown
app.state._cleanup_task = cleanup_task
logger.info("标注图目录: %s(永久保留,不自动清理)", static_dir)
yield
# 关闭
logger.info("网关关闭中...")
# 停止清理任务
if app.state._cleanup_shutdown:
app.state._cleanup_shutdown.set()
if app.state._cleanup_task:
app.state._cleanup_task.cancel()
try:
await app.state._cleanup_task
except asyncio.CancelledError:
pass
if app.state._pool_shutdown:
await app.state._pool_shutdown()
logger.info("网关已关闭")
@@ -132,51 +112,6 @@ def _get_pool_status_safe():
return {"total": 0, "healthy": 0, "busy": 0}
async def _cleanup_loop(annotations_dir: Path, cfg: dict, shutdown: asyncio.Event):
"""定期清理 static/annotations/ 中过期的标注图文件。
配置项可选 config.json 中设定
- cleanup.interval_minutes: 清理间隔默认 60
- cleanup.max_age_hours: 文件保留时长小时默认 24
"""
cleanup_cfg = cfg.get("cleanup", {})
interval_s = cleanup_cfg.get("interval_minutes", 60) * 60
max_age_s = cleanup_cfg.get("max_age_hours", 24) * 3600
logger.info(
"清理任务启动 | 间隔=%dmin | 保留=%dh | 目录=%s",
interval_s // 60, max_age_s // 3600, annotations_dir,
)
while not shutdown.is_set():
try:
await asyncio.wait_for(shutdown.wait(), timeout=interval_s)
break # shutdown
except asyncio.TimeoutError:
pass # 正常到时,执行清理
now = time.time()
deleted = 0
for f in annotations_dir.iterdir():
if f.name == ".gitkeep":
continue
if not f.is_file():
continue
try:
age_s = now - f.stat().st_mtime
if age_s > max_age_s:
f.unlink()
deleted += 1
logger.debug("清理过期文件: %s (age=%.1fh)", f.name, age_s / 3600)
except Exception:
logger.warning("清理文件失败: %s", f.name, exc_info=True)
if deleted:
logger.info("清理完成: 删除 %d 个过期文件", deleted)
logger.info("清理任务已停止")
@app.get("/gateway-health", include_in_schema=False)
async def gateway_health():
"""网关自身健康检查(区别于 worker 的 /health)。"""
@@ -539,10 +474,15 @@ async def hair_grow(request: Request):
return await _proxy(request, "/api/v1/hair/grow")
@app.post("/api/v1/hair/grow-b", tags=["生发"])
@app.post("/api/v1/hair/grow-b", tags=["生发"], deprecated=True)
async def hair_grow_b(request: Request):
"""接口3B端生发"""
return await _proxy(request, "/api/v1/hair/grow-b")
"""接口3B端生发(已屏蔽)"""
# 在网关层直接拦截,不转发到 worker 池——对所有上游 worker 立即生效。
import uuid as _uuid
return JSONResponse(status_code=200, content={
"code": 1007, "message": "接口3/api/v1/hair/grow-b)已屏蔽,暂不提供服务",
"request_id": f"gw-{_uuid.uuid4().hex[:8]}", "data": None,
})
@app.post("/api/v1/face/features", tags=["人脸分析"])
-4
View File
@@ -22,9 +22,5 @@
"request_timeout_seconds": 600,
"retry_on_failure": true,
"max_retries": 1
},
"cleanup": {
"interval_minutes": 60,
"max_age_hours": 24
}
}
-4
View File
@@ -31,10 +31,6 @@ DEFAULTS = {
"retry_on_failure": True,
"max_retries": 1,
},
"cleanup": {
"interval_minutes": 60,
"max_age_hours": 24,
},
"request_log": {
"enabled": True,
"log_file": "gateway/request_log.jsonl",
+3 -3
View File
@@ -2,8 +2,8 @@
设计原则与用户约定
- 入参/出参日志里**绝不内嵌图片 base64**图片统一存盘后用 URL 引用保持日志短小
- 入参图片image_file / *_base64当前网关不存盘这里补存到 static_dirin_ 前缀
复用现有 24h 清理循环自动回收url 输入图本身就在远端不重新下载直接记 URL
- 入参图片image_file / *_base64补存到 static_dirin_ 前缀永久保留
url 输入图本身就在远端不重新下载直接记 URL
- 出参响应在 forward.py 已把 base64 改写成 *_url无大图记录完整 data
但用递归摘要器截断超长结构landmarks长字符串单条上限 ~8KB
@@ -39,7 +39,7 @@ def save_image_bytes(
"""把图片字节存盘并返回公网 URL。失败返回 None(不抛异常)。
存到 static_dir/{prefix}{uuid}.{ext}URL = {public_base_url}/static/annotations/{file}
forward.py rewrite_base64_to_url 落盘路径/URL 规则一致可被同一清理循环回收
forward.py rewrite_base64_to_url 落盘路径/URL 规则一致文件永久保留不自动清理
"""
if not data:
return None
+1 -1
View File
@@ -410,7 +410,7 @@
},
"60": {
"inputs": {
"text": "填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜"
"text": "填充遮罩区域的头发"
},
"class_type": "JjkText",
"_meta": {
+2 -2
View File
@@ -16,7 +16,7 @@ from . import comfyui
logger = logging.getLogger("hair.worker")
_DEFAULT_PROMPT = "填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜"
_DEFAULT_PROMPT = "填充遮罩区域的头发"
_REPO = os.path.dirname(os.path.dirname(__file__))
_REPAINT_WORKFLOW = os.path.join(_REPO, "0716add-hair-api.json")
@@ -62,7 +62,7 @@ def run_redraw(image_bytes: bytes, mask_bytes: bytes,
Args:
image_bytes: 人物图片字节JPG/PNG
mask_bytes: 遮罩图片字节支持红//alpha 遮罩格式
prompt: 提示词None 用默认 "填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜"
prompt: 提示词None 用默认 "填充遮罩区域的头发"
timeout: ComfyUI 超时秒数
front: True 时任务插到 ComfyUI 队列最前接口2 时延敏感路径用
+1 -1
View File
@@ -206,7 +206,7 @@ def generate():
return jsonify({"error": msg}), 400
image_file = request.files["image"]
mask_file = request.files["mask"]
prompt_text = request.form.get("prompt", "填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜")
prompt_text = request.form.get("prompt", "填充遮罩区域的头发")
log.info(
"收到请求: image=%s mask=%s prompt=%r",
image_file.filename, mask_file.filename, prompt_text,
+1 -1
View File
@@ -27,7 +27,7 @@ def prep_and_upload():
def run_once(fname, model, dtype, steps):
wf = A.build_workflow(fname, "填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜")
wf = A.build_workflow(fname, "填充遮罩区域的头发")
wf["16"]["inputs"]["unet_name"] = model
wf["16"]["inputs"]["weight_dtype"] = dtype
wf["1"]["inputs"]["steps"] = steps
+1 -1
View File
@@ -33,7 +33,7 @@ def upload(scale=1.0):
def run(fname, steps):
wf = A.build_workflow(fname, "填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜")
wf = A.build_workflow(fname, "填充遮罩区域的头发")
wf["16"]["inputs"]["unet_name"] = MODEL
wf["16"]["inputs"]["weight_dtype"] = DTYPE
wf["1"]["inputs"]["steps"] = steps
+1 -1
View File
@@ -30,7 +30,7 @@ SEED = 123456789
imgs = []
labels = []
for steps in [2, 3, 4, 6]:
wf = A.build_workflow(fname, "填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜", seed=SEED)
wf = A.build_workflow(fname, "填充遮罩区域的头发", seed=SEED)
wf["16"]["inputs"]["unet_name"] = MODEL
wf["16"]["inputs"]["weight_dtype"] = DTYPE
wf["1"]["inputs"]["steps"] = steps
+1 -1
View File
@@ -55,7 +55,7 @@ button { padding: 10px 24px; border: none; border-radius: 6px; cursor: pointer;
</div>
<div class="controls" style="margin-top:16px">
<label>提示词:</label>
<input type="text" id="promptInput" value="填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜">
<input type="text" id="promptInput" value="填充遮罩区域的头发">
</div>
<div style="text-align:center; margin-top:16px">
<button class="btn-generate" id="generateBtn" disabled>🚀 生成</button>
+1 -1
View File
@@ -25,7 +25,7 @@ resp = requests.post(
"image": ("original.jpg", img_data, "image/jpeg"),
"mask": ("mask.png", mask_data, "image/png"),
},
data={"prompt": "填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜"},
data={"prompt": "填充遮罩区域的头发"},
timeout=600,
)
+2 -2
View File
@@ -69,7 +69,7 @@
</div>
<div class="upload-row" style="margin-top:10px">
<label style="font-size:13px;font-weight:600;white-space:nowrap">重绘提示词</label>
<input type="text" id="localTestPrompt" value="填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜" style="flex:1;min-width:200px;padding:8px;border:1px solid #ddd;border-radius:8px">
<input type="text" id="localTestPrompt" value="填充遮罩区域的头发" style="flex:1;min-width:200px;padding:8px;border:1px solid #ddd;border-radius:8px">
</div>
<div class="upload-row" style="margin-top:10px">
<label style="font-size:13px;font-weight:600;white-space:nowrap">X-Internal-Token</label>
@@ -354,7 +354,7 @@ function dataUriToBlob(dataUri) {
async function runLocalRedraw() {
if (!_finalDataUri || !_maskDataUri) { setStatus('缺少 final 或遮罩,请先生成', 'error'); return; }
const prompt = $('localTestPrompt').value.trim() || '填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜';
const prompt = $('localTestPrompt').value.trim() || '填充遮罩区域的头发';
const btn = $('redrawBtn');
btn.disabled = true; btn.textContent = '⏳ 重绘中...';
flog('===== 调后端重绘 =====', 'info');
+2 -2
View File
@@ -56,7 +56,7 @@
</div>
<div class="upload-row" style="margin-top:10px">
<label style="font-size:13px;font-weight:600;white-space:nowrap">重绘提示词</label>
<input type="text" id="localTestPrompt" value="填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜" style="flex:1;min-width:200px;padding:8px;border:1px solid #ddd;border-radius:8px">
<input type="text" id="localTestPrompt" value="填充遮罩区域的头发" style="flex:1;min-width:200px;padding:8px;border:1px solid #ddd;border-radius:8px">
</div>
<div class="params">
<div class="pf">
@@ -169,7 +169,7 @@ async function submitTest() {
async function runLocalRedraw() {
if (!_finalDataUri || !_maskDataUri) { setStatus('缺少 final 或遮罩,请先生成', 'error'); return; }
const prompt = $('localTestPrompt').value.trim() || '填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜';
const prompt = $('localTestPrompt').value.trim() || '填充遮罩区域的头发';
const btn = $('redrawBtn');
btn.disabled = true; btn.textContent = '⏳ 重绘中...';
+1 -1
View File
@@ -107,7 +107,7 @@
<div class="hint">JPG/PNG &nbsp;|&nbsp; 生发图生成较慢(数十秒~数分钟),请耐心等待</div>
<div style="margin-top:10px;display:flex;align-items:center;gap:8px">
<label style="font-size:13px;font-weight:600;color:#374151;white-space:nowrap">💬 提示词</label>
<input type="text" id="promptInput" value="填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜" style="flex:1;padding:8px 12px;border:1px solid #d1d5db;border-radius:6px;font-size:13px;max-width:500px">
<input type="text" id="promptInput" value="填充遮罩区域的头发" style="flex:1;padding:8px 12px;border:1px solid #d1d5db;border-radius:6px;font-size:13px;max-width:500px">
</div>
<div id="statusBar" class="status hidden"></div>
</div>
+1 -1
View File
@@ -94,7 +94,7 @@
</div>
<div class="upload-group" style="margin-top:14px">
<div class="label">💬 提示词(prompt</div>
<input type="text" id="promptInput" value="填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜" style="width:100%;padding:8px 12px;border:1px solid #d1d5db;border-radius:8px;font-size:14px;max-width:500px">
<input type="text" id="promptInput" value="填充遮罩区域的头发" style="width:100%;padding:8px 12px;border:1px solid #d1d5db;border-radius:8px;font-size:14px;max-width:500px">
</div>
<div style="margin-top:14px;display:flex;gap:12px;align-items:center">
<button class="btn btn-primary" id="submitBtn" onclick="submitTest()">🚀 提交</button>
+1 -1
View File
@@ -104,7 +104,7 @@
<div class="hint">JPG/PNG &nbsp;|&nbsp; 生发图生成较慢(数十秒~数分钟),请耐心等待 &nbsp;|&nbsp; 工作流: add_hair2.json</div>
<div style="margin-top:10px;display:flex;align-items:center;gap:8px">
<label style="font-size:13px;font-weight:600;color:#374151;white-space:nowrap">💬 提示词</label>
<input type="text" id="promptInput" value="填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜" style="flex:1;padding:8px 12px;border:1px solid #d1d5db;border-radius:6px;font-size:13px;max-width:500px">
<input type="text" id="promptInput" value="填充遮罩区域的头发" style="flex:1;padding:8px 12px;border:1px solid #d1d5db;border-radius:6px;font-size:13px;max-width:500px">
</div>
<div id="statusBar" class="status hidden"></div>
</div>