save code

This commit is contained in:
Ubuntu
2026-06-13 12:11:31 +08:00
parent 1233952571
commit d8a6e53979
14 changed files with 1074 additions and 6 deletions
+547 -6
View File
@@ -1,13 +1,554 @@
from fastapi import FastAPI
"""旷视五接口 - 第一版(Mock 实现)
app = FastAPI(title="hair")
当前为 Mock 服务,所有接口返回固定示例值,不做真实算法计算。
图片字段统一返回 https://hair.xiangsilian.com/static/sample.jpg。
"""
import json
from typing import Any, List, Optional
from fastapi import FastAPI, File, Form, UploadFile
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel, Field
# ---------------------------------------------------------------------------
# App & 全局常量
# ---------------------------------------------------------------------------
BASE_URL = "https://hair.xiangsilian.com"
SAMPLE_IMAGE_URL = f"{BASE_URL}/static/sample.jpg"
app = FastAPI(
title="旷视五接口",
version="0.1.0",
description="""
## 概述
本服务提供五个人像分析接口,当前为 **Mock 第一版**:
- 接口已全部上线,传任意合法参数均可正常响应
- 所有字段返回固定示例值,图片字段统一指向示例图片
- 真实算法逻辑后续接入,字段结构不变
## 图片传参说明
每个接口的图片参数均支持三种方式,**严格互斥,必须且只能选其一**:
| 方式 | 字段名 | 说明 |
|------|--------|------|
| 文件上传 | `image_file` | `multipart/form-data`,单文件 ≤ 1 MB |
| URL | `image_url` | 图片的完整 HTTP/HTTPS 地址 |
| base64 | `image_base64` | 需携带前缀,如 `data:image/jpeg;base64,xxxx` |
> 传 0 个或同时传多个,均返回错误码 `1007`。
## 图片要求
- 格式:**JPG / PNG**
- 分辨率:最低 1080×1920,最大 4000×5000
- 人脸数量:仅支持**单人**,多人返回错误码 `1005`
- 文件大小:≤ 1 MB(文件上传方式)
## 统一响应结构
```json
{
"code": 0,
"message": "success",
"request_id": "唯一请求标识",
"data": {}
}
```
`code = 0` 表示成功,非 0 表示失败,`message` 为具体原因。
## 错误码一览
| code | 说明 |
|------|------|
| 1001 | 无法识别人像 |
| 1002 | 人像分辨率过低 |
| 1003 | 非正面照 / 角度过大 |
| 1004 | 性别标签无法判定 |
| 1005 | 检测到多张人脸(仅支持单人)|
| 1006 | 文件超出 1 MB 限制 |
| 1007 | 图片参数错误(未传或同时传多个)|
| 1008 | 图片格式不支持(仅 JPG / PNG)|
""",
)
app.mount("/static", StaticFiles(directory="static"), name="static")
@app.get("/")
async def hello():
return {"message": "Hello, World!"}
# ---------------------------------------------------------------------------
# 通用响应模型
# ---------------------------------------------------------------------------
def ok(data: Any) -> dict:
return {
"code": 0,
"message": "success",
"request_id": "mock-request-id",
"data": data,
}
@app.get("/health")
def err(code: int, message: str) -> dict:
return {
"code": code,
"message": message,
"request_id": "mock-request-id",
"data": None,
}
# ---------------------------------------------------------------------------
# 通用图片请求 BodyJSON 方式,用于 url / base64
# ---------------------------------------------------------------------------
_image_fields_desc = (
"图片传参方式严格互斥,必须且只能选其一:\n"
"- **image_file**multipart/form-data 上传,≤ 1 MB\n"
"- **image_url**(完整 HTTP/HTTPS 地址)\n"
"- **image_base64**(需携带前缀,如 `data:image/jpeg;base64,xxxx`\n\n"
"同时传多个或一个都不传,均返回错误码 `1007`。"
)
class ImageJsonBody(BaseModel):
image_url: Optional[str] = Field(
default=None,
description="图片 URL(与 image_base64 二选一,不可同时传)",
examples=["https://hair.xiangsilian.com/static/sample.jpg"],
)
image_base64: Optional[str] = Field(
default=None,
description="图片 base64,需带前缀,如 `data:image/jpeg;base64,xxxx`",
examples=["data:image/jpeg;base64,/9j/4AAQSkZJRgAB..."],
)
model_config = {
"json_schema_extra": {
"examples": [
{
"summary": "使用 URL 传图",
"value": {"image_url": "https://hair.xiangsilian.com/static/sample.jpg"},
},
{
"summary": "使用 base64 传图",
"value": {"image_base64": "data:image/jpeg;base64,/9j/4AAQSkZJRgAB..."},
},
]
}
}
# ---------------------------------------------------------------------------
# 接口 1:四庭七眼测量标注
# ---------------------------------------------------------------------------
@app.post(
"/api/v1/face/measure",
summary="接口1 四庭七眼测量标注",
tags=["人脸分析"],
description=f"""
输入用户正面照,返回:
- 标注好四庭七眼数据的 **PNG 图片**(仅标注图层,不含人物)
- 四庭(顶庭/上庭/中庭/下庭)各段**厘米数值及占比**
- 七眼(眼宽/脸宽/两眼间距)**厘米数值及占比**
- 五个关键分界点的**原图像素坐标**(头顶/发际线/眉心/鼻翼下缘/下巴尖)
{_image_fields_desc}
图片同时支持 `multipart/form-data` 文件上传(字段名 `image_file`),或 JSON Body 传 `image_url` / `image_base64`。
---
**坐标说明**:所有坐标以原图像素为基准,原点为图片左上角,x 向右,y 向下。
**标注图片 UI 规范**(真实版本生效):
- 字体/线条颜色:`#FFFFFF 100%`
- 四庭数值在图片**左侧**呈现,七眼间距**上下穿插**展示
- 字体:PingFangSC-Regular 10pt,线宽 1pt
- 横线/竖线渐变消失,虚线两侧带箭头
""",
responses={
200: {
"description": "成功",
"content": {
"application/json": {
"example": {
"code": 0,
"message": "success",
"request_id": "mock-request-id",
"data": {
"annotated_image_url": SAMPLE_IMAGE_URL,
"face_total_height_cm": 13.76,
"four_courts": {
"top_court_cm": 3.44,
"upper_court_cm": 3.44,
"middle_court_cm": 3.44,
"lower_court_cm": 3.44,
"ratios": {
"top_court": 0.25,
"upper_court": 0.25,
"middle_court": 0.25,
"lower_court": 0.25,
},
},
"seven_eyes": {
"eye_width_cm": 3.44,
"face_width_cm": 24.08,
"inter_eye_distance_cm": 3.44,
"ratios": {"eye_width": 0.143, "inter_eye_distance": 0.143},
},
"landmarks": {
"hair_top": {"x": 540, "y": 120},
"hairline": {"x": 540, "y": 430},
"brow_center": {"x": 540, "y": 740},
"nose_bottom": {"x": 540, "y": 1050},
"chin_tip": {"x": 540, "y": 1360},
},
},
}
}
},
},
400: {
"description": "参数错误 / 图片识别失败",
"content": {
"application/json": {
"examples": {
"图片参数错误": {"value": {"code": 1007, "message": "图片参数错误:必须且只能传 image_file / image_url / image_base64 其中一个", "request_id": "x", "data": None}},
"无法识别人像": {"value": {"code": 1001, "message": "无法识别人像", "request_id": "x", "data": None}},
"多张人脸": {"value": {"code": 1005, "message": "检测到多张人脸,仅支持单人照片", "request_id": "x", "data": None}},
}
}
},
},
},
)
async def face_measure(
image_file: Optional[UploadFile] = File(default=None, description="上传图片文件(JPG/PNG,≤ 1 MB"),
image_url: Optional[str] = Form(default=None, description="图片 URL"),
image_base64: Optional[str] = Form(default=None, description="图片 base64(需带 data:image/...;base64, 前缀)"),
):
data = {
"annotated_image_url": SAMPLE_IMAGE_URL,
"face_total_height_cm": 13.76,
"four_courts": {
"top_court_cm": 3.44,
"upper_court_cm": 3.44,
"middle_court_cm": 3.44,
"lower_court_cm": 3.44,
"ratios": {
"top_court": 0.25,
"upper_court": 0.25,
"middle_court": 0.25,
"lower_court": 0.25,
},
},
"seven_eyes": {
"eye_width_cm": 3.44,
"face_width_cm": 24.08,
"inter_eye_distance_cm": 3.44,
"ratios": {"eye_width": 0.143, "inter_eye_distance": 0.143},
},
"landmarks": {
"hair_top": {"x": 540, "y": 120},
"hairline": {"x": 540, "y": 430},
"brow_center": {"x": 540, "y": 740},
"nose_bottom": {"x": 540, "y": 1050},
"chin_tip": {"x": 540, "y": 1360},
},
}
return ok(data)
# ---------------------------------------------------------------------------
# 接口 2C 端生发
# ---------------------------------------------------------------------------
@app.post(
"/api/v1/hair/grow",
summary="接口2 C端生发",
tags=["生发"],
description=f"""
输入用户正面照,返回多个生发方案,每个方案包含:
- 生发后效果图 URL
- 对应的发际线形(如花瓣形、波浪形)
- 合适度排序(order=1 最优)
{_image_fields_desc}
图片同时支持 `multipart/form-data` 文件上传(字段名 `image_file`)。
---
**beauty_enabled**:是否对生发后图片开启美颜,默认 `false`。
""",
responses={
200: {
"description": "成功",
"content": {
"application/json": {
"example": {
"code": 0,
"message": "success",
"request_id": "mock-request-id",
"data": {
"results": [
{"image_url": SAMPLE_IMAGE_URL, "hairline_type": "花瓣形", "order": 1},
{"image_url": SAMPLE_IMAGE_URL, "hairline_type": "波浪形", "order": 2},
]
},
}
}
},
},
400: {
"description": "参数错误 / 图片识别失败",
"content": {
"application/json": {
"examples": {
"图片参数错误": {"value": {"code": 1007, "message": "图片参数错误:必须且只能传 image_file / image_url / image_base64 其中一个", "request_id": "x", "data": None}},
"非正面照": {"value": {"code": 1003, "message": "角度问题,请上传正面照", "request_id": "x", "data": None}},
}
}
},
},
},
)
async def hair_grow(
image_file: Optional[UploadFile] = File(default=None, description="上传图片文件(JPG/PNG,≤ 1 MB"),
image_url: Optional[str] = Form(default=None, description="图片 URL"),
image_base64: Optional[str] = Form(default=None, description="图片 base64(需带 data:image/...;base64, 前缀)"),
beauty_enabled: bool = Form(default=False, description="是否开启美颜效果,默认 false"),
):
data = {
"results": [
{"image_url": SAMPLE_IMAGE_URL, "hairline_type": "花瓣形", "order": 1},
{"image_url": SAMPLE_IMAGE_URL, "hairline_type": "波浪形", "order": 2},
]
}
return ok(data)
# ---------------------------------------------------------------------------
# 接口 3B 端生发
# ---------------------------------------------------------------------------
@app.post(
"/api/v1/hair/grow-b",
summary="接口3 B端生发(医生/操作端)",
tags=["生发"],
description="""
医生/操作端在用户原图上**手动划线标注**目标发际线后,上传划线图与原图,返回:
- 最合适的发际线图片 URL
- 生发后效果图 URL
- 发际线形
**划线图片**:字段名前缀为 `marked_image_`,同样支持文件/URL/base64 三选一。
**原始用户照片**:字段名前缀为 `original_image_`,同样支持文件/URL/base64 三选一,**必填**。
> 两组图片的传参方式可以不同,例如划线图用 URL,原图用 base64,但各自组内严格互斥。
""",
responses={
200: {
"description": "成功",
"content": {
"application/json": {
"example": {
"code": 0,
"message": "success",
"request_id": "mock-request-id",
"data": {
"best_hairline_image_url": SAMPLE_IMAGE_URL,
"hair_growth_image_url": SAMPLE_IMAGE_URL,
"hairline_type": "花瓣形",
},
}
}
},
},
400: {
"description": "参数错误",
"content": {
"application/json": {
"example": {"code": 1007, "message": "图片参数错误", "request_id": "x", "data": None}
}
},
},
},
)
async def hair_grow_b(
marked_image_file: Optional[UploadFile] = File(default=None, description="划线图片文件(JPG/PNG,≤ 1 MB"),
marked_image_url: Optional[str] = Form(default=None, description="划线图片 URL"),
marked_image_base64: Optional[str] = Form(default=None, description="划线图片 base64"),
original_image_file: Optional[UploadFile] = File(default=None, description="原始用户照片文件(JPG/PNG,≤ 1 MB"),
original_image_url: Optional[str] = Form(default=None, description="原始用户照片 URL"),
original_image_base64: Optional[str] = Form(default=None, description="原始用户照片 base64"),
):
data = {
"best_hairline_image_url": SAMPLE_IMAGE_URL,
"hair_growth_image_url": SAMPLE_IMAGE_URL,
"hairline_type": "花瓣形",
}
return ok(data)
# ---------------------------------------------------------------------------
# 接口 4:用户特征
# ---------------------------------------------------------------------------
@app.post(
"/api/v1/face/features",
summary="接口4 用户特征分析",
tags=["人脸分析"],
description=f"""
输入用户照片,返回 N 个用户面部特征字段。
{_image_fields_desc}
图片同时支持 `multipart/form-data` 文件上传(字段名 `image_file`)。
---
**返回格式**`data.features` 为一个 **JSON 字符串**(不是对象),需要在客户端 `JSON.parse()` 后使用。
当前优先返回字段:
| 字段 | 类型 | 说明 |
|------|------|------|
| face_shape | string | 脸形,如"鹅蛋脸" |
| eyebrow_shape | string | 眉形,如"柳叶眉" |
| facial_age | int | 面部年龄 |
| dynamic_static_type | string | 动静类型,如"静态" |
| gender | string | 性别,如"" |
| gene_style | object | 面部特征对应的基因风格 |
> 字段后续可随时增删,不固定,客户端按需取用即可。
""",
responses={
200: {
"description": "成功",
"content": {
"application/json": {
"example": {
"code": 0,
"message": "success",
"request_id": "mock-request-id",
"data": {
"features": '{"face_shape": "鹅蛋脸", "eyebrow_shape": "柳叶眉", "facial_age": 26, "dynamic_static_type": "静态", "gender": "", "gene_style": {"label": "面部特征标签", "style": "基因风格示例"}}',
},
}
}
},
},
400: {
"description": "参数错误 / 图片识别失败",
"content": {
"application/json": {
"example": {"code": 1001, "message": "无法识别人像", "request_id": "x", "data": None}
}
},
},
},
)
async def face_features(
image_file: Optional[UploadFile] = File(default=None, description="上传图片文件(JPG/PNG,≤ 1 MB"),
image_url: Optional[str] = Form(default=None, description="图片 URL"),
image_base64: Optional[str] = Form(default=None, description="图片 base64(需带 data:image/...;base64, 前缀)"),
):
features = json.dumps(
{
"face_shape": "鹅蛋脸",
"eyebrow_shape": "柳叶眉",
"facial_age": 26,
"dynamic_static_type": "静态",
"gender": "",
"gene_style": {"label": "面部特征标签", "style": "基因风格示例"},
},
ensure_ascii=False,
)
return ok({"features": features})
# ---------------------------------------------------------------------------
# 接口 5:发际线 PNG 生成
# ---------------------------------------------------------------------------
@app.post(
"/api/v1/hairline/generate",
summary="接口5 发际线PNG生成",
tags=["人脸分析"],
description=f"""
输入用户照片,返回 N 张用户发际线的 PNG 图片,并标注最合适发际线的面部中间点坐标。
{_image_fields_desc}
图片同时支持 `multipart/form-data` 文件上传(字段名 `image_file`)。
---
**返回说明**
- `hairline_images`:发际线 PNG 列表,数量 N 不固定,已按合适度**从高到低排序**(`order=1` 最合适)
- `best_hairline_center_point`:最合适发际线的面部中间点坐标,以**原图像素**为基准(左上角为原点,x 向右,y 向下)
""",
responses={
200: {
"description": "成功",
"content": {
"application/json": {
"example": {
"code": 0,
"message": "success",
"request_id": "mock-request-id",
"data": {
"hairline_images": [
{"image_url": SAMPLE_IMAGE_URL, "order": 1},
{"image_url": SAMPLE_IMAGE_URL, "order": 2},
],
"best_hairline_center_point": {"x": 540, "y": 430},
},
}
}
},
},
400: {
"description": "参数错误 / 图片识别失败",
"content": {
"application/json": {
"example": {"code": 1002, "message": "人像分辨率过低", "request_id": "x", "data": None}
}
},
},
},
)
async def hairline_generate(
image_file: Optional[UploadFile] = File(default=None, description="上传图片文件(JPG/PNG,≤ 1 MB"),
image_url: Optional[str] = Form(default=None, description="图片 URL"),
image_base64: Optional[str] = Form(default=None, description="图片 base64(需带 data:image/...;base64, 前缀)"),
):
data = {
"hairline_images": [
{"image_url": SAMPLE_IMAGE_URL, "order": 1},
{"image_url": SAMPLE_IMAGE_URL, "order": 2},
],
"best_hairline_center_point": {"x": 540, "y": 430},
}
return ok(data)
# ---------------------------------------------------------------------------
# 健康检查
# ---------------------------------------------------------------------------
@app.get("/health", include_in_schema=False)
async def health():
return {"status": "ok"}
@app.get("/", include_in_schema=False)
async def index():
return {"service": "旷视五接口", "version": "0.1.0", "docs": f"{BASE_URL}/docs"}
+370
View File
@@ -0,0 +1,370 @@
# 旷视五个接口 — 接口文档(输入 / 输出定义)
> 本文档依据《旷视具体需求.docx》整理,描述各接口的**输入**与**输出**。
> 文中标记为 **【待确认】** 的内容为需求文档中尚未明确、需与对方对齐的点。
> **当前为第一版 Mock 服务**:所有接口已上线,但**不做真实算法计算**,统一返回下文「响应示例」中的默认值;所有图片字段均返回示例图 `https://hair.xiangsilian.com/static/sample.jpg`。真实逻辑后续接入。
---
## 服务地址与接口路径
- **Base URL**`https://hair.xiangsilian.com`
- **在线接口文档(Swagger**`https://hair.xiangsilian.com/docs`
| 接口 | 方法 | 路径 |
|------|------|------|
| 1 四庭七眼测量 | POST | `/api/v1/face/measure` |
| 2 C 端生发 | POST | `/api/v1/hair/grow` |
| 3 B 端生发 | POST | `/api/v1/hair/grow-b` |
| 4 用户特征 | POST | `/api/v1/face/features` |
| 5 发际线 PNG 生成 | POST | `/api/v1/hairline/generate` |
---
## 通用约定
以下约定适用于全部 5 个接口(最终以联调为准)。
### 请求
| 项目 | 约定 |
|------|------|
| 协议 | HTTPS |
| 请求方式 | `POST` |
| 图片传参方式 | 三选一,三种都支持:① `multipart/form-data` 上传文件、② 图片 URL、③ base64 字符串 |
| 字符编码 | UTF-8 |
| 坐标基准 | 所有返回的坐标均以**原图像素**为基准(原点为图片左上角,x 向右、y 向下) |
图片传参字段(三者**严格互斥**,必须且只能传其一;传了 0 个或 ≥2 个均报错):
| 参数 | 类型 | 说明 |
|------|------|------|
| image_file | file | 上传文件(`multipart/form-data` |
| image_url | string | 图片 URL |
| image_base64 | string | 图片 base64 字符串,**需带前缀**,如 `data:image/png;base64,xxxx` |
### 图片输入要求(接口 1/2/3/4/5 通用)
| 项目 | 要求 |
|------|------|
| 图片类型 | 用户正面照(人像) |
| 人脸数量 | 默认仅支持**单人**;若检测到 2 或 3 张人脸(即多人),报错并提示客户端 |
| 单文件大小 | ≤ 1 MB |
| 最低分辨率 | 720 × 1024 |
| 最大分辨率 | 2160 × 3840 |
| 格式 | 目前仅支持 JPG / PNG |
### 统一响应结构
```json
{
"code": 0,
"message": "success",
"request_id": "唯一请求标识",
"data": { }
}
```
| 字段 | 类型 | 说明 |
|------|------|------|
| code | int | 0 表示成功,非 0 表示失败 |
| message | string | 结果描述 / 错误原因 |
| request_id | string | 请求追踪 ID |
| data | object | 业务数据,结构见各接口 |
### 通用错误码(识别失败时返回,见需求文档)
| code | message | 说明 |
|------|---------|------|
| 1001 | 无法识别人像 | 图片中未检测到人脸 |
| 1002 | 人像分辨率过低 | 低于最低分辨率要求 |
| 1003 | 角度问题,非正面照 | 非正面 / 角度过大 |
| 1004 | 性别标签判断异常 | 男女标签无法判定 **【待确认】** 是否作为错误 |
| 1005 | 检测到多张人脸 | 默认仅支持单人,检测到 2 人或以上时返回 |
| 1006 | 文件超出大小限制 | 单文件超过 1 MB |
| 1007 | 图片参数错误 | file / url / base64 未传,或同时传了多个(三者严格互斥) |
| 1008 | 图片格式不支持 | 非 JPG / PNG |
---
## 接口 1:四庭七眼测量标注接口
**说明**:输入用户照片,输出一张标注好四庭七眼数据的 PNG 图片(**不包含人物本体,仅标注图层**),同时返回四庭、七眼的比例与厘米数值。
**请求**`POST /api/v1/face/measure`
### 输入
图片参数见「通用约定 → 图片传参字段」(`image_file` / `image_url` / `image_base64` 三选一)。本接口无其他专属参数。
### 输出(data
| 字段 | 类型 | 说明 |
|------|------|------|
| annotated_image_url | string | 标注好数据的 PNG 图片(仅标注图层,不含人物) |
| face_total_height_cm | number | 全脸总高度(cm |
| four_courts | object | 四庭数据,见下表 |
| seven_eyes | object | 七眼数据,见下表 |
| landmarks | object | 关键分界点坐标(头顶 / 发际线 / 眉心 / 鼻翼下缘 / 下巴尖),原图像素坐标 |
`four_courts`(四庭,自上而下):
| 字段 | 类型 | 说明 |
|------|------|------|
| top_court_cm | number | 顶庭长度(cm),示例:约占全脸高度 22% |
| upper_court_cm | number | 上庭长度(cm),约占 25% |
| middle_court_cm | number | 中庭长度(cm),约占 28% |
| lower_court_cm | number | 下庭长度(cm),约占 25% |
| ratios | object | 各庭占全脸高度的百分比 |
`seven_eyes`(七眼,横向):
| 字段 | 类型 | 说明 |
|------|------|------|
| eye_width_cm | number | 单眼宽度(cm |
| face_width_cm | number | 脸宽(cm |
| inter_eye_distance_cm | number | 两眼间距(cm |
| ratios | object | 七眼各段占脸宽的比例 |
### 标注图片(UI)规范
| 项目 | 要求 |
|------|------|
| 字体及线颜色 | `#FFFFFF` 100% |
| 数值排布 | 四庭数值统一在图片**左侧**呈现;七眼间距**上下穿插**展示 |
| 字体 | PingFangSC-Regular,字号 10pt |
| 线宽 | 横线、竖线、虚线均为 1pt |
| 线样式 | 横线、竖线渐变消失;虚线两侧呈现箭头 |
> **【待确认】** 标注图片需提供设计稿后才能最终确定样式。
### 响应示例(当前 Mock 返回值)
```json
{
"code": 0,
"message": "success",
"request_id": "mock-request-id",
"data": {
"annotated_image_url": "https://hair.xiangsilian.com/static/sample.jpg",
"face_total_height_cm": 13.76,
"four_courts": {
"top_court_cm": 3.44,
"upper_court_cm": 3.44,
"middle_court_cm": 3.44,
"lower_court_cm": 3.44,
"ratios": { "top_court": 0.25, "upper_court": 0.25, "middle_court": 0.25, "lower_court": 0.25 }
},
"seven_eyes": {
"eye_width_cm": 3.44,
"face_width_cm": 24.08,
"inter_eye_distance_cm": 3.44,
"ratios": { "eye_width": 0.143, "inter_eye_distance": 0.143 }
},
"landmarks": {
"hair_top": { "x": 540, "y": 120 },
"hairline": { "x": 540, "y": 430 },
"brow_center": { "x": 540, "y": 740 },
"nose_bottom": { "x": 540, "y": 1050 },
"chin_tip": { "x": 540, "y": 1360 }
}
}
}
```
---
## 接口 2C 端生发接口
**说明**:输入用户正面照,输出生发后的图片,以及推荐的发际线(可能多张),并按合适度排序。
**请求**`POST /api/v1/hair/grow`
### 输入
图片参数见「通用约定 → 图片传参字段」。专属参数:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| beauty_enabled | bool | 否 | 生发图是否带美颜效果,默认 false。提供该开关 |
### 输出(data
`results`:发际线方案数组(可能多张),每个元素:
| 字段 | 类型 | 说明 |
|------|------|------|
| image_url | string | 生发后图片 URL |
| hairline_type | string | 图片对应的发际线形(如:花瓣形、波浪形) |
| order | int | 排序序号(1 = 最优,2 次之 …) |
### 响应示例(当前 Mock 返回值)
```json
{
"code": 0,
"message": "success",
"request_id": "mock-request-id",
"data": {
"results": [
{ "image_url": "https://hair.xiangsilian.com/static/sample.jpg", "hairline_type": "花瓣形", "order": 1 },
{ "image_url": "https://hair.xiangsilian.com/static/sample.jpg", "hairline_type": "波浪形", "order": 2 }
]
}
}
```
> 识别失败时返回通用错误码(1001 / 1002 / 1003 等)。
---
## 接口 3B 端生发接口
**说明**:输入医生/操作端的「划线图片」(在原图上标注了目标发际线),输出最合适的发际线图片 + 生发后图片。
**请求**`POST /api/v1/hair/grow-b`
### 输入
划线图片同样支持「文件 / URL / base64」三种方式(字段:`marked_image_file` / `marked_image_url` / `marked_image_base64`)。
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| marked_image_* | file / string | 是 | 已划线(标注发际线)的图片,三选一 |
| original_image_* | file / string | 是 | 原始用户照片,需同时上传 |
### 输出(data
| 字段 | 类型 | 说明 |
|------|------|------|
| best_hairline_image_url | string | 最合适的发际线图片 |
| hair_growth_image_url | string | 生发后图片 |
| hairline_type | string | 发际线形,需返回 |
### 响应示例(当前 Mock 返回值)
```json
{
"code": 0,
"message": "success",
"request_id": "mock-request-id",
"data": {
"best_hairline_image_url": "https://hair.xiangsilian.com/static/sample.jpg",
"hair_growth_image_url": "https://hair.xiangsilian.com/static/sample.jpg",
"hairline_type": "花瓣形"
}
}
```
---
## 接口 4:用户特征接口
**说明**:输入用户照片,输出 N 个用户特征字段。
**请求**`POST /api/v1/face/features`
### 输入
图片参数见「通用约定 → 图片传参字段」。本接口无其他专属参数。
### 输出(data
`data` 直接返回一个 **JSON 字符串**`features`),其内部字段不固定、可随时调整,由业务方约定。当前优先返回的字段如下(仅作示例,最终以实际返回为准):
| 字段 | 类型 | 说明 |
|------|------|------|
| face_shape | string | 脸形 |
| eyebrow_shape | string | 眉形 |
| facial_age | int | 面部年龄 |
| dynamic_static_type | string | 动静类型 |
| gender | string | 性别 |
| gene_style | object | 面部特征对应面部标签的「基因风格」 |
### 响应示例(当前 Mock 返回值)
```json
{
"code": 0,
"message": "success",
"request_id": "mock-request-id",
"data": {
"features": "{\"face_shape\": \"鹅蛋脸\", \"eyebrow_shape\": \"柳叶眉\", \"facial_age\": 26, \"dynamic_static_type\": \"静态\", \"gender\": \"女\", \"gene_style\": {\"label\": \"面部特征标签\", \"style\": \"基因风格示例\"}}"
}
}
```
> `features` 为字符串形式的 JSON,字段后续可随时增删,不固定。
---
## 接口 5:发际线 PNG 生成接口
**说明**:输入用户照片,返回 N 张用户发际线的 PNG 图片,并返回「最合适发际线」的面部中间点坐标。
**请求**`POST /api/v1/hairline/generate`
### 输入
图片参数见「通用约定 → 图片传参字段」。本接口无其他专属参数。
### 输出(data
| 字段 | 类型 | 说明 |
|------|------|------|
| hairline_images | object[] | N 张用户发际线 PNG,**数量 N 不固定**,已按合适度排序,元素见下表 |
| best_hairline_center_point | object | 最合适发际线的「面部中间点」坐标,原图像素:`{ "x": number, "y": number }` |
`hairline_images` 元素:
| 字段 | 类型 | 说明 |
|------|------|------|
| image_url | string | 发际线 PNG 图片 URL |
| order | int | 排序序号(1 = 最合适,依次递增) |
### 响应示例(当前 Mock 返回值)
```json
{
"code": 0,
"message": "success",
"request_id": "mock-request-id",
"data": {
"hairline_images": [
{ "image_url": "https://hair.xiangsilian.com/static/sample.jpg", "order": 1 },
{ "image_url": "https://hair.xiangsilian.com/static/sample.jpg", "order": 2 }
],
"best_hairline_center_point": { "x": 540, "y": 430 }
}
}
```
---
## 汇总:输入输出一览
| 接口 | 输入 | 主要输出 |
|------|------|----------|
| 1 四庭七眼测量 | 用户照片 | 标注 PNG(无人物)+ 四庭/七眼厘米数值与坐标 |
| 2 C 端生发 | 用户照片 | 生发后图片 + 多张发际线(带类型与排序) |
| 3 B 端生发 | 划线图片 | 最合适发际线图片 + 生发后图片 |
| 4 用户特征 | 用户照片 | N 个用户特征字段(脸形/眉形/年龄/动静/性别/基因风格…) |
| 5 发际线 PNG | 用户照片 | N 张发际线 PNG + 最合适发际线面部中间点坐标 |
---
## 已确认结论
- 图片传参:**文件 / URL / base64 三种都支持,严格互斥**(必须且只能传一个,base64 需带 `data:image/...;base64,` 前缀)。
- 默认单人;检测到 2~3 人报错提示客户端;单文件 **≤ 1 MB**;格式仅 **JPG / PNG**
- 所有坐标基准:**原图像素**。
- 接口 2:提供「美颜」开关 `beauty_enabled`
- 接口 3:需同时上传原图,需返回发际线类型。
- 接口 4`data` 返回**一个 JSON 字符串**,字段不固定,可随时调整。
- 接口 5:发际线 PNG 数量 **N 不固定**,需按合适度**排序**。
- 英文字段命名由本文档约定(见各接口表格)。
## 待与需求方确认的问题清单
1. 接口 1 标注图片设计稿(见下方说明)。
+156
View File
@@ -0,0 +1,156 @@
# 旷视具体需求
## 五个接口
---
## 1. 四庭七眼测量接口
**输入**:照片
**输出**:标注好数据的 PNG 图片(**不要人物**),以及四庭七眼坐标或图片,要显示厘米数(需要设计稿)。
### 接口返回数据要求
#### 四庭比例数值
- `<顶庭:XXcm>`,约占 `<全脸高度的 22%>`
- `<上庭:长约XXcm>`,约占 `<全脸高度的 25%>`
- `<中庭:长约XXcm>`,约占 `<全脸高度的 28%>`
- `<下庭:长约XXcm>`,约占 `<全脸高度的 25%>`
- `<全脸长约xxcm>`
#### 七眼比例数据
- `<眼宽度约xxcm>`
- `<脸宽xxcm>`
- `<两眼间距约xxcm>`
### UI 图片要求
| 项目 | 要求 |
|------|------|
| 字体及线颜色色值 | `#FFFFFF` 100% |
| 数值排布 | 四庭数值统一在图片**左侧**呈现;七眼间距**上下穿插**展示 |
| 字体 | PingFangSC-Regular,字号 10pt |
| 线宽 | 横线、竖线、虚线均为 1pt |
| 线样式 | 横线、竖线渐变消失;虚线两侧呈现箭头 |
### 参考图:分析图片规范 · 版本 2
![分析图片规范版本2](旷视具体需求_media/image4.jpeg)
图中标注说明:
| 标注 | 含义 |
|------|------|
| 头顶 | 头顶位置 |
| 发际线 | 发际线位置 |
| 眉心 | 眉心位置 |
| 鼻翼下缘 | 鼻翼下缘位置 |
| 下巴尖 | 下巴尖位置 |
| 顶庭 / 上庭 / 中庭 / 下庭 | 四庭分段(示例各约 3.44cm) |
### 设计稿参考(魔镜 2
![魔镜2设计稿](旷视具体需求_media/image6.png)
> 虚线:两侧箭头呈现。
---
## 2. C 端生发接口
**输入**:图片(用户拍摄的正面照)
**输出**:生发后图片 + 发际线图片(可能多张)
### 图片要求
- 上传是否有大小要求?图片最大、最小的要求是多少?
- **最低分辨率**1080 × 1920
- **最大分辨率**4000 × 5000
### 识别与错误
返回图片是否可以识别?如果图片识别失败,会返回具体错误原因吗?
- 【无法识别人像】【人像分辨率过低】
- 【角度问题,非正面照】
- 【男女】标签判断
### 其他
- 生发图是否可以带美颜效果?
### 需要返回的图片内容
示例 1(排序 1):
- `<生成后图片url>`
- `<图片对应的发际线形>`【花瓣形】
- `<排序>`【1】
示例 2(排序 2):
- `<生成后图片url>`
- `<图片对应的发际线形>`【波浪形】
- `<排序>`【2】
---
## 3. B 端生发接口
**输入**:划线图片(医生/操作端在原图上标注目标发际线)
**输出**:最合适发际线图片 + 生发后图片
---
## 4. 用户特征接口
**输入**:图片
**输出**N 个用户特征字段
### 优先返回
| 字段 | 说明 |
|------|------|
| `<脸形>` | 脸形 |
| `<眉形>` | 眉形 |
| `<面部年龄>` | 面部年龄 |
| `<动静类型>` | 动静类型 |
| `<性别>` | 性别 |
| `<面部特征>` 对应面部标签的 `<基因风格>` | 基因风格 |
**剩余标签 & 数据**:后续返回即可。
![用户特征参考](旷视具体需求_media/image9.png)
---
## 5. 发际线 PNG 生成接口
**输入**:用户照片
**输出**
- N 张用户发际线的 PNG 图片
- 最合适发际线面部中间点的坐标
---
## 文档附图
以下为原 Word 文档中的其他嵌入图片(水印/版式参考):
| 图片 | 说明 |
|------|------|
| ![image2](旷视具体需求_media/image2.png) | 文档附图 |
| ![image3](旷视具体需求_media/image3.png) | 文档附图 |
| ![image5](旷视具体需求_media/image5.png) | 文档附图 |
---
> 本文档由 `旷视具体需求.docx` 自动转换生成。图片保存在 `旷视具体需求_media/` 目录。
Binary file not shown.

After

Width:  |  Height:  |  Size: 89 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

+1
View File
@@ -1,2 +1,3 @@
fastapi==0.115.12
uvicorn[standard]==0.34.2
python-multipart==0.0.31
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB