From 215663177adee6d7aab1e589360ac6c6149a14ba Mon Sep 17 00:00:00 2001 From: xsl Date: Wed, 8 Jul 2026 20:38:31 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9Emask=E5=8C=BA?= =?UTF-8?q?=E5=9F=9F=E9=87=8D=E7=BB=98=E5=8A=9F=E8=83=BD=20+=20=E7=BE=BD?= =?UTF-8?q?=E5=8C=96=E8=B4=B4=E5=9B=9E=E4=BC=98=E5=8C=96=20+=20=E5=8F=91?= =?UTF-8?q?=E4=B8=9D=E5=A2=9E=E5=BC=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增功能: - inpaint_mask.py: mask区域重绘服务(enhance/pure_inpaint两种模式) 严格只在mask区做SD inpainting,发丝自然化 - /inpaint 页面 + /api/inpaint 接口:画板手绘mask + 提示词编辑 + denoising可调 - enhance_hair.webui_img2img: 新增denoising_strength参数(原硬编码0.35) 羽化贴回优化(hair_swap_manual.py 步骤⑥): - feather_px(羽化范围)+ feather_alpha(羽化强度)拆分为两个独立参数 - 改为只羽化边缘(mask内部保持硬切,仅边缘带渐变) - 新增黑底羽化对比图(纯黑背景凸显边缘过渡) 其他改动: - OSS/COS密钥脱敏:改为可选import,未设环境变量时不崩溃 - manual页面:发型列表改为本次训练的8个(含hair_flow) - enhance二次增强:重绘区改为「原图头发∪手绘mask」并外扩10%+减发际线带3cm - 发型清单文档更新:179→180个可用发型(+hair_flow) 涉及文件: - 新增: inpaint_mask.py, inpaint.html - 修改: app.py, manual.html, hair_swap_manual.py, enhance_hair.py - 修改: oss_module.py, upload_oss.py, cos_module.py(密钥脱敏) - 文档: HAIRSTYLES_AVAILABLE.md, hairstyles_available.csv --- docs/HAIRSTYLES_AVAILABLE.md | 16 +- docs/hairstyles_available.csv | 361 ++++++++++---------- hair_grow_service/app.py | 85 ++++- hair_grow_service/static/inpaint.html | 323 ++++++++++++++++++ hair_grow_service/static/manual.html | 24 +- project/hair_service_sd/core/cos_module.py | 7 +- project/hair_service_sd/core/oss_module.py | 21 +- project/hair_service_sd/hair_swap_manual.py | 58 +++- project/hair_service_sd/inpaint_mask.py | 64 ++++ project/hair_service_sd/upload_oss.py | 21 +- 10 files changed, 751 insertions(+), 229 deletions(-) create mode 100644 hair_grow_service/static/inpaint.html create mode 100644 project/hair_service_sd/inpaint_mask.py diff --git a/docs/HAIRSTYLES_AVAILABLE.md b/docs/HAIRSTYLES_AVAILABLE.md index c124252..9417a92 100644 --- a/docs/HAIRSTYLES_AVAILABLE.md +++ b/docs/HAIRSTYLES_AVAILABLE.md @@ -1,6 +1,6 @@ # 可用发型清单 -> 当前共 **179 个发型**可用于换发型功能(hair_flow 训练完成后为 180 个)。 +> 当前共 **180 个发型**可用于换发型功能。 > 数据来源:`/api/hairstyles` 接口实时返回,要求发型同时具备「LoRA权重 + ref材质 + config.json」三件齐全。 > 全量清单见同目录 `hairstyles_available.csv`。 @@ -10,11 +10,11 @@ | 类别 | 数量 | 说明 | |------|------|------| -| **总可用发型** | **179** | 三件齐全,可直接换发型 | +| **总可用发型** | **180** | 三件齐全,可直接换发型 | | ├ 男款 (boy) | 93 | 适合男性脸型 | -| ├ 女款 (girl) | 86 | 适合女性脸型 | -| ├ 中文名命名 | 7 | 本次按脸型训练的新发型 | -| ├ 英文名命名 | 15 | 历史测试发型 | +| ├ 女款 (girl) | 87 | 适合女性脸型(+hair_flow) | +| ├ 中文名命名 | 7 | 按脸型训练的新发型 | +| ├ 英文名命名 | 16 | 历史测试发型 + hair_flow | | └ 数字ID命名 | 157 | 生产环境发型(雪花算法ID) | **性别说明**:gender 字段决定发型被分到"男款/女款",但换发型时**男女发型都能用**(只是预览图用对应性别的标准脸)。 @@ -55,6 +55,7 @@ | hair_id | gender | 备注 | |---------|--------|------| +| **hair_flow** | **girl** | **波浪卷发,最新训练(2026-07-07)** | | huaban1 | girl | 画板1 | | huaban2 | girl | 画板2 | | new_test_001 | boy | 测试 | @@ -104,8 +105,9 @@ ### 2. 调试页(带可视化) - `http://localhost:8888/debug` — 全参数调试台 -- `http://localhost:8888/manual` — 手绘 mask 重绘 -- `http://localhost:8888/hairline` — 发际线 mask 实验 +- `http://localhost:8888/manual` — 手绘 mask 换发型(含 enhance 二次增强 + 羽化贴回) +- `http://localhost:8888/inpaint` — **mask区域重绘**(发丝自然化,enhance/pure_inpaint两种模式) +- `http://localhost:8888/hairline` — 发际线 mask 实验(4种方案对比) ### 3. API 调用 ```bash diff --git a/docs/hairstyles_available.csv b/docs/hairstyles_available.csv index facf039..0700eab 100644 --- a/docs/hairstyles_available.csv +++ b/docs/hairstyles_available.csv @@ -1,180 +1,181 @@ -hair_id,gender,预览图,LoRA大小MB -1905785164224868354,girl,有,144 -1907641420518580226,boy,有,144 -1907641442341543937,boy,有,144 -1907641528173780994,boy,有,144 -1907650883958923266,boy,有,144 -1907650924350070785,boy,有,144 -1907651105032298498,boy,有,144 -1907651244006367233,boy,有,144 -1907651308460236802,girl,有,144 -1907651348528422914,girl,有,144 -1907651413477220354,girl,有,144 -1907651464794529794,girl,有,144 -1907651525024735234,girl,有,144 -1907651578753769473,girl,有,144 -1907651632629604354,girl,有,144 -1907651680352395265,girl,有,144 -1907651747310264321,girl,有,144 -1907805226322419713,boy,有,144 -1929905737381048322,boy,有,144 -1929905770088230914,boy,有,144 -1929906202869100546,boy,有,144 -1929906230224351234,boy,有,144 -1929906315553271809,boy,有,144 -1929906349401305089,boy,有,144 -1929906429839667202,boy,有,144 -1935354779623858177,boy,有,144 -1935354812175851522,boy,有,144 -1935354869738479617,boy,有,144 -1935354938051108865,boy,有,144 -1935355030892027905,boy,有,144 -1935355149217538050,girl,有,144 -1935355188652384257,girl,有,144 -1935355258143612929,girl,有,144 -1935355292960530433,girl,有,144 -1935355361726144514,girl,有,144 -1935355403207811074,girl,有,144 -1935355449991077889,girl,有,144 -1935355504156319745,girl,有,144 -1935355551300296705,girl,有,144 -1935355607394918402,girl,有,144 -1935355698319040514,girl,有,144 -1935355740559876097,girl,有,144 -1935355778526715905,girl,有,144 -1935355868347736065,girl,有,144 -1935355934743568386,girl,有,144 -1935355969141055490,girl,有,144 -1935356042201636865,girl,有,144 -1935356092684279809,girl,有,144 -1935356131338985473,girl,有,144 -1935356178227109890,girl,有,144 -1938489620162781185,boy,有,144 -1938489674713899010,boy,有,144 -1938489739088076801,boy,有,144 -1938489840707674114,boy,有,144 -1938489915521474561,boy,有,144 -1938489994072399874,boy,有,144 -1938490058547240961,boy,有,144 -1938492790033195009,girl,有,144 -1938492830059438081,girl,有,144 -1938841049238970370,girl,有,144 -1938848709308948482,girl,有,144 -1938848730150445058,girl,有,144 -1938848782956732417,girl,有,144 -1939330299344560130,girl,有,144 -1939339435616608258,girl,有,144 -1939351219396255745,boy,有,144 -1939351238526476289,boy,有,144 -1939351319849836545,boy,有,144 -1939351392453238785,girl,有,144 -1939351512506802177,girl,有,144 -1940007538260287490,boy,有,144 -1941163289636872193,boy,有,144 -1941163324332154882,boy,有,144 -1941163373069967362,boy,有,144 -1941163615962112002,boy,有,144 -1941163645464846338,boy,有,144 -1941163754357366785,boy,有,144 -1941163816533729282,boy,有,144 -1941163884078800898,boy,有,144 -1941163952840220673,boy,有,144 -1941164012827156482,boy,有,144 -1941352458711572482,boy,有,144 -1941352541028982786,boy,有,144 -1941352599665352705,boy,有,144 -1943143930804936705,boy,有,144 -1943144007388733441,boy,有,144 -1945789428732895233,boy,有,144 -1953267161121464322,boy,有,144 -1953267288284372994,boy,有,144 -1953270137953230849,girl,有,144 -1953270523594317826,girl,有,144 -1953270724765720577,boy,有,144 -1953271922151432194,boy,有,144 -1953272913265467394,boy,有,144 -1953288800932503554,boy,有,144 -1954536242109784066,boy,有,144 -1954536297776586754,boy,有,144 -1954536402516746241,boy,有,144 -1954536893304840194,boy,有,144 -1954536956919848962,boy,有,144 -1954537022875279362,boy,有,144 -1954537085882114050,boy,有,144 -1954537191729569793,boy,有,144 -1954537709436706817,girl,有,144 -1954537743532204034,girl,有,144 -1954537926731014146,girl,有,144 -1954537961069780994,girl,有,144 -1954538119216013313,girl,有,144 -1954538156499181569,girl,有,144 -1954538259330932738,girl,有,144 -1954538335306555393,girl,有,144 -1980312945301954561,boy,有,144 -1980313052302843905,girl,有,144 -1980313166576656385,girl,有,144 -1980313210109337601,girl,有,144 -1980313433502162946,boy,有,144 -1980313576506957825,girl,有,144 -1980313740403580930,girl,有,144 -1980313819915001858,girl,有,144 -1980314032369082369,girl,有,144 -1980314146483511297,girl,有,144 -1980314209322573825,girl,有,144 -1980314290419441665,girl,有,144 -1980314381058351106,girl,有,144 -1980314477720281090,girl,有,144 -1980314710101499906,boy,有,144 -1980314764421931009,boy,有,144 -1980314868713299969,boy,有,144 -1980315054516772866,boy,有,144 -1980315179934851074,girl,有,144 -1980315238260842497,girl,有,144 -1980361636742209537,girl,有,144 -1980367742952579074,boy,有,144 -1980367781510815746,boy,有,144 -1980367800787836929,boy,有,144 -1980367852847538177,boy,有,144 -1980367968627105793,boy,有,144 -1980368109866098690,boy,有,144 -1980368205752082434,boy,有,144 -1980368368847593473,boy,有,144 -1980368465631158274,boy,有,144 -1980368588293578754,boy,有,144 -1980368787732733953,boy,有,144 -1980368837162606594,boy,有,144 -1980368937435832321,boy,有,144 -1980413320612847618,girl,有,144 -1980415346071609345,girl,有,144 -1980415897056354306,girl,有,144 -1980422351658196994,boy,有,144 -1980422373388886018,boy,有,144 -1981177324700549122,girl,有,144 -1981177378714796034,boy,有,144 -1981177477423546370,boy,有,144 -1981177594750812162,boy,有,144 -1981177742742634497,boy,有,144 -1981177931859607554,boy,有,144 -3325266,girl,有,144 -huaban1,girl,有,144 -huaban2,girl,有,144 -new_test_001,boy,有,144 -newf_1214,girl,有,144 -newf_12141,girl,有,144 -newf_121445553,boy,有,144 -newf_12314,girl,有,144 -newf_1235314,girl,有,144 -newf_204,girl,有,144 -newf_216,girl,有,144 -test001,boy,有,144 -test002,girl,有,144 -test003,boy,有,144 -test0036,boy,有,144 -test888,boy,有,144 -圆-心形,girl,有,144 -圆-椭圆,girl,有,144 -圆-波浪,girl,有,144 -圆-直线,girl,有,144 -圆-花瓣,girl,有,144 -心形-心形,girl,有,144 -心形-椭圆,girl,有,144 +hair_id,gender +1905785164224868354,girl +1907641420518580226,boy +1907641442341543937,boy +1907641528173780994,boy +1907650883958923266,boy +1907650924350070785,boy +1907651105032298498,boy +1907651244006367233,boy +1907651308460236802,girl +1907651348528422914,girl +1907651413477220354,girl +1907651464794529794,girl +1907651525024735234,girl +1907651578753769473,girl +1907651632629604354,girl +1907651680352395265,girl +1907651747310264321,girl +1907805226322419713,boy +1929905737381048322,boy +1929905770088230914,boy +1929906202869100546,boy +1929906230224351234,boy +1929906315553271809,boy +1929906349401305089,boy +1929906429839667202,boy +1935354779623858177,boy +1935354812175851522,boy +1935354869738479617,boy +1935354938051108865,boy +1935355030892027905,boy +1935355149217538050,girl +1935355188652384257,girl +1935355258143612929,girl +1935355292960530433,girl +1935355361726144514,girl +1935355403207811074,girl +1935355449991077889,girl +1935355504156319745,girl +1935355551300296705,girl +1935355607394918402,girl +1935355698319040514,girl +1935355740559876097,girl +1935355778526715905,girl +1935355868347736065,girl +1935355934743568386,girl +1935355969141055490,girl +1935356042201636865,girl +1935356092684279809,girl +1935356131338985473,girl +1935356178227109890,girl +1938489620162781185,boy +1938489674713899010,boy +1938489739088076801,boy +1938489840707674114,boy +1938489915521474561,boy +1938489994072399874,boy +1938490058547240961,boy +1938492790033195009,girl +1938492830059438081,girl +1938841049238970370,girl +1938848709308948482,girl +1938848730150445058,girl +1938848782956732417,girl +1939330299344560130,girl +1939339435616608258,girl +1939351219396255745,boy +1939351238526476289,boy +1939351319849836545,boy +1939351392453238785,girl +1939351512506802177,girl +1940007538260287490,boy +1941163289636872193,boy +1941163324332154882,boy +1941163373069967362,boy +1941163615962112002,boy +1941163645464846338,boy +1941163754357366785,boy +1941163816533729282,boy +1941163884078800898,boy +1941163952840220673,boy +1941164012827156482,boy +1941352458711572482,boy +1941352541028982786,boy +1941352599665352705,boy +1943143930804936705,boy +1943144007388733441,boy +1945789428732895233,boy +1953267161121464322,boy +1953267288284372994,boy +1953270137953230849,girl +1953270523594317826,girl +1953270724765720577,boy +1953271922151432194,boy +1953272913265467394,boy +1953288800932503554,boy +1954536242109784066,boy +1954536297776586754,boy +1954536402516746241,boy +1954536893304840194,boy +1954536956919848962,boy +1954537022875279362,boy +1954537085882114050,boy +1954537191729569793,boy +1954537709436706817,girl +1954537743532204034,girl +1954537926731014146,girl +1954537961069780994,girl +1954538119216013313,girl +1954538156499181569,girl +1954538259330932738,girl +1954538335306555393,girl +1980312945301954561,boy +1980313052302843905,girl +1980313166576656385,girl +1980313210109337601,girl +1980313433502162946,boy +1980313576506957825,girl +1980313740403580930,girl +1980313819915001858,girl +1980314032369082369,girl +1980314146483511297,girl +1980314209322573825,girl +1980314290419441665,girl +1980314381058351106,girl +1980314477720281090,girl +1980314710101499906,boy +1980314764421931009,boy +1980314868713299969,boy +1980315054516772866,boy +1980315179934851074,girl +1980315238260842497,girl +1980361636742209537,girl +1980367742952579074,boy +1980367781510815746,boy +1980367800787836929,boy +1980367852847538177,boy +1980367968627105793,boy +1980368109866098690,boy +1980368205752082434,boy +1980368368847593473,boy +1980368465631158274,boy +1980368588293578754,boy +1980368787732733953,boy +1980368837162606594,boy +1980368937435832321,boy +1980413320612847618,girl +1980415346071609345,girl +1980415897056354306,girl +1980422351658196994,boy +1980422373388886018,boy +1981177324700549122,girl +1981177378714796034,boy +1981177477423546370,boy +1981177594750812162,boy +1981177742742634497,boy +1981177931859607554,boy +3325266,girl +hair_flow,girl +huaban1,girl +huaban2,girl +new_test_001,boy +newf_1214,girl +newf_12141,girl +newf_121445553,boy +newf_12314,girl +newf_1235314,girl +newf_204,girl +newf_216,girl +test001,boy +test002,girl +test003,boy +test0036,boy +test888,boy +圆-心形,girl +圆-椭圆,girl +圆-波浪,girl +圆-直线,girl +圆-花瓣,girl +心形-心形,girl +心形-椭圆,girl diff --git a/hair_grow_service/app.py b/hair_grow_service/app.py index 124e53d..d4870f4 100644 --- a/hair_grow_service/app.py +++ b/hair_grow_service/app.py @@ -87,6 +87,12 @@ def manual_page(): return send_from_directory(os.path.join(BASE_DIR, "static"), "manual.html") +@app.route("/inpaint") +def inpaint_page(): + """mask区域重绘测试页(纯发丝增强/自然重绘)""" + return send_from_directory(os.path.join(BASE_DIR, "static"), "inpaint.html") + + @app.route("/api/swap", methods=["POST"]) def api_swap_proxy(): """代理换发型请求到 8801(避免前端跨域问题)""" @@ -340,6 +346,7 @@ def api_swap_manual(): blend_dilate=(int(bd[0]), int(bd[1])), seamless_dilate=(int(sd[0]), int(sd[1])), feather_px=int(g("feather_px", 0)), + feather_alpha=float(g("feather_alpha", 1.0)), enhance=bool(g("enhance", False)), enhance_denoising=float(g("enhance_denoising", 0.35)), ) @@ -402,14 +409,25 @@ def preview_img(hair_id): @app.route("/train_src/") def train_src_img(hair_id): - """返回发型的训练原图(hair_type_images/.jpg/.png)。 - 用于测试页展示发型真实样子,而非套在标准脸上的效果图。 + """返回发型的训练原图,用于测试页展示发型真实样子。 + 依次查找:hair_type_images/ → images/ → train_material//images 输入 → 预览图(兜底)。 """ - src_dir = "/home/xsl/change_hair/hair_type_images" - for ext in (".jpg", ".jpeg", ".png"): - path = os.path.join(src_dir, hair_id + ext) - if os.path.exists(path): - return send_from_directory(src_dir, hair_id + ext) + # 候选源目录(按优先级) + candidate_dirs = [ + "/home/xsl/change_hair/hair_type_images", + "/home/xsl/change_hair/images", + "/home/xsl/change_hair/data/batch_train_inputs/" + hair_id, + ] + for src_dir in candidate_dirs: + for ext in (".jpg", ".jpeg", ".png"): + path = os.path.join(src_dir, hair_id + ext) + if os.path.exists(path): + return send_from_directory(src_dir, hair_id + ext) + # 兜底:返回套脸预览图(previews/.jpg) + preview_dir = os.path.join(BASE_DIR, "static", "previews") + preview_path = os.path.join(preview_dir, hair_id + ".jpg") + if os.path.exists(preview_path): + return send_from_directory(preview_dir, hair_id + ".jpg") return ("", 404) @@ -507,6 +525,59 @@ def api_grow(): return jsonify({"state": -1, "msg": f"生发失败: {e}"}), 500 +@app.route("/api/inpaint", methods=["POST"]) +def api_inpaint(): + """mask区域重绘接口(严格只在mask区做SD inpainting)。 + + 入参 JSON: + img: 原图 base64 + mask: 手绘mask base64(白色=重绘区) + prompt: 提示词(空则用模式默认值) + denoising_strength: 重绘强度 0~1(默认0.5) + mode: "enhance" 或 "pure_inpaint"(默认enhance) + 返回: {state, result, mask_used, info} + """ + try: + d = request.json + img_b64 = d.get("img", "") + mask_b64 = d.get("mask", "") + if not img_b64 or not mask_b64: + return jsonify({"state": -1, "msg": "img 和 mask 不能为空(请先手绘重绘区域)"}), 400 + + img = _b64_to_ndarray(img_b64, color=True) + mask = _b64_to_ndarray(mask_b64, color=False) + if img is None or mask is None: + return jsonify({"state": -1, "msg": "img 或 mask 解析失败"}), 400 + + prompt = d.get("prompt", "") + denoising = float(d.get("denoising_strength", 0.5)) + mode = d.get("mode", "enhance") + if mode not in ("enhance", "pure_inpaint"): + mode = "enhance" + + print(f"[inpaint] mode={mode}, denoising={denoising}, prompt={prompt[:40]}, img={img.shape}") + + import sys + sys.path.insert(0, "/home/xsl/change_hair/project/hair_service_sd") + from inpaint_mask import inpaint_mask as do_inpaint + result, mask_used, info = do_inpaint( + img, mask, prompt=prompt, + denoising_strength=denoising, mode=mode + ) + + _, buf = cv2.imencode(".jpg", result, [cv2.IMWRITE_JPEG_QUALITY, 95]) + result_b64 = base64.b64encode(buf).decode("utf-8") + _, mbuf = cv2.imencode(".png", mask_used) + mask_used_b64 = "data:image/png;base64," + base64.b64encode(mbuf).decode("utf-8") + + return jsonify({"state": 0, "result": result_b64, + "mask_used": mask_used_b64, "info": info}) + except Exception as e: + print(f"[inpaint] 失败: {e}") + traceback.print_exc() + return jsonify({"state": -1, "msg": f"mask重绘失败: {e}"}), 500 + + if __name__ == "__main__": print(f"生发服务启动,端口 {PORT}") print(f"测试页面: http://0.0.0.0:{PORT}") diff --git a/hair_grow_service/static/inpaint.html b/hair_grow_service/static/inpaint.html new file mode 100644 index 0000000..11b63e8 --- /dev/null +++ b/hair_grow_service/static/inpaint.html @@ -0,0 +1,323 @@ + + + + + +mask区域重绘 + + + +
+
+

🖌️ mask区域重绘(发丝自然化)

+

上传图片 → 涂抹要重绘的区域 → 调提示词和强度 → 生成。严格只在涂抹区重绘,其余保留原图

+
+ +
+ +
+ +
+

① 画笔工具

+
在右侧图片上涂抹需要重绘的区域(白色=重绘,黑色=保留原图)
+
+ + +
+
+
画笔大小 25px
+ +
+ + +

② 重绘模式

+
+ + +
+ +

③ 提示词

+
+ +
+ +

④ 重绘强度

+
+
denoising_strength 0.50越低越保留原图
+ +
+ + +
+ 说明:
+ • 严格只在涂抹区域重绘,mask外完全保留原图
+ • enhance模式:发丝细节增强(默认0.35强度)
+ • pure_inpaint模式:自然重绘(默认0.5强度)
+ • 改提示词可控制重绘方向(如"detailed curly hair") +
+
+ + +
+
+
+ +

点击上传图片

+

支持 jpg/png,建议人脸清晰照

+
+ + +
+ + +
重绘中,约15-30秒...
+
+ +
+

对比结果

+
+

原图

+

使用的mask

+

重绘结果

+
+
+
+
+
+ + + + + + diff --git a/hair_grow_service/static/manual.html b/hair_grow_service/static/manual.html index ba25c95..f2eddfd 100644 --- a/hair_grow_service/static/manual.html +++ b/hair_grow_service/static/manual.html @@ -159,9 +159,13 @@ body { font-family: -apple-system, "Segoe UI", "Microsoft YaHei", sans-serif; ba
-
feather_px 0
+
feather_px 0
+
+
feather_alpha 1.00
+ +
webui端固定:
cfg=7 steps=20 sampler=DPM++ 2M Karras @@ -182,7 +186,7 @@ body { font-family: -apple-system, "Segoe UI", "Microsoft YaHei", sans-serif; ba
-

③ 选发型

+

③ 选发型

加载中...
未选择
@@ -225,14 +229,6 @@ body { font-family: -apple-system, "Segoe UI", "Microsoft YaHei", sans-serif; ba