Compare commits
3
Commits
1b9f3fdb6f
...
3fe5f6cf0d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3fe5f6cf0d | ||
|
|
e17677158a | ||
|
|
1e6c2e54de |
@@ -1297,19 +1297,15 @@ async def hairline_grow(
|
||||
hairgrow_strength: float = Form(default=0.75, description="区域生发强度(仅 hairgrow 后端),默认 0.75"),
|
||||
is_hr: bool = Form(default=False, description="高清模式(换发型输出 1152×1536,否则 576×768)"),
|
||||
seg_model: str = Form(default="segformer", description="头发分割模型:bisenet | segformer(默认 segformer)"),
|
||||
mask_type: str = Form(default="eroded", description="遮罩类型:eroded(内缩) | closed(闭合区域) | pushed(发际线外推)(默认 eroded)"),
|
||||
erode_cm: float = Form(default=1.2, description="遮罩外缘朝中心151内缩距离(厘米,同接口9),默认 1.2"),
|
||||
hairline_push_cm: float = Form(default=1.0, description="发际线外推距离(厘米,往头发方向推进;仅 mask_type=pushed 生效),默认 1.0"),
|
||||
hairline_edge: str = Form(default="column", description="发际线提取方式:column(逐列最低点) | contour(形态学轮廓)(仅 pushed 生效),默认 column"),
|
||||
swap_mode: str = Form(default="ext_mask", description="换发型取图模式:ext_mask(改造换发型用接口9遮罩) | as_is(不改换发型,贴回再裁)(默认 ext_mask)"),
|
||||
blend_method: str = Form(default="feather", description="接缝融合:feather(高斯羽化) | alpha_gradient(距离渐变) | seamless(泊松无缝) | multiband(多频段金字塔)(默认 feather)"),
|
||||
feather_px: int = Form(default=15, description="羽化/渐变过渡宽度(像素),默认 15"),
|
||||
erode_cm: float = Form(default=0.6, description="baseline 参考内缩距离(厘米),默认 0.6"),
|
||||
hairline_push_cm: float = Form(default=1.0, description="发际线外推距离(厘米,往头发方向推进),默认 1.0"),
|
||||
hairline_edge: str = Form(default="column", description="发际线提取方式:column | contour,默认 column"),
|
||||
swap_mode: str = Form(default="ext_mask", description="换发型取图模式:ext_mask | as_is(默认 ext_mask)"),
|
||||
edge_erode_px: int = Form(default=3, description="贴图前遮罩内缩像素(防边缘露皮/光晕),默认 3"),
|
||||
denoising_strength: float = Form(default=0.6, description="换发型 webui 重绘强度(越大生发越激进),默认 0.6"),
|
||||
color_match: bool = Form(default=False, description="融合前对生成图做 Reinhard 颜色校正(消除整体色差,seamless 下自动跳过),默认 false"),
|
||||
mb_levels: int = Form(default=5, description="multiband 多频段金字塔层数(2~6,越大低频色差抹得越宽,仅 multiband 生效),默认 5"),
|
||||
mb_levels: int = Form(default=5, description="多频段金字塔层数(2~6,越大低频色差抹得越宽),默认 5"),
|
||||
):
|
||||
"""接口11:发际线生发 + 分步可视化"""
|
||||
"""接口11:发际线生发 + 分步可视化。遮罩固定 pushed、融合固定 multiband。"""
|
||||
raw, e = await resolve_image_bytes(image_file, image_url, image_base64)
|
||||
if e is not None:
|
||||
return e
|
||||
@@ -1323,14 +1319,16 @@ async def hairline_grow(
|
||||
from face_analysis.hairline_grow import generate_hairline_grow, NoFaceError, SwapError
|
||||
from uuid import uuid4 as _uuid4
|
||||
rid = _uuid4().hex[:8]
|
||||
logger.info("[%s] 接口11 收到请求: mask_type=%s hairline_push_cm=%s hairline_edge=%s",
|
||||
rid, mask_type, hairline_push_cm, hairline_edge)
|
||||
logger.info("[%s] 接口11 收到请求: hairline_push_cm=%s hairline_edge=%s mb_levels=%s",
|
||||
rid, hairline_push_cm, hairline_edge, mb_levels)
|
||||
try:
|
||||
data = await run_in_threadpool(
|
||||
generate_hairline_grow, image, hairline_id, is_hr, seg_model,
|
||||
mask_type, erode_cm, swap_mode, blend_method, feather_px, edge_erode_px,
|
||||
denoising_strength, gen_backend, hairgrow_strength, color_match, mb_levels,
|
||||
hairline_push_cm=hairline_push_cm, hairline_edge=hairline_edge, rid=rid)
|
||||
generate_hairline_grow, image, hairline_id,
|
||||
is_hr=is_hr, seg_model=seg_model, erode_cm=erode_cm, swap_mode=swap_mode,
|
||||
edge_erode_px=edge_erode_px, denoising_strength=denoising_strength,
|
||||
gen_backend=gen_backend, hairgrow_strength=hairgrow_strength,
|
||||
mb_levels=mb_levels, hairline_push_cm=hairline_push_cm,
|
||||
hairline_edge=hairline_edge, rid=rid)
|
||||
except NoFaceError:
|
||||
return err(1001, "无法识别人像")
|
||||
except SwapError as se:
|
||||
@@ -1348,17 +1346,15 @@ async def hairline_grow(
|
||||
|
||||
@app.post(
|
||||
"/api/v1/hairline/grow_v2",
|
||||
summary="接口12 发际线生发(固定金字塔融合,仅返回最终图)",
|
||||
summary="接口12 发际线生发(固定 pushed 遮罩 + 多频段融合,仅返回最终图)",
|
||||
tags=["生发"],
|
||||
description=f"""
|
||||
接口11 的固定参数精简版,适合生产直调。与接口11 共用同一管线,区别仅在于:
|
||||
接口11 的固定参数精简版,适合生产直调。与接口11 共用同一管线,遮罩固定 pushed(发际线外推),
|
||||
融合固定 multiband(多频段金字塔)。本接口固定 `erode_cm=0.6`、`mb_levels=5` 不暴露,
|
||||
**只返回 `final_base64`**(最终合成图),不附带分步可视化。
|
||||
|
||||
- **固定** `blend_method=multiband`(多频段金字塔融合)、`mb_levels=5`、`erode_cm=0.6`
|
||||
(外缘朝中心151 内缩 0.6cm)。这三项不可调,故本接口不暴露。
|
||||
- **返回值精简**:只返回 `final_base64`(最终合成图),不再附带接口11 的分步可视化。
|
||||
|
||||
其余参数(hairline_id、seg_model、gen_backend、is_hr、denoising_strength、color_match、
|
||||
edge_erode_px 等)仍保留为可选 Form,调用方可按需覆盖,未传则用接口11 同款默认值。
|
||||
其余参数(hairline_id、seg_model、gen_backend、is_hr、denoising_strength、edge_erode_px、
|
||||
hairline_push_cm、hairline_edge 等)保留为可选 Form,调用方可按需覆盖。
|
||||
|
||||
{_image_fields_desc}
|
||||
""",
|
||||
@@ -1372,16 +1368,13 @@ async def hairline_grow_v2(
|
||||
hairgrow_strength: float = Form(default=0.75, description="区域生发强度(仅 hairgrow 后端),默认 0.75"),
|
||||
is_hr: bool = Form(default=False, description="高清模式(换发型输出 1152×1536,否则 576×768)"),
|
||||
seg_model: str = Form(default="segformer", description="头发分割模型:bisenet | segformer(默认 segformer)"),
|
||||
mask_type: str = Form(default="eroded", description="遮罩类型:eroded(内缩) | closed(闭合区域) | pushed(发际线外推)(默认 eroded)"),
|
||||
swap_mode: str = Form(default="ext_mask", description="换发型取图模式:ext_mask(改造换发型用接口9遮罩) | as_is(不改换发型,贴回再裁)(默认 ext_mask)"),
|
||||
feather_px: int = Form(default=15, description="羽化/渐变过渡宽度(像素,本接口固定 multiband 故不生效,仅留作兼容)"),
|
||||
swap_mode: str = Form(default="ext_mask", description="换发型取图模式:ext_mask | as_is(默认 ext_mask)"),
|
||||
edge_erode_px: int = Form(default=3, description="贴图前遮罩内缩像素(防边缘露皮/光晕),默认 3"),
|
||||
denoising_strength: float = Form(default=0.6, description="换发型 webui 重绘强度(越大生发越激进),默认 0.6"),
|
||||
color_match: bool = Form(default=False, description="融合前对生成图做 Reinhard 颜色校正(消除整体色差),默认 false"),
|
||||
hairline_push_cm: float = Form(default=1.0, description="发际线外推距离(厘米,仅 mask_type=pushed 生效),默认 1.0"),
|
||||
hairline_edge: str = Form(default="column", description="发际线提取方式:column | contour(仅 pushed 生效),默认 column"),
|
||||
hairline_push_cm: float = Form(default=1.0, description="发际线外推距离(厘米),默认 1.0"),
|
||||
hairline_edge: str = Form(default="column", description="发际线提取方式:column | contour,默认 column"),
|
||||
):
|
||||
"""接口12:发际线生发(固定 multiband/mb_levels=5/erode_cm=0.6,仅返回最终图)。"""
|
||||
"""接口12:发际线生发(固定 pushed 遮罩 + multiband 融合,仅返回最终图)。"""
|
||||
raw, e = await resolve_image_bytes(image_file, image_url, image_base64)
|
||||
if e is not None:
|
||||
return e
|
||||
@@ -1394,12 +1387,13 @@ async def hairline_grow_v2(
|
||||
from fastapi.concurrency import run_in_threadpool
|
||||
from face_analysis.hairline_grow import generate_hairline_grow, NoFaceError, SwapError
|
||||
try:
|
||||
# 固定三项:blend_method=multiband、mb_levels=5、erode_cm=0.6
|
||||
# 固定:mask_type=pushed、blend_method=multiband、erode_cm=0.6、mb_levels=5
|
||||
data = await run_in_threadpool(
|
||||
generate_hairline_grow, image, hairline_id, is_hr, seg_model,
|
||||
mask_type, 0.6, swap_mode, "multiband", feather_px, edge_erode_px,
|
||||
denoising_strength, gen_backend, hairgrow_strength, color_match, 5,
|
||||
hairline_push_cm=hairline_push_cm, hairline_edge=hairline_edge)
|
||||
generate_hairline_grow, image, hairline_id,
|
||||
is_hr=is_hr, seg_model=seg_model, erode_cm=0.6, swap_mode=swap_mode,
|
||||
edge_erode_px=edge_erode_px, denoising_strength=denoising_strength,
|
||||
gen_backend=gen_backend, hairgrow_strength=hairgrow_strength,
|
||||
mb_levels=5, hairline_push_cm=hairline_push_cm, hairline_edge=hairline_edge)
|
||||
except NoFaceError:
|
||||
return err(1001, "无法识别人像")
|
||||
except SwapError as se:
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
# 发际线生发遮罩算法(pushed 模式)
|
||||
|
||||
> 对应接口11 `/api/v1/hairline/grow`、接口12 `/api/v1/hairline/grow_v2`,`mask_type=pushed`。
|
||||
> 对应接口11 `/api/v1/hairline/grow`、接口12 `/api/v1/hairline/grow_v2`。
|
||||
> 遮罩算法固定为 pushed,融合算法固定为 multiband(已移除其他选项)。
|
||||
> 代码:`face_analysis/hairline_grow.py`(`_extract_hairline` / `_pushed_mask` / `compute_mask`)。
|
||||
|
||||
## 概述
|
||||
|
||||
pushed 模式是发际线生发的默认遮罩算法(接口11/12 的 `mask_type` 三选一:`eroded` / `closed` / `pushed`,当前只用 pushed)。它从头发分割结果中提取「头发/皮肤交界线」(发际线),以眉心为圆心逐点径向外推一段距离,与 baseline 组成闭合区域作为最终遮罩。这样遮罩顶部会覆盖现有头发下沿一小段,贴回生发结果时顶部与真头发重叠、过渡自然。
|
||||
pushed 是发际线生发的**唯一**遮罩算法,multiband(多频段金字塔)是**唯一**融合算法。它从头发分割结果中提取「头发/皮肤交界线」(发际线),以眉心为圆心逐点径向外推一段距离,与 baseline 组成闭合区域作为最终遮罩。这样遮罩顶部会覆盖现有头发下沿一小段,贴回生发结果时顶部与真头发重叠、过渡自然。
|
||||
|
||||
> 接口12 `/api/v1/hairline/grow_v2` 只需传 `image` + `hairline_id`,遮罩和融合全部固定,无需任何算法选择参数。
|
||||
|
||||
## 算法流程(5 步)
|
||||
|
||||
@@ -54,15 +57,18 @@ segformer(默认)或 bisenet 得到的头发二值掩码。
|
||||
|
||||
## 关键参数
|
||||
|
||||
遮罩算法(pushed)和融合算法(multiband)已固定,接口不再暴露选择参数。可调的只有:
|
||||
|
||||
| 参数 | 默认 | 说明 |
|
||||
|---|---|---|
|
||||
| `mask_type` | `eroded`(接口默认)/ `pushed`(当前推荐) | pushed 走上述流程;eroded/closed 走旧的 top_fill→closed/eroded 流程 |
|
||||
| `hairline_push_cm` | 1.0 | 内轮廓径向外推距离(厘米),= push_px / px_per_cm。`px_per_cm` 由虹膜直径标定 |
|
||||
| `hairline_edge` | `column` | 兼容保留的入参;新版内轮廓提取(轮廓+内侧判定)不再按它分支,取值不影响结果 |
|
||||
| `hairline_edge` | `column` | 兼容保留的入参;内轮廓提取(轮廓+内侧判定)不再按它分支,取值不影响结果 |
|
||||
| `mb_levels` | 5 | 多频段金字塔层数(2~6,越大低频色差抹得越宽)|
|
||||
| `erode_cm` | 0.6(接口12 固定)| baseline 参考内缩距离,对 pushed 影响很小 |
|
||||
|
||||
## 与旧模式(eroded/closed)的区别
|
||||
## 与旧模式(eroded/closed,已移除)的区别
|
||||
|
||||
| | eroded/closed | pushed(当前) |
|
||||
| | eroded/closed(已移除) | pushed(当前唯一) |
|
||||
|---|---|---|
|
||||
| 遮罩来源 | top_fill(头发向下填充含额头)外缘内缩 | 头发内轮廓线 径向外推成带 |
|
||||
| 截断方式 | 用 baseline 组上半区 upper | 内侧判定 + 下颌 y 截断(不再用 baseline) |
|
||||
|
||||
@@ -64,16 +64,14 @@ DEFAULTS = {
|
||||
"gen_backend": "swaphair", # swaphair(换发型LoRA) | hairgrow(区域生发inpaint)
|
||||
"is_hr": False,
|
||||
"seg_model": "segformer", # bisenet | segformer
|
||||
"mask_type": "eroded", # eroded | closed
|
||||
"erode_cm": 1.2,
|
||||
"hairline_push_cm": 1.0, # 发际线径向外推距离(厘米)
|
||||
"hairline_edge": "column", # column(逐列下沿)
|
||||
"swap_mode": "ext_mask", # ext_mask | as_is(仅 swaphair)
|
||||
"denoising_strength": 0.6, # 仅 swaphair
|
||||
"hairgrow_strength": 0.75, # 仅 hairgrow
|
||||
"blend_method": "feather", # feather | alpha_gradient | seamless | multiband
|
||||
"feather_px": 15,
|
||||
"edge_erode_px": 3,
|
||||
"color_match": False, # True 时对生成图做 Reinhard 颜色校正(seamless 下自动跳过)
|
||||
"mb_levels": 5, # multiband 金字塔层数(2~6,越大色差抹得越宽)
|
||||
"erode_cm": 0.6, # 接口12 固定值(pushed 模式下仅用于 baseline 截断参考,影响很小)
|
||||
}
|
||||
|
||||
|
||||
@@ -662,21 +660,25 @@ def _composite(orig, swap_result, mask_bool, blend_method, feather_px, edge_erod
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def generate_hairline_grow(image_bgr, hairline_id, is_hr=False, seg_model="segformer",
|
||||
mask_type="eroded", erode_cm=1.2, swap_mode="ext_mask",
|
||||
blend_method="feather", feather_px=15, edge_erode_px=3,
|
||||
erode_cm=0.6, swap_mode="ext_mask",
|
||||
edge_erode_px=3,
|
||||
denoising_strength=0.6, gen_backend="swaphair",
|
||||
hairgrow_strength=0.75, color_match=False, mb_levels=5,
|
||||
hairline_push_cm=0.0, hairline_edge="column", rid=None):
|
||||
hairgrow_strength=0.75, mb_levels=5,
|
||||
hairline_push_cm=1.0, hairline_edge="column", rid=None):
|
||||
"""接口11 完整管线。返回可直接进 ok() 的 data dict。未检出人脸抛 NoFaceError。
|
||||
|
||||
rid: 调用方的 request id,用于日志关联。为 None 时自动生成。
|
||||
遮罩算法固定为 pushed(发际线外推),融合算法固定为 multiband(多频段金字塔),
|
||||
不再支持其他选项。rid: 调用方的 request id,用于日志关联。为 None 时自动生成。
|
||||
"""
|
||||
mask_type = "pushed" # 固定:只支持 pushed 遮罩算法
|
||||
blend_method = "multiband" # 固定:只支持 multiband 融合
|
||||
if rid is None:
|
||||
rid = uuid4().hex[:8]
|
||||
logger.info("[%s] ===== generate_hairline_grow 开始 =====", rid)
|
||||
logger.info("[%s] 参数: mask_type=%r erode_cm=%s blend=%s hairline_push_cm=%s hairline_edge=%r "
|
||||
"seg=%s gen_backend=%s swap_mode=%s", rid, mask_type, erode_cm, blend_method,
|
||||
hairline_push_cm, hairline_edge, seg_model, gen_backend, swap_mode)
|
||||
logger.info("[%s] 参数(固定 mask=pushed blend=multiband): erode_cm=%s hairline_push_cm=%s "
|
||||
"hairline_edge=%r mb_levels=%s seg=%s gen_backend=%s swap_mode=%s",
|
||||
rid, erode_cm, hairline_push_cm, hairline_edge, mb_levels,
|
||||
seg_model, gen_backend, swap_mode)
|
||||
h, w = image_bgr.shape[:2]
|
||||
landmarks = detector.detect(image_bgr)
|
||||
if landmarks is None:
|
||||
@@ -685,7 +687,7 @@ def generate_hairline_grow(image_bgr, hairline_id, is_hr=False, seg_model="segfo
|
||||
px_per_cm = estimate_scale_factor(landmarks, w, h)
|
||||
logger.info("[%s] 人脸检出 px_per_cm=%.3f 图尺寸=%dx%d", rid, px_per_cm, w, h)
|
||||
|
||||
# 步骤1:接口9 遮罩
|
||||
# 步骤1:接口9 遮罩(固定 pushed)
|
||||
t0 = time.time()
|
||||
mask_bool, mask_viz = compute_mask(
|
||||
image_bgr, landmarks, seg_model, mask_type, erode_cm, px_per_cm,
|
||||
@@ -706,11 +708,11 @@ def generate_hairline_grow(image_bgr, hairline_id, is_hr=False, seg_model="segfo
|
||||
hard_paste = image_bgr.copy()
|
||||
hard_paste[mask_bool] = swap_result[mask_bool]
|
||||
|
||||
# 步骤4:接缝融合
|
||||
# 步骤4:接缝融合(固定 multiband)
|
||||
t0 = time.time()
|
||||
final, alpha = _composite(
|
||||
image_bgr, swap_result, mask_bool, blend_method, feather_px, edge_erode_px,
|
||||
color_match=color_match, mb_levels=mb_levels)
|
||||
image_bgr, swap_result, mask_bool, blend_method, 0, edge_erode_px,
|
||||
color_match=False, mb_levels=mb_levels)
|
||||
t_blend = time.time() - t0
|
||||
|
||||
data = {
|
||||
@@ -723,9 +725,7 @@ def generate_hairline_grow(image_bgr, hairline_id, is_hr=False, seg_model="segfo
|
||||
"erode_cm": round(float(erode_cm), 2),
|
||||
"swap_mode": swap_mode,
|
||||
"blend_method": blend_method,
|
||||
"feather_px": int(feather_px),
|
||||
"edge_erode_px": int(edge_erode_px),
|
||||
"color_match": bool(color_match) and blend_method != "seamless",
|
||||
"mb_levels": int(mb_levels),
|
||||
"hairline_push_cm": round(float(hairline_push_cm), 2),
|
||||
"hairline_edge": hairline_edge,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""批量调用接口12(/api/v1/hairline/grow_v2)生成对比素材。
|
||||
|
||||
20 张女生照片 × 5 种发际线发型 × {高清, 非高清} = 200 张输出。
|
||||
并发 4,失败的跳过并记录原因。结果图落盘到 static/report_hairline_v2/img/,
|
||||
20 张女生照片 × 5 种发际线发型(仅非高清)= 100 张输出。
|
||||
并发 2,失败的跳过并记录原因。结果图落盘到 static/report_hairline_v2/img/,
|
||||
元数据落盘 static/report_hairline_v2/results.json,供生成报告用。
|
||||
|
||||
用法: python scripts/batch_grow_v2.py
|
||||
@@ -31,7 +31,7 @@ HAIRSTYLES = [
|
||||
("chang_xinxing", "心形"),
|
||||
("chang_huaban", "花瓣"),
|
||||
]
|
||||
HR_OPTIONS = [(True, "hr"), (False, "nohr")]
|
||||
HR_OPTIONS = [(False, "nohr")] # 仅非高清
|
||||
|
||||
|
||||
def list_inputs():
|
||||
@@ -139,7 +139,7 @@ def main():
|
||||
"total": total, "ok": ok, "fail": fail,
|
||||
"elapsed_sec": round(elapsed, 1), "concurrency": CONCURRENCY,
|
||||
"hairstyles": [{"id": h, "cn": c} for h, c in HAIRSTYLES],
|
||||
"hr_options": [{"is_hr": True, "tag": "hr"}, {"is_hr": False, "tag": "nohr"}],
|
||||
"hr_options": [{"is_hr": is_hr, "tag": tag} for is_hr, tag in HR_OPTIONS],
|
||||
"faces": [os.path.splitext(f)[0] for f in faces],
|
||||
"generated_at": time.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
}
|
||||
|
||||
@@ -78,12 +78,11 @@ def main():
|
||||
.face-head h2 {{ margin:0; font-size:18px; }}
|
||||
.face-head .orig-thumb {{ width:64px; height:64px; object-fit:cover;
|
||||
border-radius:8px; border:1px solid var(--border); }}
|
||||
.grid {{ display:grid; grid-template-columns:140px repeat({len(hairstyles)*2}, 1fr);
|
||||
gap:8px; align-items:start; }}
|
||||
.grid {{ display:grid; grid-template-columns:repeat({len(hairstyles)+1}, 1fr);
|
||||
gap:10px; align-items:start; }}
|
||||
.col-hdr {{ font-size:12px; color:var(--muted); text-align:center; padding:6px 4px;
|
||||
border-bottom:1px solid var(--border); }}
|
||||
.row-lbl {{ font-size:13px; color:var(--accent); padding:8px 4px;
|
||||
border-right:1px solid var(--border); }}
|
||||
.col-hdr.orig {{ color:var(--accent); }}
|
||||
.cell {{ position:relative; }}
|
||||
.cell img {{ width:100%; border-radius:6px; display:block;
|
||||
border:1px solid var(--border); }}
|
||||
@@ -117,7 +116,7 @@ def main():
|
||||
<body>
|
||||
<header>
|
||||
<h1>发际线生发对比报告 <span class="tag">接口12 · grow_v2</span></h1>
|
||||
<div class="sub">固定参数:blend_method=multiband · mb_levels=5 · erode_cm=0.6 · 生成后端=swaphair</div>
|
||||
<div class="sub">固定:pushed 遮罩 + multiband 融合 · mb_levels=5 · erode_cm=0.6 · 非高清</div>
|
||||
<div class="sub">生成时间:{html.escape(meta['generated_at'])} · 并发 {meta['concurrency']} · 耗时 {meta['elapsed_sec']}s</div>
|
||||
<div class="stats">
|
||||
<div class="stat"><div class="n">{meta['total']}</div><div class="l">总调用</div></div>
|
||||
@@ -131,45 +130,39 @@ def main():
|
||||
""")
|
||||
|
||||
# 图例
|
||||
parts.append('<div class="legend">每张脸横向为 5 种发际线发型,每种发型分 '
|
||||
'<span class="tag">高清 is_hr=true</span>'
|
||||
'<span class="tag no">非高清 is_hr=false</span> 两列。</div>')
|
||||
parts.append('<div class="legend">每张脸:第一列为原图,其余 5 列为发际线生发结果(非高清)。点击任意图片可放大。</div>')
|
||||
|
||||
# 每张脸一个区块
|
||||
# 每张脸一个区块:原图 + 5 发型横向并排
|
||||
for stem in faces:
|
||||
orig_rel = f"orig/{stem}.jpg"
|
||||
parts.append('<div class="face-block">')
|
||||
parts.append(f' <div class="face-head">'
|
||||
f'<img class="orig-thumb zoomable" src="{url(orig_rel)}" '
|
||||
f'data-full="{url(orig_rel)}" alt="原图 {html.escape(stem)}">'
|
||||
f'<h2>{html.escape(stem)}</h2>'
|
||||
f'</div>')
|
||||
# 网格表头
|
||||
parts.append(f' <div class="face-head"><h2>{html.escape(stem)}</h2></div>')
|
||||
# 网格:第一列原图,其余 5 列发型结果
|
||||
parts.append('<div class="grid">')
|
||||
parts.append('<div class="col-hdr"></div>')
|
||||
# 表头
|
||||
parts.append('<div class="col-hdr orig">原图</div>')
|
||||
for h in hairstyles:
|
||||
parts.append(f'<div class="col-hdr" style="grid-column:span 2;">{html.escape(h["cn"])}<br><span style="opacity:.6">{html.escape(h["id"])}</span></div>')
|
||||
# 行:原图占满左侧第一列的高度由第一行承载;每行 = 一个 hr 选项
|
||||
for hro in hr_opts:
|
||||
hr_tag = hro["tag"]
|
||||
is_hr = hro["is_hr"]
|
||||
tag_cls = "" if is_hr else " no"
|
||||
tag_txt = "高清" if is_hr else "非高清"
|
||||
parts.append(f'<div class="row-lbl">{tag_txt}<span class="tag{tag_cls}">{hr_tag}</span></div>')
|
||||
for h in hairstyles:
|
||||
r = idx.get((stem, h["id"], hr_tag))
|
||||
if r and r["ok"]:
|
||||
ms = r["ms"]
|
||||
cap = f"{html.escape(h['cn'])} · {tag_txt} · {ms}ms"
|
||||
parts.append(
|
||||
f'<div class="cell"><img class="zoomable" src="{url(r["out"])}" '
|
||||
f'data-full="{url(r["out"])}" loading="lazy" '
|
||||
f'alt="{html.escape(stem)} {html.escape(h["cn"])} {hr_tag}">'
|
||||
f'<div class="cap">{ms}ms</div></div>')
|
||||
else:
|
||||
err = (r or {}).get("error", "未执行")
|
||||
parts.append(
|
||||
f'<div class="cell fail"><div class="failbox">{html.escape(err)}</div></div>')
|
||||
parts.append(f'<div class="col-hdr">{html.escape(h["cn"])}<br><span style="opacity:.6">{html.escape(h["id"])}</span></div>')
|
||||
# 第一列:原图大图(可点击放大)
|
||||
parts.append(
|
||||
f'<div class="cell"><img class="zoomable" src="{url(orig_rel)}" '
|
||||
f'data-full="{url(orig_rel)}" loading="lazy" alt="原图 {html.escape(stem)}">'
|
||||
f'<div class="cap">原图</div></div>')
|
||||
# 5 发型结果(hr_opts 现在只有 nohr 一档,直接取)
|
||||
hr_tag = hr_opts[0]["tag"] if hr_opts else "nohr"
|
||||
for h in hairstyles:
|
||||
r = idx.get((stem, h["id"], hr_tag))
|
||||
if r and r["ok"]:
|
||||
ms = r["ms"]
|
||||
parts.append(
|
||||
f'<div class="cell"><img class="zoomable" src="{url(r["out"])}" '
|
||||
f'data-full="{url(r["out"])}" loading="lazy" '
|
||||
f'alt="{html.escape(stem)} {html.escape(h["cn"])}">'
|
||||
f'<div class="cap">{ms}ms</div></div>')
|
||||
else:
|
||||
err = (r or {}).get("error", "未执行")
|
||||
parts.append(
|
||||
f'<div class="cell fail"><div class="failbox">{html.escape(err)}</div></div>')
|
||||
parts.append('</div>') # grid
|
||||
parts.append('</div>') # face-block
|
||||
|
||||
@@ -179,21 +172,19 @@ def main():
|
||||
parts.append('<div class="face-block">')
|
||||
parts.append(f'<h2>失败用例({len(fails)})</h2>')
|
||||
parts.append('<table class="fail-table"><thead><tr>'
|
||||
'<th>人脸</th><th>发型</th><th>高清</th><th>耗时(ms)</th><th>原因</th>'
|
||||
'<th>人脸</th><th>发型</th><th>耗时(ms)</th><th>原因</th>'
|
||||
'</tr></thead><tbody>')
|
||||
for r in fails:
|
||||
parts.append(
|
||||
f'<tr><td>{html.escape(r["stem"])}</td>'
|
||||
f'<td>{html.escape(r["hair_cn"])}</td>'
|
||||
f'<td>{r["hr_tag"]}</td>'
|
||||
f'<td>{r["ms"]}</td>'
|
||||
f'<td>{html.escape(r["error"] or "")}</td></tr>')
|
||||
parts.append('</tbody></table></div>')
|
||||
|
||||
parts.append(f"""
|
||||
<footer>
|
||||
接口:<code>POST /api/v1/hairline/grow_v2</code> ·
|
||||
固定 multiband / mb_levels=5 / erode_cm=0.6 ·
|
||||
接口:<code>POST /api/v1/hairline/grow_v2</code> · 固定 pushed 遮罩 + multiband 融合 ·
|
||||
数据源 results.json · 点击任意图片可放大查看
|
||||
</footer>
|
||||
</main>
|
||||
|
||||
@@ -113,32 +113,23 @@
|
||||
</div>
|
||||
|
||||
<div class="pf">
|
||||
<label>mask_type <span class="desc">遮罩类型</span></label>
|
||||
<select id="maskType">
|
||||
<option value="eroded">eroded(外缘内缩,默认)</option>
|
||||
<option value="closed">closed(闭合区域,未内缩)</option>
|
||||
<option value="pushed">pushed(发际线外推)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="pf">
|
||||
<label>erode_cm <span class="desc">外缘内缩距离(cm)</span></label>
|
||||
<label>erode_cm <span class="desc">baseline参考内缩(cm)</span></label>
|
||||
<div class="row">
|
||||
<input type="number" id="erodeCm" min="0" max="5" step="0.1" value="1.2">
|
||||
<input type="range" id="erodeRange" min="0" max="3" step="0.1" value="1.2">
|
||||
<input type="number" id="erodeCm" min="0" max="5" step="0.1" value="0.6">
|
||||
<input type="range" id="erodeRange" min="0" max="3" step="0.1" value="0.6">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pf" data-show="mask:pushed">
|
||||
<label>hairline_push_cm <span class="desc">发际线外推距离(仅pushed)</span></label>
|
||||
<div class="pf">
|
||||
<label>hairline_push_cm <span class="desc">发际线外推距离(cm)</span></label>
|
||||
<div class="row">
|
||||
<input type="number" id="hairlinePushCm" min="0" max="3" step="0.1" value="1.0">
|
||||
<input type="range" id="hairlinePushRange" min="0" max="3" step="0.1" value="1.0">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pf" data-show="mask:pushed">
|
||||
<label>hairline_edge <span class="desc">发际线提取方式(仅pushed)</span></label>
|
||||
<div class="pf">
|
||||
<label>hairline_edge <span class="desc">发际线提取方式</span></label>
|
||||
<select id="hairlineEdge">
|
||||
<option value="column">column(逐列最低点,稳定)</option>
|
||||
<option value="contour">contour(形态学轮廓,精确)</option>
|
||||
@@ -146,30 +137,7 @@
|
||||
</div>
|
||||
|
||||
<div class="pf">
|
||||
<label>blend_method <span class="desc">接缝融合算法</span></label>
|
||||
<select id="blendMethod">
|
||||
<option value="feather">feather(高斯羽化,默认)</option>
|
||||
<option value="alpha_gradient">alpha_gradient(距离变换内渐变)</option>
|
||||
<option value="seamless">seamless(泊松无缝克隆)</option>
|
||||
<option value="multiband">multiband(多频段金字塔融合)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="pf" data-show="blend:feather,alpha_gradient,multiband">
|
||||
<label>color_match <span class="desc">颜色校正前置</span></label>
|
||||
<div class="chk"><input type="checkbox" id="colorMatch"><span style="font-size:13px">融合前对生成图做 Reinhard 颜色校正(消除整体色差,seamless 自动跳过)</span></div>
|
||||
</div>
|
||||
|
||||
<div class="pf" data-show="blend:feather,alpha_gradient">
|
||||
<label>feather_px <span class="desc">羽化/渐变过渡宽度(px)</span></label>
|
||||
<div class="row">
|
||||
<input type="number" id="featherPx" min="1" max="80" step="1" value="15">
|
||||
<input type="range" id="featherRange" min="1" max="60" step="1" value="15">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pf" data-show="blend:multiband">
|
||||
<label>mb_levels <span class="desc">多频段金字塔层数(仅multiband)</span></label>
|
||||
<label>mb_levels <span class="desc">多频段金字塔层数</span></label>
|
||||
<div class="row">
|
||||
<input type="number" id="mbLevels" min="2" max="6" step="1" value="5">
|
||||
<input type="range" id="mbLevelsRange" min="2" max="6" step="1" value="5">
|
||||
@@ -291,9 +259,8 @@ function renderMetrics(d) {
|
||||
{ label: 'hairline_id', value: d.hairline_id },
|
||||
{ label: 'swap_mode', value: d.swap_mode },
|
||||
{ label: 'seg_model', value: d.seg_model },
|
||||
{ label: 'mask_type', value: d.mask_type },
|
||||
{ label: 'mask', value: d.mask_type },
|
||||
{ label: 'blend', value: d.blend_method },
|
||||
{ label: 'color_match', value: d.color_match },
|
||||
{ label: 'mb_levels', value: d.mb_levels },
|
||||
{ label: 'hairline_push', value: d.hairline_push_cm + 'cm' },
|
||||
{ label: 'hairline_edge', value: d.hairline_edge },
|
||||
@@ -317,12 +284,9 @@ function renderResult(d) {
|
||||
$('finalOut').onclick = function(){ zoom(this.src); };
|
||||
renderMetrics(d);
|
||||
const grid = $('stepsGrid'); grid.innerHTML = '';
|
||||
// 按 mask_type 过滤步骤:pushed 只显示 baseline/upper/头发分割/发际线/外推;
|
||||
// eroded/closed 只显示 baseline/upper/头发分割/填充/闭合,不显示发际线/外推。
|
||||
const mt = d.mask_type;
|
||||
const showKeys = new Set(['baseline_overlay','upper_overlay','hair_seg_overlay','mask_overlay','mask','swap_raw','hard_paste','alpha','final']);
|
||||
if (mt === 'pushed') { showKeys.add('hairline_overlay'); showKeys.add('pushed_overlay'); }
|
||||
else { showKeys.add('top_fill_overlay'); showKeys.add('closed_overlay'); }
|
||||
// 固定 pushed:只展示 pushed 相关步骤(不展示 top_fill/closed)
|
||||
const showKeys = new Set(['baseline_overlay','upper_overlay','hair_seg_overlay',
|
||||
'hairline_overlay','pushed_overlay','mask_overlay','mask','swap_raw','hard_paste','alpha','final']);
|
||||
STEPS.filter(st => showKeys.has(st.key)).forEach(st =>
|
||||
grid.appendChild(stepCard(st.title, st.sub, pick(s, st.key))));
|
||||
}
|
||||
@@ -343,18 +307,13 @@ async function submitTest() {
|
||||
form.append('hairgrow_strength', $('hgStrength').value || '0.75');
|
||||
form.append('is_hr', $('isHr').checked ? 'true' : 'false');
|
||||
form.append('seg_model', $('segModel').value);
|
||||
form.append('mask_type', $('maskType').value);
|
||||
form.append('erode_cm', $('erodeCm').value || '1.2');
|
||||
setStatus('正在请求 mask_type=' + $('maskType').value
|
||||
+ ' hairline_push_cm=' + ($('hairlinePushCm').value||'1.0')
|
||||
form.append('erode_cm', $('erodeCm').value || '0.6');
|
||||
setStatus('正在请求(固定 pushed + multiband) hairline_push_cm=' + ($('hairlinePushCm').value||'1.0')
|
||||
+ ' hairline_edge=' + $('hairlineEdge').value
|
||||
+ '(换发型走 GPU,约 10~15s)...', 'info');
|
||||
form.append('swap_mode', $('swapMode').value);
|
||||
form.append('blend_method', $('blendMethod').value);
|
||||
form.append('feather_px', $('featherPx').value || '15');
|
||||
form.append('edge_erode_px', $('edgeErodePx').value || '3');
|
||||
form.append('denoising_strength', $('denoise').value || '0.6');
|
||||
form.append('color_match', $('colorMatch').checked ? 'true' : 'false');
|
||||
form.append('mb_levels', $('mbLevels').value || '5');
|
||||
form.append('hairline_push_cm', $('hairlinePushCm').value || '1.0');
|
||||
form.append('hairline_edge', $('hairlineEdge').value);
|
||||
@@ -397,7 +356,7 @@ function copyJson() {
|
||||
}
|
||||
|
||||
// ---- 参数持久化 + 联动 ----
|
||||
const FIELDS = ['token','genBackend','hairlineId','swapMode','segModel','maskType','erodeCm','blendMethod','featherPx','edgeErodePx','denoise','hgStrength','mbLevels','hairlinePushCm','hairlineEdge'];
|
||||
const FIELDS = ['token','genBackend','hairlineId','swapMode','segModel','erodeCm','edgeErodePx','denoise','hgStrength','mbLevels','hairlinePushCm','hairlineEdge'];
|
||||
function saveField(id) { try { localStorage.setItem('if11_' + id, $(id).type === 'checkbox' ? ($(id).checked?'1':'0') : $(id).value); } catch(e){} }
|
||||
function linkNumRange(numId, rngId) {
|
||||
const num = $(numId), rng = $(rngId);
|
||||
@@ -419,42 +378,34 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
try { $('isHr').checked = localStorage.getItem('if11_isHr') === '1'; } catch(e){}
|
||||
$('isHr').addEventListener('change', () => saveField('isHr'));
|
||||
try { $('colorMatch').checked = localStorage.getItem('if11_colorMatch') === '1'; } catch(e){}
|
||||
$('colorMatch').addEventListener('change', () => saveField('colorMatch'));
|
||||
linkNumRange('erodeCm','erodeRange');
|
||||
linkNumRange('featherPx','featherRange');
|
||||
linkNumRange('edgeErodePx','edgeErodeRange');
|
||||
linkNumRange('denoise','denoiseRange');
|
||||
linkNumRange('hgStrength','hgStrengthRange');
|
||||
linkNumRange('mbLevels','mbLevelsRange');
|
||||
linkNumRange('hairlinePushCm','hairlinePushRange');
|
||||
// 初始化滑块值
|
||||
$('erodeRange').value = Math.min(3, parseFloat($('erodeCm').value)||1.2);
|
||||
$('featherRange').value = Math.min(60, parseFloat($('featherPx').value)||15);
|
||||
$('erodeRange').value = Math.min(3, parseFloat($('erodeCm').value)||0.6);
|
||||
$('edgeErodeRange').value = Math.min(30, parseFloat($('edgeErodePx').value)||3);
|
||||
$('denoiseRange').value = Math.min(1.0, parseFloat($('denoise').value)||0.6);
|
||||
$('hgStrengthRange').value = Math.min(1.0, parseFloat($('hgStrength').value)||0.75);
|
||||
$('mbLevelsRange').value = Math.min(6, Math.max(2, parseFloat($('mbLevels').value)||5));
|
||||
$('hairlinePushRange').value = Math.min(3, parseFloat($('hairlinePushCm').value)||1.0);
|
||||
|
||||
// 参数联动:按 gen_backend / blend_method 显隐无用参数(data-show 声明)
|
||||
// 参数联动:按 gen_backend 显隐无用参数(data-show 声明,遮罩/融合已固定无选项)
|
||||
function applyVisibility() {
|
||||
const gen = $('genBackend').value, bm = $('blendMethod').value, mt = $('maskType').value;
|
||||
const gen = $('genBackend').value;
|
||||
document.querySelectorAll('.pf[data-show]').forEach(el => {
|
||||
const conds = el.dataset.show.split(',').map(s => s.trim());
|
||||
let vis = true;
|
||||
conds.forEach(c => {
|
||||
const [k, v] = c.split(':');
|
||||
if (k === 'gen' && v !== gen) vis = false;
|
||||
if (k === 'blend' && v !== bm) vis = false;
|
||||
if (k === 'mask' && v !== mt) vis = false;
|
||||
});
|
||||
el.style.display = vis ? '' : 'none';
|
||||
});
|
||||
}
|
||||
$('genBackend').addEventListener('change', applyVisibility);
|
||||
$('blendMethod').addEventListener('change', applyVisibility);
|
||||
$('maskType').addEventListener('change', applyVisibility);
|
||||
applyVisibility();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
<h1>接口11 调试页 <span style="font-size:13px;color:#888">(带前后端日志)</span></h1>
|
||||
<div class="subtitle">
|
||||
独立调试页,每一步都记录日志。提交后展开"前端日志"和"后端日志",点按钮可下载。<br>
|
||||
重点排查:mask_type 是否真的传成 pushed、①-f/①-g 是否有图、最终遮罩是否走了新算法。
|
||||
重点排查:遮罩是否走 pushed、①-f/①-g 是否有图、最终遮罩是否正确。遮罩固定 pushed、融合固定 multiband。
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
@@ -68,14 +68,6 @@
|
||||
<input type="text" id="token" value="dev-shared-secret-2026" style="flex:1;min-width:200px;padding:8px;border:1px solid #ddd;border-radius:8px">
|
||||
</div>
|
||||
<div class="params">
|
||||
<div class="pf">
|
||||
<label>mask_type <span class="desc">遮罩类型(关键!)</span></label>
|
||||
<select id="maskType">
|
||||
<option value="eroded">eroded(外缘内缩)</option>
|
||||
<option value="closed">closed(闭合区域)</option>
|
||||
<option value="pushed">pushed(发际线外推)★</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="pf">
|
||||
<label>发际线类型 ID</label>
|
||||
<select id="hairlineId">
|
||||
@@ -94,30 +86,30 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="pf">
|
||||
<label>erode_cm <span class="desc">外缘内缩(cm)</span></label>
|
||||
<div class="row"><input type="number" id="erodeCm" min="0" max="5" step="0.1" value="1.2"></div>
|
||||
<label>erode_cm <span class="desc">baseline参考内缩(cm)</span></label>
|
||||
<div class="row"><input type="number" id="erodeCm" min="0" max="5" step="0.1" value="0.6"></div>
|
||||
</div>
|
||||
<div class="pf" data-show="mask:pushed">
|
||||
<label>hairline_push_cm <span class="desc">发际线外推(cm,仅pushed)</span></label>
|
||||
<div class="pf">
|
||||
<label>hairline_push_cm <span class="desc">发际线外推(cm)</span></label>
|
||||
<div class="row"><input type="number" id="hairlinePushCm" min="0" max="3" step="0.1" value="1.0"></div>
|
||||
</div>
|
||||
<div class="pf" data-show="mask:pushed">
|
||||
<label>hairline_edge <span class="desc">发际线提取(仅pushed)</span></label>
|
||||
<div class="pf">
|
||||
<label>hairline_edge <span class="desc">发际线提取</span></label>
|
||||
<select id="hairlineEdge">
|
||||
<option value="column">column(逐列最低点)</option>
|
||||
<option value="contour">contour(形态学轮廓)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="pf">
|
||||
<label>blend_method <span class="desc">融合算法</span></label>
|
||||
<select id="blendMethod">
|
||||
<option value="feather">feather</option>
|
||||
<option value="alpha_gradient">alpha_gradient</option>
|
||||
<option value="seamless">seamless</option>
|
||||
<option value="multiband">multiband</option>
|
||||
</select>
|
||||
<label>mb_levels <span class="desc">多频段金字塔层数</span></label>
|
||||
<div class="row"><input type="number" id="mbLevels" min="2" max="6" step="1" value="5"></div>
|
||||
</div>
|
||||
<div class="pf">
|
||||
<label>edge_erode_px <span class="desc">贴图前遮罩内缩(px)</span></label>
|
||||
<div class="row"><input type="number" id="edgeErodePx" min="0" max="40" step="1" value="3"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="font-size:12px;color:#888;margin-top:8px">遮罩算法固定 pushed、融合固定 multiband,无需选择。</div>
|
||||
</div>
|
||||
|
||||
<div class="status hidden" id="statusBar"></div>
|
||||
@@ -202,19 +194,17 @@ function stepCard(title, sub, src) {
|
||||
}
|
||||
|
||||
function renderResult(d) {
|
||||
flog('renderResult 开始,mask_type=' + d.mask_type, 'info');
|
||||
flog('renderResult 开始(固定 pushed + multiband)', 'info');
|
||||
const s = d.steps || {};
|
||||
$('finalInput').src = pick(s, 'input') || '';
|
||||
$('finalInput').onclick = function(){ zoom(this.src); };
|
||||
$('finalOut').src = pick(s, 'final') || '';
|
||||
$('finalOut').onclick = function(){ zoom(this.src); };
|
||||
const grid = $('stepsGrid'); grid.innerHTML = '';
|
||||
// 按 mask_type 过滤步骤
|
||||
const mt = d.mask_type;
|
||||
const showKeys = new Set(['baseline_overlay','upper_overlay','hair_seg_overlay','mask_overlay','mask','swap_raw','hard_paste','alpha','final']);
|
||||
if (mt === 'pushed') { showKeys.add('hairline_overlay'); showKeys.add('pushed_overlay'); }
|
||||
else { showKeys.add('top_fill_overlay'); showKeys.add('closed_overlay'); }
|
||||
$('stepsInfo').textContent = '(mask_type=' + mt + ',按此过滤步骤)';
|
||||
// 固定 pushed:只展示 pushed 相关步骤(不展示 top_fill/closed)
|
||||
const showKeys = new Set(['baseline_overlay','upper_overlay','hair_seg_overlay',
|
||||
'hairline_overlay','pushed_overlay','mask_overlay','mask','swap_raw','hard_paste','alpha','final']);
|
||||
$('stepsInfo').textContent = '(固定 pushed + multiband)';
|
||||
STEPS.filter(st => showKeys.has(st.key)).forEach(st => {
|
||||
const src = pick(s, st.key);
|
||||
const hasImg = src && src.length > 50;
|
||||
@@ -229,16 +219,15 @@ async function submitTest() {
|
||||
if (!file) { setStatus('请先选择图片', 'error'); return; }
|
||||
flog('===== 提交测试 =====', 'info');
|
||||
// 记录前端实际读取的每个参数值(关键诊断)
|
||||
const mt = $('maskType').value;
|
||||
flog('前端读取 mask_type=' + mt, 'info');
|
||||
flog('前端读取 hairline_push_cm=' + ($('hairlinePushCm').value || '(默认)'), 'info');
|
||||
flog('前端读取 hairline_edge=' + ($('hairlineEdge').value || '(默认)'), 'info');
|
||||
flog('前端读取 hairline_id=' + $('hairlineId').value, 'info');
|
||||
flog('前端读取 seg_model=' + $('segModel').value, 'info');
|
||||
flog('前端读取 mb_levels=' + ($('mbLevels').value || '(默认)'), 'info');
|
||||
|
||||
const btn = $('submitBtn');
|
||||
btn.disabled = true; btn.textContent = '⏳ 请求中...';
|
||||
setStatus('正在请求 mask_type=' + mt + ' ...', 'info');
|
||||
setStatus('正在请求(固定 pushed + multiband)...', 'info');
|
||||
$('resultsArea').classList.remove('hidden');
|
||||
|
||||
const form = new FormData();
|
||||
@@ -248,15 +237,11 @@ async function submitTest() {
|
||||
form.append('hairgrow_strength', '0.75');
|
||||
form.append('is_hr', 'false');
|
||||
form.append('seg_model', $('segModel').value);
|
||||
form.append('mask_type', mt);
|
||||
form.append('erode_cm', $('erodeCm').value || '1.2');
|
||||
form.append('erode_cm', $('erodeCm').value || '0.6');
|
||||
form.append('swap_mode', 'ext_mask');
|
||||
form.append('blend_method', $('blendMethod').value);
|
||||
form.append('feather_px', '15');
|
||||
form.append('edge_erode_px', '3');
|
||||
form.append('edge_erode_px', $('edgeErodePx').value || '3');
|
||||
form.append('denoising_strength', '0.6');
|
||||
form.append('color_match', 'false');
|
||||
form.append('mb_levels', '5');
|
||||
form.append('mb_levels', $('mbLevels').value || '5');
|
||||
form.append('hairline_push_cm', $('hairlinePushCm').value || '1.0');
|
||||
form.append('hairline_edge', $('hairlineEdge').value);
|
||||
|
||||
@@ -279,7 +264,7 @@ async function submitTest() {
|
||||
|
||||
if (json.code === 0) {
|
||||
const d = json.data;
|
||||
flog('后端返回 mask_type=' + d.mask_type + ' mask_pixels=' + d.mask_pixels, 'info');
|
||||
flog('后端返回 mask_type=' + d.mask_type + ' blend=' + d.blend_method + ' mask_pixels=' + d.mask_pixels, 'info');
|
||||
flog('后端返回 hairline_push_cm=' + d.hairline_push_cm + ' hairline_edge=' + d.hairline_edge, 'info');
|
||||
flog('后端 _rid=' + d._rid, 'info');
|
||||
// 详细记录 steps 每个字段长度
|
||||
@@ -327,20 +312,11 @@ async function downloadBackendLog() {
|
||||
}
|
||||
}
|
||||
|
||||
// 参数联动
|
||||
function applyVisibility() {
|
||||
const mt = $('maskType').value;
|
||||
document.querySelectorAll('.pf[data-show]').forEach(el => {
|
||||
const [k, v] = el.dataset.show.split(':');
|
||||
el.style.display = (k === 'mask' && v === mt) ? '' : 'none';
|
||||
});
|
||||
}
|
||||
$('maskType').addEventListener('change', function(){ flog('mask_type 切换为: ' + this.value, 'info'); applyVisibility(); });
|
||||
// 参数联动(遮罩/融合已固定,无下拉联动)
|
||||
$('imageFile').addEventListener('change', function(){ if(this.files.length) flog('选择图片: ' + this.files[0].name, 'info'); });
|
||||
|
||||
flog('调试页加载完成', 'info');
|
||||
flog('请选择图片,mask_type 切到 pushed,然后点提交', 'info');
|
||||
applyVisibility();
|
||||
flog('遮罩固定 pushed、融合固定 multiband,选好图片直接提交', 'info');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user