Compare commits
3
Commits
b4714cedf3
...
f509fe99b4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f509fe99b4 | ||
|
|
a1d458eb20 | ||
|
|
92e628b0d5 |
@@ -999,6 +999,8 @@ async def face_features(
|
|||||||
`female`:1=ellipse,2=flower,3=heart,4=straight,5=wave;`male`:1=ellipse,2=inverse_arc,3=m,4=straight。
|
`female`:1=ellipse,2=flower,3=heart,4=straight,5=wave;`male`:1=ellipse,2=inverse_arc,3=m,4=straight。
|
||||||
- 可选 `use_mask` / `prompt`:同接口2 的生发控制参数。
|
- 可选 `use_mask` / `prompt`:同接口2 的生发控制参数。
|
||||||
注:生发黑模板固定取 `hairline_texture_black/`(middle 档),即三档叠图分别用各自贴图、但生发目标固定 middle。
|
注:生发黑模板固定取 `hairline_texture_black/`(middle 档),即三档叠图分别用各自贴图、但生发目标固定 middle。
|
||||||
|
- 可选 `generate_grow_image`(默认 `true`):是否生成生发效果图(ComfyUI 生发,全流程最耗时)。
|
||||||
|
`false` 时跳过生发,各发型 `grown_image_*` 恒为 `null`,仅返回三档发际线叠图与中心点,大幅降低耗时。
|
||||||
|
|
||||||
**返回说明**:
|
**返回说明**:
|
||||||
|
|
||||||
@@ -1077,6 +1079,7 @@ async def hairline_generate(
|
|||||||
hair_style: Optional[str] = Form(default=None, description="发型序号逗号分隔(必填,如 1,2,3)。female:1-5 male:1-4"),
|
hair_style: Optional[str] = Form(default=None, description="发型序号逗号分隔(必填,如 1,2,3)。female:1-5 male:1-4"),
|
||||||
use_mask: bool = Form(default=True, description="生发是否启用 inpaint 遮罩(同接口2,测试对比用)"),
|
use_mask: bool = Form(default=True, description="生发是否启用 inpaint 遮罩(同接口2,测试对比用)"),
|
||||||
prompt: str = Form(default="填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜", description="ComfyUI 提示词(同接口2),会替换工作流节点60的文本"),
|
prompt: str = Form(default="填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜", description="ComfyUI 提示词(同接口2),会替换工作流节点60的文本"),
|
||||||
|
generate_grow_image: bool = Form(default=True, description="是否生成生发效果图(ComfyUI 生发,最耗时)。默认 true 出图;false 时跳过生发,各发型 grown_image 恒为 null,仅返回三档发际线叠图与中心点"),
|
||||||
):
|
):
|
||||||
if gender not in ("male", "female"):
|
if gender not in ("male", "female"):
|
||||||
return err(1004, "gender 必填且只能为 male / female")
|
return err(1004, "gender 必填且只能为 male / female")
|
||||||
@@ -1100,7 +1103,8 @@ async def hairline_generate(
|
|||||||
from hairline.service import generate_hairline_pngs
|
from hairline.service import generate_hairline_pngs
|
||||||
|
|
||||||
res = await run_in_threadpool(
|
res = await run_in_threadpool(
|
||||||
generate_hairline_pngs, image, gender, hair_styles, use_mask, prompt)
|
generate_hairline_pngs, image, gender, hair_styles, use_mask, prompt,
|
||||||
|
generate_grow_image=generate_grow_image)
|
||||||
if res is None:
|
if res is None:
|
||||||
return err(1001, "无法识别人像")
|
return err(1001, "无法识别人像")
|
||||||
|
|
||||||
|
|||||||
+9
-57
@@ -20,7 +20,6 @@
|
|||||||
| 3 B 端生发 | POST | `/api/v1/hair/grow-b` |
|
| 3 B 端生发 | POST | `/api/v1/hair/grow-b` |
|
||||||
| 4 用户特征 | POST | `/api/v1/face/features` |
|
| 4 用户特征 | POST | `/api/v1/face/features` |
|
||||||
| 5 发际线 PNG 生成 | POST | `/api/v1/hairline/generate` |
|
| 5 发际线 PNG 生成 | POST | `/api/v1/hairline/generate` |
|
||||||
| 7 C 端生发 v2 | POST | `/api/v1/hair/grow-v2` |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -87,6 +86,7 @@
|
|||||||
| 1006 | 文件超出大小限制 | 单文件超过 1 MB |
|
| 1006 | 文件超出大小限制 | 单文件超过 1 MB |
|
||||||
| 1007 | 图片参数错误 | file / url / base64 未传,或同时传了多个(三者严格互斥) |
|
| 1007 | 图片参数错误 | file / url / base64 未传,或同时传了多个(三者严格互斥) |
|
||||||
| 1008 | 图片格式不支持 | 非 JPG / PNG |
|
| 1008 | 图片格式不支持 | 非 JPG / PNG |
|
||||||
|
| 1009 | 未授权 | 缺少或错误的 `X-Internal-Token`(`/api/*` 路径鉴权) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -109,6 +109,8 @@
|
|||||||
| four_courts | object | 四庭数据,见下表 |
|
| four_courts | object | 四庭数据,见下表 |
|
||||||
| seven_eyes | object | 七眼数据,见下表 |
|
| seven_eyes | object | 七眼数据,见下表 |
|
||||||
| landmarks | object | 关键分界点坐标(头顶 / 发际线 / 眉心 / 鼻翼下缘 / 下巴尖),原图像素坐标 |
|
| landmarks | object | 关键分界点坐标(头顶 / 发际线 / 眉心 / 鼻翼下缘 / 下巴尖),原图像素坐标 |
|
||||||
|
| left_position | object | MediaPipe 21 号关键点坐标(左脸定位点),原图像素:`{ "x": int, "y": int }` |
|
||||||
|
| right_position | object | MediaPipe 251 号关键点坐标(右脸定位点,与 21 号镜像),原图像素:`{ "x": int, "y": int }` |
|
||||||
|
|
||||||
`four_courts`(四庭,自上而下):
|
`four_courts`(四庭,自上而下):
|
||||||
|
|
||||||
@@ -211,6 +213,8 @@
|
|||||||
| four_courts | object | 三庭数据(上/中/下庭,各含 cm 与 ratio;**无顶庭**) |
|
| four_courts | object | 三庭数据(上/中/下庭,各含 cm 与 ratio;**无顶庭**) |
|
||||||
| seven_eyes | object | 七眼数据(眼宽/脸宽/两眼间距 cm + 占比 ratios + **eye2~eye6** 共 5 段宽度) |
|
| seven_eyes | object | 七眼数据(眼宽/脸宽/两眼间距 cm + 占比 ratios + **eye2~eye6** 共 5 段宽度) |
|
||||||
| landmarks | object | 四个关键点像素坐标(发际线/眉心/鼻翼下缘/下巴尖) |
|
| landmarks | object | 四个关键点像素坐标(发际线/眉心/鼻翼下缘/下巴尖) |
|
||||||
|
| left_position | object | MediaPipe 21 号关键点坐标(左脸定位点),原图像素:`{ "x": int, "y": int }` |
|
||||||
|
| right_position | object | MediaPipe 251 号关键点坐标(右脸定位点,与 21 号镜像),原图像素:`{ "x": int, "y": int }` |
|
||||||
|
|
||||||
> 接口6 是**三庭五眼**:`four_courts`/`landmarks` 不含顶庭与头顶点(无 `top_court_cm`/`hair_top`);`seven_eyes` 只含 **eye2~eye6**(左脸颊/左眼/两眼间距/右眼/右脸颊,5 段),**无 eye1/eye7**(耳外段需头发轮廓端线,仅接口1 有)。
|
> 接口6 是**三庭五眼**:`four_courts`/`landmarks` 不含顶庭与头顶点(无 `top_court_cm`/`hair_top`);`seven_eyes` 只含 **eye2~eye6**(左脸颊/左眼/两眼间距/右眼/右脸颊,5 段),**无 eye1/eye7**(耳外段需头发轮廓端线,仅接口1 有)。
|
||||||
|
|
||||||
@@ -405,6 +409,7 @@
|
|||||||
| hair_style | string | **是** | 发型序号,**逗号分隔多选**(如 `1,2,3`),决定返回哪些发际线类型。female:1=ellipse, 2=flower, 3=heart, 4=straight, 5=wave;male:1=ellipse, 2=inverse_arc, 3=m, 4=straight。缺失/越界/非法返回 `1007` |
|
| hair_style | string | **是** | 发型序号,**逗号分隔多选**(如 `1,2,3`),决定返回哪些发际线类型。female:1=ellipse, 2=flower, 3=heart, 4=straight, 5=wave;male:1=ellipse, 2=inverse_arc, 3=m, 4=straight。缺失/越界/非法返回 `1007` |
|
||||||
| use_mask | bool | 否 | 生发是否启用 inpaint 遮罩,默认 `true`。`false` 时用干净原图生成(空遮罩、不烧模板黑线),供测试对比 |
|
| use_mask | bool | 否 | 生发是否启用 inpaint 遮罩,默认 `true`。`false` 时用干净原图生成(空遮罩、不烧模板黑线),供测试对比 |
|
||||||
| prompt | string | 否 | ComfyUI 提示词,默认「补充遮罩区域的头发,加一点美颜」,会替换工作流节点 60 的文本 |
|
| prompt | string | 否 | ComfyUI 提示词,默认「补充遮罩区域的头发,加一点美颜」,会替换工作流节点 60 的文本 |
|
||||||
|
| generate_grow_image | bool | 否 | 是否生成生发效果图(ComfyUI 生发,全流程最耗时),默认 `true`。传 `false` 时跳过生发,各发型 `grown_image_*` 恒为 `null`,仅返回三档发际线叠图与中心点,可大幅降低耗时 |
|
||||||
|
|
||||||
> ⚠️ 三档叠图分别用 `hairline_texture` / `hairline_texture_high` / `hairline_texture_low` 三套同名贴图;**生发黑模板固定取自 `hairline_texture_black/`(middle 档)**,即生发目标固定压到 middle 档,每个发型仅 1 张生发图。
|
> ⚠️ 三档叠图分别用 `hairline_texture` / `hairline_texture_high` / `hairline_texture_low` 三套同名贴图;**生发黑模板固定取自 `hairline_texture_black/`(middle 档)**,即生发目标固定压到 middle 档,每个发型仅 1 张生发图。
|
||||||
|
|
||||||
@@ -426,7 +431,7 @@
|
|||||||
| image_middle_url | string | middle 档发际线曲线**透明 PNG** URL(仅曲线,透明底,**不含人物**,需叠加原图显示) |
|
| image_middle_url | string | middle 档发际线曲线**透明 PNG** URL(仅曲线,透明底,**不含人物**,需叠加原图显示) |
|
||||||
| image_high_url | string | high 档发际线曲线**透明 PNG** URL(同上,high 档曲线) |
|
| image_high_url | string | high 档发际线曲线**透明 PNG** URL(同上,high 档曲线) |
|
||||||
| image_low_url | string | low 档发际线曲线**透明 PNG** URL(同上,low 档曲线) |
|
| image_low_url | string | low 档发际线曲线**透明 PNG** URL(同上,low 档曲线) |
|
||||||
| grown_image_url | string \| null | **生发后图片** URL(ComfyUI「植发」效果图,完整人像照片,生发失败时为 `null`) |
|
| grown_image_url | string \| null | **生发后图片** URL(ComfyUI「植发」效果图,完整人像照片,生发失败或 `generate_grow_image=false` 时为 `null`) |
|
||||||
| order | int | 发型序号(= 传入的 hair_style 值) |
|
| order | int | 发型序号(= 传入的 hair_style 值) |
|
||||||
|
|
||||||
> worker 侧返回 `image_middle_base64` / `image_high_base64` / `image_low_base64` / `grown_image_base64`,网关落盘后改写为上表对应的 `*_url`。
|
> worker 侧返回 `image_middle_base64` / `image_high_base64` / `image_low_base64` / `grown_image_base64`,网关落盘后改写为上表对应的 `*_url`。
|
||||||
@@ -443,6 +448,8 @@
|
|||||||
| landmarks | object | 5 个纵向关键点像素坐标(hair_top/hairline/brow_center/nose_bottom/chin_tip),结构同接口1 |
|
| landmarks | object | 5 个纵向关键点像素坐标(hair_top/hairline/brow_center/nose_bottom/chin_tip),结构同接口1 |
|
||||||
| hairline_source | string | 发际线来源:`segmentation`(真实分割)/ `estimated`(比例估算) |
|
| hairline_source | string | 发际线来源:`segmentation`(真实分割)/ `estimated`(比例估算) |
|
||||||
| head_pose | object | 头部姿态角度(yaw/pitch/roll,单位:度) |
|
| head_pose | object | 头部姿态角度(yaw/pitch/roll,单位:度) |
|
||||||
|
| left_position | object | MediaPipe 21 号关键点坐标(左脸定位点),原图像素:`{ "x": int, "y": int }` |
|
||||||
|
| right_position | object | MediaPipe 251 号关键点坐标(右脸定位点,与 21 号镜像),原图像素:`{ "x": int, "y": int }` |
|
||||||
|
|
||||||
> `eye1`~`eye7` 为从左到右共 7 段宽度,eye1=左耳外段、eye7=右耳外段,某侧耳朵不可见时对应段为 `null`。详见接口1说明。
|
> `eye1`~`eye7` 为从左到右共 7 段宽度,eye1=左耳外段、eye7=右耳外段,某侧耳朵不可见时对应段为 `null`。详见接口1说明。
|
||||||
|
|
||||||
@@ -508,60 +515,6 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 接口 7:C 端生发 v2 接口
|
|
||||||
|
|
||||||
**说明**:功能与[接口 2](#接口-2c-端生发接口)完全一致,仅 ComfyUI 工作流不同——使用 `add_hair2.json` 替代 `add_hair.json`。
|
|
||||||
|
|
||||||
**请求**:`POST /api/v1/hair/grow-v2`
|
|
||||||
|
|
||||||
### 输入
|
|
||||||
|
|
||||||
与接口 2 完全相同。图片参数见「通用约定 → 图片传参字段」。专属参数:
|
|
||||||
|
|
||||||
| 参数 | 类型 | 必填 | 说明 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| gender | string | **是** | 性别:`male` / `female`。决定使用的发际线贴图集合 |
|
|
||||||
| hair_style | string | **是** | 发型序号,**逗号分隔多选**(如 `1,2,3`)。female:1=ellipse, 2=flower, 3=heart, 4=straight, 5=wave;male:1=ellipse, 2=inverse_arc, 3=m, 4=straight。越界/非法返回 `1007` |
|
|
||||||
| beauty_enabled | bool | 否 | 生发图是否带美颜效果,默认 false(当前阶段不生效) |
|
|
||||||
| use_mask | bool | 否 | 是否启用 inpaint 遮罩,默认 `true`。`false` 时用干净原图生成(空遮罩、不烧模板黑线) |
|
|
||||||
| prompt | string | 否 | ComfyUI 提示词,默认「补充遮罩区域的头发,加一点美颜」,会替换工作流节点 60 的文本 |
|
|
||||||
|
|
||||||
### 输出(data)
|
|
||||||
|
|
||||||
与接口 2 完全相同。`results`:发际线方案数组,**数量 = 所选发型数**。每个元素:
|
|
||||||
|
|
||||||
| 字段 | 类型 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| image_url | string | 发际线曲线**透明 PNG** URL(仅曲线,透明底,**不含人物**,需叠加原图显示) |
|
|
||||||
| grown_image_url | string | **生发后图片** URL(ComfyUI/Flux「植发 3 个月」效果图,完整人像照片) |
|
|
||||||
| hairline_type | string | 发际线类型 key |
|
|
||||||
| order | int | 排序序号 |
|
|
||||||
|
|
||||||
> ⚠️ 与接口 2 的区别:本接口使用 `add_hair2.json` 工作流(Flux-2 Klein 9b),输入/遮罩节点同为 26,
|
|
||||||
> SaveImage 输出节点为 75。
|
|
||||||
|
|
||||||
### 响应示例
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": 0,
|
|
||||||
"message": "success",
|
|
||||||
"request_id": "mock-request-id",
|
|
||||||
"data": {
|
|
||||||
"results": [
|
|
||||||
{
|
|
||||||
"image_url": "https://hair.xiangsilian.com/static/sample.jpg",
|
|
||||||
"grown_image_url": "https://hair.xiangsilian.com/static/sample.jpg",
|
|
||||||
"hairline_type": "ellipse",
|
|
||||||
"order": 1
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 汇总:输入输出一览
|
## 汇总:输入输出一览
|
||||||
|
|
||||||
| 接口 | 输入 | 主要输出 |
|
| 接口 | 输入 | 主要输出 |
|
||||||
@@ -572,7 +525,6 @@
|
|||||||
| 3 B 端生发 | 划线图片 | 最合适发际线图片 + 生发后图片 |
|
| 3 B 端生发 | 划线图片 | 最合适发际线图片 + 生发后图片 |
|
||||||
| 4 用户特征 | 用户照片 | 6 个用户特征字段(脸形/眉形/年龄/动静/性别/基因风格) |
|
| 4 用户特征 | 用户照片 | 6 个用户特征字段(脸形/眉形/年龄/动静/性别/基因风格) |
|
||||||
| 5 发际线 PNG | 用户照片 + gender + hair_style(多选) | 每个选中发型 middle/high/low 三档发际线叠图 + 生发图 + 最合适发际线面部中间点坐标 |
|
| 5 发际线 PNG | 用户照片 + gender + hair_style(多选) | 每个选中发型 middle/high/low 三档发际线叠图 + 生发图 + 最合适发际线面部中间点坐标 |
|
||||||
| 7 C 端生发 v2 | 用户照片 + gender + hair_style | 同接口2,使用 add_hair2.json 工作流 |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ RIGHT_EYE_INNER = 362 # 右眼内角
|
|||||||
RIGHT_EYE_OUTER = 263 # 右眼外角
|
RIGHT_EYE_OUTER = 263 # 右眼外角
|
||||||
LEFT_CHEEK = 234 # 左脸颧弓(脸宽左端)
|
LEFT_CHEEK = 234 # 左脸颧弓(脸宽左端)
|
||||||
RIGHT_CHEEK = 454 # 右脸颧弓(脸宽右端)
|
RIGHT_CHEEK = 454 # 右脸颧弓(脸宽右端)
|
||||||
|
LEFT_POSITION = 21 # 左脸前侧定位点(脸颊/耳前区域,与 251 镜像)
|
||||||
|
RIGHT_POSITION = 251 # 右脸前侧定位点(与 21 镜像)
|
||||||
|
|
||||||
# --- 鼻尖(solvePnP 用,可选) ---
|
# --- 鼻尖(solvePnP 用,可选) ---
|
||||||
NOSE_TIP = 1 # 鼻尖(也有用 4 的版本)
|
NOSE_TIP = 1 # 鼻尖(也有用 4 的版本)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from face_analysis.calibration import (
|
|||||||
from face_analysis.face_mesh_landmarks import (
|
from face_analysis.face_mesh_landmarks import (
|
||||||
GLABELLA_9, GLABELLA_151, NOSE_BOTTOM, CHIN_TIP,
|
GLABELLA_9, GLABELLA_151, NOSE_BOTTOM, CHIN_TIP,
|
||||||
LEFT_EYE_OUTER, LEFT_EYE_INNER, RIGHT_EYE_INNER, RIGHT_EYE_OUTER,
|
LEFT_EYE_OUTER, LEFT_EYE_INNER, RIGHT_EYE_INNER, RIGHT_EYE_OUTER,
|
||||||
LEFT_CHEEK, RIGHT_CHEEK,
|
LEFT_CHEEK, RIGHT_CHEEK, LEFT_POSITION, RIGHT_POSITION,
|
||||||
)
|
)
|
||||||
from face_analysis.hair_segmenter import locate_hairline_by_segmentation
|
from face_analysis.hair_segmenter import locate_hairline_by_segmentation
|
||||||
|
|
||||||
@@ -147,12 +147,17 @@ def measure_seven_eyes(landmarks, image_width, image_height):
|
|||||||
class MeasureResult:
|
class MeasureResult:
|
||||||
"""测量结果,提供 to_response() 输出与接口文档同构的 data 字段。"""
|
"""测量结果,提供 to_response() 输出与接口文档同构的 data 字段。"""
|
||||||
|
|
||||||
def __init__(self, vertical, eyes, px_per_cm, hairline_source, head_pose):
|
def __init__(self, vertical, eyes, px_per_cm, hairline_source, head_pose,
|
||||||
|
landmarks=None, image_width=None, image_height=None):
|
||||||
self.vertical = vertical
|
self.vertical = vertical
|
||||||
self.eyes = eyes
|
self.eyes = eyes
|
||||||
self.px_per_cm = px_per_cm
|
self.px_per_cm = px_per_cm
|
||||||
self.hairline_source = hairline_source
|
self.hairline_source = hairline_source
|
||||||
self.head_pose = head_pose # (yaw, pitch, roll) 或 None
|
self.head_pose = head_pose # (yaw, pitch, roll) 或 None
|
||||||
|
# 原始 mediapipe 点集 + 图像尺寸,供 to_response 输出 21/251 号定位点
|
||||||
|
self.landmarks = landmarks
|
||||||
|
self.w = image_width
|
||||||
|
self.h = image_height
|
||||||
|
|
||||||
# 各庭厘米
|
# 各庭厘米
|
||||||
self.top_cm = vertical["top_court_px"] / px_per_cm
|
self.top_cm = vertical["top_court_px"] / px_per_cm
|
||||||
@@ -207,6 +212,17 @@ class MeasureResult:
|
|||||||
},
|
},
|
||||||
"hairline_source": self.hairline_source,
|
"hairline_source": self.hairline_source,
|
||||||
}
|
}
|
||||||
|
# left/right_position:mediapipe 21/251 号定位点(原图像素,与 landmarks 同坐标系)。
|
||||||
|
# landmarks 缺省(如测试直构 MeasureResult)时不输出,保持向后兼容。
|
||||||
|
if self.landmarks is not None and self.w and self.h:
|
||||||
|
lm = _lm_list(self.landmarks)
|
||||||
|
|
||||||
|
def _pt_lm(idx):
|
||||||
|
px, py = normalized_to_pixel(lm[idx], self.w, self.h)
|
||||||
|
return {"x": int(round(px)), "y": int(round(py))}
|
||||||
|
|
||||||
|
data["left_position"] = _pt_lm(LEFT_POSITION)
|
||||||
|
data["right_position"] = _pt_lm(RIGHT_POSITION)
|
||||||
if self.head_pose is not None:
|
if self.head_pose is not None:
|
||||||
yaw, pitch, roll = self.head_pose
|
yaw, pitch, roll = self.head_pose
|
||||||
data["head_pose"] = {
|
data["head_pose"] = {
|
||||||
@@ -220,7 +236,8 @@ def measure_face(landmarks, hair_mask, image_width, image_height, head_pose=None
|
|||||||
vertical, source = decide_vertical(landmarks, image_width, image_height, hair_mask)
|
vertical, source = decide_vertical(landmarks, image_width, image_height, hair_mask)
|
||||||
eyes = measure_seven_eyes(landmarks, image_width, image_height)
|
eyes = measure_seven_eyes(landmarks, image_width, image_height)
|
||||||
px_per_cm = estimate_scale_factor(landmarks, image_width, image_height)
|
px_per_cm = estimate_scale_factor(landmarks, image_width, image_height)
|
||||||
return MeasureResult(vertical, eyes, px_per_cm, source, head_pose)
|
return MeasureResult(vertical, eyes, px_per_cm, source, head_pose,
|
||||||
|
landmarks, image_width, image_height)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
+13
-5
@@ -396,13 +396,16 @@ def _grow_from_texture(image_bgr: np.ndarray, ctx: dict, white_path: str | None,
|
|||||||
|
|
||||||
def generate_hairline_pngs(image_bgr: np.ndarray, gender: str,
|
def generate_hairline_pngs(image_bgr: np.ndarray, gender: str,
|
||||||
hair_styles: list[int], use_mask: bool = True,
|
hair_styles: list[int], use_mask: bool = True,
|
||||||
prompt: str | None = None):
|
prompt: str | None = None,
|
||||||
|
generate_grow_image: bool = True):
|
||||||
"""接口5:对选中发型返回 middle/high/low 三档发际线透明叠图 + 生发图(同接口2)。
|
"""接口5:对选中发型返回 middle/high/low 三档发际线透明叠图 + 生发图(同接口2)。
|
||||||
|
|
||||||
入参同接口2:先选 gender,再多选 hair_styles(必填,1-indexed 按贴图排序)。
|
入参同接口2:先选 gender,再多选 hair_styles(必填,1-indexed 按贴图排序)。
|
||||||
每个选中发型返回三档叠图(middle/high/low,RGBA 透明层只含发际线曲线)与一张生发图;
|
每个选中发型返回三档叠图(middle/high/low,RGBA 透明层只含发际线曲线)与一张生发图;
|
||||||
三档贴图同名,生发黑模板固定取自 hairline_texture_black/(middle),故生发目标固定 middle 档。
|
三档贴图同名,生发黑模板固定取自 hairline_texture_black/(middle),故生发目标固定 middle 档。
|
||||||
use_mask/prompt:同接口2 的生发参数。
|
use_mask/prompt:同接口2 的生发参数。
|
||||||
|
generate_grow_image(默认 True):是否生成生发图(ComfyUI,最耗时)。False 时跳过生发,
|
||||||
|
各发型 grown_png 恒为 None,可大幅降低耗时(仅留三档发际线叠图与中心点)。
|
||||||
Returns: {"images":[{hairline_type,order,overlays:{middle,high,low}((H,W,4) RGBA 透明层),grown_png}],
|
Returns: {"images":[{hairline_type,order,overlays:{middle,high,low}((H,W,4) RGBA 透明层),grown_png}],
|
||||||
"best_centers":{"middle":(x,y),"high":(x,y),"low":(x,y)}};无人脸 None。
|
"best_centers":{"middle":(x,y),"high":(x,y),"low":(x,y)}};无人脸 None。
|
||||||
best_centers 取首个选中发型三档各自的发际线中点。
|
best_centers 取首个选中发型三档各自的发际线中点。
|
||||||
@@ -424,8 +427,9 @@ def generate_hairline_pngs(image_bgr: np.ndarray, gender: str,
|
|||||||
tex_by_level = {lv: get_texture_map(lv)[gender] for lv in _TEXTURE_DIRS}
|
tex_by_level = {lv: get_texture_map(lv)[gender] for lv in _TEXTURE_DIRS}
|
||||||
|
|
||||||
# use_mask=False:干净原图+空遮罩与贴图无关,只跑一次 ComfyUI,选中项复用
|
# use_mask=False:干净原图+空遮罩与贴图无关,只跑一次 ComfyUI,选中项复用
|
||||||
|
# generate_grow_image=False:完全跳过生发(最耗时),grown_png 恒为 None
|
||||||
shared_grown = None
|
shared_grown = None
|
||||||
if not use_mask:
|
if generate_grow_image and not use_mask:
|
||||||
shared_grown = _grow_from_texture(image_bgr, ctx, None, use_mask=False, prompt=prompt)
|
shared_grown = _grow_from_texture(image_bgr, ctx, None, use_mask=False, prompt=prompt)
|
||||||
|
|
||||||
def _center_of(overlay):
|
def _center_of(overlay):
|
||||||
@@ -444,9 +448,13 @@ def generate_hairline_pngs(image_bgr: np.ndarray, gender: str,
|
|||||||
for lv in _TEXTURE_DIRS:
|
for lv in _TEXTURE_DIRS:
|
||||||
white = load_texture_rgba(tex_by_level[lv][s - 1][1])
|
white = load_texture_rgba(tex_by_level[lv][s - 1][1])
|
||||||
overlays[lv] = build_overlay_layer(h, w, ctx["points"], ext_faces, uv, white)
|
overlays[lv] = build_overlay_layer(h, w, ctx["points"], ext_faces, uv, white)
|
||||||
# 生发:固定 middle 黑模板
|
# 生发:固定 middle 黑模板(generate_grow_image=False 时跳过,恒 None)
|
||||||
grown_png = shared_grown if not use_mask else \
|
if not generate_grow_image:
|
||||||
_grow_from_texture(image_bgr, ctx, mid_path, use_mask=True, prompt=prompt)
|
grown_png = None
|
||||||
|
elif not use_mask:
|
||||||
|
grown_png = shared_grown
|
||||||
|
else:
|
||||||
|
grown_png = _grow_from_texture(image_bgr, ctx, mid_path, use_mask=True, prompt=prompt)
|
||||||
images.append({"hairline_type": key, "order": s,
|
images.append({"hairline_type": key, "order": s,
|
||||||
"overlays": overlays, "grown_png": grown_png})
|
"overlays": overlays, "grown_png": grown_png})
|
||||||
# best_centers:首个选中发型三档(middle/high/low)发际线中点
|
# best_centers:首个选中发型三档(middle/high/low)发际线中点
|
||||||
|
|||||||
+16
-40
@@ -57,7 +57,6 @@
|
|||||||
<a href="#if4">接口4</a>
|
<a href="#if4">接口4</a>
|
||||||
<a href="#if5">接口5</a>
|
<a href="#if5">接口5</a>
|
||||||
<a href="#if6">接口6</a>
|
<a href="#if6">接口6</a>
|
||||||
<a href="#if7">接口7</a>
|
|
||||||
<a href="#errors">错误码</a>
|
<a href="#errors">错误码</a>
|
||||||
<a href="#test">在线测试</a>
|
<a href="#test">在线测试</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -109,6 +108,8 @@
|
|||||||
<tr><td><code>landmarks</code></td><td>object</td><td>5 个关键点像素坐标:hair_top/hairline/brow_center/nose_bottom/chin_tip</td></tr>
|
<tr><td><code>landmarks</code></td><td>object</td><td>5 个关键点像素坐标:hair_top/hairline/brow_center/nose_bottom/chin_tip</td></tr>
|
||||||
<tr><td><code>hairline_source</code></td><td>string</td><td>发际线来源:<code>"segmentation"</code>(真实分割,可信度高)/ <code>"estimated"</code>(比例估算,可信度低)</td></tr>
|
<tr><td><code>hairline_source</code></td><td>string</td><td>发际线来源:<code>"segmentation"</code>(真实分割,可信度高)/ <code>"estimated"</code>(比例估算,可信度低)</td></tr>
|
||||||
<tr><td><code>head_pose</code></td><td>object</td><td>头部姿态角度:<code>{ yaw, pitch, roll }</code>(度),接近 0 表示正面照</td></tr>
|
<tr><td><code>head_pose</code></td><td>object</td><td>头部姿态角度:<code>{ yaw, pitch, roll }</code>(度),接近 0 表示正面照</td></tr>
|
||||||
|
<tr><td><code>left_position</code></td><td>object</td><td>MediaPipe 21 号关键点坐标(左脸定位点),原图像素:<code>{ x: number, y: number }</code></td></tr>
|
||||||
|
<tr><td><code>right_position</code></td><td>object</td><td>MediaPipe 251 号关键点坐标(右脸定位点,与 21 号镜像),原图像素:<code>{ x: number, y: number }</code></td></tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<p style="margin-top:12px;font-size:12px;color:#64748b">💡 前端把标注图叠加到原图上即可呈现测量效果(标注图白色线条 #FFFFFF,透明底)。</p>
|
<p style="margin-top:12px;font-size:12px;color:#64748b">💡 前端把标注图叠加到原图上即可呈现测量效果(标注图白色线条 #FFFFFF,透明底)。</p>
|
||||||
@@ -189,17 +190,17 @@ const { code, data } = await res.json();
|
|||||||
<div class="card" id="if4">
|
<div class="card" id="if4">
|
||||||
<h2>4. 用户特征分析 <span class="badge post">POST</span> <code>/api/v1/face/features</code></h2>
|
<h2>4. 用户特征分析 <span class="badge post">POST</span> <code>/api/v1/face/features</code></h2>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p class="desc">上传照片 → 火山方舟豆包视觉模型分析 → 返回几十项面部特征(脸型/眉形/肤色/四季色彩…)。</p>
|
<p class="desc">上传照片 → 火山方舟豆包视觉模型分析 → 返回固定 6 项面部特征(脸型/眉形/面部年龄/动静类型/性别/基因风格)。</p>
|
||||||
|
|
||||||
<p><strong>入参</strong>:image_file / image_url / image_base64 三选一。无其他参数。</p>
|
<p><strong>入参</strong>:image_file / image_url / image_base64 三选一。无其他参数。</p>
|
||||||
|
|
||||||
<p style="margin-top:12px"><strong>data 字段</strong></p>
|
<p style="margin-top:12px"><strong>data 字段</strong></p>
|
||||||
<table>
|
<table>
|
||||||
<tr><th>字段</th><th>类型</th><th>说明</th></tr>
|
<tr><th>字段</th><th>类型</th><th>说明</th></tr>
|
||||||
<tr><td><code>features</code></td><td>string</td><td><strong>JSON 字符串</strong>(不是对象!客户端需 <code>JSON.parse()</code>)</td></tr>
|
<tr><td><code>features</code></td><td>string</td><td><strong>JSON 字符串</strong>(不是对象!客户端需 <code>JSON.parse()</code>)。解析后得到<strong>固定 6 个英文字段</strong></td></tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<p style="margin-top:8px"><strong>features 英文优先字段</strong>(其余中文字段同时返回,共~42个):</p>
|
<p style="margin-top:8px"><strong>features 字段</strong>(固定返回 6 个):</p>
|
||||||
<table>
|
<table>
|
||||||
<tr><th>字段</th><th>说明</th><th>字段</th><th>说明</th></tr>
|
<tr><th>字段</th><th>说明</th><th>字段</th><th>说明</th></tr>
|
||||||
<tr><td>face_shape</td><td>脸型</td><td>eyebrow_shape</td><td>眉形</td></tr>
|
<tr><td>face_shape</td><td>脸型</td><td>eyebrow_shape</td><td>眉形</td></tr>
|
||||||
@@ -214,7 +215,7 @@ const { code, data } = await res.json();
|
|||||||
const { code, data } = await res.json();
|
const { code, data } = await res.json();
|
||||||
const features = JSON.parse(data.features); // ← 注意:data.features 是字符串!
|
const features = JSON.parse(data.features); // ← 注意:data.features 是字符串!
|
||||||
console.log(features.face_shape); // "鹅蛋脸"
|
console.log(features.face_shape); // "鹅蛋脸"
|
||||||
console.log(features['四季色彩季型']); // "冷夏型"(中文字段也保留)</pre>
|
console.log(features.gene_style); // "自然型"</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -230,12 +231,13 @@ console.log(features['四季色彩季型']); // "冷夏型"(中文字段也保
|
|||||||
<tr><td>image_file / image_url / image_base64</td><td>—</td><td>三选一</td><td>用户正面照</td></tr>
|
<tr><td>image_file / image_url / image_base64</td><td>—</td><td>三选一</td><td>用户正面照</td></tr>
|
||||||
<tr><td>gender</td><td>string</td><td>✅ 必填</td><td><code>"male"</code> / <code>"female"</code></td></tr>
|
<tr><td>gender</td><td>string</td><td>✅ 必填</td><td><code>"male"</code> / <code>"female"</code></td></tr>
|
||||||
<tr><td>hair_style</td><td>string</td><td>✅ 必填</td><td>发型序号,逗号分隔多选(如 <code>1,2,3</code>)。缺失/越界返回 1007</td></tr>
|
<tr><td>hair_style</td><td>string</td><td>✅ 必填</td><td>发型序号,逗号分隔多选(如 <code>1,2,3</code>)。缺失/越界返回 1007</td></tr>
|
||||||
|
<tr><td>generate_grow_image</td><td>bool</td><td>否</td><td>是否生成生发效果图(ComfyUI 生发,全流程最耗时),默认 <code>true</code>。传 <code>false</code> 时跳过生发,各发型 <code>grown_image_url</code> 恒为 <code>null</code>,仅返回三档发际线叠图与中心点,大幅降低耗时</td></tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<p style="margin-top:12px"><strong>data 字段</strong></p>
|
<p style="margin-top:12px"><strong>data 字段</strong></p>
|
||||||
<table>
|
<table>
|
||||||
<tr><th>字段</th><th>类型</th><th>说明</th></tr>
|
<tr><th>字段</th><th>类型</th><th>说明</th></tr>
|
||||||
<tr><td><code>hairline_images[]</code></td><td>object[]</td><td>选中发型列表,每项含 <code>hairline_type</code>、<code>image_middle_url</code>/<code>image_high_url</code>/<code>image_low_url</code> 三档<strong>透明 PNG 叠图</strong>(仅曲线,需叠加原图)、<code>grown_image_url</code> 生发图(完整人像,失败为 null)、<code>order</code></td></tr>
|
<tr><td><code>hairline_images[]</code></td><td>object[]</td><td>选中发型列表,每项含 <code>hairline_type</code>、<code>image_middle_url</code>/<code>image_high_url</code>/<code>image_low_url</code> 三档<strong>透明 PNG 叠图</strong>(仅曲线,需叠加原图)、<code>grown_image_url</code> 生发图(完整人像,失败或 <code>generate_grow_image=false</code> 时为 null)、<code>order</code></td></tr>
|
||||||
<tr><td><code>best_hairline_center_point</code></td><td>object \| null</td><td>首个选中发型 <strong>middle 档</strong>发际线中心点像素坐标 <code>{ x: number, y: number }</code></td></tr>
|
<tr><td><code>best_hairline_center_point</code></td><td>object \| null</td><td>首个选中发型 <strong>middle 档</strong>发际线中心点像素坐标 <code>{ x: number, y: number }</code></td></tr>
|
||||||
<tr><td><code>high_hairline_center_point</code></td><td>object \| null</td><td>同上,<strong>high 档</strong>发际线中点(发际线偏高,y 更小)</td></tr>
|
<tr><td><code>high_hairline_center_point</code></td><td>object \| null</td><td>同上,<strong>high 档</strong>发际线中点(发际线偏高,y 更小)</td></tr>
|
||||||
<tr><td><code>low_hairline_center_point</code></td><td>object \| null</td><td>同上,<strong>low 档</strong>发际线中点(发际线偏低,y 更大)</td></tr>
|
<tr><td><code>low_hairline_center_point</code></td><td>object \| null</td><td>同上,<strong>low 档</strong>发际线中点(发际线偏低,y 更大)</td></tr>
|
||||||
@@ -251,6 +253,8 @@ console.log(features['四季色彩季型']); // "冷夏型"(中文字段也保
|
|||||||
<tr><td><code>landmarks</code></td><td>object</td><td>5 个关键点像素坐标:hair_top/hairline/brow_center/nose_bottom/chin_tip</td></tr>
|
<tr><td><code>landmarks</code></td><td>object</td><td>5 个关键点像素坐标:hair_top/hairline/brow_center/nose_bottom/chin_tip</td></tr>
|
||||||
<tr><td><code>hairline_source</code></td><td>string</td><td>发际线来源:<code>"segmentation"</code>(真实分割)/ <code>"estimated"</code>(比例估算)</td></tr>
|
<tr><td><code>hairline_source</code></td><td>string</td><td>发际线来源:<code>"segmentation"</code>(真实分割)/ <code>"estimated"</code>(比例估算)</td></tr>
|
||||||
<tr><td><code>head_pose</code></td><td>object</td><td>头部姿态角度:<code>{ yaw, pitch, roll }</code>(度)</td></tr>
|
<tr><td><code>head_pose</code></td><td>object</td><td>头部姿态角度:<code>{ yaw, pitch, roll }</code>(度)</td></tr>
|
||||||
|
<tr><td><code>left_position</code></td><td>object</td><td>MediaPipe 21 号关键点坐标(左脸定位点),原图像素:<code>{ x: number, y: number }</code></td></tr>
|
||||||
|
<tr><td><code>right_position</code></td><td>object</td><td>MediaPipe 251 号关键点坐标(右脸定位点,与 21 号镜像),原图像素:<code>{ x: number, y: number }</code></td></tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<p style="margin-top:12px;font-size:12px;color:#64748b">💡 前端无需额外请求接口1 即可拿到四庭七眼测量数值;<code>face_measure</code> 为 <code>null</code> 时(角度过大/无人脸等)仅隐藏测量区块,发际线结果照常展示。</p>
|
<p style="margin-top:12px;font-size:12px;color:#64748b">💡 前端无需额外请求接口1 即可拿到四庭七眼测量数值;<code>face_measure</code> 为 <code>null</code> 时(角度过大/无人脸等)仅隐藏测量区块,发际线结果照常展示。</p>
|
||||||
@@ -282,41 +286,12 @@ console.log(features['四季色彩季型']); // "冷夏型"(中文字段也保
|
|||||||
<tr><td><code>four_courts</code></td><td>object</td><td>三庭:upper/middle/lower,各含 _cm 和 ratios(<strong>无 top_court</strong>)</td></tr>
|
<tr><td><code>four_courts</code></td><td>object</td><td>三庭:upper/middle/lower,各含 _cm 和 ratios(<strong>无 top_court</strong>)</td></tr>
|
||||||
<tr><td><code>seven_eyes</code></td><td>object</td><td>七眼:<code>eye_width_cm</code>/<code>face_width_cm</code>/<code>inter_eye_distance_cm</code> + <code>ratios</code> + <strong><code>eye2</code>~<code>eye6</code></strong>(左脸颊/左眼/两眼间距/右眼/右脸颊,5 段宽度 cm;<strong>无 eye1/eye7</strong>)</td></tr>
|
<tr><td><code>seven_eyes</code></td><td>object</td><td>七眼:<code>eye_width_cm</code>/<code>face_width_cm</code>/<code>inter_eye_distance_cm</code> + <code>ratios</code> + <strong><code>eye2</code>~<code>eye6</code></strong>(左脸颊/左眼/两眼间距/右眼/右脸颊,5 段宽度 cm;<strong>无 eye1/eye7</strong>)</td></tr>
|
||||||
<tr><td><code>landmarks</code></td><td>object</td><td>4 个关键点:hairline/brow_center/nose_bottom/chin_tip(<strong>无 hair_top</strong>)</td></tr>
|
<tr><td><code>landmarks</code></td><td>object</td><td>4 个关键点:hairline/brow_center/nose_bottom/chin_tip(<strong>无 hair_top</strong>)</td></tr>
|
||||||
|
<tr><td><code>left_position</code></td><td>object</td><td>MediaPipe 21 号关键点坐标(左脸定位点),原图像素:<code>{ x: number, y: number }</code></td></tr>
|
||||||
|
<tr><td><code>right_position</code></td><td>object</td><td>MediaPipe 251 号关键点坐标(右脸定位点,与 21 号镜像),原图像素:<code>{ x: number, y: number }</code></td></tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ======== 接口7 ======== -->
|
|
||||||
<div class="card" id="if7">
|
|
||||||
<h2>7. C端生发 v2 <span class="badge post">POST</span> <code>/api/v1/hair/grow-v2</code> <span class="badge warn">v2</span></h2>
|
|
||||||
<div class="card-body">
|
|
||||||
<p class="desc">功能与<a href="#if2">接口2</a>完全一致,仅 ComfyUI 工作流不同——使用 <code>add_hair2.json</code> 替代 <code>add_hair.json</code>(Flux-2 Klein 9b)。</p>
|
|
||||||
|
|
||||||
<p><strong>入参</strong></p>
|
|
||||||
<table>
|
|
||||||
<tr><th>参数</th><th>类型</th><th>必填</th><th>说明</th></tr>
|
|
||||||
<tr><td>image_file / image_url / image_base64</td><td>—</td><td>三选一</td><td>用户正面照</td></tr>
|
|
||||||
<tr><td>gender</td><td>string</td><td>✅ 必填</td><td><code>"male"</code> / <code>"female"</code></td></tr>
|
|
||||||
<tr><td>hair_style</td><td>int</td><td>✅ 必填</td><td>发型序号。female: 1~5,male: 1~4</td></tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<p style="margin-top:12px"><strong>data.results[] 元素</strong>(同接口2)</p>
|
|
||||||
<table>
|
|
||||||
<tr><th>字段</th><th>类型</th><th>说明</th></tr>
|
|
||||||
<tr><td><code>image_url</code></td><td>string</td><td>发际线曲线<strong>透明 PNG</strong>(仅曲线,需叠加原图显示,同接口2)</td></tr>
|
|
||||||
<tr><td><code>grown_image_url</code></td><td>string</td><td>生发后效果图(完整人像)⚠ 可空</td></tr>
|
|
||||||
<tr><td><code>hairline_type</code></td><td>string</td><td>发际线类型 key</td></tr>
|
|
||||||
<tr><td><code>order</code></td><td>int</td><td>排序</td></tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<p style="margin-top:8px;font-size:12px;color:#64748b">
|
|
||||||
Female 5 种:ellipse/flower/heart/straight/wave |
|
|
||||||
Male 4 种:ellipse/m/straight/inverse_arc<br>
|
|
||||||
⚠ 工作流: add_hair2.json(Flux-2 Klein 9b),输入节点 26,输出节点 75。
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ======== 错误码 ======== -->
|
<!-- ======== 错误码 ======== -->
|
||||||
<div class="card" id="errors">
|
<div class="card" id="errors">
|
||||||
<h2>⚠ 错误码</h2>
|
<h2>⚠ 错误码</h2>
|
||||||
@@ -326,12 +301,14 @@ console.log(features['四季色彩季型']); // "冷夏型"(中文字段也保
|
|||||||
<tr><td>1001</td><td>无法识别人像</td><td>未检测到人脸</td></tr>
|
<tr><td>1001</td><td>无法识别人像</td><td>未检测到人脸</td></tr>
|
||||||
|
|
||||||
<tr><td>1003</td><td>角度问题,非正面照</td><td>非正面 / 角度过大</td></tr>
|
<tr><td>1003</td><td>角度问题,非正面照</td><td>非正面 / 角度过大</td></tr>
|
||||||
|
<tr><td>1004</td><td>gender 必填且只能为 male / female</td><td>接口2/5 的 <code>gender</code> 缺失或非法</td></tr>
|
||||||
<tr><td>1005</td><td>检测到多张人脸</td><td>仅支持单人</td></tr>
|
<tr><td>1005</td><td>检测到多张人脸</td><td>仅支持单人</td></tr>
|
||||||
|
|
||||||
<tr><td>1007</td><td>图片参数错误 / 后端不可用</td><td>参数传错 / 服务繁忙请稍后重试</td></tr>
|
<tr><td>1007</td><td>图片参数错误 / 后端不可用</td><td>参数传错 / 服务繁忙请稍后重试</td></tr>
|
||||||
<tr><td>1008</td><td>图片格式不支持</td><td>非 JPG/PNG / base64 解码失败</td></tr>
|
<tr><td>1008</td><td>图片格式不支持</td><td>非 JPG/PNG / base64 解码失败</td></tr>
|
||||||
|
<tr><td>1009</td><td>未授权</td><td>缺少或错误的 <code>X-Internal-Token</code>(<code>/api/*</code> 路径鉴权)</td></tr>
|
||||||
</table>
|
</table>
|
||||||
<p style="font-size:12px;color:#94a3b8;margin-top:8px">1004 已废弃(接口2 不再自动判性别,改由客户端传 gender 参数)。</p>
|
<p style="font-size:12px;color:#94a3b8;margin-top:8px">注:1004 仍在使用(接口2/5 的 gender 校验);接口7(grow-v2)已弃用,请改用接口2。</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -344,10 +321,9 @@ console.log(features['四季色彩季型']); // "冷夏型"(中文字段也保
|
|||||||
<tr><td>1. 四庭七眼</td><td><a href="/static/test_interface1.html" class="link">/static/test_interface1.html</a></td><td>上传照片 → 原图+标注叠加,底图/标注开关,指标卡片</td></tr>
|
<tr><td>1. 四庭七眼</td><td><a href="/static/test_interface1.html" class="link">/static/test_interface1.html</a></td><td>上传照片 → 原图+标注叠加,底图/标注开关,指标卡片</td></tr>
|
||||||
<tr><td>2. C端生发</td><td><a href="/static/test_interface2.html" class="link">/static/test_interface2.html</a></td><td>上传+性别 → 方案一覧(原图/叠加/生发),双图对比</td></tr>
|
<tr><td>2. C端生发</td><td><a href="/static/test_interface2.html" class="link">/static/test_interface2.html</a></td><td>上传+性别 → 方案一覧(原图/叠加/生发),双图对比</td></tr>
|
||||||
<tr><td>3. B端生发</td><td><a href="/static/test_interface3.html" class="link">/static/test_interface3.html</a></td><td>划线图上传 → 生发效果图</td></tr>
|
<tr><td>3. B端生发</td><td><a href="/static/test_interface3.html" class="link">/static/test_interface3.html</a></td><td>划线图上传 → 生发效果图</td></tr>
|
||||||
<tr><td>4. 用户特征</td><td><a href="/static/test_interface4.html" class="link">/static/test_interface4.html</a></td><td>上传照片 → 42项面部特征表格 + 原始JSON</td></tr>
|
<tr><td>4. 用户特征</td><td><a href="/static/test_interface4.html" class="link">/static/test_interface4.html</a></td><td>上传照片 → 6项面部特征 + 原始JSON</td></tr>
|
||||||
<tr><td>5. 发际线PNG</td><td><a href="/static/test_interface5.html" class="link">/static/test_interface5.html</a></td><td>上传+性别 → 发际线方案+中心点坐标</td></tr>
|
<tr><td>5. 发际线PNG</td><td><a href="/static/test_interface5.html" class="link">/static/test_interface5.html</a></td><td>上传+性别 → 发际线方案+中心点坐标</td></tr>
|
||||||
<tr><td>6. 四庭七眼 v2</td><td><a href="/static/test_interface6.html" class="link">/static/test_interface6.html</a></td><td>同接口1,去顶庭 · 竖线发际线→下巴 · 无头部端线</td></tr>
|
<tr><td>6. 四庭七眼 v2</td><td><a href="/static/test_interface6.html" class="link">/static/test_interface6.html</a></td><td>同接口1,去顶庭 · 竖线发际线→下巴 · 无头部端线</td></tr>
|
||||||
<tr><td>7. C端生发 v2</td><td><a href="/static/test_interface7.html" class="link">/static/test_interface7.html</a></td><td>同接口2,使用 add_hair2.json 工作流(Flux-2 Klein 9b)</td></tr>
|
|
||||||
</table>
|
</table>
|
||||||
<p style="font-size:12px;color:#94a3b8;margin-top:12px">
|
<p style="font-size:12px;color:#94a3b8;margin-top:12px">
|
||||||
完整 API 文档:<a href="/docs" class="link">/docs</a>(Swagger UI)
|
完整 API 文档:<a href="/docs" class="link">/docs</a>(Swagger UI)
|
||||||
|
|||||||
@@ -113,6 +113,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>生发效果图</label>
|
||||||
|
<label class="checkbox-inline" style="font-weight:normal;display:flex;align-items:center;gap:6px">
|
||||||
|
<input type="checkbox" id="genGrowImg" checked> generate_grow_image(默认开;关闭后跳过最耗时的生发,仅返回三档叠图与中心点)
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<button class="btn btn-primary" id="submitBtn" onclick="submitTest()">🚀 提交</button>
|
<button class="btn btn-primary" id="submitBtn" onclick="submitTest()">🚀 提交</button>
|
||||||
<button class="btn btn-outline btn-sm" onclick="clearResults()">清除</button>
|
<button class="btn btn-outline btn-sm" onclick="clearResults()">清除</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -206,7 +212,7 @@ async function submitTest() {
|
|||||||
$('submitBtn').disabled = true; $('submitBtn').textContent = '⏳ ...';
|
$('submitBtn').disabled = true; $('submitBtn').textContent = '⏳ ...';
|
||||||
setStatus('请求中...', 'info'); $('resultsArea').classList.add('hidden');
|
setStatus('请求中...', 'info'); $('resultsArea').classList.add('hidden');
|
||||||
|
|
||||||
const fd = new FormData(); fd.append('image_file', f); fd.append('gender', $('gender').value); fd.append('hair_style', checked.join(','));
|
const fd = new FormData(); fd.append('image_file', f); fd.append('gender', $('gender').value); fd.append('hair_style', checked.join(',')); fd.append('generate_grow_image', $('genGrowImg').checked ? 'true' : 'false');
|
||||||
const _reqStart = performance.now();
|
const _reqStart = performance.now();
|
||||||
try {
|
try {
|
||||||
const r = await fetch(API_BASE + '/api/v1/hairline/generate', { method:'POST', headers: { 'X-Internal-Token': 'dev-shared-secret-2026' }, body:fd });
|
const r = await fetch(API_BASE + '/api/v1/hairline/generate', { method:'POST', headers: { 'X-Internal-Token': 'dev-shared-secret-2026' }, body:fd });
|
||||||
|
|||||||
Reference in New Issue
Block a user