436 lines
18 KiB
Rust
436 lines
18 KiB
Rust
//! AI Model Face & Hair Detail Fix Template
|
||
//!
|
||
//! This template performs detailed face and hair enhancement for AI model photos.
|
||
//! It uses segmentation masks and local inpainting to improve facial features and hair quality.
|
||
//!
|
||
//! Features:
|
||
//! - Automatic face and hair segmentation
|
||
//! - Two-stage local inpainting (hair first, then face)
|
||
//! - Configurable denoising strength
|
||
//! - High-quality detail enhancement
|
||
|
||
use std::collections::HashMap;
|
||
use serde_json::json;
|
||
use crate::types::{
|
||
WorkflowTemplateData, TemplateMetadata, ParameterSchema, ParameterType,
|
||
ComfyUIWorkflow, ComfyUINode, NodeMeta
|
||
};
|
||
|
||
/// AI Model Face & Hair Detail Fix Template
|
||
pub static AI_MODEL_FACE_HAIR_FIX_TEMPLATE: once_cell::sync::Lazy<WorkflowTemplateData> =
|
||
once_cell::sync::Lazy::new(|| {
|
||
create_ai_model_face_hair_fix_template()
|
||
});
|
||
|
||
fn create_ai_model_face_hair_fix_template() -> WorkflowTemplateData {
|
||
let metadata = TemplateMetadata {
|
||
id: "ai-model-face-hair-fix".to_string(),
|
||
name: "AI Model Face & Hair Detail Fix".to_string(),
|
||
description: Some("Professional face and hair detail enhancement for AI model photos using segmentation and local inpainting".to_string()),
|
||
category: Some("enhancement".to_string()),
|
||
tags: Some(vec![
|
||
"face-enhancement".to_string(),
|
||
"hair-enhancement".to_string(),
|
||
"portrait".to_string(),
|
||
"ai-model".to_string(),
|
||
"detail-fix".to_string(),
|
||
"inpainting".to_string(),
|
||
]),
|
||
version: Some("1.0.0".to_string()),
|
||
author: Some("ComfyUI SDK".to_string()),
|
||
created_at: None,
|
||
updated_at: None,
|
||
};
|
||
|
||
let mut parameters = HashMap::new();
|
||
|
||
parameters.insert("input_image".to_string(), ParameterSchema {
|
||
param_type: ParameterType::String,
|
||
required: Some(true),
|
||
default: Some(json!("20250806-190606.jpg")),
|
||
description: Some("Input image filename to enhance".to_string()),
|
||
r#enum: None,
|
||
min: None,
|
||
max: None,
|
||
pattern: None,
|
||
items: None,
|
||
properties: None,
|
||
});
|
||
|
||
parameters.insert("face_prompt".to_string(), ParameterSchema {
|
||
param_type: ParameterType::String,
|
||
required: Some(true),
|
||
default: Some(json!("A girl, slim figure, oval face, beauty, Pink and greasy lips,")),
|
||
description: Some("Positive prompt for face enhancement".to_string()),
|
||
r#enum: None,
|
||
min: None,
|
||
max: None,
|
||
pattern: None,
|
||
items: None,
|
||
properties: None,
|
||
});
|
||
|
||
parameters.insert("face_denoise".to_string(), ParameterSchema {
|
||
param_type: ParameterType::String,
|
||
required: Some(true),
|
||
default: Some(json!("0.30")),
|
||
description: Some("Denoising strength for face enhancement".to_string()),
|
||
r#enum: None,
|
||
min: None,
|
||
max: None,
|
||
pattern: None,
|
||
items: None,
|
||
properties: None,
|
||
});
|
||
|
||
let workflow = create_workflow();
|
||
|
||
WorkflowTemplateData {
|
||
metadata,
|
||
workflow,
|
||
parameters,
|
||
}
|
||
}
|
||
|
||
fn create_workflow() -> ComfyUIWorkflow {
|
||
let mut workflow = HashMap::new();
|
||
|
||
// Node 6: 遮罩边缘滑条快速模糊
|
||
workflow.insert("6".to_string(), ComfyUINode {
|
||
class_type: "EG_ZZ_MHHT".to_string(),
|
||
inputs: {
|
||
let mut inputs = HashMap::new();
|
||
inputs.insert("模糊强度".to_string(), json!(3));
|
||
inputs.insert("mask".to_string(), json!(["53", 1]));
|
||
inputs
|
||
},
|
||
_meta: Some(NodeMeta {
|
||
title: Some("2🐕遮罩边缘滑条快速模糊".to_string()),
|
||
}),
|
||
});
|
||
|
||
// Node 8: Checkpoint加载器
|
||
workflow.insert("8".to_string(), ComfyUINode {
|
||
class_type: "CheckpointLoaderSimple".to_string(),
|
||
inputs: {
|
||
let mut inputs = HashMap::new();
|
||
inputs.insert("ckpt_name".to_string(), json!("juggernaut_reborn_sd1,5.safetensors"));
|
||
inputs
|
||
},
|
||
_meta: Some(NodeMeta {
|
||
title: Some("Checkpoint加载器(简易)".to_string()),
|
||
}),
|
||
});
|
||
|
||
// Node 9: CLIP文本编码 (头发)
|
||
workflow.insert("9".to_string(), ComfyUINode {
|
||
class_type: "CLIPTextEncode".to_string(),
|
||
inputs: {
|
||
let mut inputs = HashMap::new();
|
||
inputs.insert("text".to_string(), json!("Smooth long hair,Hair details, quality hair,Smooth hair"));
|
||
inputs.insert("clip".to_string(), json!(["8", 1]));
|
||
inputs
|
||
},
|
||
_meta: Some(NodeMeta {
|
||
title: Some("CLIP文本编码".to_string()),
|
||
}),
|
||
});
|
||
|
||
// Node 10: CLIP文本编码 (负面头发)
|
||
workflow.insert("10".to_string(), ComfyUINode {
|
||
class_type: "CLIPTextEncode".to_string(),
|
||
inputs: {
|
||
let mut inputs = HashMap::new();
|
||
inputs.insert("text".to_string(), json!("Frizzy hair, dry hair, split ends,bad quality, poor quality, doll, disfigured, jpg, toy, bad anatomy, missing limbs, missing fingers, 3d, cgi"));
|
||
inputs.insert("clip".to_string(), json!(["8", 1]));
|
||
inputs
|
||
},
|
||
_meta: Some(NodeMeta {
|
||
title: Some("CLIP文本编码".to_string()),
|
||
}),
|
||
});
|
||
|
||
// Node 13: CLIP文本编码 (脸部)
|
||
workflow.insert("13".to_string(), ComfyUINode {
|
||
class_type: "CLIPTextEncode".to_string(),
|
||
inputs: {
|
||
let mut inputs = HashMap::new();
|
||
inputs.insert("text".to_string(), json!(["59", 0]));
|
||
inputs.insert("clip".to_string(), json!(["8", 1]));
|
||
inputs
|
||
},
|
||
_meta: Some(NodeMeta {
|
||
title: Some("CLIP文本编码".to_string()),
|
||
}),
|
||
});
|
||
|
||
// Node 14: CLIP文本编码 (负面脸部)
|
||
workflow.insert("14".to_string(), ComfyUINode {
|
||
class_type: "CLIPTextEncode".to_string(),
|
||
inputs: {
|
||
let mut inputs = HashMap::new();
|
||
inputs.insert("text".to_string(), json!("(NSFW:1.2),(worst quality:1.2),(low quality:1.2),(normal quality:1.2),low resolution,watermark,"));
|
||
inputs.insert("clip".to_string(), json!(["8", 1]));
|
||
inputs
|
||
},
|
||
_meta: Some(NodeMeta {
|
||
title: Some("CLIP文本编码".to_string()),
|
||
}),
|
||
});
|
||
|
||
// Node 22: Segformer Ultra V2 (头发)
|
||
workflow.insert("22".to_string(), ComfyUINode {
|
||
class_type: "LayerMask: SegformerUltraV2".to_string(),
|
||
inputs: {
|
||
let mut inputs = HashMap::new();
|
||
inputs.insert("detail_method".to_string(), json!("VITMatte(local)"));
|
||
inputs.insert("detail_erode".to_string(), json!(44));
|
||
inputs.insert("detail_dilate".to_string(), json!(6));
|
||
inputs.insert("black_point".to_string(), json!(0.030000000000000006));
|
||
inputs.insert("white_point".to_string(), json!(0.99));
|
||
inputs.insert("process_detail".to_string(), json!(true));
|
||
inputs.insert("device".to_string(), json!("cuda"));
|
||
inputs.insert("max_megapixels".to_string(), json!(2));
|
||
inputs.insert("image".to_string(), json!(["30", 0]));
|
||
inputs.insert("segformer_pipeline".to_string(), json!(["28", 0]));
|
||
inputs
|
||
},
|
||
_meta: Some(NodeMeta {
|
||
title: Some("LayerMask: Segformer Ultra V2".to_string()),
|
||
}),
|
||
});
|
||
|
||
// Node 25: 局部重绘采样器 (头发)
|
||
workflow.insert("25".to_string(), ComfyUINode {
|
||
class_type: "EG_CYQ_JB".to_string(),
|
||
inputs: {
|
||
let mut inputs = HashMap::new();
|
||
inputs.insert("seed".to_string(), json!(646617836820462i64));
|
||
inputs.insert("steps".to_string(), json!(30));
|
||
inputs.insert("cfg".to_string(), json!(4.5200000000000005));
|
||
inputs.insert("sampler_name".to_string(), json!("dpmpp_2s_ancestral"));
|
||
inputs.insert("scheduler".to_string(), json!("karras"));
|
||
inputs.insert("denoise".to_string(), json!(0.4));
|
||
inputs.insert("重绘模式".to_string(), json!("原图"));
|
||
inputs.insert("遮罩延展".to_string(), json!(10));
|
||
inputs.insert("仅局部重绘".to_string(), json!(true));
|
||
inputs.insert("局部重绘大小".to_string(), json!(768));
|
||
inputs.insert("重绘区域扩展".to_string(), json!(50));
|
||
inputs.insert("遮罩羽化".to_string(), json!(5));
|
||
inputs.insert("TEXT".to_string(), json!("2🐕出品,必出精品"));
|
||
inputs.insert("model".to_string(), json!(["8", 0]));
|
||
inputs.insert("image".to_string(), json!(["30", 0]));
|
||
inputs.insert("vae".to_string(), json!(["8", 2]));
|
||
inputs.insert("mask".to_string(), json!(["47", 0]));
|
||
inputs.insert("positive".to_string(), json!(["9", 0]));
|
||
inputs.insert("negative".to_string(), json!(["10", 0]));
|
||
inputs
|
||
},
|
||
_meta: Some(NodeMeta {
|
||
title: Some("2🐕局部重绘采样器".to_string()),
|
||
}),
|
||
});
|
||
|
||
// Node 26: 局部重绘采样器 (脸部)
|
||
workflow.insert("26".to_string(), ComfyUINode {
|
||
class_type: "EG_CYQ_JB".to_string(),
|
||
inputs: {
|
||
let mut inputs = HashMap::new();
|
||
inputs.insert("seed".to_string(), json!(969968724502727i64));
|
||
inputs.insert("steps".to_string(), json!(30));
|
||
inputs.insert("cfg".to_string(), json!(4.5200000000000005));
|
||
inputs.insert("sampler_name".to_string(), json!("dpmpp_2s_ancestral"));
|
||
inputs.insert("scheduler".to_string(), json!("karras"));
|
||
inputs.insert("denoise".to_string(), json!(["71", 0]));
|
||
inputs.insert("重绘模式".to_string(), json!("原图"));
|
||
inputs.insert("遮罩延展".to_string(), json!(10));
|
||
inputs.insert("仅局部重绘".to_string(), json!(true));
|
||
inputs.insert("局部重绘大小".to_string(), json!(768));
|
||
inputs.insert("重绘区域扩展".to_string(), json!(50));
|
||
inputs.insert("遮罩羽化".to_string(), json!(5));
|
||
inputs.insert("TEXT".to_string(), json!("2🐕出品,必出精品"));
|
||
inputs.insert("model".to_string(), json!(["8", 0]));
|
||
inputs.insert("image".to_string(), json!(["25", 1]));
|
||
inputs.insert("vae".to_string(), json!(["8", 2]));
|
||
inputs.insert("mask".to_string(), json!(["6", 0]));
|
||
inputs.insert("positive".to_string(), json!(["13", 0]));
|
||
inputs.insert("negative".to_string(), json!(["14", 0]));
|
||
inputs
|
||
},
|
||
_meta: Some(NodeMeta {
|
||
title: Some("2🐕局部重绘采样器".to_string()),
|
||
}),
|
||
});
|
||
|
||
// Node 28: Segformer Clothes Pipeline (头发)
|
||
workflow.insert("28".to_string(), ComfyUINode {
|
||
class_type: "LayerMask: SegformerClothesPipelineLoader".to_string(),
|
||
inputs: {
|
||
let mut inputs = HashMap::new();
|
||
inputs.insert("model".to_string(), json!("segformer_b3_clothes"));
|
||
inputs.insert("face".to_string(), json!(false));
|
||
inputs.insert("hair".to_string(), json!(true));
|
||
inputs.insert("hat".to_string(), json!(false));
|
||
inputs.insert("sunglass".to_string(), json!(false));
|
||
inputs.insert("left_arm".to_string(), json!(false));
|
||
inputs.insert("right_arm".to_string(), json!(false));
|
||
inputs.insert("left_leg".to_string(), json!(false));
|
||
inputs.insert("right_leg".to_string(), json!(false));
|
||
inputs.insert("left_shoe".to_string(), json!(false));
|
||
inputs.insert("right_shoe".to_string(), json!(false));
|
||
inputs.insert("upper_clothes".to_string(), json!(false));
|
||
inputs.insert("skirt".to_string(), json!(false));
|
||
inputs.insert("pants".to_string(), json!(false));
|
||
inputs.insert("dress".to_string(), json!(false));
|
||
inputs.insert("belt".to_string(), json!(false));
|
||
inputs.insert("bag".to_string(), json!(false));
|
||
inputs.insert("scarf".to_string(), json!(false));
|
||
inputs
|
||
},
|
||
_meta: Some(NodeMeta {
|
||
title: Some("LayerMask: Segformer Clothes Pipeline".to_string()),
|
||
}),
|
||
});
|
||
|
||
// Node 30: 加载图像
|
||
workflow.insert("30".to_string(), ComfyUINode {
|
||
class_type: "LoadImage".to_string(),
|
||
inputs: {
|
||
let mut inputs = HashMap::new();
|
||
inputs.insert("image".to_string(), json!("{{input_image}}"));
|
||
inputs
|
||
},
|
||
_meta: Some(NodeMeta {
|
||
title: Some("加载图像".to_string()),
|
||
}),
|
||
});
|
||
|
||
// Node 44: Mask Fill Holes
|
||
workflow.insert("44".to_string(), ComfyUINode {
|
||
class_type: "Mask Fill Holes".to_string(),
|
||
inputs: {
|
||
let mut inputs = HashMap::new();
|
||
inputs.insert("masks".to_string(), json!(["22", 1]));
|
||
inputs
|
||
},
|
||
_meta: Some(NodeMeta {
|
||
title: Some("Mask Fill Holes".to_string()),
|
||
}),
|
||
});
|
||
|
||
// Node 47: Grow Mask With Blur
|
||
workflow.insert("47".to_string(), ComfyUINode {
|
||
class_type: "GrowMaskWithBlur".to_string(),
|
||
inputs: {
|
||
let mut inputs = HashMap::new();
|
||
inputs.insert("expand".to_string(), json!(5));
|
||
inputs.insert("incremental_expandrate".to_string(), json!(5));
|
||
inputs.insert("tapered_corners".to_string(), json!(true));
|
||
inputs.insert("flip_input".to_string(), json!(false));
|
||
inputs.insert("blur_radius".to_string(), json!(9.4));
|
||
inputs.insert("lerp_alpha".to_string(), json!(0.9900000000000002));
|
||
inputs.insert("decay_factor".to_string(), json!(1));
|
||
inputs.insert("fill_holes".to_string(), json!(false));
|
||
inputs.insert("mask".to_string(), json!(["44", 0]));
|
||
inputs
|
||
},
|
||
_meta: Some(NodeMeta {
|
||
title: Some("Grow Mask With Blur".to_string()),
|
||
}),
|
||
});
|
||
|
||
// Node 52: Segformer Clothes Pipeline (脸部)
|
||
workflow.insert("52".to_string(), ComfyUINode {
|
||
class_type: "LayerMask: SegformerClothesPipelineLoader".to_string(),
|
||
inputs: {
|
||
let mut inputs = HashMap::new();
|
||
inputs.insert("model".to_string(), json!("segformer_b3_clothes"));
|
||
inputs.insert("face".to_string(), json!(true));
|
||
inputs.insert("hair".to_string(), json!(false));
|
||
inputs.insert("hat".to_string(), json!(false));
|
||
inputs.insert("sunglass".to_string(), json!(true));
|
||
inputs.insert("left_arm".to_string(), json!(false));
|
||
inputs.insert("right_arm".to_string(), json!(false));
|
||
inputs.insert("left_leg".to_string(), json!(false));
|
||
inputs.insert("right_leg".to_string(), json!(false));
|
||
inputs.insert("left_shoe".to_string(), json!(false));
|
||
inputs.insert("right_shoe".to_string(), json!(false));
|
||
inputs.insert("upper_clothes".to_string(), json!(false));
|
||
inputs.insert("skirt".to_string(), json!(false));
|
||
inputs.insert("pants".to_string(), json!(false));
|
||
inputs.insert("dress".to_string(), json!(false));
|
||
inputs.insert("belt".to_string(), json!(false));
|
||
inputs.insert("bag".to_string(), json!(false));
|
||
inputs.insert("scarf".to_string(), json!(false));
|
||
inputs
|
||
},
|
||
_meta: Some(NodeMeta {
|
||
title: Some("LayerMask: Segformer Clothes Pipeline".to_string()),
|
||
}),
|
||
});
|
||
|
||
// Node 53: Segformer Ultra V2 (脸部)
|
||
workflow.insert("53".to_string(), ComfyUINode {
|
||
class_type: "LayerMask: SegformerUltraV2".to_string(),
|
||
inputs: {
|
||
let mut inputs = HashMap::new();
|
||
inputs.insert("detail_method".to_string(), json!("VITMatte(local)"));
|
||
inputs.insert("detail_erode".to_string(), json!(6));
|
||
inputs.insert("detail_dilate".to_string(), json!(4));
|
||
inputs.insert("black_point".to_string(), json!(0.01));
|
||
inputs.insert("white_point".to_string(), json!(0.99));
|
||
inputs.insert("process_detail".to_string(), json!(false));
|
||
inputs.insert("device".to_string(), json!("cuda"));
|
||
inputs.insert("max_megapixels".to_string(), json!(2));
|
||
inputs.insert("image".to_string(), json!(["30", 0]));
|
||
inputs.insert("segformer_pipeline".to_string(), json!(["52", 0]));
|
||
inputs
|
||
},
|
||
_meta: Some(NodeMeta {
|
||
title: Some("LayerMask: Segformer Ultra V2".to_string()),
|
||
}),
|
||
});
|
||
|
||
// Node 58: 保存图像
|
||
workflow.insert("58".to_string(), ComfyUINode {
|
||
class_type: "SaveImage".to_string(),
|
||
inputs: {
|
||
let mut inputs = HashMap::new();
|
||
inputs.insert("filename_prefix".to_string(), json!("ComfyUI"));
|
||
inputs.insert("images".to_string(), json!(["26", 1]));
|
||
inputs
|
||
},
|
||
_meta: Some(NodeMeta {
|
||
title: Some("保存图像".to_string()),
|
||
}),
|
||
});
|
||
|
||
// Node 59: String (脸部提示词)
|
||
workflow.insert("59".to_string(), ComfyUINode {
|
||
class_type: "String".to_string(),
|
||
inputs: {
|
||
let mut inputs = HashMap::new();
|
||
inputs.insert("String".to_string(), json!("A girl, slim figure, oval face, beauty, Pink and greasy lips,{{face_prompt}}"));
|
||
inputs
|
||
},
|
||
_meta: Some(NodeMeta {
|
||
title: Some("String".to_string()),
|
||
}),
|
||
});
|
||
|
||
// Node 71: Float (脸部去噪强度)
|
||
workflow.insert("71".to_string(), ComfyUINode {
|
||
class_type: "Float".to_string(),
|
||
inputs: {
|
||
let mut inputs = HashMap::new();
|
||
inputs.insert("Number".to_string(), json!("{{face_denoise}}"));
|
||
inputs
|
||
},
|
||
_meta: Some(NodeMeta {
|
||
title: Some("Float".to_string()),
|
||
}),
|
||
});
|
||
|
||
workflow
|
||
}
|