最新计算机视觉项目实战-驾驶员疲劳检测_计算机视觉疲劳检测(1),2024年最新最新阿里P7技术体系

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

	rightEye = shape[rStart:rEnd]
	leftEAR = eye_aspect_ratio(leftEye)
	rightEAR = eye_aspect_ratio(rightEye)

然后我们把左眼和右眼分别求了一下EAR数值。这里的`eye_aspect_ratio`函数就是计算EAR数值的。



def eye_aspect_ratio(eye):
# 计算距离,竖直的
A = dist.euclidean(eye[1], eye[5])
B = dist.euclidean(eye[2], eye[4])
# 计算距离,水平的
C = dist.euclidean(eye[0], eye[3])
# ear值
ear = (A + B) / (2.0 * C)
return ear


其中`dist.euclidean`表示计算欧式距离,和公式中计算EAR数值一摸一样。



	ear = (leftEAR + rightEAR) / 2.0

	# 绘制眼睛区域
	leftEyeHull = cv2.convexHull(leftEye)
	rightEyeHull = cv2.convexHull(rightEye)
	cv2.drawContours(frame, [leftEyeHull], -1, (0, 255, 0), 1)
	cv2.drawContours(frame, [rightEyeHull], -1, (0, 255, 0), 1)

然后对于左眼和右眼都进行了EAR求解然后求了一个平均值,然后根据凸包的概念,对眼睛区域进行了绘图。将左眼区域和右眼区域绘图出来。



	if ear < EYE_AR_THRESH:
		COUNTER += 1

	else:
		# 如果连续几帧都是闭眼的,总数算一次
		if COUNTER >= EYE_AR_CONSEC_FRAMES:
			TOTAL += 1

		# 重置
		COUNTER = 0

	# 显示
	cv2.putText(frame, "Blinks: {}".format(TOTAL), (10, 30),
		cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
	cv2.putText(frame, "EAR: {:.2f}".format(ear), (300, 30),
		cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)

cv2.imshow("Frame", frame)
key = cv2.waitKey(10) & 0xFF

if key == 27:
	break

vs.release()
cv2.destroyAllWindows()


最后进行了一次阈值判断,如果EAR连续三帧都小于0.3,那么我们就把TOTAL加一,这样记录一次闭眼的过程。然后最后将EAR数值和TOTAL的数值展示在视频当中。最后完成整体的训练。


### 🌟项目结果展示


![在这里插入图片描述](https://img-blog.csdnimg.cn/10447afa2e3d4b4ea21e27d74c74d5a9.png)  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/a64e5e5c8ab04d7fb07e6d90154f2fdf.png)


### 🌟项目改进方向(打哈欠检测疲劳方法)


我们知道在疲劳检测当中,光光检测眨眼可能不是特别准确,因此我们还要在其他可以展示驾驶员疲劳的点来结合展示驾驶员是否处于疲劳驾驶阶段。我们了解到还可以通过嘴巴打哈欠,和点头来展示驾驶员是否疲劳。我们首先来考虑嘴巴打哈欠。  
 首先我们来看一下嘴巴的关键点。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/ae32477caa3a4c6eaff75a5fc5c56816.png)  
 我们使用对眨眼检测的方法继续对嘴巴使用同样的方法检测是否张嘴!对应公式是:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/393f53a6a75c4f1aa53ff6097d86ccb9.png)



def mouth_aspect_ratio(mouth):
A = np.linalg.norm(mouth[2] - mouth[9]) # 51, 59
B = np.linalg.norm(mouth[4] - mouth[7]) # 53, 57
C = np.linalg.norm(mouth[0] - mouth[6]) # 49, 55
mar = (A + B) / (2.0 * C)
return mar


这里面我们选择的是嘴部区域内的六个点,来判断驾驶员是否进行了张嘴!



MAR_THRESH = 0.5
MOUTH_AR_CONSEC_FRAMES = 3


同样我们也要设置一个阈值,解释同对眨眼检测的时候一样。



(mStart, mEnd) = FACIAL_LANDMARKS_68_IDXS[“mouth”]


首先我们取到68关键点中对应的嘴部区域。



mouth = shape[mStart:mEnd]
mar = mouth_aspect_ratio(mouth)


然后通过函数`mouth_aspect_ratio`来计算出来mar数值!然后进行凸包检测,并且要画出来。



	mouthHull = cv2.convexHull(mouth)
	cv2.drawContours(frame, [mouthHull], -1, (0, 255, 0), 1)


	left = rect.left()#绘制出来人脸框
	top = rect.top()
	right = rect.right()
	bottom = rect.bottom()
	cv2.rectangle(frame, (left, top), (right, bottom), (0, 255, 0), 3)

这里面我们要加上一点就是说我们要绘制出来人脸框框!



	if mar > MAR_THRESH:  # 张嘴阈值0.5
		mCOUNTER += 1
		cv2.putText(frame, "Yawning!", (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
	else:
		# 如果连续3次都小于阈值,则表示打了一次哈欠
		if mCOUNTER >= MOUTH_AR_CONSEC_FRAMES:  # 阈值:3
			mTOTAL += 1
		# 重置嘴帧计数器
		mCOUNTER = 0
	cv2.putText(frame, "Yawning: {}".format(mTOTAL), (150, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
	cv2.putText(frame, "mCOUNTER: {}".format(mCOUNTER), (300, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
	cv2.putText(frame, "MAR: {:.2f}".format(mar), (480, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)

然后进行判断,并且在视频当中展示出来!


### 🌟项目改进方向(点头检测疲劳)


检测流程:  
 **2D人脸关键点检测;3D人脸模型匹配;求解3D点和对应2D点的转换关系;根据旋转矩阵求解欧拉角。**  
 一个物体相对于相机的姿态可以使用旋转矩阵和平移矩阵来表示。  
 !](https://bbs.csdn.net/topics/618545628)


1. 欧拉角  
 简单来说,欧拉角就是物体绕坐标系三个坐标轴(x,y,z轴)的旋转角度。
2. 世界坐标系和其他坐标系的转换  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/3c143c5b1c2f47fa9070c0a2cef4b832.png)世界坐标系到相机坐标系转换:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/df8b66028ea74964b0466abc7c8bc8b8.png)  
 相机坐标系到像素坐标系转换:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/dcdc7dd651b74c4c952b82e431b1772b.png)  
 因此像素坐标系和世界坐标系的关系如下:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/04481c9af12844d3acb36b4b40d14ced.png)  
 然后我们根据论文来定义一下:



object_pts = np.float32([[6.825897, 6.760612, 4.402142], #33左眉左上角
[1.330353, 7.122144, 6.903745], #29左眉右角
[-1.330353, 7.122144, 6.903745], #34右眉左角
[-6.825897, 6.760612, 4.402142], #38右眉右上角
[5.311432, 5.485328, 3.987654], #13左眼左上角
[1.789930, 5.393625, 4.413414], #17左眼右上角
[-1.789930, 5.393625, 4.413414], #25右眼左上角
[-5.311432, 5.485328, 3.987654], #21右眼右上角
[2.005628, 1.409845, 6.165652], #55鼻子左上角
[-2.005628, 1.409845, 6.165652], #49鼻子右上角
[2.774015, -2.080775, 5.048531], #43嘴左上角
[-2.774015, -2.080775, 5.048531],#39嘴右上角
[0.000000, -3.116408, 6.097667], #45嘴中央下角
[0.000000, -7.415691, 4.070434]])#6下巴角

K = [6.5308391993466671e+002, 0.0, 3.1950000000000000e+002,
0.0, 6.5308391993466671e+002, 2.3950000000000000e+002,
0.0, 0.0, 1.0]# 等价于矩阵[fx, 0, cx; 0, fy, cy; 0, 0, 1]

图像中心坐标系(uv):相机畸变参数[k1, k2, p1, p2, k3]

D = [7.0834633684407095e-002, 6.9140193737175351e-002, 0.0, 0.0, -1.3073460323689292e+000]
reprojectsrc = np.float32([[10.0, 10.0, 10.0],
[10.0, 10.0, -10.0],
[10.0, -10.0, -10.0],
[10.0, -10.0, 10.0],
[-10.0, 10.0, 10.0],
[-10.0, 10.0, -10.0],
[-10.0, -10.0, -10.0],
[-10.0, -10.0, 10.0]])

绘制正方体12轴

line_pairs = [[0, 1], [1, 2], [2, 3], [3, 0],
[4, 5], [5, 6], [6, 7], [7, 4],
[0, 4], [1, 5], [2, 6], [3, 7]]


其中`reprojectsrc`和`line_pairs`这两个属于矩形和矩形连接框框的操作。后续会用得到。



cam_matrix = np.array(K).reshape(3, 3).astype(np.float32)
dist_coeffs = np.array(D).reshape(5, 1).astype(np.float32)


这里我们对K和D矩阵进行了reshape了一下!



def get_head_pose(shape): # 头部姿态估计
# (像素坐标集合)填写2D参考点,注释遵循https://ibug.doc.ic.ac.uk/resources/300-W/
# 17左眉左上角/21左眉右角/22右眉左上角/26右眉右上角/36左眼左上角/39左眼右上角/42右眼左上角/
# 45右眼右上角/31鼻子左上角/35鼻子右上角/48左上角/54嘴右上角/57嘴中央下角/8下巴角
image_pts = np.float32([shape[17], shape[21], shape[22], shape[26], shape[36],
shape[39], shape[42], shape[45], shape[31], shape[35],
shape[48], shape[54], shape[57], shape[8]])
# solvePnP计算姿势——求解旋转和平移矩阵:
# rotation_vec表示旋转矩阵,translation_vec表示平移矩阵,cam_matrix与K矩阵对应,dist_coeffs与D矩阵对应。
_, rotation_vec, translation_vec = cv2.solvePnP(object_pts, image_pts, cam_matrix, dist_coeffs)
# projectPoints重新投影误差:原2d点和重投影2d点的距离(输入3d点、相机内参、相机畸变、r、t,输出重投影2d点)
reprojectdst, _ = cv2.projectPoints(reprojectsrc, rotation_vec, translation_vec, cam_matrix, dist_coeffs)
reprojectdst = tuple(map(tuple, reprojectdst.reshape(8, 2))) # 以8行2列显示

# 计算欧拉角calc euler angle
# 参考https://docs.opencv.org/2.4/modules/calib3d/doc/camera\_calibration\_and\_3d\_reconstruction.html#decomposeprojectionmatrix
rotation_mat, _ = cv2.Rodrigues(rotation_vec)  # 罗德里格斯公式(将旋转矩阵转换为旋转向量)
pose_mat = cv2.hconcat((rotation_mat, translation_vec))  # 水平拼接,vconcat垂直拼接
# decomposeProjectionMatrix将投影矩阵分解为旋转矩阵和相机矩阵
_, _, _, _, _, _, euler_angle = cv2.decomposeProjectionMatrix(pose_mat)

pitch, yaw, roll = [math.radians(_) for _ in euler_angle]

pitch = math.degrees(math.asin(math.sin(pitch)))
roll = -math.degrees(math.asin(math.sin(roll)))
yaw = math.degrees(math.asin(math.sin(yaw)))
print('pitch:{}, yaw:{}, roll:{}'.format(pitch, yaw, roll))

return reprojectdst, euler_angle  # 投影误差,欧拉角

这里我们对一些关键点进行了定位,并且我们将世界坐标系转化成了2D上的坐标。最后我们通过CV2计算出来了欧拉角,这样我们就可以判断司机是否点头了!  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/5d9adb8011c145f887c5c1fceb2de165.png)



HAR_THRESH = 0.3
NOD_AR_CONSEC_FRAMES = 3
hCOUNTER = 0
hTOTAL = 0


同样这里我们也要设定一个阈值和计数器!



	reprojectdst, euler_angle = get_head_pose(shape)
	har = euler_angle[0, 0]  # 取pitch旋转角度
	if har > HAR_THRESH:  # 点头阈值0.3
		hCOUNTER += 1
	else:
		# 如果连续3次都小于阈值,则表示瞌睡点头一次
		if hCOUNTER >= NOD_AR_CONSEC_FRAMES:  # 阈值:3
			hTOTAL += 1
		# 重置点头帧计数器
		hCOUNTER = 0

	# 绘制正方体12轴
	for start, end in line_pairs:
		cv2.line(frame, (int(reprojectdst[start][0]),int(reprojectdst[start][1])), (int(reprojectdst[end][0]),int(reprojectdst[end][1])), (0, 0, 255))
	# 显示角度结果
	cv2.putText(frame, "X: " + "{:7.2f}".format(euler_angle[0, 0]), (10, 90), cv2.FONT_HERSHEY_SIMPLEX, 0.75,
				(0, 255, 0), thickness=2)  # GREEN
	cv2.putText(frame, "Y: " + "{:7.2f}".format(euler_angle[1, 0]), (150, 90), cv2.FONT_HERSHEY_SIMPLEX, 0.75,
				(255, 0, 0), thickness=2)  # BLUE
	cv2.putText(frame, "Z: " + "{:7.2f}".format(euler_angle[2, 0]), (300, 90), cv2.FONT_HERSHEY_SIMPLEX, 0.75,
				(0, 0, 255), thickness=2)  # RED
	cv2.putText(frame, "Nod: {}".format(hTOTAL), (450, 90), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 0), 2)

	for (x, y) in shape:
		cv2.circle(frame, (x, y), 1, (0, 0, 255), -1)

if TOTAL >= 50 or mTOTAL >= 15:
	cv2.putText(frame, "SLEEP!!!", (100, 200), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 3)

这里也是一些判断操作和将信息在视频中展示出来。  
 最后的效果如下:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/6e410dca643b4be7b3ff5744bdd67311.png)  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/615592dbe2cb416595883044af193677.png)


### 🌟GUI界面设计展示


![在这里插入图片描述](https://img-blog.csdnimg.cn/87dc8734f96d4981b688d38815626279.png)  
 并且可以完成摄像头检测!  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/be29109888a848e8ba7486878b858ab7.png)  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/ea4d87a9bf754c02a4fc63e8382cd88b.png)



> 
> 🔎**支持**:🎁🎁🎁**如果觉得博主的文章还不错或者您用得到的话,可以免费的关注一下博主,如果三连收藏支持就更好啦!这就是给予我最大的支持!**


![img](https://img-blog.csdnimg.cn/img_convert/639f3254f4fe08ad44c4a1c028806d1f.png)
![img](https://img-blog.csdnimg.cn/img_convert/79effd1a0aef67c37e237434aa6360e2.png)
![img](https://img-blog.csdnimg.cn/img_convert/ce6aae21268e931beeda9a624f220c66.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**

三连收藏支持就更好啦!这就是给予我最大的支持!**


[外链图片转存中...(img-uFIEtugm-1715824625329)]
[外链图片转存中...(img-PnlXBVcH-1715824625329)]
[外链图片转存中...(img-iFBmQFox-1715824625329)]

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值