Dlib模型之驾驶员疲劳检测总结(可视化界面)

目的

经查阅相关文献,疲劳在人体面部表情中表现出大致三个类型:打哈欠(嘴巴张大且相对较长时间保持这一状态)、眨眼(或眼睛微闭,此时眨眼次数增多,且眨眼速度变慢)、点头(瞌睡点头)。本实验从人脸朝向、位置、瞳孔朝向、眼睛开合度、眨眼频率、瞳孔收缩率等数据入手,并通过这些数据,实时地计算出驾驶员的注意力集中程度,分析驾驶员是否疲劳驾驶和及时作出安全提示。

技术背景

环境:
Win10、Python3.7、anaconda3、JupyterNotebook、wxFromBuilder
技术:

Opencv:图像处理
Dlib:一个很经典的用于图像处理的开源库,shape_predictor_68_face_landmarks.dat是一个用于人脸68个关键点检测的dat模型库,使用这个模型库可以很方便地进行人脸检测,并进行简单的应用。
Numpy:基于Python的n维数值计算扩展。
Imutils :一系列使得opencv 便利的功能,包括图像旋转、缩放、平移,骨架化、边缘检测、显示
matplotlib 图像(imutils.opencv2matplotlib(image)。
wx: 可视化UI界面

import dlib                     # 人脸识别的库dlib(单独安装)
import numpy as np              # 数据处理的库numpy
import cv2                      # 图像处理的库OpenCv
import wx                       # 构造显示界面的GUI
import wx.xrc
import wx.adv
# import the necessary packages
from scipy.spatial import distance as dist
from imutils.video import FileVideoStream
from imutils.video import VideoStream
from imutils import face_utils
import numpy as np # 数据处理的库 numpy
import argparse
import imutils
import datetime,time
import math
import os

正文

(1)环境搭建

安装配置环境:anaconda3、JupyterNotebook,搭建opencv和dlib环境

pip install opencv-python
pip install numpy
pip install wxpython
pip install imutils

(1.1)opencv3.4.1图像处理

  1. 安装opencv3.4.1:官网下载 Windows版,双击安装
    在这里插入图片描述
  2. 配置系统环境变量:在path中添加你的安装路径: “D:\mydownload\opencv341\opencv\build\x64\vc15\bin”
    在这里插入图片描述
  3. 然后重启电脑!

(1.2)dlib人脸识别库

ps.这个步骤不能pip直接安装,需要先下载boosting和cmake搭建环境,再解压安装获得dlib库文件,最后复制关键文件到python安装目录下!
需要下载的目录:
在这里插入图片描述

具体安装教程:python3.7添加dlib模块——需要耐心安装

注意:
b2 -a –with-python address-model=64 toolset=msvc runtime-link=static
这一句运行时需要删去-with
b2 -a -python address-model=64 toolset=msvc runtime-link=static

其他步骤根据教程安装即可!

(1.3)wxFromBuilder可视化界面

可视化界面设计工具下载:
在这里插入图片描述
这是可视化拖拽控件,使用步骤:

  1. 新建画布:先选择forms里面的Frame,开始的时候必须用这个,其他控件都是在里面。
    在这里插入图片描述

  2. 布局设置:在Layout里面选择一个布局。布局就是用来将Frame分成单独的小方格。各种布局都不一样,有的是只有行没有列,有的是行列都可以。根据需要添加即可。
    在这里插入图片描述

  3. 添加控件:在common里面选择基础的按钮等控件。在这里很难控制好位置,因为只有选定好了在哪个方格里,添加的时候才能添加。添加后的位置不好变化。所以,必须先设计好正规布局,然后再往里面添加小控件。
    在这里插入图片描述
    对齐规则:如果不满意都是在左侧,可以在上面中选择是居中还是左对齐。(填充,边框等)
    在这里插入图片描述
    可以在语言栏中点击查看:python代码
    在这里插入图片描述

  4. 右侧设置属性(Properties):名称,相对位置等;
    在这里插入图片描述

  5. 点击事件:填写事件函数名
    在这里插入图片描述
    python自动生成函数:
    在这里插入图片描述

  6. 导出设计界面:选中MyProject2:Project,在属性(Properties)中代码规则(code_generation)勾选上python
    在这里插入图片描述
    键盘按F8,项目同目录下自动生成noname.py界面
    在这里插入图片描述
    综上,本次UI界面设计:
    在这里插入图片描述

  7. wxpython加载界面:(添加封面图和图标)
    在这里插入图片描述
    在python中显示,需要初始化加载画布即可:

    self.frame = Fatigue_detecting(parent=None,title="Fatigue Demo")
    self.frame.Show(True)
    

完整界面代码:

# -*- coding: utf-8 -*- 
import wx                       # 构造显示界面的GUI
import wx.xrc
import wx.adv

###########################################################################
## Class Fatigue_detecting
###########################################################################

COVER = 'D:/myworkspace/JupyterNotebook/fatigue_detecting/images/camera.png'

class Fatigue_detecting(wx.Frame):

    def __init__( self, parent, title ):
        wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = title, pos = wx.DefaultPosition, size = wx.Size( 873,535 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL )
                
        self.SetSizeHints( wx.DefaultSize, wx.DefaultSize )
        self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_MENU ) )

        bSizer1 = wx.BoxSizer( wx.VERTICAL )
        bSizer2 = wx.BoxSizer( wx.HORIZONTAL )
        bSizer3 = wx.BoxSizer( wx.VERTICAL )

        self.m_animCtrl1 = wx.adv.AnimationCtrl( self, wx.ID_ANY, wx.adv.NullAnimation, wx.DefaultPosition, wx.DefaultSize, wx.adv.AC_DEFAULT_STYLE ) 
        bSizer3.Add( self.m_animCtrl1, 1, wx.ALL|wx.EXPAND, 5 )        
        bSizer2.Add( bSizer3, 9, wx.EXPAND, 5 )
        bSizer4 = wx.BoxSizer( wx.VERTICAL )
        sbSizer1 = wx.StaticBoxSizer( wx.StaticBox( self, wx.ID_ANY, u"参数设置" ), wx.VERTICAL )
        sbSizer2 = wx.StaticBoxSizer( wx.StaticBox( sbSizer1.GetStaticBox(), wx.ID_ANY, u"视频源" ), wx.VERTICAL )
        gSizer1 = wx.GridSizer( 0, 2, 0, 8 )
        m_choice1Choices = [ u"摄像头ID_0", u"摄像头ID_1", u"摄像头ID_2" ]
        self.m_choice1 = wx.Choice( sbSizer2.GetStaticBox(), wx.ID_ANY, wx.DefaultPosition, wx.Size( 90,25 ), m_choice1Choices, 0 )
        self.m_choice1.SetSelection( 0 )
        gSizer1.Add( self.m_choice1, 0, wx.ALL, 5 )
        self.camera_button1 = wx.Button( sbSizer2.GetStaticBox(), wx.ID_ANY, u"开始检测", wx.DefaultPosition, wx.Size( 90,25 ), 0 )
        gSizer1.Add( self.camera_button1, 0, wx.ALL, 5 )
        self.vedio_button2 = wx.Button( sbSizer2.GetStaticBox(), wx.ID_ANY, u"打开视频文件", wx.DefaultPosition, wx.Size( 90,25 ), 0 )
        gSizer1.Add( self.vedio_button2, 0, wx.ALL, 5 )

        self.off_button3 = wx.Button( sbSizer2.GetStaticBox(), wx.ID_ANY, u"暂停", wx.DefaultPosition, wx.Size( 90,25 ), 0 )
        gSizer1.Add( self.off_button3, 0, wx.ALL, 5 )
        sbSizer2.Add( gSizer1, 1, wx.EXPAND, 5 )
        sbSizer1.Add( sbSizer2, 2, wx.EXPAND, 5 )
        sbSizer3 = wx.StaticBoxSizer( wx.StaticBox( sbSizer1.GetStaticBox(), wx.ID_ANY, u"疲劳检测" ), wx.VERTICAL )
        bSizer5 = wx.BoxSizer( wx.HORIZONTAL )
        self.yawn_checkBox1 = wx.CheckBox( sbSizer3.GetStaticBox(), wx.ID_ANY, u"打哈欠检测", wx.Point( -1,-1 ), wx.Size( -1,15 ), 0 )
        self.yawn_checkBox1.SetValue(True) 
        bSizer5.Add( self.yawn_checkBox1, 0, wx.ALL, 5 )
        self.blink_checkBox2 = wx.CheckBox( sbSizer3.GetStaticBox(), wx.ID_ANY, u"闭眼检测", wx.Point( -1,-1 ), wx.Size( -1,15 ), 0 )
        self.blink_checkBox2.SetValue(True) 
        bSizer5.Add( self.blink_checkBox2, 0, wx.ALL, 5 )
        sbSizer3.Add( bSizer5, 1, wx.EXPAND, 5 )
        bSizer6 = wx.BoxSizer( wx.HORIZONTAL )
        self.nod_checkBox7 = wx.CheckBox( sbSizer3.GetStaticBox(), wx.ID_ANY, u"点头检测", wx.Point( -1,-1 ), wx.Size( -1,15 ), 0 )
        self.nod_checkBox7.SetValue(True) 
        bSizer6.Add( self.nod_checkBox7, 0, wx.ALL, 5 )
        self.m_staticText1 = wx.StaticText( sbSizer3.GetStaticBox(), wx.ID_ANY, u"疲劳时间(秒):", wx.DefaultPosition, wx.Size( -1,15 ), 0 )
        self.m_staticText1.Wrap( -1 )
        bSizer6.Add( self.m_staticText1, 0, wx.ALL, 5 )
        m_listBox2Choices = [ u"3", u"4", u"5", u"6", u"7", u"8" ]
        self.m_listBox2 = wx.ListBox( sbSizer3.GetStaticBox(), wx.ID_ANY, wx.DefaultPosition, wx.Size( 50,24 ), m_listBox2Choices, 0 )
        bSizer6.Add( self.m_listBox2, 0, 0, 5 )
        sbSizer3.Add( bSizer6, 1, wx.EXPAND, 5 )
        sbSizer1.Add( sbSizer3, 2, 0, 5 )
        sbSizer4 = wx.StaticBoxSizer( wx.StaticBox( sbSizer1.GetStaticBox(), wx.ID_ANY, u"脱岗检测" ), wx.VERTICAL )
        bSizer8 = wx.BoxSizer( wx.HORIZONTAL )
        self.m_checkBox4 = wx.CheckBox( sbSizer4.GetStaticBox(), wx.ID_ANY, u"脱岗检测", wx.DefaultPosition, wx.Size( -1,15 ), 0 )
        self.m_checkBox4.SetValue(True) 
        bSizer8.Add( self.m_checkBox4, 0, wx.ALL, 5 )
        self.m_staticText2 = wx.StaticText( sbSizer4.GetStaticBox(), wx.ID_ANY, u"脱岗时间(秒):", wx.DefaultPosition, wx.Size( -1,15 ), 0 )
        self.m_staticText2.Wrap( -1 )
        bSizer8.Add( self.m_staticText2, 0, wx.ALL, 5 )
        m_listBox21Choices = [ u"5", u"10", u"15", u"20", u"25", u"30" ]
        self.m_listBox21 = wx.ListBox( sbSizer4.GetStaticBox(), wx.ID_ANY, wx.DefaultPosition, wx.Size( 50,24 ), m_listBox21Choices, 0 )
        bSizer8.Add( self.m_listBox21, 0, 0, 5 )
        sbSizer4.Add( bSizer8, 1, 0, 5 )
        sbSizer1.Add( sbSizer4, 1, 0, 5 )
        sbSizer5 = wx.StaticBoxSizer( wx.StaticBox( sbSizer1.GetStaticBox(), wx.ID_ANY, u"分析区域" ), wx.VERTICAL )
        bSizer9 = wx.BoxSizer( wx.HORIZONTAL )
        self.m_staticText3 = wx.StaticText( sbSizer5.GetStaticBox(), wx.ID_ANY, u"检测区域:   ", wx.DefaultPosition, wx.DefaultSize, 0 )
        self.m_staticText3.Wrap( -1 )
        bSizer9.Add( self.m_staticText3, 0, wx.ALL, 5 )
        m_choice2Choices = [ u"全视频检测", u"部分区域选取" ]
        self.m_choice2 = wx.Choice( sbSizer5.GetStaticBox(), wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, m_choice2Choices, 0 )
        self.m_choice2.SetSelection( 0 )
        bSizer9.Add( self.m_choice2, 0, wx.ALL, 5 )
        sbSizer5.Add( bSizer9, 1, wx.EXPAND, 5 )
        sbSizer1.Add( sbSizer5, 1, 0, 5 )
        sbSizer6 = wx.StaticBoxSizer( wx.StaticBox( sbSizer1.GetStaticBox(), wx.ID_ANY, u"状态输出" ), wx.VERTICAL )
        self.m_textCtrl3 = wx.TextCtrl( sbSizer6.GetStaticBox(), wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_MULTILINE|wx.TE_READONLY )
        sbSizer6.Add( self.m_textCtrl3, 1, wx.ALL|wx.EXPAND, 5 )
        sbSizer1.Add( sbSizer6, 5, wx.EXPAND, 5 )
        bSizer4.Add( sbSizer1, 1, wx.EXPAND, 5 )
        bSizer2.Add( bSizer4, 3, wx.EXPAND, 5 )
        bSizer1.Add( bSizer2, 1, wx.EXPAND, 5 )

        self.SetSizer( bSizer1 )  
        self.Layout()
        self.Centre( wx.BOTH )
        
        # 封面图片
        self.image_cover = wx.Image(COVER, wx.BITMAP_TYPE_ANY)
        # 显示图片在m_animCtrl1上
        self.bmp = wx.StaticBitmap(self.m_animCtrl1, -1, wx.Bitmap(self.image_cover))

        # 设置窗口标题的图标
        self.icon = wx.Icon('./images/123.ico', wx.BITMAP_TYPE_ICO)
        self.SetIcon(self.icon)
        print("wxpython界面初始化加载完成!")
            
class main_app(wx.App):
    """
     在OnInit() 里边申请Frame类,这样能保证一定是在app后调用,
     这个函数是app执行完自己的__init__函数后就会执行
    """
    # OnInit 方法在主事件循环开始前被wxPython系统调用,是wxpython独有的
    def OnInit(self):
        self.frame = Fatigue_detecting(parent=None,title="Fatigue Demo")
        self.frame.Show(True)
        return True   

    
if __name__ == "__main__":
    app = main_app()
    app.MainLoop()
    

(2)检测原理

下载开源数据集:
shape_predictor_68_face_landmarks.dat

官方下载地址:https://pypi.org/simple/dlib/
码云下载:https://gitee.com/cungudafa/fatigue_detecting/tree/master/model
基于图像驾驶员疲劳检测技术研究 ,更多参考:

Dlib模型之驾驶员疲劳检测系列(眨眼、打哈欠、瞌睡点头、可视化界面)

标准参数说明

疲劳认定标准:

  • 眨眼:连续3帧内,眼睛长宽比为 0.2
  • 打哈欠:连续3帧内,嘴部长宽比为 0.5
  • 瞌睡点头:连续3帧内,pitch(x)旋转角为 0.3

(3)wxpython主要控件

下拉选择框,按钮,复选框,文本框,视频界面
在这里插入图片描述
wxpython主要控件监听事件self.m_choice1.Bind(event):

        # Connect Events
        self.m_choice1.Bind( wx.EVT_CHOICE, self.cameraid_choice )#绑定事件
        self.camera_button1.Bind( wx.EVT_BUTTON, self.camera_on )#开
        self.vedio_button2.Bind( wx.EVT_BUTTON, self.vedio_on )
        self.off_button3.Bind( wx.EVT_BUTTON, self.off )#关

        self.m_listBox2.Bind( wx.EVT_LISTBOX, self.AR_CONSEC_FRAMES )# 闪烁阈值设置
        self.m_listBox21.Bind( wx.EVT_LISTBOX, self.OUT_AR_CONSEC_FRAMES )# 脱岗时间设置

在这里插入图片描述

(3.1)选择

  • event.GetString()获取选择内容,
  • 并写入文本框self.m_textCtrl3.AppendText()
  • 把值传给全局变量(在def __init__( ):中定义的self.VIDEO_STREAM = 0
	 def cameraid_choice( self, event ):
        # 摄像头编号
        cameraid = int(event.GetString()[-1])# 截取最后一个字符
        if cameraid == 0:
            self.m_textCtrl3.AppendText(u"准备打开本地摄像头!!!\n")
        if cameraid == 1 or cameraid == 2:
            self.m_textCtrl3.AppendText(u"准备打开外置摄像头!!!\n")
        self.VIDEO_STREAM = cameraid
        
	def AR_CONSEC_FRAMES( self, event ):
        self.m_textCtrl3.AppendText(u"设置疲劳间隔为:\t"+event.GetString()+"秒\n")
        self.AR_CONSEC_FRAMES_check = int(event.GetString())
        
    def OUT_AR_CONSEC_FRAMES( self, event ):
        self.m_textCtrl3.AppendText(u"设置脱岗间隔为:\t"+event.GetString()+"秒\n")
        self.OUT_AR_CONSEC_FRAMES_check = int(event.GetString())

选择本地摄像头,或外接摄像头设备:
在这里插入图片描述
修改检测时间参数:
在这里插入图片描述

(3.2)打开本地视频

import os

def vedio_on( self, event ):  
        # 选择文件夹对话框窗口
        dialog = wx.FileDialog(self,u"选择视频检测",os.getcwd(),'',wildcard="(*.mp4)|*.mp4",style=wx.FD_OPEN | wx.FD_CHANGE_DIR)
        if dialog.ShowModal() == wx.ID_OK:
            #如果确定了选择的文件夹,将文件夹路径写到m_textCtrl3控件
            self.m_textCtrl3.SetValue(u"文件路径:"+dialog.GetPath()+"\n")
            self.VIDEO_STREAM = str(dialog.GetPath())# 更新全局变量路径
            dialog.Destroy    

在这里插入图片描述

(3.3)opencv图转wxpython图像

opencv显示视频流是imshow

cv2.imshow("Frame", frame)
# 释放摄像头 release camera
cap.release()
# do a bit of cleanup
cv2.destroyAllWindows()

opencv中imread的图片内部是BGR排序,wxPython的StaticBitmap需要的图片是RGB排序,不转换会出现颜色变换

    def _learning_face(self,event):
        
        #建cv2摄像头对象,这里使用电脑自带摄像头,如果接了外部摄像头,则自动切换到外部摄像头
        self.cap = cv2.VideoCapture(self.VIDEO_STREAM)
        #self.cap = cv2.VideoCapture(0)
        # 成功打开视频,循环读取视频流
        while(self.cap.isOpened()):
            flag, im_rd = self.cap.read()
            # opencv中imread的图片内部是BGR排序,wxPython的StaticBitmap需要的图片是RGB排序,不转换会出现颜色变换
            height,width = im_rd.shape[:2]
            image1 = cv2.cvtColor(im_rd, cv2.COLOR_BGR2RGB)
            pic = wx.Bitmap.FromBuffer(width,height,image1)
            # 显示图片在画布上:
            self.bmp.SetBitmap(pic)

        # 释放摄像头
        self.cap.release()

(4)主要代码思路

使用多线程,子线程运行后台的程序,主线程更新前台的UI,这样不会互相影响


    def camera_on(self,event):
        """使用多线程,子线程运行后台的程序,主线程更新前台的UI,这样不会互相影响"""
        import _thread
        # 创建子线程,按钮调用这个方法,
        _thread.start_new_thread(self._learning_face, (event,))
        
   	def off(self,event):
        """关闭摄像头,显示封面页"""
        self.cap.release()
        self.bmp.SetBitmap(wx.Bitmap(self.image_cover))
        

(5)运行效果

在这里插入图片描述
在这里插入图片描述


附:
完整项目代码:
https://gitee.com/cungudafa/fatigue_detecting

发布了195 篇原创文章 · 获赞 335 · 访问量 12万+

猜你喜欢

转载自blog.csdn.net/cungudafa/article/details/103712875
今日推荐