ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python+dlib人脸识别实战:128D特征向量与欧式距离算法详解

Python+dlib人脸识别实战:128D特征向量与欧式距离算法详解 这份资源的主要内容是围绕 与 dlib 库来实现人脸识别的功能展开的, 它的核心机制是基于欧式距离算法来进行工作的, 具体操作是将人脸图像转换为 128D 特征向量后, 通过计算这些特征之间存在的差异程度来判断它们是不是属于同一张脸, 在实际应用中通常会把这个差距阈值设定在 0.6 以下, 一旦低于这个数值就视为是同一人, 这份内容专门面向那些想要入门图像识别技术的人群或者是正在进行课程设计、毕业设计的在校学生, 旨在帮助他们理解并掌握人脸识别从最初的图像采集阶段, 到后续的特征提取过程, 再到将提取出的特征保存到存储中, 最后进行比对和识别这一整套完整流程。压缩的文件一共是 20 个, 大小大概是 27.36MB, 这里头包含了 5 个 py 格式的源码文件, 还有 3 个 pyc 缓存数据, 3 张 jpeg 格式的图片以及 2 张 png 格式的测试图片, 另外有 2 个 dat 文件格式的模型文件, 以及一份 docx 格式的设计报告, 另外还有 csv 格式的特征数据, txt 格式的依赖说明文档, md 格式说明文档等等, 总之是把代码、数据还有文档这三类的材料都给涵盖了。目前, 已经有 665 个人下载学习了。读者能够获取可以直接运行的人脸注册与识别脚本, 以及预训练模型和特征数据。此外还可以借助设计报告来梳理算法原理与实现思路。这可以作为人脸识别项目实践的参考。1. 从一张包含一百二十八维特征向量的数据说起, 来仔细探讨一下这套dlib人脸识别源码究竟能够跑出什么样的实际结果。许多人在初次接触人脸识别技术时候, 脑子里常常想着的是摄像头只要进行一照, 那名字就直接被出来了, 但是真正落实到编写代码这上面去, 核心内容其实只有一件事情发生: 那就是要把人脸这一个图像对象转化成数字组成的字符串, 随后再进行比较两个字符串之间的数值差异有多大。这份 dlib的欧式距离算法进行人脸识别.zip 这个项目就是专门用来做这件事的, 而且它做得非常出色, 非常彻底。它并没有采用深度学习训练框架来工作, 也完全没有给你增添配置 GPU 环境的麻烦。相反, 它是直接利用 dlib 库里面已经准备好的残差神经网络模型, 把面部特征数据映射转换成长度为 128 维度的向量表示, 然后通过计算这些向量之间的欧式距离来分析和判断图片里面的人到底是不是同一个人。在摘要那段文字里面, 有一句话特别重要, 说的是, 一般的状况下, 只要那个数据值是在零点六以下的话, 那么就可以被认定是同一张脸了, 这句话其实就是我们整套逻辑体系当中, 用来做出最终判断的那条标准界线。它适合的是那类人群, 具体来说, 就是那些人刚刚学习完毕基础的语法知识之后, 想要去寻找一个实例项目来进行人脸识别方面的练习操作, 并且要求这个项目能够顺利运行起来, 能够确保自己看懂其中的代码逻辑, 还能对相关的参数做更改, 同时它也适合那些需要快速的去验证所谓的从注册到人脸比对这一个完整流程的工程师们, 因为他们不想让自己的时间被训练过程所耗费和拖住。在这个项目的包里已经带有了设计报告、源码文件、测试图片以及两个关键的模型文件, 当你拿到手之后就可以直接复现实验结果, 完全不用自己去翻那个所谓的 dlib 官网去费力气寻找 .dat 文件。下面的情况是我先进行跑通, 然后对内容进行拆解分解, 最后在过程中注意规避可能出现的坑, 按照这样一个顺序从头到尾对下面提到的这份资源全部检查一遍。2. 环境与模型文件涉及两个 .dat 文件和具备 128D 特征提取功能的链路部分。针对为何选择 dlib 而不使用程序自带的人脸模块, 具体分析如下。使用 Haar 级联和 LBPH 进行人脸检测和简单识别是够用的, 但是它的识别本质属于纹理统计范畴, 一旦更换光照条件或者改变拍摄角度, 那么识别结果就容易出现问题。dlib 这套方案使用的是 .dat数据文件, 这是一个在大量人脸数据之上训练好的残差网络, 它会输出一个 128 维度的向量, 对于同一个人的不同照片来说, 这些向量之间的欧式距离会非常小, 而对于不同人之间的对比, 它们则会出现较大的距离差距。这个「距离」就是摘要里说的欧式距离算法, 公式看起来很朴素:distance sqrt(sum((a_i - b_i)^2 for i in range(128)))进行128个维度上的逐项相减、平方、求和以及开根号操作。这背后并没有任何玄学原理, 它仅仅是将初中几何知识中用来计算两点间距离的公式, 扩展并应用到了128个维度的空间中而已。选择使用该方案的理由也非常直白: 现有的模型资源已经是现成的, 用户完全不必自己去投入精力训练数据。同时, 128个维度的数据体量相较于原始像素数据要小上几个数量级, 这直接带来了比对速度明显加快的效果。至于0.6这一阈值设置, 它在大多数的公开测试集上面都展现出十分稳定的表现, 因此被界定为工程实践层面里属于「既能够满足需求又便于参数调整」的一种默认最佳选项。。2.2 依赖安装与模型文件放置包里面的那个以.txt为后缀的文件就是入口, 你得先去看看它上面写了什么东西, 然后再去决定要安装哪些东西。常见做法是创建一个新的、干净的虚拟环境, 这样可以避免和电脑系统里已经存在的dlib版本发生冲突。# 创建虚拟环境Python 3.6 均可dlib 对 3.7~3.9 支持最稳python -m venv venv# Linux/macOS 激活source venv/bin/activate# Windows 激活venv\Scripts\activate# 按 requirements.txt 安装dlib 编译较慢建议先装 cmakepip install cmakepip install -r requirements.txt这里需要特别关注一个参数, 如果在 .txt 文件中编写的是 dlib19.xx 这样的格式, 那么应该尽量避免随意将其升级到最新版本, 因为 .dat 文件和 .dat 文件对于 dlib 这个库的版本存在着隐性的依赖关系, 一旦版本跨度变得过大, 就可能会出现加载失败的情况。当安装的过程彻底完成以后, 你应当把两个后缀为.dat的文件放在与脚本相同的目录里面, 或者也可以使用绝对路径的方式把这些文件传递给dlib.ector()之后的模型加载函数来运行。import dlib# 人脸检测器基于 HOG 特征CPU 上就能跑detector dlib.get_frontal_face_detector()# 5 点关键点模型定位双眼、鼻尖、嘴角用于对齐sp dlib.shape_predictor(shape_predictor_5_face_landmarks.dat)# 128D 特征提取模型核心输出就是用来算欧式距离的向量facerec dlib.face_recognition_model_v1(dlib_face_recognition_resnet_model_v1.dat)这里有一段逻辑方面的说明文字, 其具体内容是: 需要负责在整张图片当中把人脸的矩形区域给框选出来然后使用sp这个工具在刚才那个矩形里面去寻找五个关键点dlib这个程序会依据这五个关键点来完成轻微的旋转以及对齐操作, 目的是为了减少因为拍摄角度或者姿态不一样而带来的误差最后接收经过对齐处理好的人脸区域部分的数据, 并且输出包含一百二十八个浮点数的结果。在参数设置方面, 你在调用的时候可以把第二个参数传进去, 这个参数是用来表示上采样次数的。它的默认值是。如果你的图片比较小, 而且人脸也比较小, 那么就可以把这个值改成 , 这样能够提高检测出来的成功率, 但是相应的, 速度会慢下来。2.3 从图片到 128D 特征.py 在做什么.py 这个文件是进行特征提取操作时的入口所在之处。这个大致的具体运行流程首先是读取图像内容, 接着将其转换为 RGB 颜色格式, 然后针对画面中检测出的人脸进行检测动作, 随后对于每一个被检测到的人脸区域都要取其关键点位置数据, 再往下就是计算出一百二十八维度的描述子向量, 最后一步是将这些结果整合成一个列表并予以返回。包里面放置的那个名为 1.csv 的文件很有可能是此前某一次执行运行之后所保存下来的一个关于特征存储的数据文件, 而那个 .py 文件则主要起到的作用是负责把已经完成注册好的人脸对应的特征信息写入到这个相应的表格当中去。import cv2import dlibimport numpy as npdef get_128d_features(img_path):img cv2.imread(img_path)# dlib 需要 RGBOpenCV 默认 BGR必须转rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB)faces detector(rgb, 1) # 上采样 1 次小脸更易检出features []for face in faces:shape sp(rgb, face)# 第 3 个参数 num_jitters 控制抖动次数默认 1调大更稳但更慢face_descriptor facerec.compute_face_descriptor(rgb, shape, 1)features.append(np.array(face_descriptor))return features参数说明: tor 是关键, 设为 1 的时候只做一次前向运算如果设为 10 或者 100 , 那么会对人脸进行多次微小的扰动处理, 然后再去取平均值这样做能够让特征变得更稳定, 但是耗时会呈线性增长在注册的阶段可以稍微设大一点, 但是在实时比对的阶段建议保持为 1返回的结果是列表的形式, 这是因为一张图片里面可能会有多张人脸, 所以后续的比对操作需要逐个来进行处理。3. 注册并与比对着, 这里面的.py、.py和0.6阈值到底该当如何用事, 且3.1条中所述的注册流程, 即是把已知的人脸写入特征库内。那个以 .py 结尾的代码文件的职责, 是把 data 目录底下的那些图片, 还有 faces 目录底下的那些图片, 逐个地进行处理, 从中提取出 128D 的特征数据, 然后把提取出来的特征数据和对应的姓名一起, 全都存储到由数字 1 开头、后面跟一个点的 csv 格式的文件里面, 也就是 1.csv。这个 CSV 文件, 它就是咱们所提到的「人脸库」。在这个库里面的每一行数据当中, 大概的情况就是先记录了姓名的信息, 紧接着后面跟着的是 128 个浮点数类型的数据。注册环节所使用的图片的质量如何, 会直接决定后续进行人脸识别时的上限到底能有多高, 所以在选择用于注册的这张图片的时候, 有着不少需要讲究的地方, 具体要求就是最好是一张正脸的照片, 光线和色彩的分布要比较均匀, 脸上不能有其他的物体遮挡住, 而且图片的分辨率也不要太低。import csvimport osfrom face_128D import get_128d_featuresdef register(face_dir, csv_path):with open(csv_path, w, newline) as f:writer csv.writer(f)for name in os.listdir(face_dir):person_dir os.path.join(face_dir, name)if not os.path.isdir(person_dir):continuefor img_name in os.listdir(person_dir):img_path os.path.join(person_dir, img_name)feats get_128d_features(img_path)for feat in feats:# 第一列姓名后面 128 列特征writer.writerow([name] feat.tolist())这里有一个逻辑上的说明, 我们做出这样的假设, 在 faces 这个文件夹下面, 会按照每个人的名字去建立子目录, 然后每一个子目录里面会存放属于这个人的多张照片, 对于每一张照片, 所提取到的每条特征信息都会单独写在一行当中, 因为同一个人名底下可能有好多照片, 所以对应会出现多行的情况, 在进行比对操作的时候, 就直接取最小距离这一项数据作为结果就可以了, 关于参数方面, CSV 文件的路径建议采用绝对路径的方式来进行指定, 这样做可以防止因为脚本的工作目录发生了改变, 从而导致了找不到该文件的情况出现。当 1.csv 这个文件已经存在的时候, 大家必须要注意一下, 究竟是选择覆盖数据, 还是执行追加操作。一般来说, 注册脚本的默认行为是进行覆盖。如果你想要实现增量注册的功能, 你就需要把模式修改成a, 同时还需要去做去重处理。3.2 比对流程欧式距离计算与 0.6 判决.py 这个代码文件是用来当做识别入口的, 它会专门去读取那些正在等待被识别的图片, 然后从图片里面提取出一百二十八维的特征数据, 接着把这个数据和存储姓名信息的 CSV 表格里面的每一行进行欧式距离计算, 并且取出最小那个距离所对应的那个人名信息, 要是计算出来的最小距离数值低于零点六, 就说明是被判定为同一个人员, 否则就直接输出表示未知的字样。import numpy as npimport csvfrom face_128D import get_128d_featuresdef load_known(csv_path):names, feats [], []with open(csv_path, r) as f:reader csv.reader(f)for row in reader:names.append(row[0])feats.append(np.array(row[1:], dtypenp.float64))return names, np.array(feats)def recognize(img_path, csv_path, threshold0.6):names, known load_known(csv_path)unknown_feats get_128d_features(img_path)results []for uf in unknown_feats:# 广播计算uf 与 known 每行逐项相减dists np.linalg.norm(known - uf, axis1)idx int(np.argmin(dists))min_dist float(dists[idx])if min_dist threshold:results.append((names[idx], min_dist))else:results.append((unknown, min_dist))return results逻辑方面的说明内容为, 使用 np..norm 这个函数处理 known 减去 uf之后的结果, 其中设置 axis参数为1, 这样可以在一行代码之内把全部已知特征对应的欧式距离全部计算完毕, 关于axis这个参数的作用是指向维度1的, 也就是128维方向进行求取范数的操作。那个等于零点六是一个由摘要提供的经验数值, 在实际执行应用任务的时候是可以进行调节的。如果把它调低到零点五, 标准会变得更为严格, 这样能够减少误认的情况发生, 但是同时也可能会将本应确认的人错误地判断为未知对象反之, 如果将其调到零点七, 标准则变得较为宽松, 虽然召回率会升高, 但却更容易出错, 导致把相貌相似的其他人错误地认作目标对象。在这个程序包里面所存在的像 cmp1.png 这种成对的图片文件或者类似的 .jpeg 格式文件, 通常情况下大概率就是被专门用于开展比对测试工作的。3.3 阈值 0.6 的边界与实测建议0.6并不是铁律, 它来自于dlib作者在LFW等多个数据集上进行的统计分析, 在实际的场景之中, 光照、年龄、化妆以及摄像头畸变等因素都会导致同一个人的距离出现上升的情况。我通常会做两件事情。第一, 使用包裹中自带的成对图片进行初步测试, 以便观察属于同一个人的情况在距离上处于怎样的区间范围, 而不同人的情况又对应着怎样的另一个区间范围。第二, 如果业务层面的条件是许可的, 便将那个阈值设定为零点五到零点五十五这个数值跨度之内, 宁可让系统显示出表示“不确定”的结果, 也绝不去做随意的、不准确的辨认动作。距离区间 常见解释 建议动作 0.4极可能同一人直接通过0.4 ~ 0.6可能同一人通过但可记录日志0.6 ~ 0.8存疑拒绝或转人工 0.8基本不同人拒绝这张表不是标准答案是给你调参时一个参照。真正上线前拿你自己的数据跑一遍 ROC看哪个阈值下误识率和拒识率平衡得最好。4. 为了避免踩雷并且能够顺利排查问题, 针对安装 dlib 失败、无法检测到人脸、以及测得的距离数据忽大忽小这些小状况该怎么办呢, 我们先来看第四点的一小节的内容, 这一节主要说的是现象部分, 也就是在执行 pip 工具去编译安装 dlib 的时候, 程序会报错, 而且整个过程可能会一直卡住, 停滞在 cmake 这个步骤, 或者是卡在 C 编译器这里, 根本运行不下去。出现这种状况的原因在于, dlib 这个库里面包含了 C 扩展部分。当我们利用 pip 工具去安装它的时候, 默认下载下来的是源码包。因此, 我们的本地电脑上面必须提前准备好 cmake 软件以及 C 的编译环境才行。如果在这之前没有正确完成相关环境的搭建, 比如在 系统上缺少相关的 Build Tools 构建工具, 在 Linux 系统中缺少 g 编译器, 或者在 macOS 系统上也没有安装好 Xcode 的命令行工具的话, 安装过程就会失败并报错。解决办法是先安装Build Tools, 并且要勾选「C 生成工具」这个选项, 然后再使用 pip 来安装 cmake, 最后再进行 dlib 的安装操作。对于 Linux 系统来说, 需要执行 sudo apt build- cmake 这些命令。而对于macOS平台而言, 则应该执行 xcode- --。倘若在实际操作的过程中, 确实无法顺利实现代码的编译工作, 那么不妨去寻找与之相配备的具体相应版本的预编译形式的 wheel 文件。在此过程里, 务必加以特别注意的事项在于, 一定要确保该 wheel 文件里面所包含的 dlib 这一库的版本号, 与当前所使用的模型文件之间存在着良好的兼容程度。4.2 现象图片里明明有人脸 返回空列表这是因为 dlib 里面的那个 HOG 检测器, 对于小脸、侧脸, 还有强逆光的这种情况, 它的敏感度是不高的。在默认的设置中, 上采样次数是零的时候, 如果人脸的像素高度低于八十左右的话, 这就很容易出现漏检的问题。找到解决办法了, 可以通过调用包含 rgb 和 1 的参数或者调用包含 rgb 和 2 的参数来提升上采样的层级规模, 不过这样做付出的代价是处理速度变慢。此外一定要确认图片确实是被正确的读取进了系统, 因为 cv2.这个模块在遇到包含中文的路径地址或者遇到根本不存在的路径地址时都会返回 None, 如果后面再直接去用这个返回值就会立刻报错出事故。在通常的操作里面, 大家经常会采用使用cv2这样的函数去处理, 具体的方式是把np和path结合起来, 并且指定数据类型是np.uint8, 然后再传入-1这个参数, 以此来读取那些包含中文的文件路径。4.3 现象同一个人两张照片距离超过 0.6被判定为未知原因是登记照和测试照片之间存在巨大的差异, 比如有的一个是正面脸而另一个是侧面脸, 有的是在室内拍摄的而另一个是在室外拍摄的, 还有一张带有眼镜而另一张没有佩戴。由于128D特征对面部姿态以及光照条件具有极高的敏感性, 因此它并不是一种能够适应所有场景的万能技术。。解决办法是在注册阶段为每个人多上传几张分别来自不同角度和调整了光照条件的图片。在后续进行比对时, 不再去计算所谓的平均距离, 而是从中取出最小的那个距离作为结果。假如当前的业务流程允许我们这么做, 那么在提取面部特征向量之前, 应当先执行人脸对齐操作。虽然dlib这个技术库里的sp模型已经提供了基于五个关键点的人脸对齐功能, 但你可以在其基础上, 依据眼睛的具体位置信息, 进一步实施仿射变换处理, 从而使得图像对齐的精度实现更进一步的规范化调整。4.现象表现为, 把CSV格式的特征文件读取进来之后, 里面全都是字符串类型的数据, 这个时候去执行np..norm这个操作的话, 就会报出类型错误的提示。因为从csv文件里读出来的数据默认都是字符串格式, 如果直接把它转换成np.array数组的话, 变量的数据类型会变成str类型, 这样后面在进行数值计算的时候程序就会崩溃。解决的策略是在进行数据读取的时候, 明确地将数据类型转换为浮点数, 这可以参考3.2节里所写明的代码方式, 通过np.array(row, dtypefloat)来实现操作。另外还需要特别留意CSV文件的情况, 如果该文件中包含了空置的内容行或者是标题头信息, 那么必须要把这些数据项予以跳过处理, 如果不这样做的话, 系统同样会出现报错的信息提示。4.5 现象换台机器跑识别结果全乱具体的状况是, 那个名为 1.csv 的文件, 是在某一台机器上, 使用某一个特定版本的 dlib 生成的, 等到更换了另一台机器之后, 因为 dlib 的版本不一样了, 那里面的一百二十八维特征的数值分布情况, 就可能会出现一些比较细微的偏移, 这就会导致各个特征之间的距离发生变大或者变小的变化。解决方法是把特征库和识别代码绑定到同一套环境里面, 换了新的环境以后就要重新注册一遍, 或者至少要使用同样一批测试图片来验证距离分布情况, 看看有没有出现明显的偏差, 这也就是为什么要对 .txt 文件进行版本锁定, 不让它随便变动。5. 有一些进阶的窍门, 那就是怎么样能把128D这个特征的作用发挥到极致, 还有我在实际操作过程中曾经遇到过的一次因为阈值设置问题而失败的经历。在基础流程已经顺利跑通这种情况下面, 这份资源还是可以接着深入挖掘的。第一点, 那个1.csv文件说到底就是一个小型向量库, 你可以考虑把它替换成直接存放到.npy格式里面去, 这样做起来加载速度会更快一一些, 同时也挺方便去做增量更新的。第二点呢, 这个128D特征并不只是用来判断是不是同一个人的那样子, 它还能够拿来做聚类工作——也就是说把一大堆未知人脸先提取出特征来然后进行数据处理操作, 系统可以自动帮我们把属于同一个人的那些结果给聚拢到一起, 这种方式挺适合作为整理相册时候用的工具。第三, 如果你需要实现实时识别的功能, 请千万不要对每一帧图像都重新读取CSV文件, 应当只在程序启动的时候将数据加载一次并放置到内存之中, 利用numpy矩阵来保持常驻状态, 这样的比对速度能够抵达毫秒级别。# 把特征库预加载成矩阵实时比对时不再读文件import numpy as npknown_matrix np.load(known_feats.npy) # shape: (N, 128)known_names np.load(known_names.npy, allow_pickleTrue)def fast_recognize(face_feat, threshold0.55):dists np.linalg.norm(known_matrix - face_feat, axis1)idx int(np.argmin(dists))return (known_names[idx], float(dists[idx])) if dists[idx] threshold else (unknown, float(dists[idx]))在参数设置方面, 我之所以在这里填写 0.55, 是因为在我的个人测试集数据里面, 同人距离的数值多数集中在 0.35 到 0.5 这个区间段当中, 如果选择把 0.6 纳入考虑范围的话, 很有可能会错误地包含一些本来不应该入选的目标对象。说起这个话题, 我曾经有过一次偷懒的行为, 直接使用了摘要里面的数字零点六作为参数, 上线了一个门禁的演示系统, 结果导致两个长相有些相似的同事被系统互相认错了, 这是一个非常悲惨且令人痛苦的教训, 因此我们的核心观点是, 无论是什么类型的阈值, 都必须使用你自己所拥有数据来进行重新调整和确认, 绝对不要盲目相信软件提供的默认数值。还有一个非常容易被忽视的关键点, 如果你在做注册操作的时候, 把相关参数调整到介于10和20这个范围里面, 就能够让属于同一个人的那些多条特征数据变得更加紧密集中, 这其实相当于是给每一位用户进行了一次独立的数据增强处理工作, 虽然这样会导致系统的整体注册速度变慢, 但是考虑到注册通常只发生一次, 所以这种时间成本是完全值得承受的, 相反, 在进行比对的阶段就应该把这个数值保持为1就可以了, 因为在那种时候, 反应速度是排在第一位的。自那一个时候起, 对于任何人脸识别项目, 只要我拿到了项目数据, 都要强制执行“用自己的成对图片画距离分布直方图”的这一操作程序, 首先先看数据, 然后再决定阈值, 以此来希望能帮助到您。
返回列表