当前位置:首页 > 技术分析 > 正文内容

辅助驾驶技术——基于mediapipe的驾驶人睡意检测

ruisui883个月前 (02-03)技术分析11

什么是MediaPipe

MediaPipe 是一款由 Google Research 开发并开源的多媒体机器学习模型应用框架。在谷歌,一系列重要产品,如 、Google Lens、ARCore、Google Home 以及 ,都已深度整合了 MediaPipe。

MediaPipe 的核心框架由 C++ 实现,并提供 Java 以及 Objective C 等语言的支持。MediaPipe 的主要概念包括数据包(Packet)、数据流(Stream)、计算单元(Calculator)、图(Graph)以及子图(Subgraph)。

利用机器学习,进行人体33个2D姿态检测与评估

MediaPipe不仅可以进行人体姿态检测,手势识别,人脸检测与识别外,还可以进行3D物体对象检测等等,本期我们就基于MediaPipe的人脸检测来进行驾驶人的睡意检测。

MediaPipe Face Mesh

MediaPipe Face Mesh是一种脸部几何解决方案,即使在移动设备上,也可以实时估计468个3D脸部界标(dlib才能检测出68点)。它采用机器学习(ML)来推断3D表面几何形状,只需要单个摄像机输入,而无需专用的深度传感器。该解决方案利用轻量级的模型架构以及整个管线中的GPU加速,可提供对实时体验至关重要的实时性能。我们要进行驾驶人睡意检测,需要使用到MediaPipe Face Mesh 468个人脸关键点的人眼睛部分。眼部区域有32个点(每个眼睛有16个)。 为了计算眼睛的坐标,我们只需要 12 个点(每只眼睛 6 个)。

利用机器学习进行人脸468点的3D坐标检测,并生成3D模型

选择的12个点如下:

左眼:[362, 385, 387, 263, 373, 380]
右眼:[33, 160, 158, 133, 153, 144]
点坐标依次为:P1, P2, P3, P4, P5, P6 

驾驶人睡意检测理论

为了检测人眼的闭合状态,我们使用眼睛纵横比比(EAR)公式:

前期我们也使用过opencv来进行人眼的睡意检测,可以参考往期内容

按照我们的目标要求,我们需要进行驾驶人睡意检测,当然我们要从视频流中来实时检测,本期我们先按照检测图片的步骤先把此流程过一遍

1、我们输入一张人脸图片

2、mediapipe来进行人脸的468点的检测

3、从468点中获取人眼检测部分的12个点

4、根据EAR计算公式来计算人眼间的距离

5、根据计算的距离值与设置值进行对比

6、获取驾驶人睡意检测概率,进行提醒操作

mediapipe驾驶人睡意检测代码实现

import cv2
import numpy as np
import matplotlib.pyplot as plt
import mediapipe as mp
mp_facemesh = mp.solutions.face_mesh
mp_drawing  = mp.solutions.drawing_utils
denormalize_coordinates = mp_drawing._normalized_to_pixel_coordinates
all_left_eye_idxs = list(mp_facemesh.FACEMESH_LEFT_EYE)
all_left_eye_idxs = set(np.ravel(all_left_eye_idxs)) 
all_right_eye_idxs = list(mp_facemesh.FACEMESH_RIGHT_EYE)
all_right_eye_idxs = set(np.ravel(all_right_eye_idxs))
all_idxs = all_left_eye_idxs.union(all_right_eye_idxs)
 
chosen_left_eye_idxs  = [362, 385, 387, 263, 373, 380]
chosen_right_eye_idxs = [33,  160, 158, 133, 153, 144]
all_chosen_idxs = chosen_left_eye_idxs + chosen_right_eye_idxs

image_int = cv2.imread(r"2.png")
image = cv2.cvtColor(image_int, cv2.COLOR_BGR2RGB) 
image = np.ascontiguousarray(image)
imgH, imgW, _ = image.shape
 
cv2.imshow('image',image_int)
cv2.waitKey(0)

首先,我们import mediapipe第三方包,以便使用mediapipe来进行人脸的468点的检测,并获取左右眼睛的32点坐标点,我们从32个坐标点中获取12个坐标点,左右眼睛各6个。然后我们加载一张需要检测的照片,方便后期进行图片的人脸468点检测。当然关于mediapipe的详细介绍,可以参考文章末尾的扩展阅读。

with mp_facemesh.FaceMesh(
    static_image_mode=True,         # Default=False
    max_num_faces=1,                # Default=1
    refine_landmarks=False,         
    min_detection_confidence=0.5,   # Default=0.5
    min_tracking_confidence= 0.5,   # Default=0.5
) as face_mesh:
     
    results = face_mesh.process(image)

我们直接使用mp_facemesh.FaceMesh中的process函数进行图片的人脸468点检测,经过此函数后,我们就得到了468个坐标点,我们需要从这468点中得到左右眼睛的12个点。其结果保存在results中。

def plot_pic(
    *,
    img_dt,
    img_eye_lmks=None,
    img_eye_lmks_chosen=None,
    face_landmarks=None,
    ts_thickness=1,
    ts_circle_radius=2,
    lmk_circle_radius=3,
    name="1",
):
    image_drawing_tool = img_dt 
    image_eye_lmks = img_dt.copy() if img_eye_lmks is None else img_eye_lmks
    img_eye_lmks_chosen = img_dt.copy() if img_eye_lmks_chosen is None else img_eye_lmks_chosen
    connections_drawing_spec = mp_drawing.DrawingSpec(
        thickness=ts_thickness, 
        circle_radius=ts_circle_radius, 
        color=(255, 255, 255)
    )
    mp_drawing.draw_landmarks(
        image=image_drawing_tool,
        landmark_list=face_landmarks,
        connections=mp_facemesh.FACEMESH_TESSELATION,
        landmark_drawing_spec=None,
        connection_drawing_spec=connections_drawing_spec,
    )
    landmarks = face_landmarks.landmark
    for landmark_idx, landmark in enumerate(landmarks):
        if landmark_idx in all_idxs:
            pred_cord = denormalize_coordinates(landmark.x,  landmark.y,  imgW, imgH)
            cv2.circle(image_eye_lmks, pred_cord,  lmk_circle_radius,  (255, 255, 255), -1 ) 
        if landmark_idx in all_chosen_idxs:
            pred_cord = denormalize_coordinates(landmark.x,  landmark.y,  imgW, imgH)
            cv2.circle(img_eye_lmks_chosen, pred_cord, lmk_circle_radius, (255, 255, 255),  -1 )
    imghstack = np.hstack((img_eye_lmks_chosen, image_eye_lmks,image_drawing_tool))
    cv2.imshow('imghstack',imghstack)
    cv2.waitKey(0)

为了方便,我们建立一个plot_pic函数来从人脸468个关键点中获取12个左右眼睛的关键点

if results.multi_face_landmarks:
    for face_id, face_landmarks in enumerate(results.multi_face_landmarks):    
        _ = plot_pic(img_dt=image.copy(), face_landmarks=face_landmarks)

得到12个关键点后,我们便可以利用EAR公式来计算驾驶人睡意检测的概率了

image_eyes_open  = cv2.imread("3.png")[:, :, ::-1]
for idx, image in enumerate([image_eyes_open]):
    image = np.ascontiguousarray(image)
    imgH, imgW, _ = image.shape
    custom_chosen_lmk_image = image.copy()
    with mp_facemesh.FaceMesh(refine_landmarks=True) as face_mesh:
        results = face_mesh.process(image).multi_face_landmarks
        if results:
            for face_id, face_landmarks in enumerate(results):
                landmarks = face_landmarks.landmark
                EAR, _ = calculate_avg_ear( landmarks, chosen_left_eye_idxs,  chosen_right_eye_idxs,  imgW,  imgH)
                cv2.putText(custom_chosen_lmk_image, f"EAR: {round(EAR, 2)}", (1, 24), cv2.FONT_HERSHEY_COMPLEX, 0.9, (255, 255, 255), 2)                
                plot(img_dt=image.copy(),img_eye_lmks_chosen=custom_chosen_lmk_image, face_landmarks=face_landmarks,ts_thickness=1,  ts_circle_radius=3,   lmk_circle_radius=3 )

可以看到,当人眼闭合时,根据EAR的计算公式,其值就会很小,当人眼睁开时,其值就会变大。

当然,我们可以多收集一些数据,计算一个人眼睁开与闭合的数据,通过多个数据,计算一个比较合适的阈值,通过此阈值,我们检测到的人眼尺寸与此阈值来做对比,以便进行驾驶人的睡意检测。

我们真正利用此技术来应用到辅助驾驶汽车上时,我们需要从汽车摄像头上实时获取视频流,以便对视频流中的人脸进行睡意检测,当然,我们检测到驾驶员有睡意情况时,我们需要进行相关的提醒。本期我们介绍了如何使用mediapipe来进行驾驶员睡意检测,但是我们只是进行了一张图片的检测,且真实情况下,人会不停的眨眼睛,因此,我们还需要进行时间的判断,超过多少时间的眼睛闭合才真正算是驾驶员有睡意,我们后期再进行详细的分享。

扩展阅读:

毫秒级人体姿态检测模型MediaPipe,这速度,还能有那个模型比拟

利用机器学习,进行人手的21个3D手关节坐标检测

利用机器学习进行人脸468点的3D坐标检测,并生成3D模型

MediaPipe 集成人脸识别,人体姿态评估,人手检测模型

颠覆2D对象检测模型,MediaPipe 3D对象检测还原真实的对象特征

MediaPipe Face Detection可运行在移动设备上的亚毫秒级人脸检测

扫描二维码推送至手机访问。

版权声明:本文由ruisui88发布,如需转载请注明出处。

本文链接:http://www.ruisui88.com/post/731.html

标签: spec.ts
分享给朋友:

“辅助驾驶技术——基于mediapipe的驾驶人睡意检测” 的相关文章

vue:组件中之间的传值

一、父子组件之间的传值----props/$emit1、父组件向子组件传值--props2.子组件想父组件传值-this.$emit('select',item)二、父组件向下(深层)子组件传值----provide/injectprovide:Object | () => O...

Git 分支管理策略汇总

最近,团队新入职了一些小伙伴,在开发过程中,他们问我 Git 分支是如何管理的,以及应该怎么提交代码?我大概说了一些规则,但仔细想来,好像也并没有形成一个清晰规范的流程。所以查了一些资料,总结出下面这篇文章,一共包含四种常见的分支管理策略,分享给大家。Git flow在这种模式下,主要维护了两类分支...

代码分支规范

一.gitflow工作流说明:主分支:master,稳定版本代码分支,对外可以随时编译发布的分支,不允许直接Push代码,只能请求合并(pull request),且只接受hotfix、release分支的代码合并。gitlab上做限制。热修复分支:hotfix,针对现场紧急问题、bug修复的代码分...

「云原生」Containerd ctr,crictl 和 nerdctl 命令介绍与实战操作

一、概述作为接替Docker运行时的Containerd在早在Kubernetes1.7时就能直接与Kubelet集成使用,只是大部分时候我们因熟悉Docker,在部署集群时采用了默认的dockershim。在V1.24起的版本的kubelet就彻底移除了dockershim,改为默认使用Conta...

2024年,不断突破的一年

迈凯伦F1车队不久前拿下了2024年度总冠军,距离上一次还是二十几年前。在此期间,另一领域内,一个充满革新活力的腕表品牌——RICHARD MILLE理查米尔,正不断发展,与F1运动、帆船、古董车展等领域,共享着对速度与极限的无尽向往。RICHARD MILLE的发展与F1车手们在赛道上的卓越表现交...

最快清除数组空值?分享 1 段优质 JS 代码片段!

本内容首发于工粽号:程序员大澈,每日分享一段优质代码片段,欢迎关注和投稿!大家好,我是大澈!本文约 600+ 字,整篇阅读约需 1 分钟。今天分享一段优质 JS 代码片段,用最简洁的代码清除了数组中的空值。老规矩,先阅读代码片段并思考,再看代码解析再思考,最后评论区留下你的见解!const arr...