feat: 新增mask区域重绘功能 + 羽化贴回优化 + 发丝增强
新增功能: - 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
This commit is contained in:
@@ -159,9 +159,13 @@ body { font-family: -apple-system, "Segoe UI", "Microsoft YaHei", sans-serif; ba
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="field-label">feather_px <span class="val" id="v-fp">0</span><span class="tip" title="严格mask贴回的边缘羽化像素。0=硬边缘,>0=高斯模糊边缘平滑过渡(消除贴回接缝)">ⓘ</span></div>
|
||||
<div class="field-label">feather_px <span class="val" id="v-fp">0</span><span class="tip" title="羽化范围(像素),边缘带宽度。0=无羽化,越大羽化范围越宽">ⓘ</span></div>
|
||||
<input type="range" id="fp" min="0" max="60" step="1" value="0" oninput="syncInt('fp')">
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="field-label">feather_alpha <span class="val" id="v-fa">1.00</span><span class="tip" title="羽化强度(0~1)。0=硬切不羽化,1=最大羽化。控制边缘过渡柔和程度">ⓘ</span></div>
|
||||
<input type="range" id="fa" min="0" max="1.0" step="0.05" value="1.0" oninput="syncFloat('fa')">
|
||||
</div>
|
||||
<div class="readonly-info">
|
||||
<b style="color:#888">webui端固定:</b><br>
|
||||
<code>cfg=7</code> <code>steps=20</code> <code>sampler=DPM++ 2M Karras</code>
|
||||
@@ -182,7 +186,7 @@ body { font-family: -apple-system, "Segoe UI", "Microsoft YaHei", sans-serif; ba
|
||||
|
||||
<!-- 中间发型选择 -->
|
||||
<div class="mid-panel">
|
||||
<h3>③ 选发型</h3>
|
||||
<h3>③ 选发型 <span id="count-info" style="font-size:10px;color:#888;font-weight:normal"></span></h3>
|
||||
<div class="hairstyle-grid" id="hairstyle-grid"><div style="color:#666;font-size:11px">加载中...</div></div>
|
||||
<div style="font-size:11px;color:#4ecca3;margin-top:8px;text-align:center" id="sel-info">未选择</div>
|
||||
<button class="btn-tool" style="margin-top:8px;width:100%" onclick="loadStatus()">↻ 刷新</button>
|
||||
@@ -225,14 +229,6 @@ body { font-family: -apple-system, "Segoe UI", "Microsoft YaHei", sans-serif; ba
|
||||
<script>
|
||||
(function(){
|
||||
const $=id=>document.getElementById(id);
|
||||
const HAIRSTYLES = [
|
||||
{face:"圆",name:"圆-心形"},{face:"圆",name:"圆-椭圆"},{face:"圆",name:"圆-波浪"},{face:"圆",name:"圆-直线"},{face:"圆",name:"圆-花瓣"},
|
||||
{face:"心形",name:"心形-心形"},{face:"心形",name:"心形-椭圆"},{face:"心形",name:"心形-波浪"},{face:"心形",name:"心形-直线"},{face:"心形",name:"心形-花瓣"},
|
||||
{face:"方脸",name:"方脸-心形"},{face:"方脸",name:"方脸-椭圆"},{face:"方脸",name:"方脸-波浪"},{face:"方脸",name:"方脸-直线"},{face:"方脸",name:"方脸-花瓣"},
|
||||
{face:"椭圆",name:"椭圆-心形"},{face:"椭圆",name:"椭圆-椭圆"},{face:"椭圆",name:"椭圆-波浪"},{face:"椭圆",name:"椭圆-直线"},{face:"椭圆",name:"椭圆-花瓣"},
|
||||
{face:"菱形",name:"菱形-心"},{face:"菱形",name:"菱形-椭圆"},{face:"菱形",name:"菱形-波浪"},{face:"菱形",name:"菱形-直线"},{face:"菱形",name:"菱形-花瓣"},
|
||||
{face:"长",name:"长-心形"},{face:"长",name:"长 -椭圆"},{face:"长",name:"长 -波浪"},{face:"长",name:"长 -直线"},{face:"长",name:"长 -花瓣"}
|
||||
];
|
||||
|
||||
let readySet=new Set(), selectedHair=null, imgB64Orig=null;
|
||||
// 画板状态
|
||||
@@ -240,6 +236,11 @@ body { font-family: -apple-system, "Segoe UI", "Microsoft YaHei", sans-serif; ba
|
||||
const imgCtx=imgCanvas.getContext("2d"),maskCtx=maskCanvas.getContext("2d",{willReadFrequently:true});
|
||||
let mode="brush",brushSize=25,drawing=false,lastX=0,lastY=0;
|
||||
|
||||
// 本次训练的发型(昨天6个 + 今天hair_flow)
|
||||
const HAIRSTYLES = [
|
||||
{name:"hair_flow"},{name:"圆-心形"},{name:"圆-椭圆"},{name:"圆-波浪"},
|
||||
{name:"圆-直线"},{name:"圆-花瓣"},{name:"心形-心形"},{name:"心形-椭圆"}
|
||||
];
|
||||
async function loadStatus(){
|
||||
try{
|
||||
const r=await fetch("/api/hairstyles");const d=await r.json();
|
||||
@@ -254,7 +255,7 @@ body { font-family: -apple-system, "Segoe UI", "Microsoft YaHei", sans-serif; ba
|
||||
const ready=readySet.has(h.name);
|
||||
const it=document.createElement("div");
|
||||
it.className="hairstyle-item"+(ready?" ready":" pending")+(h.name===selectedHair?" selected":"");
|
||||
it.innerHTML=`<img loading="lazy" src="/train_src/${encodeURIComponent(h.name)}" onerror="this.style.objectFit='contain';this.src='data:image/svg+xml;utf8,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%22100%22 height=%22100%22><text x=%2250%22 y=%2255%22 text-anchor=%22middle%22 fill=%22%23666%22 font-size=%2210%22>训练中</text></svg>'"><div class="name">${h.name}</div>`;
|
||||
it.innerHTML=`<img loading="lazy" src="/train_src/${encodeURIComponent(h.name)}" onerror="this.style.objectFit='contain';this.src='data:image/svg+xml;utf8,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%22100%22 height=%22100%22><text x=%2250%22 y=%2255%22 text-anchor=%22middle%22 fill=%22%23666%22 font-size=%2210%22>无图</text></svg>'"><div class="name">${h.name}</div>`;
|
||||
if(ready) it.onclick=()=>{
|
||||
selectedHair=h.name;
|
||||
$("sel-info").textContent="已选: "+h.name;
|
||||
@@ -366,6 +367,7 @@ body { font-family: -apple-system, "Segoe UI", "Microsoft YaHei", sans-serif; ba
|
||||
is_hr:$("sw-is_hr").classList.contains("on"),
|
||||
dilate_kernel:[parseInt($("dk0").value),parseInt($("dk1").value)],
|
||||
feather_px:parseInt($("fp").value),
|
||||
feather_alpha:parseFloat($("fa").value),
|
||||
denoising_strength:parseFloat($("den").value),
|
||||
enhance:$("sw-enhance").classList.contains("on"),
|
||||
enhance_denoising:parseFloat($("ed").value),
|
||||
|
||||
Reference in New Issue
Block a user