定位
pf-face-service 是图模工坊的独立人脸检测微服务——为”自动避脸裁剪”提供算力。它已从早期的 Python/FastAPI 整体重写为 Rust(c6445c8),换来单文件二进制、更低内存与更强的预处理正确性。
- 语言/框架:Rust 2021 + Axum 0.8
- 推理引擎:ORT(ONNX Runtime)
2.0.0-rc.9 - 模型:SCRFD-500M(带关键点,
scrfd_500m_bnkps.onnx~2.5MB,CPU 即可实时) - 图像:
image 0.25|张量:ndarray|下载:reqwest(rustls)|EXIF:kamadak-exif - 端口:开发
3010(生产 Docker3011)|服务版本0.2.0
为什么用 Rust 重写? 单文件静态二进制部署最省心(无需 Python 运行时/虚拟环境),内存占用低,且能精细控制预处理(EXIF、步长对齐、插值),把”模型物理契约”落到实处(见 AGENTS.md 第五章)。
源码结构
1 | apps/pf-face-service/ |
两个 handler 直接写在 main.rs(无独立 routes/handlers 文件):CorsLayer::permissive() + TraceLayer,用 tokio::Semaphore(inference_concurrency) 限流,推理走 spawn_blocking。两个端点均无鉴权——务必置于内网。
接口协议
POST /api/face/detect
1 | // 请求(camelCase) |
x/y/w/h为相对原图的归一化坐标[0,1](左上原点 + 宽高),保留 6 位小数;score亦[0,1]。缩放算在调用方。elapsedMs覆盖整个 handler(下载 + 预处理 + 推理),非纯推理耗时。- 错误码:
"4000"(下载/解码失败,HTTP 400)、"5000"(推理失败/panic,500)、"5003"(模型未就绪/并发错误,503)。
GET /health
HTTP 恒 200,就绪与否看 code:
1 | { "code": "0000", "message": "ok", |
模型加载失败时 code="5000"、status="degraded"、modelLoaded=false、reason 给原因。
调用链路
1 | 三端前端 / 合成 Job |
用户端在传图后由后台并发队列调用(minScore 0.45)做人脸元数据补偿,供自动填充/避脸裁剪使用(见 用户端 H5)。
模型物理契约
严格按 SCRFD 训练基准对齐(AGENTS.md 第五章):
- 张量布局:NCHW
(1, 3, H, W);色彩:RGB(to_rgb8) - 归一化:
(pixel - 127.5) / 128.0(mean 127.5, 1/std = 1/128) - 输入尺寸不是固定 640×640:动态——最长边限制到
maxDetectSize(默认 640),两边向下取整到 32 的倍数(步长对齐,防一维展平索引逐行漂移) - 解码:strides
[8, 16, 32],每格num_anchors = 2,按名读取score_8/16/32+bbox_8/16/32(关键点 kps 输出未消费) - ONNX 输入节点名
"input.1",GraphOptimizationLevel::Level3,intra/inter op 线程数来自 config
预处理保真(image_loader.rs)
- EXIF 姿态矫正(
correct_image_orientation):用 kamadak-exif 读Orientation,1~8 全覆盖(2→水平翻转、3→180°、4→垂直翻转、5→90°+翻转、6→90°、7→90°+翻转、8→270°),修移动端横置/倒置漏检 - 插值红线:缩放用
FilterType::Triangle(双线性),绝不用 Nearest——避免阶梯锯齿破坏卷积核边缘梯度 - 下载 + 预处理在
spawn_blocking内,URL 必须 http/https
配置外置(config.json)
算法敏感超参全部外置(AGENTS.md 硬约束):
1 | { |
配置搜索顺序:$CONFIG_PATH → ./config.json → apps/pf-face-service/config.json → ../pf-face-service/config.json → 内建默认。
环境变量覆盖(优先级最高):HOST/FACE_HOST、PORT/FACE_SERVICE_PORT、FACE_MODEL_PATH、FACE_DETECT_TIMEOUT_MS、FACE_MAX_DETECT_SIZE、FACE_MIN_SCORE、FACE_MAX_FACES、FACE_NMS_IOU_THRESHOLD、FACE_INTRA_OP_THREADS、FACE_INTER_OP_THREADS、FACE_INFERENCE_CONCURRENCY、RUST_LOG。
构建与运行
1 | cd apps/pf-face-service |
容器化(deploy/pf-face-service/Dockerfile):基础镜像 debian:trixie-slim + libgomp1 libstdc++6,拷入预编译好的 Linux 二进制 + models/,PORT=3011、FACE_MODEL_PATH=/app/models/scrfd_500m_bnkps.onnx、TZ=Asia/Shanghai,EXPOSE 3011。镜像内不做 cargo build——由部署脚本用 rust:latest 容器预编译(见 部署)。
性能与边界
| 维度 | 表现 |
|---|---|
| 单图检测(CPU) | 数十毫秒级 |
| 内存 | 远低于旧 Python 版(单二进制 + 小模型) |
| 并发 | inferenceConcurrency(默认 4)Semaphore 限流 + spawn_blocking |
| 鉴权 | 无——必须内网,经 pf-service 代理 |
| 输入 | 仅 http/https URL;不接受 base64/文件上传 |
二开提示
换检测模型(如识别二维码/文字行/物体):把同协议 ONNX 放到 models/,在 detector.rs 调整解码逻辑,保持响应字段不变并升 modelVersion(取 ONNX 文件名 stem),上游零侵入。