feat(接口99): 人脸特殊点检测 + 网关转发路由;文档同步至当前代码
- worker: POST /api/v1/face/special-points,60 个固定编号关键点(6组) + 5 个发际线特殊点(沿指定关键点方向射线步进,与 BiSeNet 头发 mask 首次相交; 双射线取中点,left_left=mid(333→332, 298→284) 与右侧镜像) - 网关: 同路径盲转发路由(调用频率低,维持转发不占网关依赖; 响应无 *_base64 字段,改写逻辑天然跳过) - 测试页 static/test_interface99.html:分组着色小点 + 点ID标注(可开关) + 发际线点大圆标签 + JSON 展示 - 文档: CLAUDE.md 重写至当前架构;实现说明/接口文档补接口 9~12/99 契约; 网关待改动补 §7(9/10已加) §8(11/12待加) §9(接口99部署步骤)
This commit is contained in:
@@ -1893,6 +1893,70 @@ async def head_band(
|
||||
return err(1007, f"处理失败:{ex}")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 接口 99:人脸特殊点检测(固定关键点 + 发际线特殊点)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@app.post(
|
||||
"/api/v1/face/special-points",
|
||||
summary="接口99 人脸特殊点检测",
|
||||
tags=["人脸分析"],
|
||||
description=f"""
|
||||
输入一张人脸照片,返回 MediaPipe Face Mesh 一组**固定编号特殊点**的像素坐标,
|
||||
以及 5 个**发际线特殊点**(射线与 BiSeNet 头发分割发际线的交点,见 hairline_grow 同款分割)。
|
||||
|
||||
**字段命名**:`<组英文名>_<点ID>`,如 `eyebrow_107`、`mouth_17`;发际线点用给定字段名
|
||||
(`hair_line_center` 等)。坐标为原图像素坐标(int),发际线点不可得(无头发/分割失败)时为 null。
|
||||
|
||||
| 组 | 字段前缀 | 点 ID |
|
||||
|----|----------|-------|
|
||||
| 眉毛 | eyebrow | 107 66 105 63 70 336 296 334 293 300 |
|
||||
| 眼睛 | eye | 189 222 224 113 226 413 442 444 342 446 |
|
||||
| 嘴 | mouth | 0 37 40 67 91 84 17 405 375 409 269 |
|
||||
| 中线鼻 | nose_midline | 9 168 197 5 4 194 278 48 437 217 |
|
||||
| 面颊 | cheek | 101 117 123 187 205 50 330 346 352 280 425 |
|
||||
| 边线 | face_edge | 162 234 58 136 149 152 378 365 288 401 366 389 251 |
|
||||
|
||||
**发际线点算法**:从关键点 a 沿 a→b 方向逐像素步进,首次进入头发 mask 处即交点;
|
||||
双射线的点取两交点中点(仅一条命中返回该点,均未命中为 null):
|
||||
- `hair_line_center`:151→10
|
||||
- `hair_line_right`:mid(108→109, 69→67)
|
||||
- `hair_line_right_right`:mid(104→103, 68→54)
|
||||
- `hair_line_left`:mid(337→338, 299→297)
|
||||
- `hair_line_left_left`:mid(333→332, 298→284)
|
||||
|
||||
{_image_fields_desc}
|
||||
""",
|
||||
)
|
||||
async def face_special_points(
|
||||
image_file: Optional[UploadFile] = File(default=None, description="上传图片文件(JPG/PNG)"),
|
||||
image_url: Optional[str] = Form(default=None, description="图片 URL"),
|
||||
image_base64: Optional[str] = Form(default=None, description="图片 base64(需带 data:image/...;base64, 前缀)"),
|
||||
):
|
||||
"""接口99:固定关键点 + 发际线特殊点,像素坐标 JSON 返回。"""
|
||||
raw, e = await resolve_image_bytes(image_file, image_url, image_base64)
|
||||
if e is not None:
|
||||
return e
|
||||
|
||||
image = cv2.imdecode(np.frombuffer(raw, np.uint8), cv2.IMREAD_COLOR)
|
||||
if image is None:
|
||||
return err(1008, "图片格式不支持(仅 JPG / PNG)")
|
||||
|
||||
try:
|
||||
from fastapi.concurrency import run_in_threadpool
|
||||
from face_analysis.special_points import detect_special_points, NoFaceError, NonFrontalError
|
||||
try:
|
||||
data = await run_in_threadpool(detect_special_points, image)
|
||||
except NoFaceError:
|
||||
return err(1001, "无法识别人像")
|
||||
except NonFrontalError:
|
||||
return err(1003, "角度问题,请上传正面照")
|
||||
return ok(data)
|
||||
except Exception as ex: # noqa: BLE001
|
||||
logger.exception("接口99 处理异常")
|
||||
return err(1007, f"处理失败:{ex}")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 接口 11:发际线生发(接口9 遮罩 + change_hair 换发型 + 按遮罩羽化贴回)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user