# 人脸识别方案 B(ai_face_det + ai_face_recog)设计文档 > 目标:在现有 **Graph(DAG)+Plugin Node** 框架中,实现“有人脸库,可识别具体姓名/陌生人”。 > > 约束: > - 插件化接入:新能力以 Node 插件形式加入 pipeline;不改动主业务流程即可启用/禁用。 > - 与现有 `publish` 对 `Frame::user_meta` 的使用不冲突。 > - 运行平台:RK3588(RKNN/NPU 优先),CPU 负责检索与业务逻辑。 --- ## 1. 总体架构 ### 1.1 现有框架要点(与本方案的衔接) - Node 插件接口:`INode`(`Init/Start/Process/Stop/UpdateConfig`),通过 `REGISTER_NODE` 导出符号加载。 - 数据承载:`Frame` 内含图像数据(DMA/CPU)以及元数据: - `frame->det`:当前用于通用检测(`DetectionResult`) - `frame->user_meta`:扩展元数据(当前 `publish` 会写入编码包 meta) - 推理:`AiScheduler` 支持 RKNN 模型加载与推理(可复用)。 ### 1.2 方案 B 的节点拆分 新增两个 **Filter** 插件: 1) `ai_face_det`:人脸检测(可选输出关键点) - 输入:RGB/BGR/NV12(根据你的 preprocess 输出) - 输出:人脸框列表(可含 5 点关键点)、检测置信度 2) `ai_face_recog`:人脸对齐/裁剪 + 特征提取 + 人脸库检索 + 陌生人判定 - 输入:原始 Frame + `ai_face_det` 的检测结果 - 输出:识别结果(person_id/name/similarity/unknown)、可选把“姓名”写回可视化/告警侧 推荐拓扑: ```text [input_*] → [preprocess] → [ai_face_det] → [ai_face_recog] → [osd/alarm/publish/storage] ``` --- ## 2. 数据与元数据协议(关键设计点) ### 2.1 为什么不直接复用 `Frame::user_meta` 当前 `publish` 节点会写 `frame->user_meta`(编码包 meta)。如果 `ai_face_*` 也直接写同一字段,会出现覆盖/冲突。 ### 2.2 推荐的元数据承载方式(二选一) **选项 1(推荐,长期干净)**:扩展 `Frame` 结构新增专用字段,例如: - `std::shared_ptr face_det;` - `std::shared_ptr face_recog;` 优点:不与 `user_meta` 冲突,类型清晰,插件协作简单。 **选项 2(兼容性强,侵入更小)**:引入“MetaBundle”统一承载多类 meta,并把 `Frame::user_meta` 变成 bundle。 - `user_meta` 保存 `std::shared_ptr`,由 bundle 内部维护多个 typed slot(例如 publish_meta、face_meta…)。 优点:不新增 Frame 字段;缺点:需要先统一改造现有写 `user_meta` 的插件(至少 publish/alarm)。 > 本文后续描述以 **选项 1** 为主(更直观)。 ### 2.3 FaceDetResult 数据结构(建议) - `faces[]`:每个元素包含 - `bbox`(x,y,w,h,图像坐标) - `score` - `landmarks[5]`(可选:左眼/右眼/鼻尖/左嘴角/右嘴角) - `track_id`(可选,后续可加跟踪) ### 2.4 FaceRecogResult 数据结构(建议) - `items[]`:每个元素与一个人脸对应 - `bbox` - `best_person_id` / `best_name` / `best_sim` - `unknown`(bool) - `second_sim`(可选,用于 margin 判定/调试) - `embedding`(可选:用于注册/调试;生产可关闭以省内存) --- ## 3. 算法与模型选型 ### 3.1 检测模型(ai_face_det) 推荐轻量:SCRFD/RetinaFace 类人脸检测(可带 5 点)。 - 输入分辨率:320/640 任选(权衡精度与速度) - 输出:bbox + score +(可选)5 点 ### 3.2 特征模型(ai_face_recog) 推荐:ArcFace 系列轻量 backbone(MobileFaceNet 等)。 - 输入:112x112 aligned face - 输出:128D 或 512D embedding - 后处理:L2 normalize ### 3.3 相似度与陌生人判定 - 相似度:cosine(归一化后点积) - 判定规则建议: - `top1_sim >= T_accept` 且(可选)`top1_sim - top2_sim >= T_margin` → 识别为该人 - 否则 unknown 阈值需要按你的数据集标定(不同模型差别大),但架构层支持动态配置。 --- ## 4. 人脸库(Gallery)设计 ### 4.1 存储形态 建议分两层: - **内存索引**:运行时检索用(向量矩阵 + person 映射) - **持久化存储**:用于重启恢复/管理 持久化方案: 1) SQLite(推荐): - `person(id, name, created_at, updated_at, extra_json)` - `embedding(person_id, emb BLOB, normed, created_at)` 2) 文件(简化版): - `gallery.bin`(向量)+ `gallery.json`(person 映射) ### 4.2 多样本策略 - 每人保存 N 个样本 embedding(建议 3~10)。 - 运行时维护 `centroid`(平均向量再归一化)提高稳定性;检索可先比 centroid,再必要时比多样本。 ### 4.3 检索实现 - 小库(≤几千人):CPU 暴力点积足够。 - 大库(≥几万人):再考虑 ANN(HNSW/Faiss),属于后续优化,不影响方案 B 的接口。 --- ## 5. 两个节点的职责、接口与配置 ### 5.1 ai_face_det(插件) **职责**: - 从 `Frame` 获取图像(必要时做色彩/resize) - 调用 `AiScheduler` 推理检测模型 - 生成 `FaceDetResult` 写入 `frame->face_det` **配置建议(示例)** ```json { "id": "face_det", "type": "ai_face_det", "role": "filter", "enable": true, "model_path": "/models/scrfd_640.rknn", "conf": 0.6, "nms": 0.4, "max_faces": 10, "output_landmarks": true, "input_format": "rgb" } ``` ### 5.2 ai_face_recog(插件) **职责**: - 读取 `frame->face_det` - 对每个 face: - (可选)按 5 点对齐(affine) - crop/resize 到识别模型输入 - `AiScheduler` 推理得到 embedding,并 L2 normalize - 在人脸库检索,输出 best match 或 unknown - 写入 `frame->face_recog` **配置建议(示例)** ```json { "id": "face_recog", "type": "ai_face_recog", "role": "filter", "enable": true, "model_path": "/models/arcface_112.rknn", "gallery": { "backend": "sqlite", "path": "/data/face_gallery.db", "load_on_start": true, "cache_in_memory": true }, "threshold": { "accept": 0.45, "margin": 0.05 }, "max_faces": 10, "align": true, "emit_embedding": false } ``` --- ## 6. 与 OSD / Alarm / Publish 的集成策略 ### 6.1 OSD 显示姓名 现状:`osd` 只读 `frame->det` 并通过 `cls_id → labels` 显示类别名,不支持动态姓名。 推荐方案: - 扩展 `osd`:若 `frame->face_recog` 存在,则在 bbox 处绘制 `name(sim)`;否则保持原逻辑。 - 或新增 `osd_face` 插件专门画人脸识别结果(与原 `osd` 并存)。 ### 6.2 Alarm 规则与上报 推荐:在 `alarm` 中新增一种事件源: - 读取 `frame->face_recog`,按规则触发(例如:出现黑名单人员、陌生人出现、某人出现次数/停留时间)。 ### 6.3 Publish/Storage - 与人脸识别无强耦合:只要不覆盖 `user_meta`,推流/录像可照常工作。 --- ## 7. 性能与线程模型建议 - `ai_face_det` 和 `ai_face_recog` 都是 filter 节点,由 GraphMgr 驱动 `Process(frame)`。 - 推理层复用 `AiScheduler`(其内部对每个 model context 有互斥保护),可减少重复加载。 - 关键优化点: - 尽量在 preprocess 输出 RGB/BGR,避免在两个节点重复色彩转换。 - 对齐与裁剪尽量复用 CPU SIMD 或 RGA(若你已有 RGA 路径)。 - 检索使用批量点积(向量矩阵乘),人数不大时可用简单循环即可。 --- ## 8. 一步步实现计划(后续按此执行) > 你确认开始开发后,我们将严格按以下步骤逐步实现与验证。 ### Step 0:确定元数据承载方式(必须先定) - 选择“Frame 新字段(face_det/face_recog)”或“MetaBundle”。 - 若选择新字段:需要同步更新相关 include、序列化/metrics(如有)以及 OSD/Alarm 读取逻辑。 ### Step 1:新增公共头文件与数据结构 - 定义 `FaceDetResult` / `FaceRecogResult`(include 目录下)。 ### Step 2:实现插件 `ai_face_det` - CMake 加入新插件目标 - 使用 `AiScheduler` 加载/推理检测模型 - 输出 `FaceDetResult` ### Step 3:实现插件 `ai_face_recog` - 读取 `FaceDetResult` - 对齐/裁剪/推理/归一化 - 实现 Gallery:SQLite 或文件(先做最简单可用版本) - 输出 `FaceRecogResult` ### Step 4:OSD/Alarm 接入(最小可用) - OSD:显示 `name`/`unknown` - Alarm:支持“陌生人出现/指定人员出现”触发 ### Step 5:配置与热更新 - `UpdateConfig` 支持阈值、gallery reload 等 ### Step 6:验证 - 使用样例视频/RTSP 流验证:识别准确率、误认率、性能、内存