当前位置:   article > 正文

基于人脸关键点的疲劳检测_基于人脸特征点实现疲劳检测_基于人脸识别的疲劳驾驶检测-csdn博客

基于人脸特征点实现疲劳检测_基于人脸识别的疲劳驾驶检测-csdn博客

闲暇之余做了一个简单的疲劳检测系统。

方案:首先获取重要的关键点位,需要眼部的和嘴部的。

对于眼部的如下:

分别采用眼部6个点位计算纵横比,超过连续三帧小于0.2判断为疲劳。分别采用眼部6个点位计算纵横比,超过连续三帧小于0.2判断为疲劳。分别采用眼部6个点位计算纵横比,超过连续三帧小于0.2判断为疲劳。

(注意,上图的数字序号是dlib算法的编号,与我的项目所使用的PFLD算法不同)

对于嘴部的如下:

采用嘴部8个点位计算纵横比,超过连续三帧大于0.6判断为疲劳。 

(注意,上图的数字序号是dlib算法的编号,与我的项目所使用的PFLD算法不同)

具体实现流程:

先检测到人脸上的关键点,这里采用的方案见博客《单人脸的关键点检测》 。然后根据获取到的点位信息进行判断,再显示于与一个系统界面,界面如下:

最终的所有代码如下:

  1. import cv2
  2. import time
  3. import numpy as np
  4. import tkinter as tk
  5. from PIL import Image, ImageTk, ImageDraw, ImageFont
  6. import torch
  7. import onnxruntime
  8. import torchvision.transforms as transforms
  9. root_window = tk.Tk()
  10. root_window.title('疲劳检测系统') # 设置窗口title
  11. width, height = 800, 600 # 设置窗口大小变量
  12. # 窗口居中,获取屏幕尺寸以计算布局参数,使窗口居屏幕中央
  13. screenwidth, screenheight = root_window.winfo_screenwidth(), root_window.winfo_screenheight()
  14. size_geo = '%dx%d+%d+%d' % (width, height, (screenwidth - width) / 2, (screenheight - height) / 2)
  15. root_window.geometry(size_geo)
  16. # 一些全局的控件
  17. global label1, label2, label3, label4, label5, label6, label7,\
  18. text1,\
  19. button1, button2, button3, \
  20. is_open_camera, cap, is_detection, is_exit_program
  21. def init():
  22. global label1, label2, label3, label4, label5, label6, label7, \
  23. text1, \
  24. button1, button2, button3, \
  25. is_open_camera, cap, is_detection, is_exit_program
  26. # 相关参数的初始化
  27. is_open_camera = False # 是否打开了摄像头
  28. is_detection = False # 是否要进行检测
  29. is_exit_program = False # 是否要退出程序
  30. cap = cv2.VideoCapture(0)
  31. cap.open(0)
  32. # 下面开始绘制界面
  33. # 1、放置两个label
  34. pd = 5 # label与周围的间距
  35. width_label1 = 475 # label1的width
  36. width_label2 = width - width_label1 - pd * 3 # label2的width
  37. label1 = tk.Label(root_window, bg='#BEBEBE')
  38. label1.place(x=pd, y=pd, width=width_label1, height=590)
  39. label2 = tk.Label(root_window, bg='#F2F2F2')
  40. label2.place(x=width_label1 + 2 * pd, y=pd, width=width_label2, height=590)
  41. # 2、放置按钮和控件
  42. button1 = tk.Button(label2, text="开启摄像头", bg='orange', font=('微软雅黑', 12), command=lambda: open_camera())
  43. button1.place(x=30, y=15)
  44. button2 = tk.Button(label2, text="开始检测", bg='orange', font=('微软雅黑', 12),
  45. command=lambda: begin_to_detection())
  46. button2.place(x=180, y=15)
  47. button3 = tk.Button(label2, text="退出程序", bg='orange', font=('微软雅黑', 12), command=lambda: exit_program())
  48. button3.place(x=30, y=70)
  49. # 3、放置检测项目选项框
  50. label3 = tk.Label(label2, text='检测项目', font=('微软雅黑', 12), bg='#BEBEBE')
  51. label3.place(x=30, y=140)
  52. # 可以检测的项目
  53. label5 = tk.Label(label2, text="1、打哈欠", font=('微软雅黑', 12, 'bold'))
  54. label5.place(x=30, y=170)
  55. label6 = tk.Label(label2, text="2、闭眼", font=('微软雅黑', 12, 'bold'))
  56. label6.place(x=180, y=170)
  57. label7 = tk.Label(label2, text="3、脱离视线", font=('微软雅黑', 12, 'bold'))
  58. label7.place(x=30, y=210)
  59. # 4、放置打印检测结果的框
  60. label4 = tk.Label(label2, text='检测结果输出', font=('微软雅黑', 12), bg='#BEBEBE')
  61. label4.place(x=30, y=270)
  62. text1 = tk.Text(label2, width=32, height=10)
  63. text1.place(x=30, y=300)
  64. def begin_to_detection():
  65. global is_detection
  66. is_detection = True
  67. def exit_program():
  68. global cap, label1, is_exit_program
  69. is_exit_program = True
  70. if cap.isOpened():
  71. cap.release()
  72. root_window.quit()
  73. print('已经退出程序!')
  74. # --------下面四个函数是用来检测人脸和关键点用的
  75. def cut_resize_letterbox(image, det, target_size):
  76. # 参数分别是:原图像、检测到的某个脸的数据[x1,y1,x2,y2,score]、关键点检测器输入大小
  77. iw, ih = image.size
  78. x, y = det[0], det[1]
  79. w, h = det[2] - det[0], det[3] - det[1]
  80. facebox_max_length = max(w, h) # 以最大的边来缩放
  81. width_margin_length = (facebox_max_length - w) / 2 # 需要填充的宽
  82. height_margin_length = (facebox_max_length - h) / 2 # 需要填充的高
  83. face_letterbox_x = x - width_margin_length
  84. face_letterbox_y = y - height_margin_length
  85. face_letterbox_w = facebox_max_length
  86. face_letterbox_h = facebox_max_length
  87. top = -face_letterbox_y if face_letterbox_y < 0 else 0
  88. left = -face_letterbox_x if face_letterbox_x < 0 else 0
  89. bottom = face_letterbox_y + face_letterbox_h - ih if face_letterbox_y + face_letterbox_h - ih > 0 else 0
  90. right = face_letterbox_x + face_letterbox_w - iw if face_letterbox_x + face_letterbox_w - iw > 0 else 0
  91. margin_image = Image.new('RGB', (iw + right - left, ih + bottom - top), (0, 0, 0)) # 新图像,全黑的z
  92. margin_image.paste(image, (left, top)) # 将image贴到margin_image,从左上角(left, top)位置开始
  93. face_letterbox = margin_image.crop( # 从margin_image中裁剪图像
  94. (face_letterbox_x, face_letterbox_y, face_letterbox_x + face_letterbox_w, face_letterbox_y + face_letterbox_h))
  95. face_letterbox = face_letterbox.resize(target_size, Image.Resampling.BICUBIC) # 重新设置图像尺寸大小
  96. # 返回:被裁剪出的图像也是即将被送入关键点检测器的图像、缩放尺度、x偏移、y偏移
  97. return face_letterbox, facebox_max_length / target_size[0], face_letterbox_x, face_letterbox_y
  98. def to_numpy(tensor):
  99. return tensor.detach().cpu().numpy() if tensor.requires_grad else tensor.cpu().numpy()
  100. def pad_image(image, target_size):
  101. '''
  102. image: 图像
  103. target_size: 输入网络中的大小
  104. return: 新图像、缩放比例、填充的宽、填充的高
  105. '''
  106. iw, ih = image.size # 原图像尺寸
  107. w, h = target_size # 640, 640
  108. scale = min(w / iw, h / ih) # 缩放比例选择最小的那个(宽高谁大缩放谁)(缩放大的,填充小的)
  109. nw = int(iw * scale + 0.5)
  110. nh = int(ih * scale + 0.5)
  111. pad_w = (w - nw) // 2 # 需要填充的宽
  112. pad_h = (h - nh) // 2 # 需要填充的高
  113. image = image.resize((nw, nh), Image.Resampling.BICUBIC) # 缩放图像(Resampling需要PIL最新版,python3.7以上)
  114. new_image = Image.new('RGB', target_size, (128, 128, 128)) # 生成灰色的新图像
  115. new_image.paste(image, (pad_w, pad_h)) # 将image张贴在生成的灰色图像new_image上
  116. return new_image, scale, pad_w, pad_h # 返回新图像、缩放比例、填充的宽、填充的高
  117. def nms(preds): # NMS筛选box
  118. arg_sort = np.argsort(preds[:, 4])[::-1]
  119. nms = preds[arg_sort] # 按照score降序将box排序
  120. # 单脸检测,为了简便和速度,直接返回分数最大的box
  121. return nms[0]
  122. def batch_process_output(pred, thresh, scale, pad_w, pad_h, iw, ih):
  123. '''
  124. iw, ih为图像原尺寸
  125. '''
  126. bool1 = pred[..., 4] > thresh # bool1.shape = [num_box] 里面的值为bool(True/False)
  127. pred = pred[bool1] # pred.shape = [n, 16],即筛选出了置信度大于thresh阈值的n个box
  128. ans = np.copy(pred)
  129. ans[:, 0] = (pred[:, 0] - pred[:, 2] / 2 - pad_w) / scale # x1
  130. np.putmask(ans[..., 0], ans[..., 0] < 0., 0.) # 将所有box的小于0.的x1换成0.
  131. ans[:, 1] = (pred[:, 1] - pred[:, 3] / 2 - pad_h) / scale # y1
  132. np.putmask(ans[..., 1], ans[..., 1] < 0., 0.) # 将所有box的小于0.的y1换成0.
  133. ans[:, 2] = (pred[:, 0] + pred[:, 2] / 2 - pad_w) / scale # x2
  134. np.putmask(ans[..., 2], ans[..., 2] > iw, iw) # 将所有box的大于iw的x2换成iw
  135. ans[:, 3] = (pred[:, 1] + pred[:, 3] / 2 - pad_h) / scale # y2
  136. np.putmask(ans[..., 3], ans[..., 3] > ih, ih) # 将所有box的大于ih的y2换成ih
  137. ans[..., 4] = ans[..., 4] * ans[..., 15] # score
  138. return ans[:, 0:5]
  139. # -------------------------------
  140. def open_camera() -> None:
  141. global label1, is_open_camera, cap, is_detection
  142. if is_open_camera is False:
  143. is_open_camera = True
  144. else:
  145. return
  146. global text1 # 检测结果
  147. ha, eye = 0, 0 # 初始化打哈欠次数、闭眼次数
  148. # ---1、参数设置---
  149. use_cuda = True # 使用cuda - gpu
  150. facedetect_input_size = (640, 640) # 人脸检测器的输入大小
  151. pfld_input_size = (112, 112) # 关键点检测器的输入大小
  152. face_path = "./yolov5face_n_640.onnx" # 人脸检测器器路径
  153. pfld_path = "./PFLD_GhostOne_112_1_opt_sim.onnx" # 关键点检测器路径
  154. video_path = "./me.mp4" # 如果用摄像头,赋值为None
  155. # ---2、获取模型--- # 这里需要注意,如果onnx需要使用gpu,则只能且仅安装onnxruntime-gpu这个包
  156. facedetect_session = onnxruntime.InferenceSession( # 检测人脸的模型
  157. path_or_bytes=face_path,
  158. providers=['CUDAExecutionProvider']
  159. )
  160. pfld_session = onnxruntime.InferenceSession( # 检测关键点的模型
  161. path_or_bytes=pfld_path,
  162. providers=['CUDAExecutionProvider']
  163. )
  164. # 3、tensor设置
  165. detect_transform = transforms.Compose([transforms.ToTensor()]) # 人脸的
  166. pfld_transform = transforms.Compose([ # 关键点的
  167. transforms.ToTensor(),
  168. transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5]) # 归一化
  169. ])
  170. # 4、加载视频
  171. cap = cv2.VideoCapture(video_path) # 如果不填路径参数就是获取摄像头
  172. # 5、先预热一下onnx
  173. data_test = torch.FloatTensor(1, 3, 640, 640)
  174. input_test = {facedetect_session.get_inputs()[0].name: to_numpy(data_test)} # 把输入包装成字典
  175. _ = facedetect_session.run(None, input_test)
  176. # 下面开始繁琐的检测和处理
  177. x = [0 for i in range(20)] # 初始化存放需要检测的关键点的x坐标
  178. y = [0 for i in range(20)] # 初始化存放需要检测的关键点的y坐标
  179. while cap.isOpened():
  180. ret, frame = cap.read() # type(frame) = <class 'numpy.ndarray'>
  181. if not ret: # 读取失败或者最后一帧
  182. cap.release()
  183. break
  184. start = time.time()
  185. # 先将每一帧,即frame转成RGB,再实现ndarray到image的转换
  186. img0 = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
  187. if not is_detection:
  188. img = ImageTk.PhotoImage(img0)
  189. label1.config(image=img)
  190. label1.image = img
  191. root_window.update()
  192. continue
  193. iw, ih = img0.size
  194. # 检测人脸前的图像处理
  195. # 处理方法是:先缩放,使宽或者高到640,且选择缩放比例小的那个维度(宽/高)
  196. # pad_image函数参数:Image格式的图像、人脸检测器的输入大小640 * 640
  197. # 返回处理过的图像,最小的缩放尺度(宽高谁大缩放谁),填充的宽、填充的高(宽高只有一个需要填充)
  198. pil_img_pad, scale, pad_w, pad_h = pad_image(img0, facedetect_input_size) # 尺寸处理
  199. # 转换成tensor
  200. tensor_img = detect_transform(pil_img_pad)
  201. detect_tensor_img = torch.unsqueeze(tensor_img, 0) # 给tensor_img加一个维度,维度大小为1
  202. if use_cuda:
  203. detect_tensor_img = detect_tensor_img.cuda()
  204. # 先检测到人脸
  205. inputs = {facedetect_session.get_inputs()[0].name: to_numpy(detect_tensor_img)} # 把输入包装成字典
  206. outputs = facedetect_session.run(None, inputs) # type(outputs) <list>
  207. preds = outputs[0][0] # shape=(25200, 16) 每一维的组成: center_x、center_y、w、h、thresh, ...
  208. # batch_process_output参数:人脸预测结果、阈值、缩放尺度、填充宽、填充高、原宽、原高
  209. # 返回经过筛选的框 type(preds) = list preds[0].shape = 5即,[x1, y1, x2, y2, score]
  210. preds = np.array(batch_process_output(preds, 0.5, scale, pad_w, pad_h, iw, ih))
  211. # 返回经过筛选的框 type(preds) = list preds[0].shape = 5即,[x1, y1, x2, y2, score]
  212. # preds = np.array(xywh2xyxy(preds, 0.5))
  213. if preds.shape[0] == 0: # 如果当前帧没有检测出人脸来,继续检测人脸
  214. text1.insert('end', '脱离视线!\n')
  215. continue
  216. # nms处理,直接返回score最大的box
  217. det = nms(preds)
  218. # draw = ImageDraw.Draw(img0)
  219. # 得到裁剪出输入关键点检测器的人脸图112x112、缩放尺度、
  220. cut_face_img, scale_l, x_offset, y_offset = cut_resize_letterbox(img0, det, pfld_input_size)
  221. # 转换成tensor
  222. tensor_img = pfld_transform(cut_face_img)
  223. pfld_tensor_img = torch.unsqueeze(tensor_img, 0) # 给tensor_img加一个维度,维度大小为1
  224. if use_cuda:
  225. pfld_tensor_img = pfld_tensor_img.cuda()
  226. # 送入关键点检测器进行检测
  227. inputs = {'input': to_numpy(pfld_tensor_img)}
  228. outputs = pfld_session.run(None, inputs)
  229. preds = outputs[0][0] # preds.shape = (196, )
  230. draw = ImageDraw.Draw(img0)
  231. for_det = [60, 61, 63, 64, 65, 67, # 0-5
  232. 68, 69, 71, 72, 73, 75, # 6-11
  233. 88, 89, 90, 91, 92, 93, 94, 95] # 12-19
  234. for i in range(len(for_det)):
  235. x[i] = preds[for_det[i] * 2] * pfld_input_size[0] * scale_l + x_offset
  236. y[i] = preds[for_det[i] * 2 + 1] * pfld_input_size[1] * scale_l + y_offset
  237. radius = 2
  238. draw.ellipse((x[i] - radius, y[i] - radius, x[i] + radius, y[i] + radius), (0, 255, 0)) # 在矩形框中绘制椭圆
  239. draw.text(xy=(90, 30), text='FPS: ' + str(int(1 / (time.time() - start))),
  240. fill=(255, 0, 0), font=ImageFont.truetype("consola.ttf", 50))
  241. draw.rectangle((det[0], det[1], det[2], det[3]), outline='yellow', width=4)
  242. # 两眼的ERA计算
  243. era_left = (y[5] - y[1] + y[4] - y[2]) / ((x[3] - x[0]) * 2.)
  244. era_right = (y[11] - y[7] + y[10] - y[8]) / ((x[9] - x[6]) * 2.)
  245. # 嘴部的ERA计算
  246. era_mouse = (y[19] - y[13] + y[18] - y[14] + y[17] - y[15]) / (x[16] - x[12]) * 3
  247. if era_left < 0.2 or era_right < 0.2: # 闭眼了
  248. if eye < 2: # 连续帧内闭眼次数小,可能是眨眼,继续监督
  249. eye += 1
  250. else: # 连续帧内闭眼达到3次或以上,判为疲劳
  251. eye += 1
  252. text1.insert('end', '闭眼,产生疲劳,请休息!\n')
  253. eye += 1
  254. else: # 没有闭眼,次数清零
  255. eye = 0
  256. if era_mouse > 0.6: # 张嘴
  257. if ha < 2: # 连续帧张嘴次数小,继续监督
  258. ha += 1
  259. else: # 连续帧内张嘴达到3次或以上,判为疲劳
  260. ha += 1
  261. text1.insert('end', '打哈欠,产生疲劳,请休息!\n')
  262. ha += 1
  263. else: # 没有张嘴,次数清零
  264. ha = 0
  265. img = ImageTk.PhotoImage(img0)
  266. label1.config(image=img)
  267. label1.image = img
  268. root_window.update()
  269. if __name__ == '__main__':
  270. print('welcome to tired detection!!!')
  271. init()
  272. root_window.mainloop()

如有问题,欢迎交流。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/237873
推荐阅读
相关标签
  

闽ICP备14008679号