初始化:换发型/换发色/训练发型服务
包含: - hair_service_sd: 主服务(换发型/换发色/生发,端口8801) - photo_service: LoRA调度+训练(端口32678) - hair_grow_service: 调试测试页(端口8888,含4个测试页) - 批量训练脚本(batch_train_hairstyles.py) - 发际线mask自动识别(hairline_mask.py,4种方案) - 手绘mask换发型(hair_swap_manual.py) - 文档:README.md + LARGE_FILES.md + docs/ 大文件(模型权重200G、训练数据123G)已排除,见 LARGE_FILES.md OSS/COS密钥已脱敏为环境变量,原文件备份在本地
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""为测试图自动生成发际线/额头区域的 mask(模拟用户手画遮罩)。
|
||||
用 RetinaFace + 1k关键点定位额头,生成该区域的白色 mask。
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
HAIR_SERVICE_DIR = "/home/xsl/change_hair/project/hair_service_sd"
|
||||
sys.path.insert(0, HAIR_SERVICE_DIR)
|
||||
os.environ.setdefault("HF_HUB_OFFLINE", "1")
|
||||
|
||||
import torch
|
||||
from utils.landmark_processor import get_max_rect
|
||||
|
||||
|
||||
def gen_hairline_mask(img_path, mask_path):
|
||||
"""用关键点定位额头,生成发际线区域 mask"""
|
||||
from models.detector import RetinaFaceDetector
|
||||
from utils.MomocvFaceAlignment1K import MomocvFaceAlignment1K
|
||||
|
||||
img = cv2.imread(img_path)
|
||||
if img is None:
|
||||
print(f"❌ 读取失败: {img_path}")
|
||||
return False
|
||||
|
||||
print(f"原图: {img_path} shape={img.shape}")
|
||||
gpu_id = 0
|
||||
|
||||
# 1. RetinaFace 检测
|
||||
detector = RetinaFaceDetector(gpu_id=gpu_id)
|
||||
dets, landms = detector.forward(img)
|
||||
if len(dets) == 0:
|
||||
print("❌ 未检测到人脸")
|
||||
return False
|
||||
max_idx = get_max_rect(dets)
|
||||
det = dets[max_idx]
|
||||
print(f"检测到人脸: {det}")
|
||||
|
||||
# 2. 1k 关键点
|
||||
aligner = MomocvFaceAlignment1K(gpu_id=gpu_id)
|
||||
landmarks_1k = aligner.stable_forward(img.copy(), [det])
|
||||
if landmarks_1k is None or len(landmarks_1k) == 0:
|
||||
print("❌ 关键点检测失败")
|
||||
return False
|
||||
pts = landmarks_1k[0] # (1000, 2)
|
||||
print(f"1k关键点 shape: {pts.shape}")
|
||||
|
||||
# 3. 构造发际线/额头 mask
|
||||
# 1k点的前312点是整脸外轮廓(含发际线)
|
||||
# 额头区:用眉骨以上的外轮廓点 + 发际线点构造凸包
|
||||
h, w = img.shape[:2]
|
||||
mask = np.zeros((h, w), dtype=np.uint8)
|
||||
|
||||
# 取脸部上半部分轮廓点(发际线到太阳穴)构造额头区域
|
||||
# 索引 0-311 是脸部外轮廓,取上半部分(额头/太阳穴)
|
||||
forehead_pts = []
|
||||
# 发际线区域:取轮廓的上半段 + 稍微往下扩展
|
||||
for i in range(0, 312, 3): # 隔点采样,减少密度
|
||||
pt = pts[i]
|
||||
# 只取上半脸(y < 图像中线偏上)
|
||||
if pt[1] < h * 0.55:
|
||||
forehead_pts.append(pt)
|
||||
# 加入额头中心区域一些点,确保覆盖额头
|
||||
forehead_pts = np.array(forehead_pts, dtype=np.int32)
|
||||
|
||||
if len(forehead_pts) < 3:
|
||||
print("❌ 额头点不足")
|
||||
return False
|
||||
|
||||
# 凸包填充
|
||||
hull = cv2.convexHull(forehead_pts)
|
||||
cv2.fillConvexPoly(mask, hull, 255)
|
||||
|
||||
# 膨胀让区域稍大一点(模拟用户涂抹)
|
||||
mask = cv2.dilate(mask, np.ones((5, 5), np.uint8), iterations=2)
|
||||
|
||||
# 统计 mask 区域
|
||||
nonzero = cv2.countNonZero(mask)
|
||||
print(f"mask 生成完成: 白色区域={nonzero}px ({nonzero/(h*w)*100:.1f}%)")
|
||||
cv2.imwrite(mask_path, mask)
|
||||
print(f"✅ mask 已保存: {mask_path}")
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
img_path = sys.argv[1] if len(sys.argv) > 1 else \
|
||||
"/home/xsl/change_hair/project/data/userImage/8488902485_20250630055547.jpg"
|
||||
mask_path = sys.argv[2] if len(sys.argv) > 2 else \
|
||||
"/home/xsl/change_hair/project/logs/hairgrow_test_mask.png"
|
||||
gen_hairline_mask(img_path, mask_path)
|
||||
Reference in New Issue
Block a user