海量人脸特征检索解决方案演进之路

1. 概述

clipboard.png

人脸识别技术在最近几年得到了长足进步,目前在人脸识别领域业界领先的厂家识别准确率均达到了99%以上,因此大量人脸相关的应用场景开始逐步落地,例如人脸支付、人员布控、寻找失踪人口等,此外,结合人脸的追踪技术,也开始出现了分析人流走向、分析景点旅客行走规律、人员行为偏好分析等。这些应用虽然表现形式多样,但最终都是基于人脸特征检索这一技术实现的。
在这里插入图片描述
  首先介绍一下人脸特征是什么。目前图像识别算法能够在一张照片中发现人脸,并能够对人脸中的轮廓进行识别和标记,算法使用这些标记点构造出表示该张人脸的特征的矩阵,这个过程称为人脸特征提取,得到的矩阵称为人脸特征矩阵,在工程上,特征矩阵一般以一维矩阵表示,以二进制数组的方式进行存储。

在这里插入图片描述
  当需要确认两张人脸照片是否同一个人时,可以通过上述公式计算这两张人脸的特征矩阵之间的相似度,以此作为两个人脸的相似度,当相似度超过一定阈值时,就认为是同一个人,该阈值是经验值,不同厂家的特征提取算法不同,得到的经验值也会不同。提高阈值,会提高准确率(认为是同一个人的情况下判断正确的占比),但会降低查全率(能匹配到的人脸在人脸库中全部匹配人脸的占比)。因此在不同的应用场景下,由于准确率和查全率的权重不同,导致了阈值也会不同。如在寻找失踪人口的场景,是宁可找错也不应放过的,阈值就会相应调低,让更多相似的人脸能被看到;在人脸支付场景,准确率是最重要的,那么阈值相应就会较高,当然也会导致匹配失败的次数增多。

把大量人脸特征集中存储可形成特征库,若要判断一个人在不在这个特征库中,只需要拿这个人的人脸照片对应的人脸特征,跟特征库里每个特征计算相似度,把相似度超过阈值的特征对应的照片找出即可。通过一个特征来比对一个特征库的场景,往往称为人脸1:N比对,与之对应的两个特征库之间的比对,往往称为人脸M:N比对。

算法场景通常会提供如下的M:N比对接口:
clipboard.png
  接口实现两个特征库之间的比对,如上图中4个特征的库和3个特征的库的比对,可得到12个相似度。当要实现1:N时,只需让其中一个特征库只包含一个特征即可。此前基于E5-2640V3 CPU(16物理核)实测1:N,每秒可实现1.5亿对特征比对。一般通过C++调用该接口完成比对。

2. 性能指标要求

clipboard.png
clipboard.png
  在今年某地市的项目中,系统从一万多人脸摄像机中采集人脸抓拍图片进行特征提取后形成特征库,业务层需要实现人脸检索功能。其中数据规模为:每天约2000万张人脸抓拍图,图片平均约30KB,人脸特征约600字节,即每月6亿个特征,每月特征库单副本约占用350GB空间,数据需要存储1年,人脸检索要求1:1亿在3秒内响应,需要支持10个并发,需要支持根据时间、摄像机编号、相似度阈值来过滤人脸。

当然这是在最近才提出的性能指标要求,两年前,在人脸相关项目还没有大面积落地时,对人脸检索的性能指标要求还在千万级以下的人脸比对,而且只需支持1-3个并发,但也由于当时没有可参考的案例,业务场景处于摸索阶段,因此在人脸检索上需要支持更丰富的检索条件,如除了时间和摄像机编号外,还需要支持根据性别、年龄段、是否戴帽子、是否戴眼镜等条件过滤数据。

下面将从最简单的方案讲起,逐步推进到支持千万级、亿级人脸比对的方案,让大家对方案的演进有个整体的了解。

3. 解决方案演进

3.1 人脸动态库方案

clipboard.png
  在内部验证阶段,使用单机存储固定特征个数(可能是一千万个)的特征库,每个特征对应记录ID、时间戳、摄像机编号等信息。每天新增的特征形成一个单独的小特征库,每天定时把小特征库合并到大特征库,并把大特征库中最旧的同量特征删除,保持特征库的大小。在检索时先对全库进行1:N,根据阈值过滤出部分记录后,再抽取对应记录的额外信息,与页面检索条件进行匹配,返回结果。

优点:由于是单机系统,方案实现和维护都比较简单
缺点:单机支持的特征数量有限,无法横向扩展,检索并发度低,过滤条件无法灵活变化

3.2 ES分布式人脸检索方案

由于业务之初并不清楚实际应用场景中哪些检索条件是用户离不开的,只有在不断的实践迭代中摸索真正能落地的使用方式,因此丰富、灵活的检索条件在当时是相对重要的。考虑现已用户的检索条件过滤数据后形成特征库,再用待搜索的人脸跟特征库做1:N。因此第一步需要使用能够支持灵活搜索,并且搜索性能极佳的引擎,基于公司技术栈和业界常用的搜索引擎考虑,最终敲定为ElasticSearch搜索引擎。

ES没有提供查询后在服务端对结果进行二次处理的接口,并且其源码中写死了最多只可返回5000条记录,若根据搜索条件过滤出来的人脸在百万级,按照源生接口,就算改大了返回记录数的限制,也只能是先汇聚到客户端,然后由客户端在本地做1:N。这会导致ES和客户端之间的带宽占用过高,并且单单是网络传输也要耗费不少时间。

当然,客户端可以跟ES Node部署在一起,由客户端提供人脸检索服务,应用端请求时,客户端直接对本机的ES Node发起请求,汇总的人脸在本机内部拷贝,可避免网络传输的问题。但是依旧存在如下问题:
  1)ES对检索结果是组装为List对象再处理后返回到客户端的,对内存消耗较大;
  2)客户端获取检索结果还是通过网卡,虽然服务器内部传输避免了带宽损耗,但内存拷贝是免不了的;
  3)ES端检索时需要汇总全部匹配的结果到查询节点,这部分的带宽是免不了的;
  4)检索性能受限于单机性能,无法横向扩展。

clipboard.png
  为了避免上面的问题,最好就能在每个ES Node内部检索到特征数据后,把形成List的过程变为直接写到事先分配好的堆外内存ByteBuffer中,紧接着通过JNI的方式调用本机的C++接口,传入ByteBuffer内存块地址,直接在本机完成1:N比对,然后才把比对结果包装为ES Node响应对象返回到ES查询节点,在查询节点内部完成相似度的排序和取TopN操作,最终返回给客户端。这个过程如上图所示,其中为了使线看起来没那么多,C++ 1:N的结果返回到ES Node的箭头只在一台服务器上画了,实际上其他服务器同样有这样的箭头。

优点:
1)不需要形成List对象,直接在本机形成内存块,节省了内存,也减少了内存拷贝;
2)1:N是每个节点并行做的,仅返回相似度超过阈值的记录到查询节点,带宽消耗极小,查询节点处理几乎不耗时间,因此可横向扩展;
3)ES自身提供了副本和查询负载均衡机制,大大节省了运维和接口开发工作量;
4)能支持几乎全部的ES检索功能,查询条件灵活,页面可随时修改检索条件而不需要修改后端实现代码。
缺点:
1)需要为ES增加一种查询类型,实现在Node中检索后直接写ByteBuffer和调用C++接口做1:N的逻辑,需要深度修改ES源码;
2)由于是通过JNI调用C++接口,若C++程序异常了,会直接导致ES Node的JVM挂掉。

但是也应注意到该方案并不能避免从ES的Share拷贝记录到ByteBuffer。由于进行人脸比对所需读取的记录数往往在百万级以上,若这些记录不在系统缓存中,就会导致需要到磁盘随机读取数据,IO消耗严重。实测单机使用7200转普通硬盘并且没有系统缓存的情况下,要在ES中随机加载100万条记录,需要约60s-80s,而实际应用场景中,需要比对的人脸数据往往大多不在缓存中,因此需要优化从Share拷贝记录到ByteBuffer的过程。
clipboard.png
  当时采用了如下方案(简称内存ES方案):
  单机上启动2个ES进程,一个ES进程的数据目录设置在硬盘中,另外一个ES进程的数据目录设置在使用tmpfs在内存中模拟的内存文件系统中,N台服务器共2*N个ES进程组成集群。关闭ES的自动分配和自动平分片功能,按月创建人脸Index,每个Index设置3个副本,3个副本中其中一个分配到使用内存文件系统的ES进程中,另外两个副本分配到其他服务器上使用磁盘存储的ES进程中,创建Index的操作专门开发工具实现。对于人脸比对检索请求,修改客户端选择分片的逻辑为优先检索使用内存存储的ES分片(通过节点名称约定实现),只有当内存存储的ES分片异常时才选用磁盘存储的分片。具体实施如上图所示。

在内存文件系统中的分片在检索时,从Share拷贝记录到ByteBuffer就变成了从内存拷贝到内存,避免了磁盘IO,大大提高了性能。但是内存拷贝虽然快,但也只是针对大块数据而言的,从Share中读取数据是随机读取,经过实际测试发现,单机从存储在内存文件系统的Share中读取记录的速度上限约为250万条/秒。也就是说,如果要求单并发对1千万人脸做比对在1秒内响应,不算其他步骤耗时的情况下,至少也要4台服务器才能支撑。

由于该集群专门用来做人脸检索,因此Index中只包含检索条件字段和特征字段,与另外的一个包含完整字段的集群(简称全量ES集群)进行数据同步,数据采集网关把数据同时发往这两个ES集群中。在发生宕机情况时,内存中的数据会全部丢失,因此需要在启动服务器之后,通过专门的运维工具把原本在该服务器上的分片创建出来,并等待其他节点中的磁盘分片自动同步数据。

缺点:
1)运维非常麻烦,内存ES集群异常恢复时往往需要读取大量数据,严重拖慢ES性能;
2)因为需要把1/3的分片数放到内存文件系统中,因此需要消耗大量内存,对于128GB内存的服务器,最多只能划分40GB内存来存放分片,因为ES自身的正常运行也需要消耗大量内存,这就使得单机可存储的数据量极少,如果按前面所说的每天2000万个特征1个月单单是特征就要350GB内存,那就需要9台128GB内存的服务器才能放得下;
3)为了使人脸比对不影响普通查询的性能,全量ES集群和内存ES集群应部署到不同的服务器组上,这样无形中又需要消耗更多的服务器;
4)单机内存拷贝速度依然较慢,需要大量服务器才能支撑大规模的人脸比对;
5)ES自身在对单个字段进行多值匹配时,耗时呈现指数型增长趋势,当匹配值个数超过100时,单单是该字段的匹配耗时就在秒级耗时了,而实际场景中用户往往会选择大量的摄像机来进行检索,这样会严重拉低ES检索性能。

3.3 基于RocksDB的分布式特征索引方案

clipboard.png
  随着应用场景的逐渐清晰和对人脸识别到的其他特征准确度的认识,发现实际在使用人脸检索功能时,其实基本只会使用时间段+摄像机编号+人脸照片这三个条件来检索人脸,因此灵活的检索条件这一项变得不那么重要了。此外,实践中发现ES方案在做人脸检索时,其实绝大多数耗时集中在根据条件进行搜索和从Share中拷贝数据形成特征库这两步,真正进行1:N消耗的时间几乎可忽略不计。
  
  前面提到,基于高性能CPU能实现1秒完成1:1.5亿的比对,假设这样的场景:用户需要检索24小时范围里全部摄像机抓拍的人脸里是否出现了某个性别为男性的人。按照每天2000万特征来算,假设这24小时里性别为男性的记录是1000万,ES方案中加载1000万特征需要4秒,1:1000万需要0.066秒,不算其他耗时则总耗时约为4.066秒;若先不过性别条件,用24小时全部的人脸比对完后,以超过阈值的记录去判断是否男性,则1:2000万需要0.133秒,超过阈值的几百个人脸再去判断性别假设需要0.5秒,则总耗时约为0.633秒。需要检索的时间范围越长,则性能差异越大。

因此,关键需要有一个能先根据时间段+摄像机编号初步过滤特征库的引擎,先进行人脸比对,然后再做条件匹配。其实这更像是方案一的升级版。具体方案如上图所示。持久化存储使用RocksDB,引擎启动后会在内存中缓存一定时间段的全量特征库用于人脸比对,数据以<小时, 摄像机编号>作为为索引对特征数据分块存储,相同索引的数据平均分布在多台机器上(即数据写入时对于相同索引的数据集,按照节点数均分,发往全部的节点上),实时写入时内存库和RocksDB双写。此外还实现了数据副本机制。
  
  选择RocksDB的原因:在本方案中由于数据写入是双写,引擎的主要压力在数据写盘,RocksDB采用LSM树存储引擎,数据写入性能较好,提供了索引机制,并且提供了C++访问接口,能大大降低开发难度。
  
  在上图的数据中,假设检索时间范围2018-01-01 06:00:00到2018-01-01 06:30:00,摄像机编号为4401150002的人脸,则定位到每个节点的红色内存块(如下图所示),全部节点并行比对人脸,如下图所示,最终汇总到接收查询请求的节点。
clipboard.png
  文章开头所说的人脸检索要求1:1亿在3秒内响应,需要支持10个并发性能指标,换算下来是要达到1:3.333亿秒内响应的效果,若按照单机1.5亿/秒的比对速度,则至少需要3台服务器。因此,本方案实际上已经能够满足性能指标要求,后面的3.4和3.5节介绍的方案是后续可能采用的方案,以应对未来可能的更大规模的人脸比对需求。

优点:
1)支持横向扩展。任意一个比对请求都会使用全部的服务器的资源进行比对,理论上服务器数量与比对性能成正比关系;
2)能有效利用内存资源,在实际实施时,往往能使用80%的内存用于特征库缓存,剩余内存用于超出缓存时间范围之外的临时数据缓存;
3)比对可基于特征库缓存直接进行,实现内存零拷贝;
4)引擎是自研的,对于源码完全掌控,出了问题更容易解决。
缺点:
1)需要自研整套分布式框架,实现内存管理和解决数据同步、一致性等问题。

3.4 基于小特征加速比对的检索方案

clipboard.png

一些厂家除了提供前面所说的约600字节的人脸特征外,还提供了50字节左右的小人脸特征,使用了更少的特征点来描述一张人脸。显然,使用小特征在判别两个人脸是否同一个人时,准确率比大特征低,但小特征库能提供更高的比对性能(因为计算量更小)。因此可以通过小特征库来加速大特征库比对。

由于两个特征矩阵的相似度计算主要为浮点运算,按照相似度计算公式,理论上特征长度变为原来的1/N,则单位时间比对次数变为原来的N倍。也就是说,若600字节大特征能做到1.5亿条/秒的比对速度,则50字节的小特征理论上能做到18亿/秒的比对速度。

具体方案见上图。相比于3.3节方案,多出了小特征内存块,小特征内存卡与大特征内存卡一一对应,使用相同的索引来标识。在检索人脸时,首先根据查询条件定位小特征内存块,使用更低的相似度阈值进行人脸比对,根据匹配结果从对应的大特征内存块中抽取特征组成大特征库(方法可以是大小特征块按ID有序排列,这样记录顺序是相同的,另外可以是每个特征块有单独的ID索引,可快速定位行号),进行第二次人脸比对,第二次比对使用客户端传入的阈值。

在该方案中,第一次比对后从大特征库取的数据量较少,但基本为随机读取,因此可以考虑使用固态硬盘来存储大特征块以节省内存。

3.5 基于GPU优化的检索方案

在这里插入图片描述

由于特征的比对主要是浮点运算,可考虑使用浮点运算能力更高的GPU来实现。在小特征50字节情况下,则8GB显存约可存储1.5亿个特征,按一天2000万特征计算,则8GB显存可存储7.5天数据,4张GPU卡则可存储一个月数据,若N台服务器上都插上4张GPU卡,则可以实现N个月特征的GPU比对提速效果。使用GPU后,还能带来一个显著的好处:更少的服务器,节省了机房空间。

上图中在3.4节方案的基础上,增加了显存存储小特征块的部分。如果在显存中存储最近一个月的热数据,在内存中存储更旧日期的小特征,当检索条件只落在最近一个月时,只需要GPU就能完成小特征的比对;若检索条件涉及最近三个月,可在GPU中比对最近一个月的小特征,同时在CPU中比对第二三个月的小特征。在实际应用场景中,用户绝大多数时间检索的是最近一个月的数据,该方案能大大提高响应速度。

4 总结

本文从需求和性能优化的角度,为大家介绍了海量人脸特征检索场景解决方案的演进过程,可以看到,不同的业务需求往往决定了具体的实现方案。可以预见,当前人脸比对性能逐渐不再是瓶颈,大量与人脸检索相关的场景都能够得以实现。如今用户已经不满足只能实现简单的人脸检索功能,他们希望能利用人脸比对的能力,实现诸如人群聚类、人员归档、陌生人行为分析等业务,这些业务最底层的逻辑是人脸聚类算法,即给出一个未知人员信息的特征库,算法根据里面每个人的两两相似度,把相同的人的照片归到一类中,实现特征集合的人员划分。在公共安全领域,人员聚类将是下一个爆发的应用场景,值得大家关注。

原文链接:https://segmentfault.com/a/1190000019224111

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是基于MATLAB的K-L变换人脸特征提取方案的代码示例: ```matlab % 读取人脸数据集(假设为face_data.mat,其中X为样本矩阵,每一行为一个样本) load('face_data.mat'); % 对所有样本进行零均值化 mean_face = mean(X, 2); % 计算所有样本的平均脸 X = X - mean_face; % 减去平均脸 % 计算协方差矩阵 C = cov(X'); % 对协方差矩阵进行特征值分解 [V, D] = eig(C); % 将特征向量按照对应的特征值从大到小排序 eigenvalues = diag(D); [~, idx] = sort(eigenvalues, 'descend'); eigenvectors = V(:, idx); % 选择前k个特征向量构成一个新的变换矩阵 k = 100; T = eigenvectors(:, 1:k); % 将每个样本乘以变换矩阵,得到降维后的人脸特征向量 Y = T' * X; % 将降维后的人脸特征向量作为人脸识别的特征 % 可以使用支持向量机等算法进行分类 % 这里仅做示例,使用kNN算法进行分类 load('face_labels.mat'); % 读取每个样本的标签 Mdl = fitcknn(Y', face_labels, 'NumNeighbors', 5); % 训练kNN模型 test_face = X(:, 1); % 假设要识别的人脸为第一个样本 test_face = T' * (test_face - mean_face); % 降维 predicted_label = predict(Mdl, test_face'); % 预测标签 disp(['Predicted label: ', num2str(predicted_label)]); % 输出预测结果 ``` 以上代码中,我们首先读取人脸数据集(假设为face_data.mat),并对所有样本进行零均值化。然后,我们计算协方差矩阵,并对其进行特征值分解,得到特征值和特征向量。接着,我们将特征向量按照对应的特征值从大到小排序,并选择前k个特征向量构成一个新的变换矩阵。然后,我们将每个样本乘以变换矩阵,得到降维后的人脸特征向量。最后,我们将降维后的人脸特征向量作为人脸识别的特征,使用kNN算法进行分类(假设已经读取每个样本的标签,存储在face_labels.mat中)。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值