首页 / 资讯中心 / 文章详情

dlib人脸识别实战:68点关键点与微笑检测完整拆解

dlib人脸识别实战:68点关键点与微笑检测完整拆解 ★ FEATURED ARTICLE
说起dlib这个人脸识别库做视觉开发的朋友应该都不陌生。它是一个C写的开源机器学习库Python封装的接口用起来相当顺手人脸检测、人脸关键点检测、人脸识别这几件事靠它一套就能搞定。这篇就围绕dlib人脸识别这个项目展开顺便把基于关键点坐标实现的微笑检测也一并拆解清楚给出可以直接抄作业的代码、参数和判断逻辑以及我实际踩过的坑。适合刚接触人脸识别的新手也适合想快速搭一个表情检测demo的开发者参考。1. 项目全景拆解一条链路从哪到哪1.1 这个项目到底要解决什么问题很多人一听“人脸识别”就觉得是个大工程2024年的今天各种深度学习框架层出不穷动不动就要上GPU训练。但实际工作中很多需求根本用不着那么重的方案。比如门禁考勤、课堂签到、趣味拍照、表情互动这类场景要的是离线可用、CPU能跑、快速落地这时候dlib就是一个特别合适的选项。这个项目要完成的事情很明确输入一张图片或一帧视频先找到人脸在哪里然后把人脸的关键点定位出来再提取一个能代表这张脸的向量用向量之间的距离判断是不是同一个人。在这个基础上额外用关键点的几何关系判断这个人是不是在微笑。整个链路看起来长实际拆开就是四个步骤人脸框检测、68点关键点定位、128维描述子提取、基于几何特征的微笑判定。这四个步骤对应dlib里的三样核心工具get_frontal_face_detector()负责框出人脸shape_predictor_68_face_landmarks.dat模型负责给出68个关键点坐标dlib_face_recognition_resnet_model_v1.dat模型负责把脸变成128维数值向量。微笑检测不需要额外的模型直接从68个点里的嘴部坐标算一个比例就行。1.2 为什么选dlib而不是OpenCV或深度学习框架我经常被问一个问题OpenCV不也有人脸检测吗为什么不用OpenCV的Haar级联分类器OpenCV自带的人脸检测确实快但它是基于Haar特征的旧方案对侧脸、遮挡、光照变化非常敏感误检漏检都挺严重。而且OpenCV只负责检测不给关键点也不给识别向量后续活儿还得自己想办法。深度学习框架如PyTorch、TensorFlow当然精度更高但部署链路长模型转换麻烦CPU推理速度也不一定比dlib快多少对一个小demo来说属于杀鸡用牛刀。dlib走的是中间路线。它的HOGSVM检测器在CPU上跑得很快CNN人脸检测模型mmod_human_face_detector.dat精度又能追平不少轻量级神经网络。68点关键点模型是经典中的经典识别模型输出128维向量直接算欧氏距离就能完成人脸比对。更关键的是dlib是纯离线库不需要联网调用任何服务数据安全性也有保障。如果你要做一个原型验证、课程设计、公司内部小工具dlib基本都是最优解。如果你要面对千万级人脸库、复杂场景识别那确实得换深度学习方案这是另一个话题了。1.3 完整流程与模块划分项目整体可以画成一条流水线读取图像或视频帧转换成RGB格式用HOG检测器做人脸检测得到若干个rectangle对象对每个人脸框用68点模型做关键点定位用识别模型计算128维人脸描述子将描述子和库里已知人脸做距离比对小于阈值则判定为同一人同时根据关键点坐标计算微笑系数超过阈值则判定为微笑模块划分上我建议把检测、关键点、识别、微笑检测写成四个独立函数。别学网上很多教程把所有逻辑堆在一个脚本里一次性跑通是爽但后边调参、换输入、做可视化的时候会想骂人。函数隔离还有一个好处每一层出了问题都能单独验证排查速度快很多。2. 环境准备从零装好dlib并下载模型2.1 平台与依赖要求dlib对Python版本有讲究太新的版本不一定有预编译包。以我常用的环境为例Python 3.8到3.10都没问题Windows、Linux、macOS都可以装。依赖方面需要CMake、C编译器和pip装的时候缺一不可。Windows用户建议先装Visual Studio的C生成工具不然从源码编译dlib大概率报错。macOS用户需要Xcode Command Line Tools直接执行xcode-select --install。Linux用户需要build-essential和cmakeUbuntu/Debian可以执行sudo apt install build-essential cmake。提示如果只是跑我这套代码不需要额外安装CUDA或cuDNN。dlib的CPU版本对你来说完全够用省掉这些环境能少踩一半坑。2.2 安装dlib的三种方式第一种是直接pip安装适合幸运儿pip install dlib如果你运气好pip会直接拉取预编译的wheel包几分钟就装完。但很多人会遇到第二种情况pip开始下载源码然后现场编译几分钟到十几分钟不等中间还可能报错。第二种方式是先装好编译环境再pip安装遇到编译错误时这个方案最有效# Ubuntu / Debian sudo apt update sudo apt install build-essential cmake pip install dlib # macOS xcode-select --install brew install cmake pip install dlib第三种方式是从源码安装适合需要改dlib内部参数或者编译CUDA版本的朋友git clone https://github.com/davisking/dlib.git cd dlib python setup.py install这里提醒一句从源码装之前确认内存足够编译期间gcc、g进程会吃不少内存4GB以下机器建议先用swap顶上不然编译到一半直接被系统杀掉前功尽弃。安装完成之后验证一下import dlib print(dlib.__version__)能打印出版本号就说明成功了。2.3 两个关键模型文件环境装好只是开始模型文件才是项目的灵魂。两个模型一个都不能少shape_predictor_68_face_landmarks.dat体积约95MB负责从人脸框内预测68个关键点dlib_face_recognition_resnet_model_v1.dat体积约20MB负责把对齐后的人脸映射为128维向量这两个文件都来自dlib官方模型库网上搜索文件名很容易找到。下载后放在项目的models目录下和其他代码分开管理。加载方式是这样import dlib detector dlib.get_frontal_face_detector() predictor dlib.shape_predictor(models/shape_predictor_68_face_landmarks.dat) face_rec_model dlib.face_recognition_model_v1(models/dlib_face_recognition_resnet_model_v1.dat)注意加载路径别写错建议用os.path.join拼路径避免在别的机器上跑起来路径全断。2.4 素材与目录准备处理图像需要OpenCV支持和一些测试素材目录结构我习惯这么建face_project/ ├── models/ │ ├── shape_predictor_68_face_landmarks.dat │ └── dlib_face_recognition_resnet_model_v1.dat ├── known_faces/ │ ├── alice_1.jpg │ └── alice_2.jpg ├── test_images/ │ └── scene.jpg ├── output/ └── main.pyknown_faces目录放的是需要“认识”的人的照片每人可以放多张识别时这些照片会生成基准描述子存进内存。test_images放的是待测试图片。准备素材时有一点要注意人脸照片尽量是正脸、光线均匀、无大面积遮挡的录入的质量直接决定后续识别效果。用人脸识别跑几百张照片的经验来看录入照质量低造成的问题比算法本身还难救。3. 人脸检测与68点关键点核心代码逐行拆解3.1 人脸检测器的使用与参数dlib的正脸检测器用法很直接import dlib import cv2 detector dlib.get_frontal_face_detector() img cv2.imread(test_images/scene.jpg) rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) faces detector(rgb, 1)第二个参数1是upsample_num_times表示检测前对图像上采样的次数。上采样一次相当于把图像放大一倍能检测到更小的人脸但代价是计算量增加、速度变慢。默认参数下有40像素以上人脸都能稳定检测如果人脸特别小可以调成1或2。如果图里人脸很大很清楚0就够了。faces是一个列表里面每个元素是一个rectangle对象有left()、top()、right()、bottom()四个方法对应人脸框的左上角和右下角坐标。多个人脸会返回多个rectangle。3.2 68点分布每个区域对应哪些索引拿到人脸框之后关键点模型就可以上场了shape predictor(rgb, face)shape.part(i)返回第i个关键点每个点都有.x和.y属性。68个点不是乱排的它们有严格的语义分布了解分布是后续做微笑检测和表情分析的基础索引范围区域点数0-16下颌轮廓1717-21左眉522-26右眉527-30鼻梁431-35鼻底与鼻孔536-41左眼轮廓642-47右眼轮廓648-59嘴部外轮廓1260-67嘴部内轮廓8记住嘴部这块48是左侧嘴角外54是右侧嘴角外51是上嘴唇外轮廓中心57是下嘴唇外轮廓中心。内唇区域里60是内唇左嘴角64是内唇右嘴角62是内唇上中心66是内唇下中心。微笑检测主要用48、54、51、57这几个点就够了当然也可以用内唇点看你自己习惯。3.3 关键点绘制与坐标取值技巧绘制关键点可以用OpenCV的cv2.circle逐点画圆。为了看得更清楚我习惯把点放大一点并把嘴部区域用不同颜色单独标出import dlib import cv2 def draw_landmarks(img, shape): for i in range(68): x shape.part(i).x y shape.part(i).y if 48 i 67: color (0, 255, 255) # 嘴部用黄色 elif 36 i 47: color (255, 0, 0) # 眼睛用蓝色 else: color (0, 255, 0) # 其他用绿色 cv2.circle(img, (x, y), 2, color, -1) # cv2.putText(img, str(i), (x, y), cv2.FONT_HERSHEY_SIMPLEX, 0.3, (255,255,255), 1) return img绘图时有个细节dlib里shape.part(i)访问的是点坐标但遍历时不能直接取shape[i]那样的切片老老实实用for i in range(68)最稳妥。坐标取值有两个容易踩的坑。第一个cv2.imread读出来是BGR顺序而dlib内部用的是RGB送去给检测器和预测器之前必须cv2.cvtColor一下否则颜色通道错乱关键点位置会飘。第二个绘制时要画在原图的BGR格式上检测时用转换后的RGB格式两者别混用。3.4 人脸识别描述子的提取与比对检测和关键点搞定之后人脸识别就水到渠成def get_face_descriptor(rgb, shape): descriptor face_rec_model.compute_face_descriptor(rgb, shape) return list(map(float, descriptor))这里返回的是Python列表长度固定为128。compute_face_descriptor本质上做了人脸对齐特征提取两步对齐用的就是68点定位出来的五官位置所以关键点检测不准的话识别精度也会跟着崩。对比两个人脸的相似度就是计算128维向量之间的欧氏距离import math def euclidean_distance(vec1, vec2): return math.sqrt(sum((a - b) ** 2 for a, b in zip(vec1, vec2)))距离越小说明越像同一个人。实际使用中阈值取0.4到0.6之间比较常见小于0.4基本是同一人0.4到0.6需要谨慎判断大于0.6基本可以认为不是同一人。但阈值没有标准答案跟录入的摄像头、光照、人脸库大小都有关系建议先拿几组“是同一个人”和“不是同一个人”的样本算出距离分布再取分界点。注册人脸库的流程很简单遍历known_faces目录下的照片对每张照片做人脸检测、关键点定位、描述子提取把名字和描述子存成字典。识别时对输入图里的每张人脸算描述子和库里所有描述子都算一遍距离取最小值小于阈值就归为对应的人否则标记为未知。import os import dlib import cv2 import math detector dlib.get_frontal_face_detector() predictor dlib.shape_predictor(models/shape_predictor_68_face_landmarks.dat) face_rec_model dlib.face_recognition_model_v1(models/dlib_face_recognition_resnet_model_v1.dat) known_faces {} for file_name in os.listdir(known_faces): if not file_name.lower().endswith((.jpg, .jpeg, .png)): continue path os.path.join(known_faces, file_name) img cv2.imread(path) rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) faces detector(rgb, 1) if not faces: print(fWarning: no face detected in {file_name}) continue shape predictor(rgb, faces[0]) descriptor list(map(float, face_rec_model.compute_face_descriptor(rgb, shape))) name os.path.splitext(file_name)[0] known_faces[name] descriptor这个注册逻辑对单张人脸照已经够用但实际应用时建议每个人都多录几张照片把多张描述子求平均作为基准向量比单张更稳。光照导致的描述子抖动在暗光下尤其明显平均之后能明显压制这种随机波动。4. 微笑检测几何特征实战与阈值调优4.1 为什么用关键点几何关系做微笑检测微笑检测严格来说属于表情识别范畴常规做法是用卷积神经网络训练一个表情分类器但那是另一个大项目。用dlib的68点几何关系也能实现一个效果不错的“微笑判定器”原理很简单人微笑的时候嘴角会向上扬起同时上下嘴唇之间距离会发生变化这两者都可以用关键点的坐标差来量化。相比训练神经网络几何方案有四个明显优势不需要额外训练数据、计算量几乎为零、实时性极好、可解释性强。缺点是它只能判断微笑这一种表情判断不了喜怒哀惧。但对很多应用场景来说只判断微笑已经够了比如微笑拍照机就是典型需求。4.2 嘴角上扬系数实现与计算我使用的是“嘴角上扬系数”方案计算公式基于48、51、54、57四个点的坐标左嘴角外点48右嘴角外点54上嘴唇外轮廓中心51下嘴唇外轮廓中心57嘴中心y坐标 (y51 y57) / 2嘴角y坐标 (y48 y54) / 2上扬差 嘴中心y坐标 - 嘴角y坐标图像坐标系的原点在左上角y轴向下所以嘴角越往上翘嘴角y值越小上扬差越大。直接使用上扬差的绝对值会受人脸大小影响所以需要归一化我这里选择用人脸框宽度作为除数def detect_smile(shape, face_width): left_mouth shape.part(48) right_mouth shape.part(54) top_lip shape.part(51) bottom_lip shape.part(57) mouth_center_y (top_lip.y bottom_lip.y) / 2.0 mouth_corner_y (left_mouth.y right_mouth.y) / 2.0 diff mouth_center_y - mouth_corner_y smile_ratio diff / float(face_width) return smile_ratio用人脸宽度归一化是个关键细节。同样的微笑弧度近距离拍摄的像素差可能是远距离拍摄的三倍如果不归一化固定阈值根本没法用。除以人脸宽度之后不同距离下同一个人同一表情算出的系数基本稳定这是整个算法能不能落地的前提。实际测试数据大概是这样面无表情时smile_ratio通常在0.01到0.03之间波动微笑时大约0.04到0.06咧嘴大笑时可以达到0.07以上。这个范围是我在普通摄像头、正常光照下实测的换成不同镜头或不同人种会有偏移但量级差不多。4.3 实时视频流微笑检测与自动拍照把上面的检测逻辑放到视频循环里就变成了一个实时微笑检测器import dlib import cv2 import time detector dlib.get_frontal_face_detector() predictor dlib.shape_predictor(models/shape_predictor_68_face_landmarks.dat) cap cv2.VideoCapture(0) smile_count 0 SMILE_THRESHOLD 0.045 REQUIRED_FRAMES 3 while True: ret, frame cap.read() if not ret: break rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) faces detector(rgb, 0) for face in faces: shape predictor(rgb, face) smile_ratio detect_smile(shape, face.width()) # 在脸上绘制关键点和系数 draw_landmarks(frame, shape) cv2.putText(frame, fsmile: {smile_ratio:.3f}, (face.left(), face.top() - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) if smile_ratio SMILE_THRESHOLD: smile_count 1 else: smile_count 0 if smile_count REQUIRED_FRAMES: ts time.strftime(%Y%m%d_%H%M%S, time.localtime()) cv2.imwrite(foutput/smile_{ts}.jpg, frame) smile_count 0 cv2.imshow(Smile Detection, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()代码里加了一个“连续N帧判定”机制不是某一帧超过阈值立刻触发拍照而是连续3帧都超过阈值才触发。这个设计的目的是过滤表情切换瞬间的抖动和噪声实测下来误触发率能降低一大截。每拍完一张就把计数清零避免连续狂拍。4.4 阈值标定与踩坑记录阈值是这套方案里最需要“因地制宜”的部分我把自己踩过的几个坑列出来帮你省点时间第一固定阈值在暗光下会失效。光线不足时人脸检测框会变小关键点也会轻微漂移同一个微笑的表情系数比明亮环境下低不少。我试过在傍晚光线偏暗的房间里测试面无表情的smile_ratio居然能飙到0.045直接触发拍照。解决办法是保持环境光线稳定或者在代码里加入平均亮度的自适应调整。第二张嘴和微笑容易混淆。张大口大笑时嘴中心y坐标下降嘴角y坐标也可能下降两者相减的结果有时和微笑差不多。如果需求只是“微笑”而不是“大笑”可以加一个嘴部张开度判断计算51和57的像素距离再除以人脸宽度超过一定值就判定为张嘴即使上扬系数达标也不算微笑。这个辅助条件我用下来效果很好。第三牙齿本身就是微笑信号。咧嘴露齿笑时牙关字区域亮度高可以在嘴部区域内计算亮斑面积辅助判断更稳。不过这部分代码量会多一些不是必须的原本的上扬系数方案对多数场景已经够用。5. 常见问题排查与性能优化5.1 安装与模型加载问题速查错误现象可能原因解决办法pip install dlib 报找不到匹配版本Python版本过新或过旧换成Python 3.8到3.10再试编译时报CMake错误本机缺少CMake和C工具链按平台装好编译工具链后重装编译时内存不足被kill编译进程占用内存过高关掉其他程序或加swap空间加载模型时文件路径错误路径写错或文件不在指定目录用绝对路径或os.path.abspath确认加载模型时版本不兼容dlib版本太旧或太新升级或降级dlib版本保持较新稳定版5.2 检测不到人脸怎么办检测不到人脸主要有几种情况人眼太小、人脸过侧、光线过暗、图片分辨率太低。按优先级排查先把detector(rgb, 1)的上采样次数调高再看是否把人脸框裁得太小最后看图片质量。如果是视频流里偶尔漏检可以在人脸框之间加一个简单的跟踪逻辑上一帧检测到人脸下一帧没检测到就沿用上一帧的人脸框继续做关键点定位直到连续丢帧超过一定次数才放弃。这个小技巧能让视频画面的检测稳定感提升不少代码量也只有十几行。如果是图片检测可以先用cv2.resize把图片放大到宽边800像素以上再送进检测器。dlib的HOG检测器在分辨率太低的图上很吃亏因为人脸区域本身像素太少梯度特征根本算不准。5.3 运行太慢怎么优化dlib的检测器在CPU上是能跑的但实时视频流里如果每帧都做全图检测帧率通常只有20fps上下加上关键点和识别之后会更低。优化思路按性价比排序第一缩小输入尺寸。视频流分辨率720p时把每一帧resize到宽边480像素再检测检测速度能翻倍对关键点精度的影响很小。第二跳帧检测。人脸不会在几毫秒内消失每3帧做一次检测中间几帧直接沿用上一帧的人脸框做关键点定位帧率提升非常明显。第三换用CNN检测器。dlib.get_frontal_face_detector()是HOG方案dlib还提供dlib.cnn_face_detection_model_v1配合mmod_human_face_detector.dat模型对侧脸、遮挡的检测能力明显更强但速度会慢一半左右。CPU资源足够、对精度要求高时可以考虑。第四把关键点检测和人脸识别拆开跑。实际做微笑检测时不需要加载128维识别模型那个模型的描述子计算开销不小。去掉识别部分之后CPU上实时跑720p的检测关键点微笑计算毫无压力。5.4 识别与微笑精度提升方向人脸识别精度受限的最大软肋是样本单一。录入一张脸的正脸照之后换了发型、戴了眼镜、角度偏了距离很容易超过阈值。最实际的办法是每个身份录3到5张不同角度和光照的照片把描述子取平均再存进人脸库。我测试过录入2张和录入5张的影集对同一人的识别距离能从0.55降到0.42差距非常可观。微笑检测这边如果想进一步提高区分度可以组合多个特征把嘴角上扬系数、嘴部张开度、左右嘴角高度差三个量拼接成一个三维特征用一个小型逻辑回归模型来分类。不过实际测试下来绝大多数场景里单个上扬系数够用了组合特征更适合对误判率极其敏感的商业项目。关于摄像头角度我的建议是让摄像头放在与人眼平齐或略高一点的位置俯仰角度太大会导致嘴部关键点透视变形不管怎么调阈值都难搞。这也是很多新手做表情识别时最容易被忽略的坑不是算法不行是安装角度不对。我个人在实际操作中最深的体会是dlib这套工具链单个环节都很成熟真正决定项目成败的是环节之间的衔接方式。比如检测阶段的人脸上采样参数、识别阶段的阈值标定、微笑检测的连续帧计数这些细节在官方示例代码里往往看不到但恰恰是这些细节让demo从“能跑”变成“能用”。再分享一个小经验做这类视觉项目一定先把单张图片跑通再上视频流。图片阶段可以打印出每一层的中间结果比如人脸框坐标、关键点坐标、描述子距离、微笑系数确认每一层的数值都在合理范围后再切换到实时视频流调试性能。这个习惯帮我在各种视觉项目里省下了大量排查时间强烈推荐你试试。
阅读完成 · 觉得有帮助?
咨询建站