接口10:头部外缘膨胀带遮罩 + 分步可视化
新增 POST /api/v1/head/band(worker + 网关代理)与测试页 test_interface10.html: - 先和接口9 一样得到内缩后的基准遮罩(含额头的闭合区域外缘朝151内缩 erode_cm、 底线不动,默认1.2cm),在此基础上取外轮廓线,去掉贴着底部分割线的那一段。 - 把外轮廓线膨胀成带子(总宽 dilate_cm,默认2cm;半径=总宽/2,虹膜标定换算)。 - 裁到分割线以上(不越过底线)。BiSeNet/SegFormer 两套并排对比。 - 两个可调参数 erode_cm + dilate_cm(页面数字框+滑块+localStorage)。 - 复用 head_mask 的构件,无新依赖;纯新增,不改动既有接口。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1146,6 +1146,92 @@ async def head_mask(
|
||||
return err(1007, f"处理失败:{ex}")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 接口 10:头部外缘膨胀带遮罩
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@app.post(
|
||||
"/api/v1/head/band",
|
||||
summary="接口10 头部外缘膨胀带遮罩",
|
||||
tags=["人脸分析"],
|
||||
description=f"""
|
||||
先和接口9 一样得到**内缩后的基准遮罩**(含额头的闭合区域外缘朝151内缩 erode_cm、底线不动,默认1.2cm),
|
||||
在此基础上生成一条沿头部外缘的膨胀带遮罩:
|
||||
|
||||
1. 取基准遮罩的**外轮廓线**,去掉贴着底部分割线的那一段(只留头发/头部外缘弧线)。
|
||||
2. 把外轮廓线膨胀成带子(**总宽 dilate_cm,默认 2cm,可调**;半径=总宽/2,虹膜标定换算像素)。
|
||||
3. 裁到分割线以上(不越过底线)。
|
||||
|
||||
{_image_fields_desc}
|
||||
|
||||
返回 `data` 内含 `steps_common`(关键点/分割线、上半区)与 `bisenet` / `segformer`
|
||||
两组结果(各含 `base_mask` / `contour` / `band_overlay` / `mask`)。经网关时所有
|
||||
`*_base64` 图片字段会被落盘改写为 `*_url`。
|
||||
""",
|
||||
responses={
|
||||
200: {
|
||||
"description": "成功",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"example": {
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"request_id": "mock-request-id",
|
||||
"data": {
|
||||
"px_per_cm": 48.12,
|
||||
"erode_cm": 1.2,
|
||||
"erode_px": 58,
|
||||
"dilate_cm": 2.0,
|
||||
"dilate_radius_px": 48,
|
||||
"image_size": {"width": 1080, "height": 1440},
|
||||
"steps_common": {"landmarks_baseline_url": SAMPLE_IMAGE_URL,
|
||||
"upper_region_url": SAMPLE_IMAGE_URL},
|
||||
"bisenet": {"base_pixels": 96000, "band_pixels": 42000,
|
||||
"base_mask_url": SAMPLE_IMAGE_URL,
|
||||
"contour_url": SAMPLE_IMAGE_URL,
|
||||
"band_overlay_url": SAMPLE_IMAGE_URL,
|
||||
"mask_url": SAMPLE_IMAGE_URL},
|
||||
"segformer": {"base_pixels": 97000, "band_pixels": 43000,
|
||||
"base_mask_url": SAMPLE_IMAGE_URL,
|
||||
"contour_url": SAMPLE_IMAGE_URL,
|
||||
"band_overlay_url": SAMPLE_IMAGE_URL,
|
||||
"mask_url": SAMPLE_IMAGE_URL},
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
async def head_band(
|
||||
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, 前缀)"),
|
||||
erode_cm: float = Form(default=1.2, description="基准遮罩外缘朝151内缩距离(厘米,同接口9),默认 1.2"),
|
||||
dilate_cm: float = Form(default=2.0, description="外轮廓线膨胀后带子总宽(厘米),默认 2.0"),
|
||||
):
|
||||
"""接口10:头部外缘膨胀带遮罩 + 分步可视化"""
|
||||
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.head_band import generate_head_band, NoFaceError
|
||||
try:
|
||||
data = await run_in_threadpool(generate_head_band, image, erode_cm, dilate_cm)
|
||||
except NoFaceError:
|
||||
return err(1001, "无法识别人像")
|
||||
return ok(data)
|
||||
except Exception as ex: # noqa: BLE001
|
||||
logger.exception("接口10 处理异常")
|
||||
return err(1007, f"处理失败:{ex}")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 健康检查
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user