【OpenCV+Dlib】C++基于眼睛宽高比的实时眨眼检测(原理+完整源码)

基于眼睛宽高比的眨眼检测

在开始之前,电脑需要配置Opencv和Dlib
OpenCV配置流程请参考这篇博客:博客链接
Dlib配置过程请参考这篇博客:博客链接
Dlib有一个十分强大的功能就是能够定位人脸的68个关键点,其关键点的分布如下:

68个关键点检测图
基于眼睛宽高比的方法即运用眼睛宽高比的变化来判断是否眨眼,以左眼为例:
以x_36表示点36(在上面的图中,应该是点37,实际上在程序中,关键点是从0到67,上图中用的1到68)的横坐标,y_36表示点36(上图中的点37)的纵坐标,以此类推
眼睛的宽度为定位点36-39的距离记为L=x_39 - x_36
眼睛的高度为定位点37-41的距离记为h1=y_41-y_37
以及定位点38-40的距离记为h2=y_40-y_38
取h1和h2的平均距离作为眼睛的高度h,即H=(h1+h2)/2
EAR为眼睛宽高比的比值,则有:
EAR=L / H
我们用0.2代表睁眼(闭眼)的阈值,即:
EAR>0.2 睁眼
EAR<0.2 闭眼
眨眼的条件为:EAR从大于0.2到小于0.2到大于0.2的过程算做为一次眨眼
正常人的眨眼频率为10-15次/min

运行结果如下:
其中勾勒的波形图中瞬间下降非常显著的部分(箭头标注)为眨眼

PS:68个关键点检测要用到的dlib检测模型放到我的百度网盘了网盘链接
密码: 5j6g

若链接失效请留言
在这里插入图片描述

源码如下:

#include <dlib\opencv.h>
#include <opencv2\opencv.hpp>
#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 <iostream>
#include <vector>
#include <cmath>
#include<time.h>

using namespace std;
using namespace dlib;
using namespace cv;

int main() {
	
	
	
	//画坐标轴
	Mat Eye_Waveform = Mat::zeros(900, 900, CV_8UC3);    //用于记录眨眼的波形图
	Point p1 = Point(10, 0);
	Point p2 = Point(10, 900);
	Point p3 = Point(0, 890);
	Point p4 = Point(900, 890);
	Scalar line_color = Scalar(255, 255, 255);
	cv::line(Eye_Waveform, p1, p2, line_color, 1, LINE_AA);
	cv::line(Eye_Waveform, p3, p4, line_color, 1, LINE_AA);
	


	//存储眼睛的上一个点的坐标
	int eye_previous_x = 10;			//原点横坐标
	int eye_previous_y = 890;		//原点纵坐标
	int eye_now_x = 1;
	int eye_now_y = 1;


	//存储眨眼的次数
	unsigned int count_blink = 0;			//眨眼次数
	

	//每次眨眼EAR都要经历从  大于0.2-小于0.2-大于0.2 的过程
	float blink_EAR_before =0.0;		//眨眼前
	float blink_EAR_now =0.2;			//眨眼中
	float blink_EAR_after = 0.0;			//眨眼后


	try {
		VideoCapture cap(0);
		if (!cap.isOpened()) {   //打开摄像头
			printf("Unable to connect a camera");
			return 1;
		}
		frontal_face_detector detector = get_frontal_face_detector();		

		shape_predictor pos_modle;
		
		//下面用到的shape_predictor_68_face_landmarks/shape_predictor_68_face_landmarks.dat  文件已经放到网盘里,上边有链接
		deserialize("D:/shape_predictor_68_face_landmarks/shape_predictor_68_face_landmarks.dat") >> pos_modle;

		while (waitKey(30) != 27) {
			Mat temp;
			cap >> temp;

			//将图像转化为dlib中的BGR的形式
			cv_image<bgr_pixel> cimg(temp);

			std::vector<dlib::rectangle> faces = detector(cimg);
			std::vector<full_object_detection> shapes;
			unsigned int faceNumber = faces.size();   //获取容器中向量的个数即人脸的个数
			for (unsigned int i = 0; i < faceNumber; i++) {
				shapes.push_back(pos_modle(cimg, faces[i]));
			}
			if (!shapes.empty()) {
				int faceNumber = shapes.size();
				for (int j = 0; j < faceNumber; j++)
				{
					for (int i = 0; i < 68; i++)
					{
						//用来画特征值的点
						cv::circle(temp, cvPoint(shapes[j].part(i).x(), shapes[j].part(i).y()), 1, cv::Scalar(0, 0, 255), -1);
						//参数说明 图像 圆心 线条宽度 颜色 线的类型
						//显示数字
						cv::putText(temp, to_string(i), cvPoint(shapes[0].part(i).x(), shapes[0].part(i).y()), cv::FONT_HERSHEY_PLAIN, 1, cv::Scalar(0, 0, 255));

					}
				}

				//左眼

					//点36的坐标 
				unsigned int x_36 = shapes[0].part(36).x();
				unsigned int y_36 = shapes[0].part(36).y();

				//点37的坐标
				unsigned int x_37 = shapes[0].part(37).x();
				unsigned int y_37 = shapes[0].part(37).y();

				//点38的坐标
				unsigned int x_38 = shapes[0].part(38).x();
				unsigned int y_38 = shapes[0].part(38).y();

				//点39的坐标
				unsigned int x_39 = shapes[0].part(39).x();
				unsigned int y_39 = shapes[0].part(39).y();

				//点40的坐标
				unsigned int x_40 = shapes[0].part(40).x();
				unsigned int y_40 = shapes[0].part(40).y();

				//点41的坐标
				unsigned int x_41 = shapes[0].part(41).x();
				unsigned int y_41 = shapes[0].part(41).y();

				/*
				//把左眼的轮廓画出来,为了证明眼睛上的取点没有取错
				Point pts[1][6];
				pts[0][0] = Point(x_36, y_36);
				pts[0][1] = Point(x_37, y_37);
				pts[0][2] = Point(x_38, y_38);
				pts[0][3] = Point(x_39, y_39);
				pts[0][4] = Point(x_40, y_40);
				pts[0][5] = Point(x_41, y_41);

				const Point* ppts[] = { pts[0] };
				int npt[] = { 6 };
				Scalar eye_color = Scalar(0, 0, 255);
				fillPoly(temp, ppts, npt, 1, eye_color, LINE_AA);  //在眼睛上勾勒多边形

				*/



				int height_left_eye1 = y_41 - y_37;			//37到41的纵向距离
				//cout << "左眼高度1\t" << height_left_eye1 << endl;
				int height_left_eye2 = y_40 - y_38;			//38到40的纵向距离
				//cout << "左眼高度2\t" << height_left_eye2 << endl;
				float height_left_eye = (height_left_eye1 + height_left_eye2) / 2;		//眼睛上下距离
				//cout << "左眼高度\t" << height_left_eye << endl;
				int length_left_eye = x_39 - x_36;
				//cout << "左眼长度\t" << length_left_eye << endl;
				if (height_left_eye == 0)  //当眼睛闭合的时候,距离可能检测为0,宽高比出错
					height_left_eye = 1;

				float EAR_left_eye;			//眼睛宽高比
				EAR_left_eye = height_left_eye / length_left_eye;

				/*
				//在屏幕上显示眼睛的高度及宽高比

				cout << "左眼宽高比" << EAR_left_eye << endl;

				显示height_left_eye、length_left_eye以及ERA_left_eye

				把hight_left_eye从float类型转化成字符串类型
				char height_left_eye_text[30];
				char length_left_eye_text[30];
				char ERA_left_eye_text[30];

				_gcvt_s(height_left_eye_text, height_left_eye, 10);   //把hight_left_eye从float类型转化成字符串类型
				_gcvt_s(length_left_eye_text, length_left_eye,10);
				_gcvt_s(ERA_left_eye_text, EAR_left_eye, 10);

				putText(temp, height_left_eye_text, Point(10, 100), FONT_HERSHEY_COMPLEX, 1.0, Scalar(12, 255, 200), 1, LINE_AA);
				putText(temp,height_left_eye_text, Point(10, 200), FONT_HERSHEY_COMPLEX, 1.0, Scalar(12, 255, 200), 1, LINE_AA);
				putText(temp, height_left_eye_text, Point(10, 300), FONT_HERSHEY_COMPLEX, 1.0, Scalar(12, 255, 200), 1, LINE_AA);
				*/

				//右眼

				//点42的坐标 
				unsigned int x_42 = shapes[0].part(42).x();
				unsigned int y_42 = shapes[0].part(42).y();

				//点37的坐标
				unsigned int x_43 = shapes[0].part(43).x();
				unsigned int y_43 = shapes[0].part(43).y();

				//点38的坐标
				unsigned int x_44 = shapes[0].part(44).x();
				unsigned int y_44 = shapes[0].part(44).y();

				//点39的坐标
				unsigned int x_45 = shapes[0].part(45).x();
				unsigned int y_45 = shapes[0].part(45).y();

				//点40的坐标
				unsigned int x_46 = shapes[0].part(46).x();
				unsigned int y_46 = shapes[0].part(46).y();

				//点41的坐标
				unsigned int x_47 = shapes[0].part(47).x();
				unsigned int y_47 = shapes[0].part(47).y();

				unsigned int height_right_eye1 = y_47 - y_43;			//37到41的纵向距离
				unsigned int height_right_eye2 = y_46 - y_44;			//38到40的纵向距离
				float height_right_eye = (height_right_eye1 + height_right_eye2) / 2;		//眼睛上下距离
				if (height_right_eye == 0)  //当眼睛闭合的时候,距离可能检测为0,宽高比出错
					height_right_eye = 1;

				unsigned int length_right_eye = x_45 - x_42;

				float EAR_right_eye;			//眼睛宽高比
				EAR_right_eye = height_right_eye / length_right_eye;

				//取两只眼睛的平均宽高比作为眼睛的宽高比
				float EAR_eyes = (EAR_left_eye + EAR_right_eye) / 2;

				//cout << "眼睛的宽高比为" << EAR_eyes << endl;
				
				//画眼睛的波形图
				eye_now_x = eye_now_x + 1;			//横坐标(每10个像素描一个点)
				eye_now_y = 900 - ( EAR_eyes * 900 );		//纵坐标
				Point poi1 = Point(eye_previous_x, eye_previous_y);		//上一个点
				Point poi2 = Point(eye_now_x, eye_now_y);				//现在的点
				Scalar eyes_color = Scalar(0, 255, 0);
				cv::line(Eye_Waveform, poi1, poi2, eyes_color,1, LINE_AA);			//画线
				eye_previous_x = eye_now_x;
				eye_previous_y = eye_now_y;
				namedWindow("Blink waveform figure", WINDOW_AUTOSIZE);

				//计算眨眼次数
				if (blink_EAR_before < EAR_eyes) {
					blink_EAR_before = EAR_eyes;
				}
				if (blink_EAR_now > EAR_eyes) {
					blink_EAR_now = EAR_eyes;
				}
				if (blink_EAR_after < EAR_eyes) {
					blink_EAR_after = EAR_eyes;
				} 
				if (blink_EAR_before > 0.2 && blink_EAR_now <= 0.2 && blink_EAR_after > 0.2) {
					count_blink = count_blink + 1;
					blink_EAR_before = 0.0;
					blink_EAR_now = 0.2;
					blink_EAR_after = 0.0;
				}

				//显示height_left_eye、length_left_eye以及ERA_left_eye

				//把hight_left_eye从float类型转化成字符串类型
				char count_blink_text[30];

				_gcvt_s(count_blink_text, count_blink, 10);   //把hight_left_eye从float类型转化成字符串类型

				putText(temp, count_blink_text, Point(10, 100), FONT_HERSHEY_COMPLEX, 1.0, Scalar(0, 0, 255), 1, LINE_AA);


			}

			//Display it all on the screen  展示每一帧的图片
			cv::imshow("Dlib标记", temp);
			cv::imshow("Blink waveform figure", Eye_Waveform);


			//计时一分钟(60秒)
			clock_t start = clock();
			clock_t finish = clock();
			double consumeTime = (double)(finish - start);//注意转换为double的位置
				if (count_blink >= 25 ) {
						if (consumeTime / 1000 < 60) {
						cout << "您已疲劳,请休息!!" << endl;
						count_blink = 0;
						return 0;
				}
			}

		}

	}
	catch (serialization_error& e) {
		cout << "You need dlib‘s default face landmarking file to run this example." << endl;
		cout << endl << e.what() << endl;
	}
	catch (exception& e) {
		cout << e.what() << endl;

	}
	
}
  • 10
    点赞
  • 69
    收藏
    觉得还不错? 一键收藏
  • 27
    评论
【项目介绍】 该资源内项目源码是个人的毕设,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到94.5分,放心下载使用! 该资源适合计算机相关专业(如人工智能、通信工程、自动化、软件工程等)的在校学生、老师或者企业员工下载,适合小白学习或者实际项目借鉴参考! 当然也可作为毕业设计、课程设计、课程作业、项目初期立项演示等。如果基础还行,可以在此代码基础之上做改动以实现更多功能。 人脸识别考勤系统 本项目是一个基于 Python 的人脸识别考勤系统,主要使用了 `dlib`、`opencv`、`tkinter`、`PIL`、`pymysql` 等库,并使用了 KNN 算法进行人脸识别。 环境安装 在运行本项目之前,请确保已经安装了以下环境和库: - Python - PyCharm - dlib(需要 Cmake、Visual Basic、C++ 依赖环境) - opencv 库 - tkinter 库 - pymysql - PIL - threading - time 功能介绍 本项目具有以下功能: - 打开系统按钮:打开摄像头捕获人脸。 - 注册按钮:将人脸、学号、姓名信息存储到数据库中。 - 查看缺勤名单:将没有识别签到的学生学号和姓名导出到打开的名单界面。 - 是否开始训练:点击“是”按钮进行人脸模型的训练。 - 帮助按钮:查看使用软件的注意事项。 注意事项 在使用本项目之前,请注意以下事项: 1. 请先打开系统。 2. 注册时请对准人脸,光线充足。 3. 训练时禁止进行其他操作。 文件说明 本项目包含以下文件: - `recognition.py`:主文件。 - `db.py`:数据库文件。 - `help_tip.py`:帮助窗口文件。 - `precamera.py`:预处理文件。 - `late_name.py`:缺勤名单文件。 - `trained_knn_model.clf`:KNN 算法模型文件。 - `knn_examples` 文件夹:包含了训练集和测试集,仅供学习交流探讨使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 27
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值