定位

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(生产 Docker 3011)|服务版本 0.2.0

为什么用 Rust 重写? 单文件静态二进制部署最省心(无需 Python 运行时/虚拟环境),内存占用低,且能精细控制预处理(EXIF、步长对齐、插值),把”模型物理契约”落到实处(见 AGENTS.md 第五章)。

源码结构

1
2
3
4
5
6
7
8
9
10
11
apps/pf-face-service/
├── Cargo.toml
├── config.json / config.example.json
├── models/scrfd_500m_bnkps.onnx
└── src/
├── lib.rs # 声明 config / detector / image_loader / types
├── main.rs # Axum server + 两个 handler
├── config.rs # 配置结构 + 文件/环境变量加载
├── detector.rs # ONNX session + SCRFD 解码 + NMS
├── image_loader.rs # 下载 + EXIF 矫正 + 缩放 + 张量构建
└── types.rs # 请求/响应 DTO

两个 handler 直接写在 main.rs(无独立 routes/handlers 文件):CorsLayer::permissive() + TraceLayer,用 tokio::Semaphore(inference_concurrency) 限流,推理走 spawn_blocking。两个端点均无鉴权——务必置于内网。

接口协议

POST /api/face/detect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 请求(camelCase)
{
"imageUrl": "http://localhost:1280/pf-bucket/public/demo.png", // 必填
"minScore": 0.35, // 可选,默认 0.35
"maxFaces": 10, // 可选,默认 10
"maxDetectSize": 1024, // 可选,缺省回落 config 的 640
"nmsIouThreshold": 0.4 // 可选,缺省回落 config 的 0.4
}

// 成功(code = "0000")
{
"code": "0000",
"message": "检测成功",
"data": {
"image": { "width": 1080, "height": 1920 }, // 原图尺寸
"faces": [{ "x": 0.33, "y": 0.18, "w": 0.22, "h": 0.22, "score": 0.98 }],
"modelVersion": "scrfd_500m_bnkps",
"elapsedMs": 13
}
}
  • 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
2
3
4
{ "code": "0000", "message": "ok",
"data": { "status": "ok", "service": "pf-face-service", "version": "0.2.0",
"modelPath": "models/scrfd_500m_bnkps.onnx",
"modelLoaded": true, "modelVersion": "scrfd_500m_bnkps", "reason": null } }

模型加载失败时 code="5000"、status="degraded"、modelLoaded=false、reason 给原因。

调用链路

1
2
3
4
5
6
7
8
9
10
三端前端 / 合成 Job
│ 1. POST /api/app/face/detect(pf-service 代理,鉴权 + 参数校验 + 统一响应)
▼
pf-service · shared/faceRoutes.ts
│ 2. HTTP POST → {faceService.baseUrl}/api/face/detect(相对图 URL 补成绝对 MinIO 地址)
▼
pf-face-service · Axum + SCRFD/ONNX
│ 3. reqwest 拉取 imageUrl(校验 http/https,带超时)
▼
MinIO / 外部图床

用户端在传图后由后台并发队列调用(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
2
3
4
5
6
{
"server": { "host": "0.0.0.0", "port": 3010, "timeoutMs": 30000, "inferenceConcurrency": 4 },
"model": { "path": "models/scrfd_500m_bnkps.onnx", "intraOpThreads": 2, "interOpThreads": 1 },
"detection": { "maxDetectSize": 640, "minScore": 0.35, "maxFaces": 10, "nmsIouThreshold": 0.4 },
"logging": { "level": "info,pf_face_service=debug" }
}

配置搜索顺序:$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
2
3
4
5
6
7
cd apps/pf-face-service
cargo run # 开发
cargo run --release # 生产
cargo test --release # 测试

# monorepo
bun dev:face # bun run --filter @wangijun/pf-face-service dev(= cargo run)

容器化(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),上游零侵入。

下一步

站内搜索

没有找到内容!