使用opencv和dlib库(C++代码)实现人脸活体检测(眨眼、张嘴、摇头检测)

前言

  • 本文章使用opencv和dlib库,使用C++代码实现了人脸活体检测,包括眨眼检测、张嘴检测以及摇头检测,可以对静态图片和活体进行有效区分。

效果展示

  • 请添加图片描述

Dlib库介绍

  • dlib是一个开源的C++机器学习库,它提供了一系列用于图像处理、人脸检测、人脸识别、物体检测、图像标注等功能的算法和工具。dlib库使用了现代C++和模板元编程的技术,并且具有高度的可移植性和可扩展性。

  • dlib库的主要特点包括:

    • 提供了一系列高效的机器学习算法,如支持向量机、最大间隔分类器、随机森林等。
    • 提供了用于图像处理的算法,如图像滤波、图像变换、边缘检测等。
    • 提供了用于人脸检测和人脸识别的算法,具有较高的准确性和性能。
    • 提供了用于物体检测和跟踪的算法,如HOG特征和级联分类器。
    • 提供了用于图像标注和图像分割的算法,如条件随机场。
    • 支持多线程和并行计算,可以充分利用多核处理器的性能。
    • 具有丰富的文档和示例代码,易于学习和使用。
  • dlib库在计算机视觉、模式识别和机器学习领域被广泛应用,是一个功能强大且易用的机器学习库。对外提供了C++和python两种接口。

OpenCV库介绍

  • OpenCV是一个基于BSD许可发行的跨平台计算机视觉库。OpenCV作为较大众的开源库,拥有了丰富的常用图像处理函数库,采用C/C++语言编写,可以运行在Linux/Windows/Mac等操作系统上,能够快速的实现一些图像处理和识别的任务。
  • 本项目主要使用OpenCV来打开摄像头,读取视频帧。

人脸识别

  • Dlib库中有一个人脸特征点模型 shape_predictor_68_face_landmarks.dat,记录了人脸的68个特征点。可以通过这个模型识别人脸特征从而检测是否检测到人脸。
  • 人脸特征点请添加图片描述

眨眼检测原理

  • 通过计算人眼纵横比(EAR),可以判断眼睛是张开还是闭合,从而判断是否眨眼。
  • 在这里插入图片描述 E A R = ∥ p 2 − p 6 ∥ + ∥ p 3 − p 5 ∥ 2 ∥ p 1 − p 4 ∥ EAR=\frac{\|p2-p6\| + \|p3-p5\|}{ 2\|p1-p4\|} EAR=2∥p1p4∥p2p6∥+p3p5∥
  • 分子计算的是眼睛的特征点在垂直方向上的距离,分母计算的是眼睛的特征点在水平方向上的距离,由于水平特征点只有一组,所以乘以2保证两组特征点的权重相同。
  • 眼睛闭合时,p2和p6、p3和p5之间的距离会减小,而p1和p4之间的距离基本保持不变。因此通过计算人眼纵横比EAR,可以判断眼睛的睁开和闭合状态。

张嘴检测原理

  • 张嘴检测的原理与眨眼检测一样,嘴部的特征点较多,可以自行选取六个特征点,计算嘴部的纵横比MAR
  • 在这里插入图片描述
  • 我这里选取的六个特征点是 49,55、51,59、53,57
  • 按照相同的方法,计算人嘴纵横比 MAR

摇头检测

  • 摇头时,鼻子中心点到左右脸颊的距离会发生变化,我们可以通过计算该距离变化,判断是否进行了摇头。
  • 比如通过计算鼻子中心点特征点31到左脸颊特征点2和右脸颊特征点16的距离变化,判断摇头动作。具体参考源代码。

Dlib和OpenCV库编译

  • 编译前需要安装Visual Studio 2015和CMake。
  • Dlib库编译
    • Dlib库下载地址。可以选择这个包下载 dlib-19.24.zip,同时需要下载这个人脸关键点检测器 shape_predictor_68_face_landmarks.dat
    • 源码下载后,需要进行编译。
    • 先在Dlib源码工程下 dlib/config.h文件末尾添加下面这一句
      •   #define DLIB__CMAKE_GENERATED_A_CONFIG_H_FILE
        
    • 然后在Dlib源码工程下,创建build_x86文件夹,进入该文件夹,执行命令
      •   cmake -G "Visual Studio 14 2015" ..
          cmake --build ./ --config Release
        
    • 编译完成后,会在build_x86\dlib\Release下生成静态库文件 dlib19.24.0_release_32bit_msvc1900.lib
  • OpenCV库编译
    • openCV库下载地址。选择一个版本的源码(Sources)进行下载,我下载的是4.5.0
    • 源码下载后,需要进行编译。在OpenCV源码工程下,创建build_x86文件夹,进入该文件夹,执行命令
      •   cmake -G "Visual Studio 14 2015" ..
          cmake --build ./ --config Release
        
    • 编译完成后,会在build_x86/lib/Release下生成静态库文件,会在build_x86/bin/Release下生成动态库文件。

创建工程

  • 先创建一个facec目录
  • 在facec目录下创建一个dlib-19.24目录,将dlib源码中的dlib目录拷贝到该目录下
  • 在facec目录下创建一个opencv-4.5.0目录,将opecv源码中的include和modules目录拷贝到该目录下
  • 在facec目录下创建一个lib目录,将编译好的dlib静态库和opencv静态库拷贝到该目录下。
  • 在facec目录下创建一个src目录,src目录下为main.cpp源代码。
  • 在facec目录下创建一个CMakeLists.txt文件。
  • 目录结构如下
  •   facec
      	├── dlib-19.24
      	│ 		└── dlib
      	├── opencv-4.5.0
      	│ 		├── include
      	│ 		└── modules
      	├── lib
      	├── src
      	│ 	└── main.cpp
      	└──CMakeLists.txt
    

编译工程

  • 在facec目录创建build_x86目录,进入该目录执行
  •   cmake -G "Visual Studio 14 2015" ..
      cmake --build ./ --config Release
    
  • 会在 bin\Release 下生成可执行程序,然后将opencv相关动态库和人脸关键点检测器shape_predictor_68_face_landmarks.dat拷贝过来,双击可执行程序就可以运行了。

相关源代码

  • main.cpp源代码
  •   #include <dlib/opencv.h>
      #include <dlib/image_processing/frontal_face_detector.h>
      #include <dlib/image_processing/render_face_detections.h>
      #include <dlib/image_processing.h>
      #include <dlib/gui_widgets.h>
      #include <stdio.h>
      #include <opencv2/opencv.hpp>
      
      using namespace dlib;
      using namespace std;
      
      int main()
      {
          
          
          try
          {
          
          
      		// 打开摄像头
              cv::VideoCapture cap(0, cv::CAP_DSHOW);
              if (!cap.isOpened())
              {
          
          
                  printf("open VideoCapture failed.\n");
                  return 1;
              }
      
      		printf("open VideoCapture success.\n");
      
              image_window win;
      
              // Load face detection and pose estimation models.
              frontal_face_detector detector = get_frontal_face_detector();
              shape_predictor pose_model;
              deserialize("shape_predictor_68_face_landmarks.dat") >> pose_model;
      
      		static int mouthIndex = 0;
      		static int leyeIndex = 0;
      		static int reyeIndex = 0;
      
      		int counter_mouth = 0;
      		int counter_leye = 0;
      		int counter_reye = 0;
      
              // Grab and process frames until the main window is closed by the user.
              while(!win.is_closed())
              {
          
          
                  // Grab a frame
                  cv::Mat frame;
                  if (!cap.read(frame))
                  {
          
          
                      break;
                  }
      
                  cv_image<bgr_pixel> cimg(frame);
      
      			// 保存图片
      			cv::Mat imgPng = dlib::toMat(cimg);
      			cv::imwrite("face.png", imgPng);
      
                  // Detect faces 
                  std::vector<rectangle> faces = detector(cimg);
                  // Find the pose of each face.
                  std::vector<full_object_detection> shapes;
      
      			printf("faces number is %d\n", faces.size());
      
      			for (unsigned long i = 0; i < faces.size(); ++i) {
          
          
      				shapes.push_back(pose_model(cimg, faces[i]));
      
      				//计算人眼纵横比
      				//左眼
      				dlib::point leyeLeft = shapes.at(i).part(37);
      				dlib::point leyeRight = shapes.at(i).part(40);
      
      				dlib::point leyeLeftUp = shapes.at(i).part(38);
      				dlib::point leyeLeftDowm = shapes.at(i).part(42);
      		
      				dlib::point leyeRightUp = shapes.at(i).part(39);
      				dlib::point leyeRightDowm = shapes.at(i).part(41);
      
      				float leyeA = sqrt(pow(leyeLeftUp.x() - leyeLeftDowm.x(), 2) + pow(leyeLeftUp.y() - leyeLeftDowm.y(), 2));
      				float leyeB = sqrt(pow(leyeRightUp.x() - leyeRightDowm.x(), 2) + pow(leyeRightUp.y() - leyeRightDowm.y(), 2));
      				float leyeC = sqrt(pow(leyeLeft.x() - leyeRight.x(), 2) + pow(leyeLeft.y() - leyeRight.y(), 2));
      
      				float leyeEVR = (leyeA + leyeB) / (2 * leyeC);
      
      				//右眼
      				dlib::point reyeLeft = shapes.at(i).part(43);
      				dlib::point reyeRight = shapes.at(i).part(46);
      
      				dlib::point reyeLeftUp = shapes.at(i).part(44);
      				dlib::point reyeLeftDowm = shapes.at(i).part(48);
      
      				dlib::point reyeRightUp = shapes.at(i).part(45);
      				dlib::point reyeRightDowm = shapes.at(i).part(47);
      
      				float reyeA = sqrt(pow(reyeLeftUp.x() - reyeLeftDowm.x(), 2) + pow(reyeLeftUp.y() - reyeLeftDowm.y(), 2));
      				float reyeB = sqrt(pow(reyeRightUp.x() - reyeRightDowm.x(), 2) + pow(reyeRightUp.y() - reyeRightDowm.y(), 2));
      				float reyeC = sqrt(pow(reyeLeft.x() - reyeRight.x(), 2) + pow(reyeLeft.y() - reyeRight.y(), 2));
      
      				float reyeEVR = (reyeA + reyeB) / (2 * reyeC);
      
      				//计算人嘴纵横比
                      dlib::point mouth_left = shapes.at(i).part(49);
      				dlib::point mouth_right = shapes.at(i).part(55);
      
      				dlib::point mouth_leftUp = shapes.at(i).part(51);
      				dlib::point mouth_leftDown = shapes.at(i).part(59);
      
      				dlib::point mouth_rightUp = shapes.at(i).part(53);
      				dlib::point mouth_rightDown = shapes.at(i).part(57);
      
      				float mouthA = sqrt(pow(mouth_leftUp.x() - mouth_leftDown.x(), 2) + pow(mouth_leftUp.y() - mouth_leftDown.y(), 2));
      				float mouthB = sqrt(pow(mouth_rightUp.x() - mouth_rightDown.x(), 2) + pow(mouth_rightUp.y() - mouth_rightDown.y(), 2));
      				float mouthC = sqrt(pow(mouth_left.x() - mouth_right.x(), 2) + pow(mouth_left.y() - mouth_right.y(), 2));
      
      				float mouthEVR = (mouthA + mouthB) / (2 * mouthC);
      
      
      				//摇头
      				//左脸边缘
      				dlib::point face_left = shapes.at(i).part(2);
      				//右脸边缘
      				dlib::point face_right = shapes.at(i).part(16);
      				//鼻子中心
      				dlib::point face_nose = shapes.at(i).part(31);
      
      				//鼻子到左脸边缘距离
      				float lfaceLength = sqrt(pow(face_nose.x() - face_left.x(), 2) + pow(face_nose.y() - face_left.y(), 2));
      				//鼻子到右脸边缘距离
      				float rfaceLength = sqrt(pow(face_nose.x() - face_right.x(), 2) + pow(face_nose.y() - face_right.y(), 2));
      				
      
      				//记录张嘴次数
      				if (mouthEVR < 0.62){
          
          
      					//闭嘴
      					counter_mouth += 1;
      				}else if(mouthEVR > 0.70){
          
          
      					//张嘴
      					if (counter_mouth >= 1) {
          
          
      						mouthIndex += 1;
      					}
      					counter_mouth = 0;
      				}
      				else {
          
          
      					//此区间处于临界区,不稳定,不做检测
      				}
      
      
      				// 显示张嘴次数
      				char mouthBuf[100] = {
          
           0 };
      				sprintf(mouthBuf, "mouth couent : %d", mouthIndex);
      				cv::putText(frame, mouthBuf, cv::Point(0, 20), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 255), 2);
      
      
      				//记录左眼眨眼次数
      				if (leyeEVR > 2.9) {
          
          
      					//闭眼
      					counter_leye += 1;
      				}
      				else {
          
          
      					//睁眼
      					if (counter_leye >= 1) {
          
          
      						leyeIndex += 1;
      					}
      					counter_leye = 0;
      				}
      
      				//记录右眼眨眼次数
      				if (reyeEVR > 5.0) {
          
          
      					//闭眼
      					counter_reye += 1;
      				}
      				else {
          
          
      					//睁眼
      					if (counter_reye >= 1) {
          
          
      						reyeIndex += 1;
      					}
      					counter_reye = 0;
      				}
      
      				//取最小值
      				if (reyeIndex > leyeIndex) {
          
          
      					reyeIndex = leyeIndex;
      				}
      
      				//显示眨眼次数
      				char eyeBuf[100] = {
          
           0 };
      				sprintf(eyeBuf, "eye count: %d", reyeIndex);
      				cv::putText(frame, eyeBuf, cv::Point(0, 45), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 255), 2);
      			}
      			
      
                  // Display it all on the screen
                  win.clear_overlay();
                  win.set_image(cimg);
                  win.add_overlay(render_face_detections(shapes));
              }
          }
          catch(serialization_error& e)
          {
          
          
              cout << endl << e.what() << endl;
          }
          catch(exception& e)
          {
          
          
              cout << e.what() << endl;
          }
      }
    
    
    
  • CMakeLists.txt文件内容
  •   cmake_minimum_required (VERSION 3.5)
      project (faceRecongize)
      
      MESSAGE(STATUS "PROJECT_SOURCE_DIR " ${PROJECT_SOURCE_DIR})
      SET(SRC_LISTS ${PROJECT_SOURCE_DIR}/src/main.cpp)
      
      set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
      # 配置头文件目录
      include_directories(${PROJECT_SOURCE_DIR}/dlib-19.24)
      include_directories(${PROJECT_SOURCE_DIR}/opencv-4.5.0/include)
      include_directories(${PROJECT_SOURCE_DIR}/opencv-4.5.0/modules/core/include)
      include_directories(${PROJECT_SOURCE_DIR}/opencv-4.5.0/modules/calib3d/include)
      include_directories(${PROJECT_SOURCE_DIR}/opencv-4.5.0/modules/features2d/include)
      include_directories(${PROJECT_SOURCE_DIR}/opencv-4.5.0/modules/flann/include)
      include_directories(${PROJECT_SOURCE_DIR}/opencv-4.5.0/modules/dnn/include)
      include_directories(${PROJECT_SOURCE_DIR}/opencv-4.5.0/modules/highgui/include)
      include_directories(${PROJECT_SOURCE_DIR}/opencv-4.5.0/modules/imgcodecs/include)
      include_directories(${PROJECT_SOURCE_DIR}/opencv-4.5.0/modules/videoio/include)
      include_directories(${PROJECT_SOURCE_DIR}/opencv-4.5.0/modules/imgproc/include)
      include_directories(${PROJECT_SOURCE_DIR}/opencv-4.5.0/modules/ml/include)
      include_directories(${PROJECT_SOURCE_DIR}/opencv-4.5.0/modules/objdetect/include)
      include_directories(${PROJECT_SOURCE_DIR}/opencv-4.5.0/modules/photo/include)
      include_directories(${PROJECT_SOURCE_DIR}/opencv-4.5.0/modules/stitching/include)
      include_directories(${PROJECT_SOURCE_DIR}/opencv-4.5.0/modules/video/include)
      
      # 设置不显示命令框
      if(MSVC)
      	#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup")
      endif()
      
      # 添加库文件
      set(PRO_OPENCV_LIB ${PROJECT_SOURCE_DIR}/lib/opencv_video450.lib
      	${PROJECT_SOURCE_DIR}/lib/opencv_core450.lib
      	${PROJECT_SOURCE_DIR}/lib/opencv_videoio450.lib
      	${PROJECT_SOURCE_DIR}/lib/opencv_calib3d450.lib
      	${PROJECT_SOURCE_DIR}/lib/opencv_dnn450.lib
      	${PROJECT_SOURCE_DIR}/lib/opencv_features2d450.lib
      	${PROJECT_SOURCE_DIR}/lib/opencv_flann450.lib
      	${PROJECT_SOURCE_DIR}/lib/opencv_highgui450.lib
      	${PROJECT_SOURCE_DIR}/lib/opencv_gapi450.lib
      	${PROJECT_SOURCE_DIR}/lib/opencv_imgcodecs450.lib
      	${PROJECT_SOURCE_DIR}/lib/opencv_imgproc450.lib
      )
      set(PRO_DLIB_LIB ${PROJECT_SOURCE_DIR}/lib/dlib19.24.0_release_32bit_msvc1900.lib)
      
      # 生成可执行程序
      ADD_EXECUTABLE(faceRecongize ${SRC_LISTS})
      # 链接库文件
      TARGET_LINK_LIBRARIES(faceRecongize ${PRO_OPENCV_LIB} ${PRO_DLIB_LIB})
    

猜你喜欢

转载自blog.csdn.net/new9232/article/details/134095167