ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

亿级人脸库5秒检索:1:N与动态比对方案设计

亿级人脸库5秒检索:1:N与动态比对方案设计

简介:围绕人脸识别项目落地编写的整体解决方案文档,面向安防系统集成、方案设计与公安信息化建设人员,帮助梳理从需求分析到建设目标、性能指标与实施原则的完整思路。文档以1个docx文件交付,压缩包约8.94MB,结构层次递进,便于按章节查阅和二次整理。内容沿动态人像天网与静态人像天网两条主线展开,覆盖重点人员布控、高危人员预警、人证合一核验、不明身份确认、身份信息查重及洗白身份排查等典型场景,并对中心库容量、实时比对处理能力、网络环境和7×24小时稳定运行等指标作出说明。已有716人学习下载,适合需要撰写人脸识别技术方案、搭建系统框架或补充项目论据的读者作为参考底稿,也可用于比较不同应用模块的建设边界与功能划分。

1. 从"5 秒出结果"这条硬指标倒推:这套人脸识别方案到底解决什么

人脸识别解决方案这六个字,网上能搜到一大堆,真正能拿去交毕设、拿去给评审过会的没几个。常见的翻车方式是:需求写成一段背景描述,指标写成"高准确率、高并发",到最后没人能验证这套东西到底行不行。真实项目是反过来的——先钉死一条必须达成的硬指标,比如在千万级注册库里做一次 1:N 检索端到端不超过 5 秒,同时还要扛住几十路前端卡口推过来的实时流。指标一立,架构、库表、算力、验收方式基本就被反推出来了。

这套方案要解决的就是这件事:把重点区域里"看得见但认不出"的问题,变成"抓得到、比得上、查得回"。它适合两类人——正在做人脸识别方向毕设或课程设计、需要一份结构完整能照着改的学生;以及要给园区、场馆、交通枢纽出一份人员管控交付方案的从业者。整套内容按需求、选型、架构、实现、验收五段推进,每一段都落到可验证的口径上。

2. 需求拆解与技术选型:三类业务库和四种比对模式的取舍

需求分析这一步最容易糊弄过去,也最容易在后面返工。一条实用的做法是把笼统的"人员管控"拆成两条完全不同的技术路线:动态和静态。动态指的是前端视频流实时进来,抓拍、提特征、和黑名单比对、报警,全链路在秒级完成;静态指的是事后拿一张照片或一段录像,去抓拍库、注册库里做检索,出这个人的历史轨迹。两者对系统的要求几乎是相反的,动态拼的是延迟和吞吐,静态拼的是召回率和大库容量。把这两条混在一起谈"性能指标",方案基本就废了。

2.1 场景分层:动态天网与静态天网的边界

动态侧的核心场景是实时预警。人员密集区域的关键出入口、通道布置人脸卡口,后端对重点关注人员做名单布控,视频流实时比对,命中就推报警。这条链路对时延极度敏感——从人脸进入画面到平台弹出报警,通常要求控制在 2 秒以内,慢一点人已经走出管控范围了。

静态侧的核心场景是身份确认和事后追查。日常巡查时用前端摄像机或移动终端抓一张脸,后端去比对注册库确认身份;或者拿一张可疑人员的照片,在抓拍库里检索他过去 90 天在哪些点位出现过、什么时间段出现,形成轨迹。这类请求是偶发的、并发的,但单次计算量大,容忍几秒的响应时间。

分层之后有个直接好处:两套链路的资源可以分开规划。动态侧按路数配算力,静态侧按日检索次数配算力,扩容时互不牵连。我一般会在方案里明确写一句"动态比对服务与静态检索服务独立部署、独立扩容",这一句在评审时非常加分,因为它说明你想过资源争抢的问题。

2.2 三类业务库的职责划分:抓拍库、注册库、黑名单库

整个系统的底座就是三个库。这三个库不是随便分的,它们的数据来源、增长速度、保留策略和比对方式全都不同,混成一个库会同时拖垮性能和可维护性。

库类型数据来源典型量级保留周期主要用途比对方式
人脸抓拍库前端卡口实时产生日增 10 万~50 万条90~180 天,冷热分层事后轨迹检索1:N 检索
人脸注册库主动采集、批量导入十万~千万级长期,随业务增长身份确认底座1:N、N:N
黑名单库业务方下发几万~几十万动态增删实时预警动态流式比对

抓拍库是量最大、最不值钱也最值钱的库。说它不值钱,是因为 90% 的抓拍图没有业务价值,全是路过的普通人;说它值钱,是因为一旦出了事,轨迹全靠它。所以抓拍库的设计重点在写入吞吐和冷热分层,不在查询灵活性。注册库相反,量稳定、查询频繁、准确率要求高,设计重点在索引质量和特征一致性。黑名单库量最小但优先级最高,必须独立索引、独立内存常驻,绝不能和抓拍库共享一套检索通道——一次大范围的历史检索把黑名单比对的算力抢走,实时预警就哑了,这种事故在实际项目里不算罕见。

2.3 比对模式选型:1:1、1:N、N:N 与动态比对

四种比对模式对应四类业务,阈值口径完全不能混用,这是新手最容易踩的坑。

1:1 人证合一:现场抓拍图和证件照做一对一比对,输出一个相似度分数,判断是不是同一个人。这种模式因为比对范围固定,阈值可以设得比较严,通常要求误识率压到万分之一以下,宁可多拦一次让人工复核,也不能放过。

1:N 静态检索:一张图去整个库里找最像的 N 个。这里的关键是 N 不能只返回 1 个——工程上一般返回 Top 10 到 Top 20,交给人工复核。因为 1:N 的排序第一位未必是正确答案,尤其在几十万路抓拍图里,光线角度差异很大。方案里要写清"返回 Top N + 相似度分数 + 人工复核"这个闭环,而不是承诺"系统自动认定"。

N:N 碰撞比对:两个库互相查,比如常住人口库和流动人口库互相比对,找出重复或冒用身份的记录。这种任务计算量是 N 乘 M 级别,绝对不能实时做,必须离线批处理、分批跑、断点续跑。

动态比对:黑名单流式比对,本质是"每来一张抓拍图,就去一个小库里查一次"。它的技术难点不在算法,在工程——每秒几十张图的并发下,怎么保证每张图都在 1 秒内出结果。

提示:1:1 的阈值和 1:N 的阈值绝对不能互相套用。1:N 的比对次数是库容量的函数,库越大,随机撞上高分的概率越高,阈值必须随容量分档调整。这一点后面避坑那章还会展开。

2.4 算法与算力:为什么不建议单机堆 GPU

算法层面,方案里用的是基于深度学习的检测、跟踪、抓拍、质量评分和特征提取一整套流程,核心是特征提取这一步——把一张人脸小图变成固定维度的特征向量,之后所有比对都是向量之间的距离计算。

算力规划上有个常见误区:把所有活都塞进一台大机器,堆四张卡。问题在于特征提取和向量比对是两种完全不同的负载——特征提取吃 GPU 算力,向量比对吃内存带宽和 CPU,两者混在一台机器上会互相拖累,而且没法独立扩容。实际项目里我倾向的做法是按服务拆分:

  • 特征提取服务:GPU 节点,按前端路数和每秒抓拍数配卡,可以水平加节点。
  • 比对检索服务:内存型节点,向量索引常驻内存,按库容量配内存。
  • 库管理服务:CPU 节点,负责库的增删改、特征入库、索引重建。

容量估算必须提前做,而且要把索引开销算进去。举个例子:1000 万条 512 维 float32 特征,裸数据约 20GB(1000 万 × 512 × 4 字节),但向量索引通常有 1.2~1.5 倍的额外开销,再加一份副本做高可用,实际要按 50GB 以上预留内存。如果库容量是 1 亿条,单节点内存就吃不消了,必须做分片——按库类型分片、按时间分片都可以,但分片键要在方案设计阶段就定死,上线后再改代价极大。

3. 总体架构设计:五层结构与分布式集群的职责划分

整套系统按五层切分:前端卡口层、接入与流媒体层、识别服务层、数据与存储层、业务应用层。分层的意义不在画图好看,而在于每一层都能独立替换和扩容。前端换品牌不影响上面四层,算法升级不动业务代码,存储扩容不动服务逻辑。

分布式架构还有一个隐性要求:任何单点故障不能导致全系统不可用。所以识别服务器要做集群,库管理要有主备,平台侧要有重试和降级。方案里写"支持线性扩展"这句话很容易,但支撑这句话的是后面这些具体设计。

3.1 前端卡口层:抓拍单元的四个关键动作

普通高清网络摄像机和人脸抓拍单元是两种东西。前者只做图像采集、编码、传输;后者内置智能分析算法,能在设备侧完成一整套抓拍流程:

  1. 人脸检测:在画面里定位所有人脸的位置和大小。
  2. 人脸跟踪:对同一个人做连续跟踪,形成一段轨迹。
  3. 质量评分:对跟踪过程中的每一帧算一个质量分,评估清晰度、角度、遮挡、亮度。
  4. 最优帧选取:从这段轨迹里挑出质量最高的一帧作为抓拍图,同时输出人脸小图坐标。

这套在设备侧完成的动作,直接决定了后端要处理的数据量。一个点位一天路过几万人,如果不过滤全部上传,后端压力会被放大一个数量级。抓拍单元通常还带人脸区域自动曝光优化和小图优化处理——逆光场景下,全图曝光正常但人脸是黑的,这种图提特征基本等于随机数,前端做区域曝光能救回很大一部分。

前端还要配抓拍策略:同一人重复抓拍的抑制时间窗(比如 30 秒内同一人只上传一条)、最小人脸像素宽度(低于这个宽度直接丢弃)、抓拍间隔。这几个参数看着琐碎,但它们对后端库容量和检索速度的影响比算法优化还大。

3.2 识别服务层:特征提取与比对服务的解耦

识别服务层是整个系统的心脏,内部再拆三块:特征提取服务、比对服务、库管理服务。三者之间通过 API 和消息队列通信,不直接共享内存或数据库连接。

这样拆的直接收益是队列隔离。实时动态比对走高优先级队列,静态检索走普通队列,离线碰撞比对走低优先级队列并限制并发。如果没有这层隔离,一个分析师提交一次全库检索,就可能把实时的黑名单报警堵在后面——白天业务高峰出这种事,运维电话会被打爆。

比对服务对外提供的接口一般有这几类:

接口输入输出典型耗时
特征提取一张人脸小图特征向量(含版本号)20~80ms
1:1 比对两张图或两个特征相似度分数 + 判定<100ms
1:N 检索一张图 + 目标库 + TopNTopN 候选列表0.5~5s
动态比对抓拍图 + 黑名单库命中/未命中 + 分数<1s

表里那个"特征向量含版本号"是个容易被忽略的细节。算法升级后特征维度或分布会变,新老特征不能混着比。工程上的做法是特征入库时带上模型版本字段,升级时按版本重建索引,或者双版本并行一段时间。没有这个字段,一次算法升级就能让整个库的历史数据变成废数据。

3.3 平台与存储层:视频流、小图、特征值三条链路

存储这块必须拆成三条独立的链路来算,混着算一定算错。

视频流存储:按路数 × 码率 × 天数算。以 1080P、4Mbps 为例,单路一天约 42GB,100 路存 30 天就是 126TB。这部分通常落在专业存储设备或云存储上,做循环覆盖。

人脸小图存储:按条数 × 单图大小算。一张人脸小图 30~80KB,日增 30 万条、保留 180 天,大约 1.6~4.3TB。这部分用对象存储最合适,按时间前缀组织,方便按时间段批量清理。

特征值存储:按条数 × 维度 × 数据类型算,前面已经估过。这部分要常驻内存做检索,落盘只做持久化和重建用。

三条链路分开算完,再汇总成一份存储清单,评审的时候这份清单比任何架构图都有说服力,因为它能对上预算。

3.4 两种联网部署形态:专网侧布控与内网侧汇聚

部署形态的选择取决于前端摄像机能接到哪张网。常见两种:

方案一,前端与平台同侧部署:前端摄像机和识别平台都在视频专网内,平台统一管理前端、转发视频流到识别服务器集群做特征提取和比对,结果通过安全隔离与信息单向导入设备送到内网侧。内网侧的用户做检索、看轨迹、排警力。这种形态的好处是视频流不出专网,带宽压力小;代价是跨网数据有延迟,通常是分钟级,实时性要求极高的场景要慎用。

方案二,平台部署在内网侧:前端卡口通过共享平台把视频推到内网,内网平台对接共享平台拿数据,再送识别服务器。这种形态的好处是业务系统、数据分析、库管理都在同一侧,链路短;代价是视频流要跨网传输,带宽和稳定性要先算清楚。

选哪种,本质上是在带宽和实时性之间做取舍。方案里应该把两种形态的适用条件写清楚,而不是只画一种。评审最常问的一句就是"如果前端接不到这张网怎么办",答不上来就说明方案没做透。

4. 关键模块实现:从人脸质量分到身份碰撞的落地细节

架构讲完只是纸面功夫,真正决定这套系统能不能跑起来的,是几个不起眼的实现细节:抓拍图怎么过滤、索引怎么建、比对结果怎么复核、批量任务怎么不把在线服务拖垮。

4.1 抓拍质量过滤:把无效人脸挡在入库之前

前端已经做了一轮过滤,后端入库前还要再做一轮。原因很简单:设备侧算力有限,质量评分模型通常做得比较轻;而且有些前端设备的质量分口径不统一,直接信它会被坑。

我一般会在接入服务里加一层硬阈值过滤,只让合格的人脸进入特征提取环节:

# 抓拍图入库前的质量门禁,返回 (是否入库, 拒收原因) QUALITY_RULES = { "min_face_width": 60, # 人脸框最小像素宽度,低于此值特征不可靠 "min_face_height": 60, "min_sharpness": 80.0, # 拉普拉斯方差,衡量清晰度 "max_yaw": 25.0, # 偏航角绝对值上限(度),侧脸过大不提特征 "max_pitch": 20.0, # 俯仰角绝对值上限(度) "max_occlusion": 0.25, # 遮挡面积占比上限 "min_brightness": 35, # 人脸区域平均亮度下限,防过暗 "max_brightness": 220, # 上限,防过曝 } def check_quality(meta: dict) -> tuple: if meta["face_w"] < QUALITY_RULES["min_face_width"] \ or meta["face_h"] < QUALITY_RULES["min_face_height"]: return False, "face_too_small" if meta["sharpness"] < QUALITY_RULES["min_sharpness"]: return False, "blurred" if abs(meta["yaw"]) > QUALITY_RULES["max_yaw"] \ or abs(meta["pitch"]) > QUALITY_RULES["max_pitch"]: return False, "pose_out_of_range" if meta["occlusion"] > QUALITY_RULES["max_occlusion"]: return False, "occluded" if not (QUALITY_RULES["min_brightness"] <= meta["brightness"] <= QUALITY_RULES["max_brightness"]): return False, "bad_exposure" return True, "ok"

这段逻辑的关键在于不合格的抓拍不是直接丢掉,而是只存元数据、不存特征。为什么呢?因为事后追轨迹的时候,一张模糊的图虽然认不出人,但它能证明"这个时间点这个点位有个人经过",这条记录对还原轨迹有价值。只存元数据不存特征,既保住了轨迹完整性,又不让无效特征污染检索索引。

参数怎么调:min_face_width是影响最大的一项。卡口场景下经过的人距摄像机 3~8 米,人脸像素宽度分布很广,设 60 能滤掉大部分无用小图,但如果你的点位普遍较远,设 80 会更干净,代价是抓拍量下降三四成。这个值必须拿现场实际数据跑一遍分布再定,拍脑袋定完上线,要么库撑爆要么查不到人。

4.2 特征入库与索引:亿级库怎么做到秒级返回

抓拍表是典型的时序宽表,写入量巨大、查询按时间范围过滤。表设计的核心是分区 + 覆盖索引,别指望靠单表索引硬扛。

-- 人脸抓拍主表,按天分区,便于按时间范围检索和过期清理 CREATE TABLE face_capture ( id BIGSERIAL, capture_time TIMESTAMPTZ NOT NULL, -- 前端设备时间,用于轨迹还原 recv_time TIMESTAMPTZ NOT NULL, -- 平台接收时间,用于排查时钟偏差 device_id VARCHAR(64) NOT NULL, scene_id VARCHAR(64) NOT NULL, -- 点位/区域编码 face_w SMALLINT, face_h SMALLINT, quality_score REAL, feat_version SMALLINT NOT NULL, -- 特征模型版本,升级时用于隔离 feature BYTEA, -- 特征向量二进制,NULL 表示未提特征 image_key VARCHAR(128), -- 小图对象存储 key PRIMARY KEY (id, capture_time) ) PARTITION BY RANGE (capture_time); -- 每天一个分区,提前建好未来 7 天,过期分区直接 DROP CREATE TABLE face_capture_20250101 PARTITION OF face_capture FOR VALUES FROM ('2025-01-01') TO ('2025-01-02'); -- 只对"有特征"的记录建部分索引,索引体积能降一个数量级 CREATE INDEX idx_capture_scene_time ON face_capture (scene_id, capture_time) WHERE feature IS NOT NULL; -- 黑名单表独立存储,独立索引,常驻内存,绝不与抓拍表混用 CREATE TABLE face_blacklist ( person_id VARCHAR(64) PRIMARY KEY, list_id VARCHAR(64) NOT NULL, -- 名单分组,支持多名单并行比对 feat_version SMALLINT NOT NULL, feature BYTEA NOT NULL, valid_from TIMESTAMPTZ NOT NULL, valid_to TIMESTAMPTZ, -- 为空表示长期有效 enabled BOOLEAN DEFAULT TRUE );

这里有三个设计点值得说。第一,capture_time和recv_time双时间字段不能省。设备时间用于轨迹还原,接收时间用于排查时钟不同步——一旦发现两者差距稳定在某个值,说明那台设备的时间源有问题。

第二,分区表的价值不只是查询快,更在于过期数据可以直接DROP PARTITION,秒级完成;如果用DELETE按时间删,几千万行的删除能锁表几十分钟,业务高峰做这事就是自找麻烦。

第三,feat_version是给未来留的后悔药。没有它,算法升级那天你会发现自己面对一个几千万行的特征废墟。

检索本身的实现分两步:粗排 + 精排。粗排用向量索引在目标库里召回 Top 100,精排再用完整特征算一次精确距离,取 Top 10 返回。粗排索引是近似的,召回率一般在 95% 以上,剩下那几个点靠 Top N 放宽和人工复核兜住。

4.3 人证合一核验:一张现场图加一张证件照怎么判

人证合一是最标准化的 1:1 场景,流程固定:现场抓一张脸,读证件信息拿到证件照,两张图各提一次特征,算相似度,和阈值比较,输出结论。

# 人证合一核验调用示例 import requests THRESHOLD_1V1 = 0.82 # 1:1 场景阈值,需用本地数据标定后确定 def verify_1v1(live_image_b64: str, id_photo_b64: str) -> dict: # 第一步:分别提特征,指定同一个特征模型版本 feat_a = requests.post("http://face-svc/v1/feature", json={"image": live_image_b64, "version": 3}).json() feat_b = requests.post("http://face-svc/v1/feature", json={"image": id_photo_b64, "version": 3}).json() # 第二步:1:1 比对,服务端只做距离计算,不做判定 resp = requests.post("http://face-svc/v1/compare", json={ "feat_a": feat_a["feature"], "feat_b": feat_b["feature"], "metric": "cosine", }, timeout=2).json() score = resp["score"] # 第三步:判定逻辑放在业务侧,阈值可按业务场景灵活调整 return { "score": score, "passed": score >= THRESHOLD_1V1, "need_manual_review": 0.70 <= score < THRESHOLD_1V1, # 灰区转人工 "feat_version": 3, }

这段代码里最值得抄的不是 API 调用,是那个灰区设计。判定结果不是简单的通过与不通过,而是三档:高分直接通过,中间分数段转人工复核,低分拒绝。实际业务里,灰区比例通常能占到 5%~15%,如果只给两档,这 15% 的人要么被误拒引发投诉,要么被误放留下风险。三档设计让人工只处理真正模糊的那部分,效率最高。

阈值0.82是个占位值,必须用自己的数据标定。做法是拿几百对已知同人、几千对已知非同人的样本跑一遍,画出 ROC 曲线,按业务能接受的误识率反查阈值。1:1 场景我一般按误识率万分之一卡,宁可让灰区大一点。

4.4 身份碰撞比对:一人多证与冒用身份排查

碰撞比对是 N:N 场景,用来找出两个库里"长得像但不是同一条记录"的人,典型用途是排查重复证件、冒用身份。这个任务的工程难点不在算法,在于怎么在有限算力下把 N 乘 M 次比对跑完还不影响在线业务。

做法是三步:先分片,再批跑,最后人工复核。

第一步分片:把源库按特征聚类或者直接按主键取模分成若干批,每批几万条,单独起任务。第二步批跑:每批只调用相似检索接口,不做全库笛卡尔积,而是在目标库里给每条源记录召回 Top 5 候选,这样计算量从 N×M 降到 N×5,结果质量损失很小——真正同一个人的记录,第一名基本跑不掉。第三步人工复核:相似度落在灰区的记录导出成清单,由业务方逐条确认,确认结果回写到库,避免下次重复比对。

# 碰撞比对任务的调度骨架:分批 + 限流 + 断点续跑 for shard in $(seq 0 31); do echo "[$(date)] start shard ${shard}" # 低优先级队列,单批并发限制为 2,避免抢占在线检索资源 curl -s -X POST "http://face-svc/v1/collision/job" \ -H "X-Priority: low" \ -d "{\"src_shard\": ${shard}, \"total_shard\": 32, \"topn\": 5, \"threshold\": 0.75}" echo "[$(date)] shard ${shard} submitted" done

total_shard设成 32 是个经验值,单批规模控制在几万条,一批跑十几分钟,出错重跑代价小。整个任务放夜间窗口执行,X-Priority: low让服务端把它排到低优先级队列,白天查看实时报警时不会看到延迟毛刺。断点续跑靠的是任务表里记录每批的状态,跑到一半服务重启,从最后成功的那批继续,不用从头再来。

5. 避坑与常见问题排查:上线后最容易翻车的五个点

方案写得再漂亮,上线第一个月照样会出事。下面这五条是我在实际项目里见过频率最高的,每条都按现象、原因、解决来写。

5.1 前端与数据侧的三个坑

坑一:库里全是"糊脸",检索越来越慢还查不到人。

现象是系统跑了三个月,抓拍库涨到几千万条,检索响应从 1 秒掉到 8 秒,但真去查一个已知的人,Top 10 里经常没有。

原因是入库过滤太松。前端只做了基础检测就全量上传,人脸宽度 30 像素的图也照样提特征入库。这种图提出来的特征向量方向基本随机,既占索引空间又干扰排序。

解决分两层:接入层按 4.1 那套规则加硬阈值,人脸宽度低于 60 像素的只存元数据不存特征;同时对已有数据做一次治理,把质量分低于阈值的记录的特征字段置空,重建部分索引,索引体积通常能降六到七成。

坑二:白天报警延迟动不动飙到十几秒。

现象很规律,早上九点到十一点、下午两点到五点,实时报警的端到端延迟从 1 秒涨到十几秒,晚上恢复。

原因是算力被抢。分析师白天上班第一件事就是提检索任务,几十个全库 1:N 检索和大范围轨迹查询把特征提取的 GPU 占满了,实时流在队列里排队。

解决是把队列拆开并设优先级,实时动态比对走独立队列,静态检索限制并发数,批量任务强制排到夜间窗口。另外给实时链路留出至少 30% 的算力余量,别把资源算到 100%。

坑三:轨迹串不起来,同一个人的记录时间倒着排。

现象是查某个人的轨迹,明明是先经过 A 点位再到 B 点位,系统里显示的时间却是 B 早于 A。

原因是时钟不统一。前端设备、接入服务器、平台服务器各自的时间源不同,有的设备出厂后就没校过时,和平台差几十秒。抓拍时间用的是设备本地时间,直接就乱了。

解决是全网统一时钟源,前端、服务器、存储设备全部对同一个时间服务;入库时同时记设备时间和接收时间,平台侧做一次偏差检测,偏差超过阈值就告警并把该设备标记为待校准。轨迹展示时优先用校正后的时间。

5.2 算法与容量侧的三个坑

坑四:阈值照搬,要么误报刷屏要么该报不报。

现象是把 1:1 场景调好的相似度阈值直接用到 1:N 检索上,结果黑名单一天报警几百次,人工复核下来全是误报;反过来把阈值调高,真正该报的人又漏了。

原因是分布不同。1:N 的比对次数是库容量的函数,库越大,随机撞出高分的概率越高。三十万的黑名单库和五十万的黑名单库,同一个阈值下的误报率能差一倍。

解决是按库容量分档设阈值,每档用实际底库做一次 ROC 标定;同时上"连续命中才算"的策略——同一个人在短时间内被多个点位命中,才升级为高优先级报警,单次命中走普通复核队列。这一招能把误报压下去一大半。

坑五:内存估算保守,扩容时发现加不进节点。

现象是库容量规划时按裸特征数据估内存,1000 万条估了 20GB,买了 32GB 的机器。上线后索引加载完占用了 28GB,再加一批数据就 OOM 了。

原因是漏算了三块开销:向量索引本身的额外结构、高可用副本、以及检索过程中的临时缓冲。实际膨胀系数通常在 1.8~2.2 倍之间。

解决是按 2 倍预留,并且在设计阶段就把分片键定好——按库类型分片还是按时间分片,决定了后续能不能平滑加节点。如果一开始没分片,单机内存到顶时只能停机迁移,那个停机窗口在 7×24 的业务里是要审批的。

注意:这五条里,前三条属于部署和参数问题,通常在验收前就能发现;后两条属于设计问题,一旦上线就很难改。方案评审时被问得最多的也正是容量估算和阈值口径这两块,提前把测算过程写进文档,比事后解释省事得多。

6. 验收与答辩怎么过:拿可复现的指标清单说话

验收这一环,最忌讳的是"演示一遍功能,大家鼓掌通过"。真正能过的验收,是把每条指标拆成可复现的测试方法,谁来跑都是同一个结果。下面这张表是我一般会写进方案末尾的验收清单,评审看到这张表,基本不会再纠缠"到底准不准"这种没法回答的问题。

指标测试方法通过口径
单次 1:N 检索响应在目标库中随机抽 200 张已知身份图,逐张检索,记录端到端耗时95 分位 ≤ 5 秒,不含网络延时
首位命中率同上 200 张图,看正确身份是否在返回 Top 1≥ 90%,统计口径写明测试集来源
Top 10 命中率同上,看正确身份是否在 Top 10 内≥ 97%
1:1 误识率用 5000 对非同人样本跑比对误识率 ≤ 0.01%
动态报警时延现场实测,从人脸进入画面到平台弹出报警≤ 2 秒
抓拍有效率随机抽 1000 条抓拍记录,人工判定可用占比≥ 85%
连续运行7×24 小时不间断跑,记录服务重启次数无因系统原因导致的重启
集群扩展停掉一台识别节点,观察业务是否中断业务不中断,性能下降 ≤ 20%

这张表里有两个细节值得单独说。一是测试集必须写清来源,是现场实拍还是公开数据集,两者结果差异很大,含糊过去评审一定会追问。二是每条指标都要有"不达标怎么办",比如首位命中率差 2 个点,是调阈值、加前端点位,还是补光——方案里提前写好预案,答辩时被问到就能直接答。

答辩环节,讲清三件事就够了:第一,指标是怎么来的,为什么是 5 秒不是 2 秒,这背后是库容量和算力的换算;第二,架构为什么这么分层,每一层解决什么问题,去掉哪一层会出什么事;第三,最难的取舍是什么,比如为什么选专网侧部署而不是内网侧,代价是什么。评委通常不关心你用了什么算法,关心的是你有没有想过边界条件。

现场演示别贪多,把一条完整链路走通比演示十个功能有用:一张测试图上传,系统返回 Top 10 候选,点开其中一条看轨迹,展示这个人过去几天在哪些点位出现过。这条链路一走通,前面所有设计的合理性就都自证了。

我吃过一次亏:早期做项目时验收只演示了功能,没做压力测试,上线第二周赶上一次大型活动,前端点位全开,系统在高峰时段直接卡死,事后复盘发现是算力预留不足。从那以后我每次出方案,验收清单里一定强制加上"高峰场景压测"和"单节点故障演练"这两条,不管甲方有没有要求,自己先把这两关过了。这套东西整理下来其实不复杂,难的是每一环都落到能验证的口径上。需要拿它当底稿改的,直接取用就行,希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进