【dlib+OpenCV实战:人脸68关键点定位+5种表情(哭/怒/笑)检测】

张开发
2026/5/31 16:53:03 15 分钟阅读
【dlib+OpenCV实战:人脸68关键点定位+5种表情(哭/怒/笑)检测】
一、环境准备在计算机视觉领域人脸关键点检测是人脸识别、表情分析、美颜滤镜等功能的核心基础。dlib 库提供了预训练的 68 点人脸关键点检测模型搭配 OpenCV 即可快速实现关键点定位与面部轮廓绘制无需复杂训练开箱即用。本文将手把手带你实现1. 人脸 68 关键点精准定位标注关键点序号2. 面部关键区域轮廓绘制眼睛、嘴巴、脸部轮廓3. 完整可运行代码 逐行解析关键模型下载dlib 需要预训练的关键点模型shape_predictor_68_face_landmarks.dat下载地址https://github.com/davisking/dlib-models二、核心原理dlib 人脸检测器检测图像中的人脸区域返回人脸边框68 关键点预测器在人脸区域内定位 68 个特征点对应脸部、眉毛、眼睛、鼻子、嘴巴OpenCV 绘制通过关键点坐标绘制圆点、文字、连线、轮廓68 关键点分布说明0~16脸部轮廓17~21右眉毛22~26左眉毛27~35鼻子36~41右眼42~47左眼48~59嘴巴外轮廓60~67嘴巴内轮廓三、代码实例功能 1人脸 68 关键点定位检测图像中的人脸标注出 68 个关键点并显示每个关键点的序号直观展示关键点分布。import numpy as np import cv2 import dlib # 1. 读取图像替换为你的图片路径 img cv2.imread(hg1.png) # 2. 初始化dlib人脸检测器无参数 detector dlib.get_frontal_face_detector() # 检测人脸第二个参数0表示不进行图像金字塔缩放提升速度 faces detector(img, 0) # 3. 加载68关键点预测模型 predictor dlib.shape_predictor(shape_predictor_68_face_landmarks.dat) # 4. 遍历检测到的人脸 for face in faces: # 获取当前人脸的68个关键点 shape predictor(img, face) # 将关键点转换为numpy数组格式x,y坐标 landmarks np.array([[p.x, p.y] for p in shape.parts()]) # 5. 遍历所有关键点绘制圆点和序号 for idx, point in enumerate(landmarks): pos (point[0], point[1]) # 关键点坐标 # 绘制绿色实心圆点 cv2.circle(img, pos, 2, color(0, 255, 0), thickness-1) # 绘制白色关键点序号 cv2.putText(img, str(idx), pos, cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 255, 255), 1, cv2.LINE_AA) # 显示结果 cv2.imshow(人脸68关键点定位, img) cv2.waitKey(0) cv2.destroyAllWindows()运行结果功能 2面部关键区域轮廓绘制基于 68 关键点绘制脸部轮廓、眉毛、眼睛、嘴巴的闭合轮廓实现面部轮廓可视化。import numpy as np import dlib import cv2 # 定义绘制连线函数绘制两点之间的直线 def drawLine(start, end): pts shape[start:end] for l in range(1, len(pts)): ptA tuple(pts[l-1]) ptB tuple(pts[l]) cv2.line(image, ptA, ptB, (0, 255, 0), 2) # 定义绘制凸轮廓函数绘制闭合的凸轮廓 def drawConvexHull(start, end): Facial shape[start:end1] hull cv2.convexHull(Facial) cv2.drawContours(image, [hull], -1, (0, 255, 0), 2) # 1. 读取图像 image cv2.imread(hg1.png) # 2. 初始化检测器和模型 detector dlib.get_frontal_face_detector() predictor dlib.shape_predictor(shape_predictor_68_face_landmarks.dat) # 3. 检测人脸 faces detector(image, 0) # 4. 遍历人脸并绘制轮廓 for face in faces: # 获取关键点并转换为数组 shape predictor(image, face) shape np.array([[p.x, p.y] for p in shape.parts()]) # 绘制眼睛、嘴巴轮廓 drawConvexHull(36, 41) # 右眼 drawConvexHull(42, 47) # 左眼 drawConvexHull(48, 59) # 嘴巴外轮廓 drawConvexHull(60, 67) # 嘴巴内轮廓 # 绘制脸部、眉毛、鼻子轮廓线 drawLine(0, 17) # 脸部轮廓 drawLine(17, 22) # 右眉毛 drawLine(22, 27) # 左眉毛 drawLine(27, 36) # 鼻子区域 # 显示结果 cv2.imshow(人脸面部轮廓绘制, image) cv2.waitKey(0) cv2.destroyAllWindows()1.检测器与模型分离◦ dlib.get_frontal_face_detector()无参数仅用于检测人脸边框◦ dlib.shape_predictor()必须传入.dat模型用于关键点检测2. 关键点格式转换dlib 返回的关键点需要转换为numpy数组才能用 OpenCV 绘制。3. 轮廓绘制技巧◦ cv2.line()绘制直线实现非闭合轮廓◦ cv2.convexHull() cv2.drawContours()生成凸包绘制闭合轮廓运行结果功能3表情识别实现方法人在微笑时嘴角会上扬嘴的宽度和与整个脸颊(下颌)的宽度之比变大。即M/J 变大。判断微笑M/J 0.45判断大笑((ABC)/3)/M0.5人脸表情识别import cv2 import numpy as np import dlib import cv2 from sklearn.metrics.pairwise import euclidean_distances from PIL import Image,ImageDraw,ImageFont def MAR(shape): # Meuclidean_distance() A euclidean_distances(shape[50].reshape(1,2),shape[58].reshape(1,2)) B euclidean_distances(shape[51].reshape(1,2),shape[57].reshape(1,2)) C euclidean_distances(shape[52].reshape(1,2),shape[56].reshape(1,2)) D euclidean_distances(shape[48].reshape(1, 2),shape[54].reshape(1, 2)) return ((ABC)/3)/D def MJR(shape): Meuclidean_distances(shape[48].reshape(1,2),shape[54].reshape(1,2)) Jeuclidean_distances(shape[3].reshape(1,2),shape[13].reshape(1,2)) return M/J def cv2AddChineseText(img,text,position,textColor(0,255,0),textSize30): img_pil Image.fromarray(cv2.cvtColor(img,cv2.COLOR_BGR2RGB)) draw ImageDraw.Draw(img_pil) font ImageFont.truetype(simsun.ttc, textSize) draw.text(position, text, textColor, fontfont) return cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2BGR) # 主程序摄像头 表情识别 def run(): # detector dlib.get_frontal_face_detector() detector dlib.get_frontal_face_detector() predictor dlib.shape_predictor(shape_predictor_68_face_landmarks.dat) frame cv2.imread(img.png) # cap cv2.VideoCapture(0) # while True: # ret,frame cap.read() faces detector(frame,0) # faces detector(frame, 0) for face in faces: shape predictor(frame,face) shape np.array([[p.x,p.y] for p in shape.parts()]) mar MAR(shape)[0][0] mjr MJR(shape)[0][0] result 正常 print(mar,mar,\tmjr,mjr) if mar 0.5: result 大笑 elif mjr 0.45: result微笑 mouthHull cv2.convexHull(shape[48:61]) frame cv2AddChineseText(frame,result,mouthHull[0,0]) cv2.drawContours(frame,[mouthHull],-1,(0,255,0),1) cv2.imshow(Frame,frame) # if cv2.waitKey(1)27: # break cv2.waitKey(0) cv2.destroyAllWindows() # cap.release() if __name__ __main__: run()运行结果摄像头表情识别import cv2 import numpy as np import dlib from sklearn.metrics.pairwise import euclidean_distances from PIL import Image, ImageDraw, ImageFont def MAR(shape): A euclidean_distances(shape[50].reshape(1, 2), shape[58].reshape(1, 2)) B euclidean_distances(shape[51].reshape(1, 2), shape[57].reshape(1, 2)) C euclidean_distances(shape[52].reshape(1, 2), shape[56].reshape(1, 2)) D euclidean_distances(shape[48].reshape(1, 2), shape[54].reshape(1, 2)) return ((A B C) / 3) / D def MJR(shape): M euclidean_distances(shape[48].reshape(1, 2), shape[54].reshape(1, 2)) J euclidean_distances(shape[3].reshape(1, 2), shape[13].reshape(1, 2)) return M / J def cv2AddChineseText(img, text, position, textColor(0, 255, 0), textSize30): img_pil Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) draw ImageDraw.Draw(img_pil) font ImageFont.truetype(simsun.ttc, textSize) draw.text(position, text, textColor, fontfont) return cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2BGR) # 主程序摄像头 表情识别 def run(): 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() faces detector(frame, 0) for face in faces: shape predictor(frame, face) shape np.array([[p.x, p.y] for p in shape.parts()]) mar MAR(shape)[0][0] mjr MJR(shape)[0][0] result 正常 print(mar, mar, \tmjr, mjr) if mar 0.5: result 大笑 elif mjr 0.45: result 微笑 mouthHull cv2.convexHull(shape[48:61]) frame cv2AddChineseText(frame, result, mouthHull[0, 0]) cv2.drawContours(frame, [mouthHull], -1, (0, 255, 0), 1) cv2.imshow(Frame, frame) if cv2.waitKey(1) 27: break cv2.destroyAllWindows() cap.release() if __name__ __main__: run()运行结果摄像头表情识别正常显示正常微笑显示微笑大笑显示大笑#增加几项表情的检测能力例如哭嘴巴咧开眼睛闭合愤怒眼睛睁圆哭泣表情检测import cv2 import numpy as np import dlib from sklearn.metrics.pairwise import euclidean_distances from PIL import Image, ImageDraw, ImageFont # 核心函数定义 def MAR(shape): A euclidean_distances(shape[50].reshape(1, 2), shape[58].reshape(1, 2))[0][0] B euclidean_distances(shape[51].reshape(1, 2), shape[57].reshape(1, 2))[0][0] C euclidean_distances(shape[52].reshape(1, 2), shape[56].reshape(1, 2))[0][0] D euclidean_distances(shape[48].reshape(1, 2), shape[54].reshape(1, 2))[0][0] return ((A B C) / 3) / D def MJR(shape): M euclidean_distances(shape[48].reshape(1, 2), shape[54].reshape(1, 2))[0][0] J euclidean_distances(shape[3].reshape(1, 2), shape[13].reshape(1, 2))[0][0] return M / J def eye_aspect_ratio(eye): A np.linalg.norm(eye[1] - eye[5]) B np.linalg.norm(eye[2] - eye[4]) C np.linalg.norm(eye[0] - eye[3]) ear (A B) / (2.0 * C) return ear def cv2AddChineseText(img, text, position, textColor(0, 255, 0), textSize25): img_pil Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) draw ImageDraw.Draw(img_pil) font ImageFont.truetype(simsun.ttc, textSize) draw.text(position, text, textColor, fontfont) return cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2BGR) def judge_expression(shape): mar MAR(shape) mjr MJR(shape) left_eye shape[36:42] right_eye shape[42:48] left_ear eye_aspect_ratio(left_eye) right_ear eye_aspect_ratio(right_eye) ear_avg (left_ear right_ear) / 2.0 # 五种表情判断逻辑优先级哭泣 愤怒 大笑 微笑 正常 # 调整哭泣参数降低门槛适配小孩哭泣特征眼睛闭合、嘴巴咧开/张开更宽松 # 哭泣眼睛闭合ear_avg0.25放宽闭合判断 嘴巴咧开mjr0.40放宽咧开判断 嘴巴张开mar0.25放宽张开判断 # 愤怒眼睛睁圆ear_avg0.35 嘴巴闭合mar0.2 # 大哭嘴巴开合度高mar0.5 # 微笑嘴巴宽度比例达标mjr0.45且非哭泣 # 正常无上述特征 if ear_avg 0.25 and mjr0.4 and mar 0.20: return 哭泣 elif ear_avg 0.20 and mjr 0.50 and mar 0.35: return 愤怒 elif ear_avg 0.50 and mjr 0.5 and mar0.45: return 大笑 elif ear_avg 0.50 and mjr 0.45 and mar0.4: return 微笑 else: return 正常 # def judge_expression(ear_avg,mjr,mar): # print(f[DEBUG] ear_avg{ear_avg:.2f},mjr{mjr:.2f},mar{mar:.2f}) # 初始化模型 detector dlib.get_frontal_face_detector() predictor dlib.shape_predictor(shape_predictor_68_face_landmarks.dat) # 主函数仅保留核心功能删减冗余文字 def run(): frame cv2.imread(cry.jpg) if frame is None: print(无法读取图片) return gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces detector(gray, 0) face_index 1 for face in faces: shape predictor(gray, face) shape np.array([[part.x, part.y] for part in shape.parts()]) expression judge_expression(shape) # 绘制轮廓 color_list [(0, 255, 0), (0, 255, 0)] current_color color_list[face_index - 1] if face_index len(color_list) else (0, 255, 0) cv2.polylines(frame, [shape[36:42]], True, current_color, 2) cv2.polylines(frame, [shape[42:48]], True, current_color, 2) cv2.polylines(frame, [shape[48:61]], True, (0, 255, 0), 2) # 显示表情仅保留人脸序号表情无多余指标 if face_index 1: text_x, text_y 10, 30 elif face_index 2: text_x, text_y frame.shape[1] - 120, 30 else: text_x, text_y 10, 30 (face_index - 1) * 50 frame cv2AddChineseText(frame, f{expression}, (text_x, text_y), current_color, 25) print(f{expression}) face_index 1 cv2.imshow(表情检测, frame) cv2.waitKey(0) cv2.destroyAllWindows() if __name__ __main__: run()ear_avg双眼平均开合度由 eye_aspect_ratio 函数计算眼睛上下眼睑距离 ÷ 眼睛宽度值越小眼睛闭得越紧比如哭泣时 ear_avg 极低愤怒时眼睛睁得大ear_avg 偏高运行结果愤怒表情检测import cv2 import numpy as np import dlib from sklearn.metrics.pairwise import euclidean_distances from PIL import Image, ImageDraw, ImageFont # 核心函数定义 def MAR(shape): A euclidean_distances(shape[50].reshape(1, 2), shape[58].reshape(1, 2))[0][0] B euclidean_distances(shape[51].reshape(1, 2), shape[57].reshape(1, 2))[0][0] C euclidean_distances(shape[52].reshape(1, 2), shape[56].reshape(1, 2))[0][0] D euclidean_distances(shape[48].reshape(1, 2), shape[54].reshape(1, 2))[0][0] return ((A B C) / 3) / D def MJR(shape): M euclidean_distances(shape[48].reshape(1, 2), shape[54].reshape(1, 2))[0][0] J euclidean_distances(shape[3].reshape(1, 2), shape[13].reshape(1, 2))[0][0] return M / J def eye_aspect_ratio(eye): A np.linalg.norm(eye[1] - eye[5]) B np.linalg.norm(eye[2] - eye[4]) C np.linalg.norm(eye[0] - eye[3]) ear (A B) / (2.0 * C) return ear def cv2AddChineseText(img, text, position, textColor(0, 255, 0), textSize25): img_pil Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) draw ImageDraw.Draw(img_pil) font ImageFont.truetype(simsun.ttc, textSize) draw.text(position, text, textColor, fontfont) return cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2BGR) def judge_expression(shape): mar MAR(shape) mjr MJR(shape) left_eye shape[36:42] right_eye shape[42:48] left_ear eye_aspect_ratio(left_eye) right_ear eye_aspect_ratio(right_eye) ear_avg (left_ear right_ear) / 2.0 # 五种表情判断逻辑优先级哭泣 愤怒 大笑 微笑 正常 # 调整哭泣参数降低门槛适配小孩哭泣特征眼睛闭合、嘴巴咧开/张开更宽松 # 哭泣眼睛闭合ear_avg0.25放宽闭合判断 嘴巴咧开mjr0.40放宽咧开判断 嘴巴张开mar0.25放宽张开判断 # 愤怒眼睛睁圆ear_avg0.35 嘴巴闭合mar0.2 # 大哭嘴巴开合度高mar0.5 # 微笑嘴巴宽度比例达标mjr0.45且非哭泣 # 正常无上述特征 if ear_avg 0.25 and mjr0.4 and mar 0.20: return 哭泣 elif ear_avg 0.20 and mjr 0.50 and mar 0.35: return 愤怒 elif ear_avg 0.50 and mjr 0.5 and mar0.45: return 大笑 elif ear_avg 0.50 and mjr 0.45 and mar0.4: return 微笑 else: return 正常 # def judge_expression(ear_avg,mjr,mar): # print(f[DEBUG] ear_avg{ear_avg:.2f},mjr{mjr:.2f},mar{mar:.2f}) # 初始化模型 detector dlib.get_frontal_face_detector() predictor dlib.shape_predictor(shape_predictor_68_face_landmarks.dat) # 主函数仅保留核心功能删减冗余文字 def run(): frame cv2.imread(angry.jpg) if frame is None: print(无法读取图片) return gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces detector(gray, 0) face_index 1 for face in faces: shape predictor(gray, face) shape np.array([[part.x, part.y] for part in shape.parts()]) expression judge_expression(shape) # 绘制轮廓 color_list [(0, 255, 0), (0, 255, 0)] current_color color_list[face_index - 1] if face_index len(color_list) else (0, 255, 0) cv2.polylines(frame, [shape[36:42]], True, current_color, 2) cv2.polylines(frame, [shape[42:48]], True, current_color, 2) cv2.polylines(frame, [shape[48:61]], True, (0, 255, 0), 2) # 显示表情仅保留人脸序号表情无多余指标 if face_index 1: text_x, text_y 10, 30 elif face_index 2: text_x, text_y frame.shape[1] - 120, 30 else: text_x, text_y 10, 30 (face_index - 1) * 50 frame cv2AddChineseText(frame, f{expression}, (text_x, text_y), current_color, 25) print(f{expression}) face_index 1 cv2.imshow(表情检测, frame) cv2.waitKey(0) cv2.destroyAllWindows() if __name__ __main__: run()运行结果

更多文章