基于dlib+OpenCV的人脸疲劳检测 + 年龄性别识别实战
一、前言在计算机视觉领域人脸相关技术一直是热门方向从人脸检测、关键点定位到疲劳检测、年龄性别识别都有着广泛的应用场景比如驾驶员疲劳监测、智能门禁、人机交互等。本文将基于dlib和OpenCV从零实现两个经典人脸应用实时人脸疲劳检测基于眼睛纵横比 EAR和年龄性别识别基于预训练 CNN 模型。二、dlib 库基础认知2.1 dlib 是什么dlib 是一个适用于 C 和 Python 的第三方开源库集成了机器学习、计算机视觉、图像处理的全套工具包支持在机器人、嵌入式设备、移动端、高性能服务器等多环境运行完全开源免费可商用是人脸相关项目的首选工具库之一。2.2 dlib vs OpenCV 人脸检测对比那么OpenCV 也有人脸检测为什么还要用 dlib这里给大家做一个直观对比OpenCV 人脸检测dlib 人脸检测优点1. CPU 实时运行速度快2. 架构简单易上手3. 支持不同比例人脸检测1. 支持正面 轻微非正面人脸2. API 语法极简调用方便3. 小遮挡场景下仍可稳定工作缺点1. 误检率高易把非人脸识别为人脸2. 仅支持正面人脸非正面效果差3. 抗遮挡能力弱1. 无法检测小脸训练数据最小人脸为 80×80小脸需自定义训练2. 检测框易缺失额头 / 下巴部分区域3. 极端非正面侧脸、俯视 / 仰视效果差追求速度用 OpenCV追求精度用 dlib两者可以根据场景灵活选择。三、dlib 安装指南很多同学在安装 dlib 时会遇到Failed building wheel for dlib的报错这里给大家两种稳定安装方法3.1 方法一pip 镜像安装直接使用国内镜像源加速安装避免网络问题pip install dlib -i https://pypi.tuna.tsinghua.edu.cn/simple注意该方法需要本地配置好 C 编译环境Visual Studio Build Tools否则会编译失败。3.2 方法二whl 文件离线安装如果编译环境有问题直接下载对应 Python 版本、系统版本的预编译 whl 文件然后执行安装。需要下载自己对应版本的比如Python3.8就是dlib-19.19.0-cp38-cp38-win_amd64.whl四、核心功能 1基于 dlib 的实时人脸疲劳检测4.1 实现原理疲劳检测的核心逻辑是眼睛纵横比Eye Aspect Ratio, EAR人眼睁开时眼睛的垂直高度和水平宽度的比值EAR较大当人眼闭合 / 半闭合疲劳状态时EAR 会显著降低。我们通过 dlib 的 68 个人脸关键点提取左右眼的 6 个关键点计算 EAR 值当 EAR 连续多帧低于阈值通常为 0.3时判定为疲劳状态发出预警。4.2 68 个人脸关键点说明dlib 的shape_predictor_68_face_landmarks.dat模型可以检测人脸的 68 个关键点其中36-41 号点右眼关键点42-47 号点左眼关键点48-67 号点嘴巴关键点可用于微笑检测等拓展功能4.3 完整代码实现import numpy as np import cv2 import dlib from sklearn.metrics.pairwise import euclidean_distances from PIL import Image, ImageDraw, ImageFont def eye_aspect_ratio(eye): # 计算眼睛纵横比EAR A euclidean_distances(eye[1].reshape(1,2), eye[5].reshape(1,2)) B euclidean_distances(eye[2].reshape(1,2), eye[4].reshape(1,2)) C euclidean_distances(eye[0].reshape(1,2), eye[3].reshape(1,2)) ear ((A B) /2.0) / C return ear def cv2AddChineseText(img, text, position, textColor(255, 0, 0), textSize50): # OpenCV绘制中文文本 if isinstance(img, np.ndarray): img Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) draw ImageDraw.Draw(img) fontStyle ImageFont.truetype(simsun.ttc, textSize, encodingutf-8) draw.text(position, text, textColor, fontfontStyle) return cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR) def drawEye(eye, frame): # 绘制眼睛轮廓 eyeHull cv2.convexHull(eye) cv2.drawContours(frame, [eyeHull], -1, (0, 255, 0), 2) # 初始化参数 COUNTER 0 # 连续闭眼帧数计数器 EYE_AR_THRESH 0.3 # EAR阈值 EYE_AR_CONSEC_FRAMES 50 # 连续闭眼50帧触发预警 # 加载dlib模型 detector dlib.get_frontal_face_detector() predictor dlib.shape_predictor(shape_predictor_68_face_landmarks.dat) # 打开摄像头 cap cv2.VideoCapture(0) while True: ret, frame cap.read() if not ret: break # 人脸检测 faces detector(frame, 0) for face in faces: # 检测人脸关键点 shape predictor(frame, face) shape np.array([[p.x, p.y] for p in shape.parts()]) # 提取左右眼关键点 rightEye shape[36:42] leftEye shape[42:48] # 计算左右眼EAR rightEAR eye_aspect_ratio(rightEye) leftEAR eye_aspect_ratio(leftEye) ear (leftEAR rightEAR) / 2.0 # 疲劳判断 if ear EYE_AR_THRESH: COUNTER 1 # 连续闭眼50帧触发预警 if COUNTER EYE_AR_CONSEC_FRAMES: frame cv2AddChineseText(frame, !!!!!危险疲劳驾驶!!!!!, (250,250), textColor(0,0,255), textSize50) else: COUNTER 0 # 绘制眼睛轮廓 drawEye(leftEye, frame) drawEye(rightEye, frame) # 显示EAR值 info fEAR: {ear[0][0]:.2f} frame cv2AddChineseText(frame, info, (0,30), textColor(0,255,0), textSize30) cv2.imshow(Frame, frame) # 按ESC退出 if cv2.waitKey(1) 27: break # 释放资源 cv2.destroyAllWindows() cap.release()4.4代码解析def eye_aspect_ratio(eye): # 计算眼睛垂直方向两个距离 A euclidean_distances(eye[1].reshape(1,2), eye[5].reshape(1,2)) B euclidean_distances(eye[2].reshape(1,2), eye[4].reshape(1,2)) # 计算眼睛水平方向距离 C euclidean_distances(eye[0].reshape(1,2), eye[3].reshape(1,2)) # 公式(垂直平均距离) / 水平长度 ear ((A B) /2.0) / C return ear原理图解一只眼睛有6 个关键点0~5A、B垂直高度C水平宽度EAR 越小 眼睛越闭通用标准EAR 0.3 判定闭眼def cv2AddChineseText(img, text, position, textColor(255, 0, 0), textSize50): if isinstance(img, np.ndarray): # 如果是OpenCV格式 img Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) # 转PIL格式 draw ImageDraw.Draw(img) fontStyle ImageFont.truetype(simsun.ttc, textSize, encodingutf-8) # 宋体 draw.text(position, text, textColor, fontfontStyle) # 写字 return cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR) # 转回OpenCV格式作用解决 OpenCV 不能直接显示中文的问题必须用 PIL 中转处理。def drawEye(eye, frame): eyeHull cv2.convexHull(eye) # 生成眼睛外轮廓 cv2.drawContours(frame, [eyeHull], -1, (0, 255, 0), 2) # 绿色线绘制帧数根据摄像头帧率调整一般摄像头 30 帧 / 秒50 帧 ≈ 1.6 秒闭眼 → 判定疲劳detector dlib.get_frontal_face_detector() # dlib人脸检测器 predictor dlib.shape_predictor(shape_predictor_68_face_landmarks.dat) # 68点模型模型加载.dat模型文件否则代码无法运行。while True: ret, frame cap.read() # 读取一帧画面 if not ret: break # 读取失败退出 faces detector(frame, 0) # 检测画面中的所有人脸 for face in faces: # 遍历每一张脸 # 1. 检测68个关键点 shape predictor(frame, face) shape np.array([[p.x, p.y] for p in shape.parts()]) # 2. 提取左右眼坐标固定索引 rightEye shape[36:42] # 右眼 leftEye shape[42:48] # 左眼 # 3. 计算双眼EAR rightEAR eye_aspect_ratio(rightEye) leftEAR eye_aspect_ratio(leftEye) ear (leftEAR rightEAR) / 2.0 # 取平均值更稳定 # 疲劳判断 if ear EYE_AR_THRESH: # 闭眼 COUNTER 1 if COUNTER EYE_AR_CONSEC_FRAMES: # 红色危险预警 frame cv2AddChineseText(frame, !!!!!危险疲劳驾驶!!!!!, (250,250), textColor(0,0,255), textSize50) else: # 睁眼 COUNTER 0 drawEye(leftEye, frame) drawEye(rightEye, frame) # 显示实时EAR值 info fEAR: {ear[0][0]:.2f} frame cv2AddChineseText(frame, info, (0,30), textColor(0,255,0), textSize30) # 显示画面 cv2.imshow(Frame, frame) # 按ESC退出 if cv2.waitKey(1) 27: break关键逻辑说明shape [36:42] 和 shape [42:48]这是 dlib 68 点中固定的眼睛索引不能改ear 平均值单眼误差大双眼平均更稳定计数器 COUNTER只有连续闭眼才累计睁眼立刻清零避免眨眼误判五、核心功能 2基于 OpenCV 的年龄性别识别5.1 实现原理年龄性别识别基于预训练的 CNN 模型参考 Levi 等人的论文《Age and Gender Classification using Convolutional Neural Networks》模型结构如下输入227×227 的人脸图像3 个卷积层 池化层 归一化2 个全连接层 Dropout输出性别2 分类/ 年龄8 分类我们直接使用预训练好的 caffemodel 模型通过 OpenCV 的 DNN 模块调用实现实时年龄性别识别。5.2 模型准备需要提前下载 4 个模型文件放到model文件夹下人脸检测模型opencv_face_detector.pbtxt、opencv_face_detector_uint8.pb年龄模型deploy_age.prototxt、age_net.caffemodel性别模型deploy_gender.prototxt、gender_net.caffemodel我已经上传了附件了5.3 完整代码实现import cv2 from PIL import Image, ImageDraw, ImageFont import numpy as np # 模型初始化 # 模型(网络模型/预训练模型):face/age/gender(脸、年龄、性别) faceProto model/opencv_face_detector.pbtxt faceModel model/opencv_face_detector_uint8.pb ageProto model/deploy_age.prototxt ageModel model/age_net.caffemodel genderProto model/deploy_gender.prototxt genderModel model/gender_net.caffemodel # 加载网络 ageNet cv2.dnn.readNet(ageModel, ageProto) # 年龄模型 genderNet cv2.dnn.readNet(genderModel, genderProto) # 性别模型 faceNet cv2.dnn.readNet(faceModel, faceProto) # 人脸检测模型 # 变量初始化 # 年龄段和性别标签 ageList [(0-2), (4-6), (8-12), (15-20), (25-32), (38-43), (48-53), (60-100)] genderList [男性, 女性] mean (78.426337603, 87.7689143744, 114.8958788766) # 模型训练时的均值用于预处理 # 自定义函数,获取人脸包围框 def getBoxes(net, frame): 获取人脸检测框的核心函数 frameHeight frame.shape[0] frameWidth frame.shape[1] # 预处理图像: 缩放、减均值 blob cv2.dnn.blobFromImage(frame, scalefactor1.0, size(300, 300), mean(104, 117, 123), swapRBTrue, cropFalse) net.setInput(blob) # 输入图片进行人脸检测 detections net.forward() # 获取检测结果 faceBoxes [] # 存储检测到的人脸框 for i in range(detections.shape[2]): confidence detections[0, 0, i, 2] # 置信度 if confidence 0.7: # 筛选置信度0.7的人脸 # 计算人脸框坐标 x1 int(detections[0, 0, i, 3] * frameWidth) y1 int(detections[0, 0, i, 4] * frameHeight) x2 int(detections[0, 0, i, 5] * frameWidth) y2 int(detections[0, 0, i, 6] * frameHeight) faceBoxes.append([x1, y1, x2, y2]) # 绘制人脸框 cv2.rectangle(frame, pt1(x1, y1), pt2(x2, y2), color(0, 255, 0), thicknessint(round(frameHeight / 150)), lineTypecv2.LINE_AA) return frame, faceBoxes # 中文文字绘制函数 def cv2AddChineseText(img, text, position, textColor(0, 255, 0), textSize30): 在图像上绘制中文文字 if isinstance(img, np.ndarray): # 转换为PIL图像 img Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) draw ImageDraw.Draw(img) # 加载系统宋体 fontStyle ImageFont.truetype(simsun.ttc, textSize, encodingutf-8) # 绘制文字 draw.text(position, text, textColor, fontfontStyle) # 转换回OpenCV格式 return cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR) # 主程序:打开摄像头 cap cv2.VideoCapture(0) # 调用摄像头 while True: ret, frame cap.read() if not ret: break frame cv2.flip(frame, flipCode1) # 镜像处理符合人眼习惯 # 获取人脸包围框 frame, faceBoxes getBoxes(faceNet, frame) if not faceBoxes: # 无人脸时跳过 print(当前镜头中没有人) cv2.imshow(result, frame) if cv2.waitKey(1) 27: # ESC退出 break continue # 遍历每个人脸预测年龄性别 for faceBox in faceBoxes: x1, y1, x2, y2 faceBox face frame[y1:y2, x1:x2] # 截取人脸区域 # 预处理缩放、减均值 blob cv2.dnn.blobFromImage(face, scalefactor1.0, size(227, 227), meanmean) # 预测性别 genderNet.setInput(blob) genderOuts genderNet.forward() gender genderList[genderOuts[0].argmax()] # 预测年龄 ageNet.setInput(blob) ageOuts ageNet.forward() age ageList[ageOuts[0].argmax()] # 绘制结果 result f{gender},{age} frame cv2AddChineseText(frame, result, (x1, y1-30), (0, 255, 0), 30) # 显示结果 cv2.imshow(result, frame) # ESC退出 if cv2.waitKey(1) 27: break # 释放资源 cv2.destroyAllWindows() cap.release()5.4代码解析# 人脸检测模型配置权重 faceProto model/opencv_face_detector.pbtxt faceModel model/opencv_face_detector_uint8.pb # 年龄预测模型 ageProto model/deploy_age.prototxt ageModel age_net.caffemodel # 性别预测模型 genderProto model/deploy_gender.prototxt genderModel gender_net.caffemodel关键说明这些都是别人训练好的深度学习模型你直接用就行必须把这些模型文件放在model/文件夹下否则代码报错模型结构.prototxt 网络结构 /.caffemodel/.pb 训练好的权重ageNet cv2.dnn.readNet(ageModel, ageProto) # 年龄模型 genderNet cv2.dnn.readNet(genderModel, genderProto) # 性别模型 faceNet cv2.dnn.readNet(faceModel, faceProto) # 人脸检测模型作用把模型文件读入内存准备好进行推理预测。# 模型输出的8个年龄段 ageList [(0-2), (4-6), (8-12), (15-20), (25-32), (38-43), (48-53), (60-100)] genderList [男性, 女性] # 性别输出标签 mean (78.426, 87.768, 114.895) # 图像预处理减均值模型要求模型输出是概率数组通过索引对应上面的文字mean是模型训练时的图像均值必须固定不能改def getBoxes(net, frame): # 获取画面宽高 frameHeight frame.shape[0] frameWidth frame.shape[1] # 图像预处理 → 变成模型能识别的格式 blob blob cv2.dnn.blobFromImage(frame, 1.0, (300, 300), (104, 117, 123), swapRBTrue) net.setInput(blob) # 把图片送入模型 detections net.forward() # 模型推理输出所有人脸检测结果 faceBoxes [] for i in range(detections.shape[2]): confidence detections[0,0,i,2] # 置信度模型认为这是不是人脸 if confidence 0.7: # 置信度0.7才保留过滤误检测 # 计算人脸在画面中的坐标 x1 int(detections[0,0,i,3] * frameWidth) y1 int(detections[0,0,i,4] * frameHeight) x2 int(detections[0,0,i,5] * frameWidth) y2 int(detections[0,0,i,6] * frameHeight) faceBoxes.append([x1,y1,x2,y2]) # 绘制绿色人脸框 cv2.rectangle(frame, (x1,y1), (x2,y2), (0,255,0), 2) return frame, faceBoxes核心原理blob把图像转换成神经网络能识别的格式detections模型输出的所有人脸位置 置信度confidence 0.7只保留模型认为可信度高的人脸返回值画好框的图片所有人脸的坐标列表faceBoxesdef cv2AddChineseText(img, text, position, textColor(0,255,0), textSize30): # OpenCV 转 PIL 格式 img Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) draw ImageDraw.Draw(img) # 使用系统宋体 fontStyle ImageFont.truetype(simsun.ttc, textSize) draw.text(position, text, textColor, fontfontStyle) # 转回 OpenCV 格式 return cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)原生 OpenCV 不支持中文会乱码必须用 PIL 库中转处理。while True: ret, frame cap.read() # 读取一帧画面 if not ret: break # 读取失败则退出 frame cv2.flip(frame, 1) # 镜像翻转画面更符合人眼习惯 # 调用人脸检测函数 → 返回画好框的图像 所有人脸坐标 frame, faceBoxes getBoxes(faceNet, frame) if not faceBoxes: print(当前镜头中没有人) cv2.imshow(result, frame) if cv2.waitKey(1) 27: break continue # 没有人脸跳过后续处理 # 遍历检测到的每一张人脸 for faceBox in faceBoxes: x1,y1,x2,y2 faceBox face frame[y1:y2, x1:x2] # 从画面中截取出人脸区域 # 把人脸转为模型输入格式 blob cv2.dnn.blobFromImage(face, 1.0, (227,227), mean, swapRBFalse) # 预测性别 genderNet.setInput(blob) genderPred genderNet.forward() # 输出[男性概率,女性概率] gender genderList[genderPred[0].argmax()] # 取概率最大的 # 预测年龄 ageNet.setInput(blob) agePred ageNet.forward() age ageList[agePred[0].argmax()] # 标注结果 result f{gender},{age} frame cv2AddChineseText(frame, result, (x1, y1-30), (0,255,0), 30) cv2.imshow(result, frame) # 显示最终画面 if cv2.waitKey(1) 27: # 按 ESC 退出 break关键逻辑face frame[y1:y2, x1:x2]从大图里只抠出人脸送给年龄性别模型genderNet.forward()模型输出两个概率[男性概率, 女性概率]argmax()取出概率最大的那个ageNet.forward()输出 8 个概率对应 8 个年龄段cv2AddChineseText在人脸框上方显示男性,(25-32)七、总结7.1 项目总结本文基于 dlib 和 OpenCV实现了两个经典的人脸应用疲劳检测基于眼睛纵横比 EAR通过 dlib 关键点定位实现实时监测年龄性别识别基于预训练 CNN 模型通过 OpenCV DNN 模块实现实时识别
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2494354.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!