VC++实现的基于人眼状态的疲劳驾驶识别系统

Krismile

发布日期: 2018-11-24 17:12:53 浏览量: 2967
评分:
star star star star star star star star star star
*转载请注明来自write-bug.com

一、文档说明

  • 文档主要对项目的程序进行说明和描述程序的思想。

  • 程序的功能

  • 程序的思想

  • 程序的源码

  • 注意之处(程序中比较难理解,比较特殊的地方)

  • 待改进之处(能使得效果更好的地方)

二、程序内容

1. main()函数

程序的功能

首先,利用Adaboost算法检测人脸,紧接着根据人脸的先验知识分割出大致的人眼区域。然后,对人眼大致区域的图像进行图像增强处理(中值滤波、非线性点运算),接着利用Ostu算法计算最佳分割阈值,对图像进行二值化处理。

然后定位人眼的具体位置,具体有以下几个步骤。首先利用直方图积分投影,根据设定的阈值判断并消除眉毛区域。然后分割出左眼和右眼的图像,分别对左右眼的图像计算直方图和直方图积分投影,从而分别确定左右眼的中心位置。

最后,根据定位出的左右眼的中心位置,人为设定人眼矩形框的大小,根据矩形框内的像素特征判断眼睛的睁开闭合状态。有三个特征,眼睛长宽比R,黑色像素占总像素的比例α,以虹膜中心点为中心的1/2中间区域的黑色像素比例β。根据模糊综合评价的思想,将这三个指标划分为相同的4个级别(见下表),然后根据百分比组合成一个函数。最终根据函数值与阈值比较,确定眼睛的睁开、闭合状态。

闭合 可能闭合 可能睁开 睁开 标准 权重
Value 0 2 6 8
R (0, 0.8] (3, 无穷] (0.8, 1.2] (1.2, 1.5] (2.5, 3] (1.5, 2.5] 2.0 0.2
α (0, 0.4] (0.4, 0.5] (0.5, 0.6] (0.6, 1] 0.65 0.4
β (0, 0.3] (0.3, 0.45] (0.45, 0.6] (0.6, 1] 0.55 0.4

为了判定驾驶员是否处于疲劳驾驶状态,需要对很多帧视频进行上述处理,根据PERCLOS原理和制定的判断规则,判断最终状态。

程序的源码

  1. /*************************************************************************
  2. 功能:检测人脸,检测人眼,识别人眼闭合状态,判断是否处于疲劳驾驶状态
  3. 改进:
  4. 1. detectFace()中用了直方图均衡化,到时看有没有必要
  5. 2. 二值化的效果不太理想,到时用实际的驾驶图片测试再看看怎么改进。
  6. 二值化之前一定要做图像增强:非线性点运算或直方图均衡化。
  7. 在OSTU找到的最优阈值基础上减了一个常数,但减太多了,导致整张图片很灰暗的情况下二值化效果很差。
  8. 3. detectFace子函数中有一个budge:返回的objects在子函数外被释放了!
  9. **************************************************************************/
  10. #include <highgui.h>
  11. #include <cv.h>
  12. #include <cxcore.h>
  13. #include "histogram.h"
  14. #include "memory.h"
  15. #include "time.h"
  16. #include "ostuThreshold.h"
  17. #include "detectFace.h"
  18. #include "histProject.h"
  19. #include "linetrans.h"
  20. #include "nonlinetrans.h"
  21. #include "getEyePos.h"
  22. #include "recoEyeState.h"
  23. #include "recoFatigueState.h"
  24. #define DETECTTIME 30 // 一次检测过程的时间长度,用检测次数衡量
  25. #define FATIGUETHRESHOLD 180 // 判断是否疲劳驾驶的阈值
  26. extern CvSeq* objectsTemp = NULL; // 传递objects的值回来main()
  27. int main()
  28. {
  29. /*************** 主程序用到的参数 **************************/
  30. IplImage * srcImg = NULL; // 存放从摄像头读取的每一帧彩色源图像
  31. IplImage * img = NULL; // 存放从摄像头读取的每一帧灰度源图像
  32. CvCapture * capture; // 指向CvCapture结构的指针
  33. CvMemStorage* storage = cvCreateMemStorage(0); // 存放矩形框序列的内存空间
  34. CvSeq* objects = NULL; // 存放检测到人脸的平均矩形框
  35. double scale_factor = 1.2; // 搜索窗口的比例系数
  36. int min_neighbors = 3; // 构成检测目标的相邻矩形的最小个数
  37. int flags = 0; // 操作方式
  38. CvSize min_size = cvSize(40, 40); // 检测窗口的最小尺寸
  39. int i, globalK;
  40. // 绘制人脸框选用的颜色
  41. int hist[256]; // 存放直方图的数组
  42. int pixelSum;
  43. int threshold; // 存储二值化最优阈值
  44. clock_t start, stop; // 计时参数
  45. IplImage* faceImg = NULL; // 存储检测出的人脸图像
  46. int temp = 0; // 临时用到的变量
  47. int temp1 = 0; // 临时用到的变量
  48. int count = 0; // 计数用的变量
  49. int flag = 0; // 标记变量
  50. int * tempPtr = NULL; // 临时指针
  51. CvRect* largestFaceRect; // 存储检测到的最大的人脸矩形框
  52. int * horiProject = NULL; // 水平方向的投影结果(数组指针)
  53. int * vertProject = NULL; // 垂直方向的投影结果(数组指针)
  54. int * subhoriProject = NULL; // 水平方向的投影结果(数组指针)
  55. int * subvertProject = NULL; // 垂直方向的投影结果(数组指针)
  56. int WIDTH; // 图像的宽度
  57. int HEIGHT; // 图像的高度
  58. int rEyeCol = 0; // 右眼所在的列数
  59. int lEyeCol = 0; // 左眼所在的列数
  60. int lEyeRow = 0; // 左眼所在的行数
  61. int rEyeRow = 0; // 右眼所在的行数
  62. int eyeBrowThreshold; // 区分眉毛与眼睛之间的阈值
  63. uchar* rowPtr = NULL; // 指向图片每行的指针
  64. uchar* rowPtrTemp = NULL; // 指向图片每行的指针, 中间变量
  65. IplImage* eyeImg = NULL; // 存储眼睛的图像
  66. CvRect eyeRect; // 存储裁剪后的人眼的矩形区域
  67. CvRect eyeRectTemp; // 临时矩形区域
  68. IplImage* lEyeImg = NULL; // 存储左眼的图像
  69. IplImage* rEyeImg = NULL; // 存储右眼的图像
  70. IplImage* lEyeImgNoEyebrow = NULL; // 存储去除眉毛之后的左眼图像
  71. IplImage* rEyeImgNoEyebrow = NULL; // 存储去除眉毛之后的右眼图像
  72. IplImage* lEyeballImg = NULL; // 存储最终分割的左眼框的图像
  73. IplImage* rEyeballImg = NULL; // 存储最终分割的右眼框的图像
  74. IplImage* lMinEyeballImg = NULL; // 存储最终分割的最小的左眼框的图像
  75. IplImage* rMinEyeballImg = NULL; // 存储最终分割的最小的右眼框的图像
  76. int lMinEyeballBlackPixel; // 存储最终分割的最小的左眼框的白色像素个数
  77. int rMinEyeballBlackPixel; // 存储最终分割的最小的右眼框的白色像素个数
  78. double lMinEyeballBlackPixelRate; // 存储最终分割的最小的左眼框的黑色像素占的比例
  79. double rMinEyeballBlackPixelRate; // 存储最终分割的最小的右眼框的黑色像素占的比例
  80. double lMinEyeballRectShape; // 存储最小左眼眶的矩形长宽比值
  81. double rMinEyeballRectShape; // 存储最小右眼眶的矩形长宽比值
  82. double lMinEyeballBeta; // 存储最小左眼眶的中间1/2区域的黑像素比值
  83. double rMinEyeballBeta; // 存储最小右边眼眶的中间1/2区域的黑像素比值
  84. int lEyeState; // 左眼睁(0)、闭(1)状态
  85. int rEyeState; // 右眼睁(0)、闭(1)状态
  86. int eyeState; // 眼睛综合睁(0)、闭(1)状态
  87. int eyeCloseNum = 0; // 统计一次检测过程中闭眼的总数
  88. int eyeCloseDuration = 0; // 统计一次检测过程中连续检测到闭眼状态的次数
  89. int maxEyeCloseDuration = 0; // 一次检测过程中连续检测到闭眼状态的次数的最大值
  90. int failFaceNum = 0; // 统计一次检测过程中未检测到人脸的总数
  91. int failFaceDuration = 0; // 统计一次检测过程中连续未检测到人脸的次数
  92. int maxFailFaceDuration = 0; // 一次检测过程中连续未检测到人脸的次数的最大值
  93. int fatigueState = 1; // 驾驶员的驾驶状态:疲劳驾驶(1),正常驾驶(0)
  94. /****************** 创建显示窗口 *******************/
  95. cvNamedWindow("img", CV_WINDOW_AUTOSIZE); // 显示灰度源图像
  96. cvNamedWindow("分割后的人脸", 1); // 显示分割出大致眼眶区域的人脸
  97. cvNamedWindow("大致的左眼区域", 1); // 显示大致的左眼区域
  98. cvNamedWindow("大致的右眼区域", 1); // 显示大致的右眼区域
  99. cvNamedWindow("l_binary"); // 显示大致右眼区域的二值化图像
  100. cvNamedWindow("r_binary"); // 显示大致左眼区域的二值化图像
  101. cvNamedWindow("lEyeImgNoEyebrow", 1); // 显示去除眉毛区域的左眼图像
  102. cvNamedWindow("rEyeImgNoEyebrow", 1); // 显示去除眉毛区域的右眼图像
  103. cvNamedWindow("lEyeCenter", 1); // 显示标出虹膜中心的左眼图像
  104. cvNamedWindow("rEyeCenter", 1); // 显示标出虹膜中心的右眼图像
  105. cvNamedWindow("lEyeballImg", 1); // 根据lEyeImgNoEyebrow大小的1/2区域重新划分的左眼图像
  106. cvNamedWindow("rEyeballImg", 1); // 根据rEyeImgNoEyebrow大小的1/2区域重新划分的右眼图像
  107. cvNamedWindow("lkai", 1); // 左眼进行开运算之后的图像
  108. cvNamedWindow("rkai", 1); // 右眼进行开运算之后的图像
  109. cvNamedWindow("lMinEyeballImg", 1); // 缩小至边界区域的左眼虹膜图像
  110. cvNamedWindow("rMinEyeballImg", 1); // 缩小至边界区域的右眼眼虹膜图像
  111. capture = cvCreateCameraCapture(0);
  112. if( capture == NULL )
  113. return -1;
  114. for( globalK = 1; globalK <= DETECTTIME; globalK ++ ){
  115. start = clock();
  116. srcImg = cvQueryFrame(capture);
  117. img = cvCreateImage(cvGetSize(srcImg), IPL_DEPTH_8U, 1);
  118. cvCvtColor(srcImg, img, CV_BGR2GRAY);
  119. if( !img )
  120. continue;
  121. cvShowImage("img", img);
  122. cvWaitKey(20);
  123. /******************** 检测人脸 *************************/
  124. cvClearMemStorage(storage); // 将存储块的 top 置到存储块的头部,既清空存储块中的存储内容
  125. detectFace(
  126. img, // 灰度图像
  127. objects, // 输出参数:检测到人脸的矩形框
  128. storage, // 存储矩形框的内存区域
  129. scale_factor, // 搜索窗口的比例系数
  130. min_neighbors, // 构成检测目标的相邻矩形的最小个数
  131. flags, // 操作方式
  132. cvSize(20, 20) // 检测窗口的最小尺寸
  133. );
  134. // 提取人脸区域
  135. if ( !objectsTemp->total ){
  136. printf("Failed to detect face!\n"); // 调试代码
  137. failFaceNum ++; // 统计未检测到人脸的次数
  138. failFaceDuration ++; // 统计连续未检测到人脸的次数
  139. // 检测过程中判断全是闭眼和检测不到人脸的情况,没有睁开眼的情况,导致maxEyeCloseDuration = 0;
  140. (eyeCloseDuration > maxEyeCloseDuration) ? maxEyeCloseDuration = eyeCloseDuration : maxEyeCloseDuration;
  141. eyeCloseDuration = 0;
  142. if( globalK == DETECTTIME ){
  143. // 当一次检测过程中,所有的过程都检测不到人脸,则要在此更新 maxFailFaceDuration
  144. (failFaceDuration > maxFailFaceDuration) ? maxFailFaceDuration = failFaceDuration : maxFailFaceDuration;
  145. printf("\nFATIGUETHRESHOLD: %d\n", FATIGUETHRESHOLD);
  146. printf("eyeCloseNum: %d\tmaxEyeCloseDuration: %d\n", eyeCloseNum, maxEyeCloseDuration);
  147. printf("failFaceNum: %d\tmaxFailFaceDuration: %d\n", failFaceNum, maxFailFaceDuration);
  148. // 进行疲劳状态的判别
  149. fatigueState = recoFatigueState(FATIGUETHRESHOLD, eyeCloseNum, maxEyeCloseDuration, failFaceNum, maxFailFaceDuration);
  150. if( fatigueState == 1 )
  151. printf("驾驶员处于疲劳驾驶状态\n\n");
  152. else if( fatigueState == 0 )
  153. printf("驾驶员处于正常驾驶状态\n\n");
  154. // 进入下一次检测过程前,将变量清零
  155. globalK = 0;
  156. lEyeState = 1;
  157. rEyeState = 1;
  158. eyeState = 1;
  159. eyeCloseNum = 0;
  160. eyeCloseDuration = 0;
  161. maxEyeCloseDuration = 0;
  162. failFaceNum = 0;
  163. failFaceDuration = 0;
  164. maxFailFaceDuration = 0;
  165. fatigueState = 1;
  166. cvWaitKey(0);
  167. }
  168. continue;
  169. }
  170. else{
  171. // 统计连续未检测到人脸的次数中的最大数值
  172. (failFaceDuration > maxFailFaceDuration) ? maxFailFaceDuration = failFaceDuration : maxFailFaceDuration;
  173. failFaceDuration = 0;
  174. // 找到检测到的最大的人脸矩形区域
  175. temp = 0;
  176. for(i = 0; i < (objectsTemp ? objectsTemp->total : 0); i ++) {
  177. CvRect* rect = (CvRect*) cvGetSeqElem(objectsTemp, i);
  178. if ( (rect->height * rect->width) > temp ){
  179. largestFaceRect = rect;
  180. temp = rect->height * rect->width;
  181. }
  182. }
  183. // 根据人脸的先验知识分割出大致的人眼区域
  184. temp = largestFaceRect->width / 8;
  185. largestFaceRect->x = largestFaceRect->x + temp;
  186. largestFaceRect->width = largestFaceRect->width - 3*temp/2;
  187. largestFaceRect->height = largestFaceRect->height / 2;
  188. largestFaceRect->y = largestFaceRect->y + largestFaceRect->height / 2;
  189. largestFaceRect->height = largestFaceRect->height / 2;
  190. cvSetImageROI(img, *largestFaceRect); // 设置ROI为检测到的最大的人脸区域
  191. faceImg = cvCreateImage(cvSize(largestFaceRect->width, largestFaceRect->height), IPL_DEPTH_8U, 1);
  192. cvCopy(img, faceImg, NULL);
  193. cvResetImageROI(img); // 释放ROI
  194. cvShowImage("分割后的人脸", faceImg);
  195. eyeRectTemp = *largestFaceRect;
  196. // 根据人脸的先验知识分割出大致的左眼区域
  197. largestFaceRect->width /= 2;
  198. cvSetImageROI(img, *largestFaceRect); // 设置ROI为检测到的最大的人脸区域
  199. lEyeImg = cvCreateImage(cvSize(largestFaceRect->width, largestFaceRect->height), IPL_DEPTH_8U, 1);
  200. cvCopy(img, lEyeImg, NULL);
  201. cvResetImageROI(img); // 释放ROI
  202. cvShowImage("大致的左眼区域", lEyeImg);
  203. // 根据人脸的先验知识分割出大致的右眼区域
  204. eyeRectTemp.x += eyeRectTemp.width / 2;
  205. eyeRectTemp.width /= 2;
  206. cvSetImageROI(img, eyeRectTemp); // 设置ROI为检测到的最大的人脸区域
  207. rEyeImg = cvCreateImage(cvSize(eyeRectTemp.width, eyeRectTemp.height), IPL_DEPTH_8U, 1);
  208. cvCopy(img, rEyeImg, NULL);
  209. cvResetImageROI(img); // 释放ROI
  210. cvShowImage("大致的右眼区域", rEyeImg);
  211. /***************** 二值化处理 **********************/
  212. // 图像增强:直方图均衡化在detectFace中实现了一次;可尝试非线性点运算
  213. /*** 二值化左眼大致区域的图像 ***/
  214. //lineTrans(lEyeImg, lEyeImg, 1.5, 0); // 线性点运算
  215. cvSmooth(lEyeImg, lEyeImg, CV_MEDIAN); // 中值滤波 默认窗口大小为3*3
  216. nonlineTrans(lEyeImg, lEyeImg, 0.8); // 非线性点运算
  217. memset(hist, 0, sizeof(hist)); // 初始化直方图的数组为0
  218. histogram(lEyeImg, hist); // 计算图片直方图
  219. // 计算最佳阈值
  220. pixelSum = lEyeImg->width * lEyeImg->height;
  221. threshold = ostuThreshold(hist, pixelSum, 45);
  222. cvThreshold(lEyeImg, lEyeImg, threshold, 255, CV_THRESH_BINARY);// 对图像二值化
  223. // 显示二值化后的图像
  224. cvShowImage("l_binary",lEyeImg);
  225. /*** 二值化右眼大致区域的图像 ***/
  226. //lineTrans(rEyeImg, rEyeImg, 1.5, 0); // 线性点运算
  227. cvSmooth(rEyeImg, rEyeImg, CV_MEDIAN); // 中值滤波 默认窗口大小为3*3
  228. nonlineTrans(rEyeImg, rEyeImg, 0.8); // 非线性点运算
  229. memset(hist, 0, sizeof(hist)); // 初始化直方图的数组为0
  230. histogram(rEyeImg, hist); // 计算图片直方图
  231. // 计算最佳阈值
  232. pixelSum = rEyeImg->width * rEyeImg->height;
  233. threshold = ostuThreshold(hist, pixelSum, 45);
  234. cvThreshold(rEyeImg, rEyeImg, threshold, 255, CV_THRESH_BINARY);// 对图像二值化
  235. // 显示二值化后的图像
  236. cvShowImage("r_binary",rEyeImg);
  237. /********************** 检测人眼 ***********************/
  238. /** 如果有明显的眉毛区域,则分割去除眉毛 **/
  239. // 分割左眼眉毛
  240. HEIGHT = lEyeImg->height;
  241. WIDTH = lEyeImg->width;
  242. // 分配内存
  243. horiProject = (int*)malloc(HEIGHT * sizeof(int));
  244. vertProject = (int*)malloc(WIDTH * sizeof(int));
  245. if( horiProject == NULL || vertProject == NULL ){
  246. printf("Failed to allocate memory\n");
  247. cvWaitKey(0);
  248. return -1;
  249. }
  250. // 内存置零
  251. for(i = 0; i < HEIGHT; i ++)
  252. *(horiProject + i) = 0;
  253. for(i = 0; i < WIDTH; i ++)
  254. *(vertProject + i) = 0;
  255. histProject(lEyeImg, horiProject, vertProject); // 计算直方图投影
  256. lEyeRow = removeEyebrow(horiProject, WIDTH, HEIGHT, 10); // 计算分割眉毛与眼框的位置
  257. // 分割右眼眉毛
  258. HEIGHT = rEyeImg->height;
  259. WIDTH = rEyeImg->width;
  260. // 分配内存
  261. horiProject = (int*)malloc(HEIGHT * sizeof(int));
  262. vertProject = (int*)malloc(WIDTH * sizeof(int));
  263. if( horiProject == NULL || vertProject == NULL ){
  264. printf("Failed to allocate memory\n");
  265. cvWaitKey(0);
  266. return -1;
  267. }
  268. // 内存置零
  269. for(i = 0; i < HEIGHT; i ++)
  270. *(horiProject + i) = 0;
  271. for(i = 0; i < WIDTH; i ++)
  272. *(vertProject + i) = 0;
  273. histProject(rEyeImg, horiProject, vertProject); // 计算直方图投影
  274. rEyeRow = removeEyebrow(horiProject, WIDTH, HEIGHT, 10); // 计算分割眉毛与眼框的位置
  275. // 显示去除眉毛后的人眼大致区域
  276. eyeRect = cvRect(0, lEyeRow, lEyeImg->width, (lEyeImg->height - lEyeRow)); // 去眉毛的眼眶区域在lEyeImg中的矩形框区域
  277. cvSetImageROI(lEyeImg, eyeRect); // 设置ROI为去除眉毛的眼眶,在下面释放ROI
  278. lEyeImgNoEyebrow = cvCreateImage(cvSize(eyeRect.width, eyeRect.height), IPL_DEPTH_8U, 1);
  279. cvCopy(lEyeImg, lEyeImgNoEyebrow, NULL);
  280. cvShowImage("lEyeImgNoEyebrow", lEyeImgNoEyebrow);
  281. eyeRectTemp = cvRect(0, rEyeRow, rEyeImg->width, (rEyeImg->height - rEyeRow)); // 去眉毛的眼眶区域在rEyeImg中的矩形框区域
  282. cvSetImageROI(rEyeImg, eyeRectTemp); // 设置ROI为去除眉毛的眼眶,在下面释放ROI
  283. rEyeImgNoEyebrow = cvCreateImage(cvSize(eyeRectTemp.width, eyeRectTemp.height), IPL_DEPTH_8U, 1);
  284. cvCopy(rEyeImg, rEyeImgNoEyebrow, NULL);
  285. cvShowImage("rEyeImgNoEyebrow", rEyeImgNoEyebrow);
  286. ///////// 定位眼睛中心点在去除眉毛图像中的行列位置 ///////////
  287. HEIGHT = lEyeImgNoEyebrow->height;
  288. WIDTH = lEyeImgNoEyebrow->width;
  289. // 分配内存
  290. subhoriProject = (int*)malloc(HEIGHT * sizeof(int));
  291. subvertProject = (int*)malloc(WIDTH * sizeof(int));
  292. if( subhoriProject == NULL || subvertProject == NULL ){
  293. printf("Failed to allocate memory\n");
  294. cvWaitKey(0);
  295. return -1;
  296. }
  297. // 内存置零
  298. for(i = 0; i < HEIGHT; i ++)
  299. *(subhoriProject + i) = 0;
  300. for(i = 0; i < WIDTH; i ++)
  301. *(subvertProject + i) = 0;
  302. histProject(lEyeImgNoEyebrow, subhoriProject, subvertProject); // 重新对分割出的左眼图像进行积分投影
  303. lEyeRow = getEyePos(subhoriProject, HEIGHT, HEIGHT/5); // 定位左眼所在的行
  304. lEyeCol = getEyePos(subvertProject, WIDTH, WIDTH/5); // 定位左眼所在的列
  305. HEIGHT = rEyeImgNoEyebrow->height;
  306. WIDTH = rEyeImgNoEyebrow->width;
  307. // 分配内存
  308. subhoriProject = (int*)malloc(HEIGHT * sizeof(int));
  309. subvertProject = (int*)malloc(WIDTH * sizeof(int));
  310. if( subhoriProject == NULL || subvertProject == NULL ){
  311. printf("Failed to allocate memory\n");
  312. cvWaitKey(0);
  313. return -1;
  314. }
  315. // 内存置零
  316. for(i = 0; i < HEIGHT; i ++)
  317. *(subhoriProject + i) = 0;
  318. for(i = 0; i < WIDTH; i ++)
  319. *(subvertProject + i) = 0;
  320. histProject(rEyeImgNoEyebrow, subhoriProject, subvertProject); // 重新对分割出的右眼图像进行积分投影
  321. rEyeRow = getEyePos(subhoriProject, HEIGHT, HEIGHT/5); // 定位右眼所在的行
  322. rEyeCol = getEyePos(subvertProject, WIDTH, WIDTH/5); // 定位右眼所在的列
  323. // 标记眼睛的位置
  324. cvCircle(lEyeImgNoEyebrow, cvPoint(lEyeCol, lEyeRow), 3, CV_RGB(0,0,255), 1, 8, 0);
  325. cvCircle(rEyeImgNoEyebrow, cvPoint(rEyeCol, rEyeRow), 3, CV_RGB(0,0,255), 1, 8, 0);
  326. cvShowImage("lEyeCenter", lEyeImgNoEyebrow);
  327. cvShowImage("rEyeCenter", rEyeImgNoEyebrow);
  328. /****************** 判断人眼睁闭状态 *************************/
  329. ///////// 分割出以找到的中心为中心的大致眼眶 /////////////
  330. // 左眼眶
  331. HEIGHT = lEyeImgNoEyebrow->height;
  332. WIDTH = lEyeImgNoEyebrow->width;
  333. // 计算大致眼眶的区域: eyeRect
  334. eyeRect = cvRect(0, 0, WIDTH, HEIGHT);
  335. calEyeSocketRegion(&eyeRect, WIDTH, HEIGHT, lEyeCol, lEyeRow);
  336. cvSetImageROI(lEyeImgNoEyebrow, eyeRect); // 设置ROI为检测到眼眶区域
  337. lEyeballImg = cvCreateImage(cvGetSize(lEyeImgNoEyebrow), IPL_DEPTH_8U, 1);
  338. cvCopy(lEyeImgNoEyebrow, lEyeballImg, NULL);
  339. cvResetImageROI(lEyeImgNoEyebrow);
  340. cvShowImage("lEyeballImg", lEyeballImg);
  341. // 右眼眶
  342. HEIGHT = rEyeImgNoEyebrow->height;
  343. WIDTH = rEyeImgNoEyebrow->width;
  344. // 计算大致眼眶的区域: eyeRectTemp
  345. eyeRect = cvRect(0, 0, WIDTH, HEIGHT);
  346. calEyeSocketRegion(&eyeRect, WIDTH, HEIGHT, rEyeCol, rEyeRow);
  347. cvSetImageROI(rEyeImgNoEyebrow, eyeRect); // 设置ROI为检测到眼眶区域
  348. rEyeballImg = cvCreateImage(cvGetSize(rEyeImgNoEyebrow), IPL_DEPTH_8U, 1);
  349. cvCopy(rEyeImgNoEyebrow, rEyeballImg, NULL);
  350. cvResetImageROI(rEyeImgNoEyebrow);
  351. cvShowImage("rEyeballImg", rEyeballImg);
  352. /////////////////////////// 闭运算 ///////////////////////////
  353. cvErode(lEyeballImg, lEyeballImg, NULL, 2); //腐蚀图像
  354. cvDilate(lEyeballImg, lEyeballImg, NULL, 2); //膨胀图像
  355. cvShowImage("lkai", lEyeballImg);
  356. cvErode(rEyeballImg, rEyeballImg, NULL, 1); //腐蚀图像
  357. cvDilate(rEyeballImg, rEyeballImg, NULL, 1); //膨胀图像
  358. cvShowImage("rkai", rEyeballImg);
  359. /////////////////// 计算最小眼睛的矩形区域 ////////////////////
  360. ///////////////////////////左眼
  361. HEIGHT = lEyeballImg->height;
  362. WIDTH = lEyeballImg->width;
  363. // 分配内存
  364. subhoriProject = (int*)malloc(HEIGHT * sizeof(int));
  365. subvertProject = (int*)malloc(WIDTH * sizeof(int));
  366. if( subhoriProject == NULL || subvertProject == NULL ){
  367. printf("Failed to allocate memory\n");
  368. cvWaitKey(0);
  369. return -1;
  370. }
  371. // 内存置零
  372. for(i = 0; i < HEIGHT; i ++)
  373. *(subhoriProject + i) = 0;
  374. for(i = 0; i < WIDTH; i ++)
  375. *(subvertProject + i) = 0;
  376. histProject(lEyeballImg, subhoriProject, subvertProject);
  377. // 计算左眼最小的矩形区域
  378. eyeRectTemp = cvRect(0, 0 , 1, 1); // 初始化
  379. getEyeMinRect(&eyeRectTemp, subhoriProject, subvertProject, WIDTH, HEIGHT, 5, 3);
  380. // 计算最小左眼矩形的长宽比, 判断眼睛状态时用的到
  381. lMinEyeballRectShape = (double)eyeRectTemp.width / (double)eyeRectTemp.height;
  382. cvSetImageROI(lEyeballImg, eyeRectTemp); // 设置ROI为检测到最小面积的眼眶
  383. lMinEyeballImg = cvCreateImage(cvGetSize(lEyeballImg), IPL_DEPTH_8U, 1);
  384. cvCopy(lEyeballImg, lMinEyeballImg, NULL);
  385. cvResetImageROI(lEyeballImg);
  386. cvShowImage("lMinEyeballImg", lMinEyeballImg);
  387. //////////////////////// 统计左眼黑像素个数 /////////////////////
  388. HEIGHT = lMinEyeballImg->height;
  389. WIDTH = lMinEyeballImg->width;
  390. // 分配内存
  391. subhoriProject = (int*)malloc(HEIGHT * sizeof(int));
  392. subvertProject = (int*)malloc(WIDTH * sizeof(int));
  393. if( subhoriProject == NULL || subvertProject == NULL ){
  394. printf("Failed to allocate memory\n");
  395. cvWaitKey(0);
  396. return -1;
  397. }
  398. // 内存置零
  399. for(i = 0; i < HEIGHT; i ++)
  400. *(subhoriProject + i) = 0;
  401. for(i = 0; i < WIDTH; i ++)
  402. *(subvertProject + i) = 0;
  403. histProject(lMinEyeballImg, subhoriProject, subvertProject);
  404. // 统计lEyeballImg中黑色像素的个数
  405. temp = 0; // 白像素个数
  406. for( i = 0; i < WIDTH; i ++ )
  407. temp += *(subvertProject + i);
  408. temp /= 255;
  409. lMinEyeballBlackPixel = WIDTH * HEIGHT - temp;
  410. lMinEyeballBlackPixelRate = (double)lMinEyeballBlackPixel / (double)(WIDTH * HEIGHT);
  411. // 统计lMinEyeballImg中的1/2区域内黑像素的比例
  412. lMinEyeballBeta = 0;
  413. lMinEyeballBeta = calMiddleAreaBlackPixRate(subvertProject, &eyeRectTemp, WIDTH, HEIGHT, lEyeCol, lMinEyeballBlackPixel);
  414. ////////////////////////////////////右眼
  415. HEIGHT = rEyeballImg->height;
  416. WIDTH = rEyeballImg->width;
  417. // 分配内存
  418. subhoriProject = (int*)malloc(HEIGHT * sizeof(int));
  419. subvertProject = (int*)malloc(WIDTH * sizeof(int));
  420. if( subhoriProject == NULL || subvertProject == NULL ){
  421. printf("Failed to allocate memory\n");
  422. cvWaitKey(0);
  423. return -1;
  424. }
  425. // 内存置零
  426. for(i = 0; i < HEIGHT; i ++)
  427. *(subhoriProject + i) = 0;
  428. for(i = 0; i < WIDTH; i ++)
  429. *(subvertProject + i) = 0;
  430. histProject(rEyeballImg, subhoriProject, subvertProject);
  431. // 计算右眼最小的矩形区域
  432. eyeRectTemp = cvRect(0, 0 , 1, 1);
  433. getEyeMinRect(&eyeRectTemp, subhoriProject, subvertProject, WIDTH, HEIGHT, 5, 3);
  434. // 计算最小右眼矩形的长宽比,判断眼睛状态时用的到
  435. rMinEyeballRectShape = (double)eyeRectTemp.width / (double)eyeRectTemp.height;
  436. cvSetImageROI(rEyeballImg, eyeRectTemp); // 设置ROI为检测到最小面积的眼眶
  437. rMinEyeballImg = cvCreateImage(cvGetSize(rEyeballImg), IPL_DEPTH_8U, 1);
  438. cvCopy(rEyeballImg, rMinEyeballImg, NULL);
  439. cvResetImageROI(rEyeballImg);
  440. cvShowImage("rMinEyeballImg", rMinEyeballImg);
  441. //////////////////////// 统计右眼黑像素个数 /////////////////////
  442. HEIGHT = rMinEyeballImg->height;
  443. WIDTH = rMinEyeballImg->width;
  444. // 分配内存
  445. subhoriProject = (int*)malloc(HEIGHT * sizeof(int));
  446. subvertProject = (int*)malloc(WIDTH * sizeof(int));
  447. if( subhoriProject == NULL || subvertProject == NULL ){
  448. printf("Failed to allocate memory\n");
  449. cvWaitKey(0);
  450. return -1;
  451. }
  452. // 内存置零
  453. for(i = 0; i < HEIGHT; i ++)
  454. *(subhoriProject + i) = 0;
  455. for(i = 0; i < WIDTH; i ++)
  456. *(subvertProject + i) = 0;
  457. histProject(rMinEyeballImg, subhoriProject, subvertProject);// 计算直方图积分投影
  458. // 统计lEyeballImg中黑色像素的个数
  459. temp = 0;
  460. for( i = 0; i < WIDTH; i ++ )
  461. temp += *(subvertProject + i);
  462. temp /= 255;
  463. rMinEyeballBlackPixel = WIDTH * HEIGHT - temp;
  464. rMinEyeballBlackPixelRate = (double)rMinEyeballBlackPixel / (double)(WIDTH * HEIGHT);
  465. // 统计lMinEyeballImg中的1/2区域内黑像素的比例
  466. rMinEyeballBeta = 0;
  467. rMinEyeballBeta = calMiddleAreaBlackPixRate(subvertProject, &eyeRectTemp, WIDTH, HEIGHT, rEyeCol, rMinEyeballBlackPixel);
  468. // 判断眼睛睁闭情况
  469. lEyeState = 1; // 左眼状态,默认闭眼
  470. rEyeState = 1; // 右眼状态,默认闭眼
  471. eyeState = 1; // 眼睛综合状态,默认闭眼
  472. if( lMinEyeballBlackPixel > 50)
  473. lEyeState = getEyeState(lMinEyeballRectShape, lMinEyeballBlackPixelRate, lMinEyeballBeta);
  474. else
  475. lEyeState = 1;
  476. if( rMinEyeballBlackPixel > 50)
  477. rEyeState = getEyeState(rMinEyeballRectShape, rMinEyeballBlackPixelRate, rMinEyeballBeta);
  478. else
  479. rEyeState = 1;
  480. (lEyeState + rEyeState) == 2 ? eyeState = 1 : eyeState=0;
  481. // 统计眼睛闭合的次数
  482. if( eyeState == 1 ){
  483. eyeCloseNum ++; // 统计 eyeCloseNum 眼睛闭合次数
  484. eyeCloseDuration ++;
  485. if( globalK == DETECTTIME){
  486. // 检测过程中判断全是闭眼情况,没有睁眼和检测不到人脸的情况
  487. (eyeCloseDuration > maxEyeCloseDuration) ? maxEyeCloseDuration = eyeCloseDuration : maxEyeCloseDuration;
  488. eyeCloseDuration = 0;
  489. }
  490. }
  491. else{
  492. (eyeCloseDuration > maxEyeCloseDuration) ? maxEyeCloseDuration = eyeCloseDuration : maxEyeCloseDuration;
  493. eyeCloseDuration = 0;
  494. }
  495. } // 承接判断是否检测到人脸的if语句
  496. // 计时:执行一次循环的时间
  497. stop = clock();
  498. //printf("run time: %f\n", (double)(stop - start) / CLOCKS_PER_SEC);
  499. printf("eyeState: %d\n", eyeState);
  500. // 调整循环变量,进入下一次检测过程
  501. if( globalK == DETECTTIME ){
  502. printf("\nFATIGUETHRESHOLD*****: %d\n", FATIGUETHRESHOLD);
  503. printf("eyeCloseNum: %d\tmaxEyeCloseDuration: %d\n", eyeCloseNum, maxEyeCloseDuration);
  504. printf("failFaceNum: %d\tmaxFailFaceDuration: %d\n", failFaceNum, maxFailFaceDuration);
  505. // 进行疲劳状态的判别
  506. fatigueState = recoFatigueState(FATIGUETHRESHOLD, eyeCloseNum, maxEyeCloseDuration, failFaceNum, maxFailFaceDuration);
  507. if( fatigueState == 1 )
  508. printf("驾驶员处于疲劳驾驶状态\n\n");
  509. else if( fatigueState == 0 )
  510. printf("驾驶员处于正常驾驶状态\n\n");
  511. // 进入下一次检测过程前,将变量清零
  512. globalK = 0;
  513. lEyeState = 1;
  514. rEyeState = 1;
  515. eyeState = 1;
  516. eyeCloseNum = 0;
  517. eyeCloseDuration = 0;
  518. maxEyeCloseDuration = 0;
  519. failFaceNum = 0;
  520. failFaceDuration = 0;
  521. maxFailFaceDuration = 0;
  522. fatigueState = 1;
  523. char c = cvWaitKey(0);
  524. if( c == 27 )
  525. break;
  526. else
  527. continue;
  528. }
  529. } // 承接检测过程的 for 循环
  530. // 释放内存
  531. cvDestroyWindow("分割后的人脸");
  532. cvDestroyWindow("大致的左眼区域");
  533. cvDestroyWindow("大致的右眼区域");
  534. cvDestroyWindow("l_binary");
  535. cvDestroyWindow("r_binary");
  536. cvDestroyWindow("lEyeImgNoEyebrow");
  537. cvDestroyWindow("rEyeImgNoEyebrow");
  538. cvDestroyWindow("lEyeCenter");
  539. cvDestroyWindow("rEyeCenter");
  540. cvDestroyWindow("lEyeballImg");
  541. cvDestroyWindow("rEyeballImg");
  542. cvDestroyWindow("lkai");
  543. cvDestroyWindow("rkai");
  544. cvDestroyWindow("lMinEyeballImg");
  545. cvDestroyWindow("rMinEyeballImg");
  546. cvReleaseMemStorage(&storage);
  547. cvReleaseImage(&eyeImg);
  548. free(horiProject);
  549. free(vertProject);
  550. free(subhoriProject);
  551. free(subvertProject);
  552. return 0;
  553. }

注意之处

  • 最佳识别效果的图像大小:500x550,太小了识别效果骤减
  • 为了传递人脸检测的序列结果到主函数中,设定了一个外部变量CvSeq *objectTemp
  • 主函数涉及到多个自定义的阈值:根据先验知识分割人眼区域,Ostu阈值减去常数CONST,区分眉毛与眼睛的阈值eyeBrowThreshold,判断眼睛具体位置时用到的中间区域,判断眼睛状态的getEyeState()中的阈值

待改进之处

  • 程序中多次用到了图像增强的算法,理清楚程序的结构,看能不能优化

  • detectFace中有直方图均衡化的代码,看是否需要进行均衡化处理?直方图均衡化对增强比较暗的图像效果很明显

  • 二值化效果有待改进,尤其是CONST的值的确定!直方图均衡化对增强比较暗的图像效果很明显

  • 理清楚主函数中内存的使用情况,尤其是指针变量

  • 自定义的阈值要根据汽车室内的监控图像质量的大小进行最后的调试

2. detectFace()

程序的功能

根据Adaboost算法检测出图片中的人脸。

源码

  1. /**************************************************
  2. 功能:检测图片中的人脸区域
  3. 输入:
  4. IplImage* srcImg, // 灰度图像
  5. CvMemStorage* storage, // 存储矩形框的内存区域
  6. double scale_factor = 1.1, // 搜索窗口的比例系数
  7. int min_neighbors = 3, // 构成检测目标的相邻矩形的最小个数
  8. int flags = 0, // 操作方式
  9. CvSize min_size = cvSize(20, 20) // 检测窗口的最小尺寸
  10. 输出参数:
  11. CvSeq* objects // 检测到人脸的矩形框
  12. 说明:1. 识别的准确率和速度关键在于cvHaarDetectObject()函数的参数的调整
  13. 2. 如果实际用于汽车内检测效果不佳时,可考虑自己搜集汽车室内图片然后训练分类器
  14. 3. 实际用于疲劳驾驶检测时,由于人脸位于图片的中央而且占的面积很大,可以将min_size和scale_factor调大一些,加快速度
  15. 4. 内含直方图均衡化
  16. **************************************************/
  17. #include "cv.h"
  18. #include "stdlib.h"
  19. #include "highgui.h"
  20. extern CvSeq* objectsTemp; // 传递objects的值会main()
  21. void detectFace(
  22. IplImage* srcImg, // 灰度图像
  23. CvSeq* objects, // 输出参数:检测到人脸的矩形框
  24. CvMemStorage* storage, // 存储矩形框的内存区域
  25. double scale_factor = 1.1, // 搜索窗口的比例系数
  26. int min_neighbors = 3, // 构成检测目标的相邻矩形的最小个数
  27. int flags = 0, // 操作方式
  28. CvSize min_size = cvSize(20, 20) // 检测窗口的最小尺寸
  29. )
  30. {
  31. // 程序用到的参数
  32. const char* cascadeName = "haarcascade_frontalface_alt2.xml"; // 级联分类器的xml文件名
  33. // 读取级联分类器xml文件
  34. CvHaarClassifierCascade* cascade = (CvHaarClassifierCascade*)cvLoad(cascadeName, 0, 0, 0);
  35. if( !cascade ) {
  36. fprintf( stderr, "ERROR: Could not load classifier cascade\n" );
  37. cvWaitKey(0);
  38. exit(-1);
  39. }
  40. // 检测人脸
  41. cvClearMemStorage(storage);
  42. objects = cvHaarDetectObjects(
  43. srcImg,
  44. cascade,
  45. storage,
  46. scale_factor,
  47. min_neighbors,
  48. flags, /*CV_HAAR_DO_CANNY_PRUNING*/
  49. min_size
  50. );
  51. objectsTemp = objects; // 为了将objects的值传递回main函数
  52. // 释放cascade的内存
  53. cvReleaseHaarClassifierCascade(&cascade);
  54. }

改进之处

  • detectFace()中有直方图均衡化的代码,看是否需要进行均衡化处理

  • 识别的准确率和速度关键在于cvHaarDetectObject()函数的参数的调整

  • 如果实际用于汽车内检测效果不佳时,可考虑自己搜集汽车室内图片然后训练分类器

  • 实际用于疲劳驾驶检测时,由于人脸位于图片的中央而且占的面积很大,可以将min_size和scale_factor调大一些,加快速度,但要保证准确率

  • 可实现并行运算

3. ostuThreshold()函数

程序功能

用Ostu最大类间距方差法计算二值化阈值,然后减去自定义常数CONST。

程序思想

由于用ostu计算得出的阈值进行二值化时效果不理想,因此考虑减去一个固定值来补偿。

源码

  1. /******************************************************
  2. 功能:用Ostu最大类间方差法计算二值化阈值
  3. 输入:
  4. hist:图像的直方图数组
  5. pixelSum:图像的像素总和
  6. CONST: 一个常数;为了适应各种特殊的要求,可实现在找到的最优分割阈值的基础上减去该常数
  7. 输出:
  8. threshold:最优阈值
  9. Date: 2014.08.14
  10. ******************************************************/
  11. #pragma once
  12. #include <stdio.h>
  13. int ostuThreshold(int * hist, int pixelSum, const int CONST)
  14. {
  15. float pixelPro[256];
  16. int i, j, threshold = 0;
  17. //计算每个像素在整幅图像中的比例
  18. for(i = 0; i < 256; i++){
  19. *(pixelPro+i) = (float)(*(hist+i)) / (float)(pixelSum);
  20. }
  21. //经典ostu算法,得到前景和背景的分割
  22. //遍历灰度级[0,255],计算出方差最大的灰度值,为最佳阈值
  23. float w0, w1, u0tmp, u1tmp, u0, u1, u,deltaTmp, deltaMax = 0;
  24. for(i = 0; i < 256; i++){
  25. w0 = w1 = u0tmp = u1tmp = u0 = u1 = u = deltaTmp = 0;
  26. for(j = 0; j < 256; j++){
  27. if(j <= i){ //背景部分
  28. //以i为阈值分类,第一类总的概率
  29. w0 += *(pixelPro+j);
  30. u0tmp += j * (*(pixelPro+j));
  31. }
  32. else //前景部分
  33. {
  34. //以i为阈值分类,第二类总的概率
  35. w1 += *(pixelPro+j);
  36. u1tmp += j * (*(pixelPro+j));
  37. }
  38. }
  39. u0 = u0tmp / w0; //第一类的平均灰度
  40. u1 = u1tmp / w1; //第二类的平均灰度
  41. u = u0tmp + u1tmp; //整幅图像的平均灰度
  42. //计算类间方差
  43. deltaTmp = w0 * (u0 - u)*(u0 - u) + w1 * (u1 - u)*(u1 - u);
  44. //找出最大类间方差以及对应的阈值
  45. if(deltaTmp > deltaMax){
  46. deltaMax = deltaTmp;
  47. threshold = i;
  48. }
  49. }
  50. printf("Ostu Threshold: %d\n", threshold);
  51. printf("real Threshold: %d\n", threshold - CONST);
  52. //返回最佳阈值;
  53. return (threshold - CONST);
  54. }

注意之处

  • 进行二值化处理之前,先进行了cvSmooth中值滤波处理、nonlineTrans非线性处理

改进之处

  • 由于ostu计算得出的阈值不太符合要求,因此可以尝试其他的阈值选取方法

  • 寻找动态确定CONST常数的方法,以适应更多不同情况。考虑原图很暗,ostu计算出来的阈值本来就很低,结果还被减去CONST导致阈值太低的情况!还有,由于图像太暗,导致二值化后黑色像素过多的情况

  • 可实现并行运算

4. histProject()函数

程序功能

计算直方图在水平方向和垂直方向的积分投影。

程序思想

按行累加实现水平方向的积分投影;按列累加实现垂直方向的积分投影。在一次遍历像素点的过程中实现水平和垂直方向的积分投影。

源码

  1. /**************************************************
  2. 功能:计算图像直方图在水平方向和垂直方向的投影
  3. 输入:
  4. srcImg:源图像
  5. 输出:
  6. horiProj: 水平方向的投影结果;1 * height数组的指针,输入前记得初始化
  7. vertProj:垂直方向的投影结果;1 * width数组的指针,输入前记得初始化
  8. **************************************************/
  9. #include "cv.h"
  10. void histProject(IplImage * srcImg, int* horiProj, int* vertProj)
  11. {
  12. // 程序用到的参数
  13. int i, j;
  14. uchar* ptr = NULL; // 指向图像当前行首地址的指针
  15. uchar* temp = NULL;
  16. int HEIGHT = srcImg->height;
  17. int WIDTH = srcImg->width;
  18. for(i = 0; i < HEIGHT; i ++){
  19. ptr = (uchar*) (srcImg->imageData + i * srcImg->widthStep);
  20. for(j = 0; j < WIDTH; j ++){
  21. temp = ptr + j; // 减少计算量
  22. *(horiProj + i) += *temp; // 计算水平方向的投影
  23. *(vertProj + j) += *temp; // 计算垂直方向的投影
  24. }
  25. }
  26. }

注意之处

  • 传递给histProject的图像必须是灰度图像

  • 因为涉及到累加运算,所以horiProject和vertProject指针一定要初始化为0

改进之处

  • 传递给histProject的图像必须是灰度图像

  • 可实现并行运算

5. getEyePos()函数

程序功能

找出数列中限定区域内的最低点的位置,即找到人眼的位置。

程序思想

先对直方图积分投影结果进行升序排序,然后找出最小值并且判断是否在设定的中间区域内,如果在则输出index索引值,否则对下一个最小值进行相同判断,直到找到第一个符合条件的最小值,然后返回该最小值的索引index。

源码

  1. #include <cv.h>
  2. #include <stdlib.h>
  3. typedef struct
  4. {
  5. int data;
  6. int index;
  7. }projectArr;
  8. // qsort的函数参数
  9. int cmpInc( const void *a ,const void *b)
  10. {
  11. return (*(projectArr *)a).data - (*(projectArr *)b).data;
  12. }
  13. int getEyePos(int* project, int size, int region)
  14. {
  15. // 参数
  16. projectArr* projectStruct = NULL;
  17. projectArr* projectTemp = NULL;
  18. int i, j, pos, sizeTemp, temp;
  19. // 分配projectStruct内存空间
  20. projectStruct = (projectArr*)malloc(size * sizeof(projectArr));
  21. projectTemp = (projectArr*)malloc(sizeof(projectArr));
  22. // 初始化内存空间
  23. for(i = 0; i < size; i ++){
  24. (projectStruct + i)->data = *(project + i);
  25. (projectStruct + i)->index = i;
  26. }
  27. // 对project从小到大快速排序
  28. //qsort(projectStruct, size, sizeof(*project), cmpInc);
  29. for(i = 0; i <= size - 2; i ++){
  30. for( j = 0; j < size - i - 1; j ++ ){
  31. if( (projectStruct + j)->data > (projectStruct + j + 1)->data ){
  32. *projectTemp = *(projectStruct + j);
  33. *(projectStruct + j) = *(projectStruct + j + 1);
  34. *(projectStruct + j + 1) = *projectTemp;
  35. }
  36. }
  37. }
  38. // 寻找中间区域的最小值及其位置
  39. sizeTemp = size / 2;
  40. temp = 0;
  41. for( i = 0; i < size; i ++ ){
  42. temp = (projectStruct+i)->index;
  43. if( (temp > sizeTemp - region) && (temp < sizeTemp + region) ){
  44. pos = (projectStruct + i)->index;
  45. // 防止指针越界访问位置元素出现负数
  46. if( pos < 0)
  47. return -1;
  48. break;
  49. }
  50. else{
  51. // 防止整个数列不存在符合条件的元素
  52. if( i == size - 1 )
  53. return -1;
  54. }
  55. }
  56. free(projectTemp);
  57. return pos;
  58. }

注意之处

  • projectStruct指针的内存释放有问题

  • 升序排序的方法用的是冒泡排序

  • 定义了外部变量结构体projectArr

改进之处

  • 用快速排序对数列进行排序,可加快速度

  • 考虑投影值相同但是index不同的情况的处理办法,因为很多时候不能很准确找到中心点就是这个原因

  • 考虑加入左右眼二值化图像的参数,消除头发或者背景等大片黑块对中心点确定的影响

6. removeEyebrow()函数

程序功能

搜索积分投影图的最低点,从而消除眉毛。

程序思想

找到眉毛与眼睛分割的点,然后去除分割点上方的部分,从而消除眉毛。在找分割点时,以3行像素的和为单位进行逐个逐个比较,找到最小的单位。然后以该单位为搜索起点,搜索第一个最高点,然后以该最高点为分割点,即图中箭头位置,去除分割点上方的部分。

源码

  1. /************************************************************
  2. 功能:搜索积分投影图中的最低点,从而消除眉毛的函数
  3. 输入:
  4. int* horiProject: 数列的指针
  5. int width: 数列的宽度
  6. int height: 数列的高度
  7. int threshold:分割眉毛的阈值,最多
  8. 输出:
  9. 返回找到的最低点行位置,结果为int类型,即眉毛与眼睛的分割线
  10. 说明:
  11. 1. 消除眉毛时可以调整eyeBrowThreshold来调整去除的效果
  12. 2. 同时可以调整连续大于阈值的次数count来调整效果。
  13. ************************************************************/
  14. int removeEyebrow(int* horiProject, int width, int height, int threshold)
  15. {
  16. // 参数
  17. int temp, temp1, count, flag, i;
  18. int eyeRow;
  19. int eyeBrowThreshold;
  20. // 定位人眼位置
  21. eyeBrowThreshold = (width - threshold) * 255; // 为了防止无法区分眼睛和眉毛的情况,可适当降低阈值
  22. // 消除眉毛区域
  23. temp = 100000000;
  24. temp1 = 0;
  25. count = 0;
  26. flag = 0; // 表示当前搜索的位置在第一个最低谷之前
  27. eyeRow = 0;
  28. for(i = 0; i < height; i = i + 3){
  29. count ++;
  30. temp1 = *(horiProject + i) + *(horiProject + i + 1) + *(horiProject + i + 2);
  31. if( (temp1 < temp) & (flag == 0) ){
  32. temp = temp1;
  33. eyeRow = i;
  34. count = 0;
  35. }
  36. if (count >= 3 || i >= height - 2){
  37. flag = 1;
  38. break;
  39. }
  40. }
  41. // 搜索第一个大于眼睛与眉毛分割阈值的点
  42. count = 0;
  43. for( i = eyeRow; i < height; i ++ ){
  44. if( *(horiProject + i) > eyeBrowThreshold){
  45. eyeRow = i;
  46. count ++;
  47. if( count >= 3 ){ // count: 统计共有多少连续的行的投影值大于阈值;
  48. eyeRow = i;
  49. break;
  50. }
  51. }
  52. else
  53. count = 0;
  54. }
  55. // 防止没有眉毛错删眼睛的情况,可根据实验结果调整参数!
  56. if( eyeRow >= height / 2 )
  57. eyeRow = 0;
  58. return eyeRow;
  59. }

注意之处

  • 消除眉毛时可以调整eyeBrowThreshold来调整去除的效果

  • 同时可以调整连续大于阈值的次数count来调整效果

  • 调整单位的像素行数,可以一定程度提高判断的准确率,但是单位太大的话不利于处理比较小的图像

改进之处

  • 有时间的话可以考虑重新设置函数的变量,使函数更易于阅读

  • 根据实际的图像调整参数,使得结果更准确

7. calEyeSocketRegion()函数

程序功能

特定功能函数:根据人眼的中心大致计算眼眶的区域。

程序思想

以人眼中心为中心,向外扩展直到扩展后的区域为原图区域的1/2大小。超出边界的情况要特殊处理。

源码

  1. /************************************************************
  2. 功能:特定功能函数:根据人眼的中心大致计算眼眶的区域
  3. 输入:
  4. CvRect* eyeRect: 眼眶矩形区域的指针
  5. int width: 数列的宽度
  6. int height: 数列的高度
  7. int EyeCol:虹膜中心所在的列位置
  8. int EyeRow:虹膜中心所在的行位置
  9. 输出:
  10. 以指针的方式返回眼眶的大致区域,eyeRect
  11. 说明:
  12. ************************************************************/
  13. void calEyeSocketRegion(CvRect* eyeRect, int width, int height, int EyeCol, int EyeRow)
  14. {
  15. // 参数
  16. int temp, temp1;
  17. temp = EyeCol - width / 4;
  18. temp1 = EyeRow - height / 4;
  19. if( (temp < 0) && (temp1 < 0) ){
  20. eyeRect->x = 0;
  21. eyeRect->width = width / 2 + temp;
  22. eyeRect->y = 0;
  23. eyeRect->height = height / 2 + temp1;
  24. }
  25. else if( (temp < 0) && (temp1 > 0) ){
  26. eyeRect->x = 0;
  27. eyeRect->width = width / 2 + temp;
  28. eyeRect->y = temp1;
  29. eyeRect->height = height / 2;
  30. }
  31. else if( (temp > 0) && (temp1 < 0) ){
  32. eyeRect->x = temp;
  33. eyeRect->width = width / 2;
  34. eyeRect->y = 0;
  35. eyeRect->height = height / 2 + temp1;
  36. }
  37. else if( (temp > 0) && (temp1 > 0) ){
  38. eyeRect->x = temp;
  39. eyeRect->width = width / 2;
  40. eyeRect->y = temp1;
  41. eyeRect->height = height / 2;
  42. }
  43. }

改进之处

  • 有时间的话可以考虑重新设置函数的变量,使函数更易于阅读

  • 根据实际的图像看是否需要调整当前比例

8. gerEyeMinRect()函数

程序功能

消除眼睛区域周边的白色区域,计算人眼最小的矩形区域。

程序思想

从上下左右想中心搜索,如果搜索到有黑色像素的行或者列则停止搜索,并记录该处位置,从而得到最小的人眼区域。

源码

  1. /************************************************************
  2. 功能:特定功能函数:计算人眼最小的矩形区域
  3. 输入:
  4. CvRect* eyeRect: 人眼最小的矩形区域的指针
  5. int* horiProject
  6. int* vertProject
  7. int width: 数列的宽度
  8. int height: 数列的高度
  9. int horiThreshold:水平方向的阈值
  10. int vertThreshold:垂直方向的阈值
  11. 输出:
  12. 通过指针返回CvRect* eyeRect: 人眼最小的矩形区域的指针
  13. ************************************************************/
  14. void getEyeMinRect(CvRect* eyeRect, int* horiProject, int* vertProject, int width, int height, int horiThreshold=5, int vertThreshold=3)
  15. {
  16. // 参数
  17. int temp, temp1, i;
  18. temp1 = (width - horiThreshold) * 255;
  19. for(i = 0; i < height; i ++){
  20. if( *(horiProject + i) < temp1 ){
  21. eyeRect->y = i;
  22. break;
  23. }
  24. }
  25. temp = i; // 记录eyeRectTemp.y的位置
  26. printf("eyeRectTemp->y: %d\n", eyeRect->y);
  27. if( temp != height ){
  28. // temp != HEIGHT: 防止没有符合*(subhoriProject + i) < temp1条件的位置;如果temp != HEIGHT则一定有满足条件的位置存在
  29. for(i = height-1; i >= 0; i --){
  30. if( *(horiProject + i) < temp1 ){
  31. temp = i;
  32. break;
  33. }
  34. }
  35. if( temp == eyeRect->y )
  36. eyeRect->height = 1;
  37. else
  38. eyeRect->height = temp - eyeRect->y;
  39. }
  40. else{
  41. eyeRect->height = 1;
  42. }
  43. printf("eyeRectTemp.height: %d\n", eyeRect->height);
  44. temp1 = (height - vertThreshold) * 255;
  45. for( i = 0; i < width; i ++ ){
  46. if( *(vertProject + i) < temp1 ){
  47. eyeRect->x = i;
  48. break;
  49. }
  50. }
  51. temp = i; // 记录eyeRectTemp.x的位置
  52. printf("eyeRectTemp.x: %d\n", eyeRect->x);
  53. if( temp != width ){
  54. for(i = width-1; i >= 0; i --){
  55. if( *(vertProject + i) < temp1 ){
  56. temp = i;
  57. break;
  58. }
  59. }
  60. // 防止宽度为0,显示图像时出错!
  61. if( temp == eyeRect->x )
  62. eyeRect->width = 1;
  63. else
  64. eyeRect->width = temp - eyeRect->x;
  65. }
  66. else{
  67. eyeRect->width = 1;
  68. }
  69. printf("eyeRectTemp.width: %d\n", eyeRect->width);
  70. }

注意之处

  • 内涵调试用的输出语句,转化为硬件代码时记得删除调试语句

改进之处

  • 有时间的话可以考虑重新设置函数的变量,使函数更易于阅读

9. lineTrans()函数

程序功能

对图像进行线性点运算,实现图像增强效果

程序思想

遍历像素点,对每个像素点根据线性方程重新计算像素值。

源码

  1. /********************************************************
  2. 功能:对图像进行线性点运算,实现图像增强
  3. 输入:
  4. IplImage* srcImg: 源灰度图像
  5. float a:乘系数a
  6. float b:常系数b
  7. 输出:
  8. IplImage* dstImg:输出经过线性变换后的图像
  9. ********************************************************/
  10. #include "cv.h"
  11. #include "highgui.h"
  12. void lineTrans(IplImage* srcImg, IplImage* dstImg, float a, float b)
  13. {
  14. int i, j;
  15. uchar* ptr = NULL; // 指向图像当前行首地址的指针
  16. uchar* pixel = NULL; // 指向像素点的指针
  17. float temp;
  18. dstImg = cvCreateImage(cvGetSize(srcImg), IPL_DEPTH_8U, 1);
  19. cvCopy(srcImg, dstImg, NULL);
  20. int HEIGHT = dstImg->height;
  21. int WIDTH = dstImg->width;
  22. for(i = 0; i < HEIGHT; i ++){
  23. ptr = (uchar*) (srcImg->imageData + i * srcImg->widthStep);
  24. for(j = 0; j < WIDTH; j ++){
  25. pixel = ptr + j;
  26. // 线性变换
  27. temp = a * (*pixel) + b;
  28. // 判断范围
  29. if ( temp > 255 )
  30. *pixel = 255;
  31. else if (temp < 0)
  32. *pixel = 0;
  33. else
  34. *pixel = (uchar)(temp + 0.5);// 四舍五入
  35. }
  36. }
  37. }

改进之处

  • 转到硬件时可以用查表的方式实现相同的效果

  • 可实现并行运算

10. nonlineTrans()函数

程序功能

对图像进行非线性点运算,实现图像增强效果。

程序思想

遍历像素点,对每个像素点根据非线性方程重新计算像素值。

源码

  1. /********************************************************
  2. 功能:对图像进行线性点运算,实现图像增强
  3. 输入:
  4. IplImage* srcImg: 源灰度图像
  5. float a:乘系数a
  6. 输出:
  7. IplImage* dstImg:输出经过线性变换后的图像
  8. ********************************************************/
  9. #include "cv.h"
  10. #include "highgui.h"
  11. #include "cv.h"
  12. void nonlineTrans(IplImage* srcImg, IplImage* dstImg, float a)
  13. {
  14. int i, j;
  15. uchar* ptr = NULL; // 指向图像当前行首地址的指针
  16. uchar* pixel = NULL; // 指向像素点的指针
  17. float temp;
  18. dstImg = cvCreateImage(cvGetSize(srcImg), IPL_DEPTH_8U, 1);
  19. cvCopy(srcImg, dstImg, NULL);
  20. int HEIGHT = dstImg->height;
  21. int WIDTH = dstImg->width;
  22. for(i = 0; i < HEIGHT; i ++){
  23. ptr = (uchar*) (srcImg->imageData + i * srcImg->widthStep);
  24. for(j = 0; j < WIDTH; j ++){
  25. pixel = ptr + j;
  26. // 非线性变换
  27. temp = *pixel + (a * (*pixel) * (255 - *pixel)) / 255;
  28. // 判断范围
  29. if ( temp > 255 )
  30. *pixel = 255;
  31. else if (temp < 0)
  32. *pixel = 0;
  33. else
  34. *pixel = (uchar)(temp + 0.5);// 四舍五入
  35. }
  36. }
  37. }

改进之处

  • 转到硬件时可以用查表的方式实现相同的效果

  • 可实现并行运算

11. recoEyeState()函数

程序功能

通过模糊综合评价的思想对指标进行分级,然后组合成一个函数,通过计算当前眼睛的函数值与阈值比较,从而判断眼睛的状态。

程序思想

根据最终提取出的人眼图像判断眼睛的睁开、闭合情况,可转化为判断评价问题,即根据现有的人眼数据,判断眼睛的状态。由于3个评价的指标评判眼睛状态的界限不太清晰,因此可通过模糊评价的方法对不同范围的指标划分等级,然后再将三个指标加权组合在一起。

源码

  1. /****************************** 判断眼睛状态 *************************
  2. 功能:通过模糊综合评价的思想判断眼睛的状态
  3. 输入:
  4. double MinEyeballRectShape:眼睛矩形区域的长宽比
  5. double MinEyeballBlackPixelRate:眼睛矩形区域黑像素点所占的比例
  6. double MinEyeballBeta:眼睛中心1/2区域黑色像素点占总黑像素点的比例
  7. 输出:
  8. 返回人眼睁开闭合的状态0:睁开,1:闭合
  9. 说明:
  10. 1. 三个输入参数的阈值是自己设定的
  11. 2. 输出的结果参数的阈值需要调整
  12. 3. 为了转硬件方便,加快运算速度,将浮点运算转为了整数运算。
  13. *******************************************************************/
  14. #include <stdlib.h>
  15. int getEyeState(double MinEyeballRectShape, double MinEyeballBlackPixelRate, double MinEyeballBeta)
  16. {
  17. int eyeState;
  18. int funcResult;
  19. int shapeFuzzyLv, pixelFuzzyLv, betaFuzzyLv; // 三个参数对应的模糊级别的值
  20. // 判定眼睛矩形区域的长宽比的模糊级别
  21. shapeFuzzyLv = 0;
  22. if( (MinEyeballRectShape >= 0) && (MinEyeballRectShape <= 0.8) )
  23. shapeFuzzyLv = 0;
  24. else if( MinEyeballRectShape <= 1.2 )
  25. shapeFuzzyLv = 2;
  26. else if( MinEyeballRectShape <= 1.5 )
  27. shapeFuzzyLv = 6;
  28. else if( MinEyeballRectShape <= 2.5 )
  29. shapeFuzzyLv = 8;
  30. else if( MinEyeballRectShape <= 3 )
  31. shapeFuzzyLv = 6;
  32. else
  33. shapeFuzzyLv = 0;
  34. // 判定眼睛矩形区域黑像素点所占比例的模糊级别
  35. pixelFuzzyLv = 0;
  36. if( (MinEyeballBlackPixelRate >= 0) && (MinEyeballBlackPixelRate <= 0.4) )
  37. pixelFuzzyLv = 0;
  38. else if( MinEyeballBlackPixelRate <= 0.50 )
  39. pixelFuzzyLv = 2;
  40. else if( MinEyeballBlackPixelRate <= 0.60 )
  41. pixelFuzzyLv = 6;
  42. else if( MinEyeballBlackPixelRate <= 1 )
  43. pixelFuzzyLv = 8;
  44. // 判定眼睛中心1/2区域黑色像素点占总黑像素点的比例的模糊级别
  45. betaFuzzyLv = 0;
  46. if( (MinEyeballBeta >= 0) && (MinEyeballBeta <= 0.3) )
  47. betaFuzzyLv = 0;
  48. else if( MinEyeballBeta <= 0.45 )
  49. betaFuzzyLv = 2;
  50. else if( MinEyeballBeta <= 0.6 )
  51. betaFuzzyLv = 6;
  52. else if( MinEyeballBeta <= 1 )
  53. betaFuzzyLv = 8;
  54. // 模糊评价函数
  55. eyeState = 1; // 默认是闭眼的
  56. funcResult = 2 * shapeFuzzyLv + 4 * pixelFuzzyLv + 4 * betaFuzzyLv;
  57. if( funcResult >= 58 )
  58. eyeState = 0;
  59. return eyeState;
  60. }

注意之处

  • 三个输入参数的阈值和模糊评价函数阈值都是自己设定的

  • 为了转硬件方便,加快运算速度,将浮点运算转为了整数运算,即将百分数扩大了十倍

改进之处

  • 使用更客观的方法确定加权系数和等级分数

  • 可根据实际的图像,调整相应的参数与阈值

12. recoFatigueState()函数

程序功能

在一次检测过程完成后,根据闭眼总次数、连续闭眼最大值、未检测到人脸的总次数、连续未检测到人脸的最大值这四个因素,判断是否处于疲劳驾驶状态!

程序思想

利用logistic方程分别构造四个因素对疲劳程度判断的函数方程,然后利用查表的方式计算出每个因素的贡献值,最后根据贡献值总和与阈值的比较得出结论。

源码

  1. /*************************************************
  2. 功能:特定功能函数——根据眼睛闭合状态和是否检测到人脸
  3. 判断驾驶状态:正常?疲劳?
  4. 输入:
  5. int eyeCloseNum:检测过程中眼睛闭状态的总次数
  6. int maxEyeCloseDuration:检测过程中眼睛连续闭合的最大次数
  7. int failFaceNum:检测过程中未检测到人脸的总次数
  8. int maxFailFaceDuration:检测过程中连续未检测到人脸的最大次数
  9. **************************************************/
  10. #include <stdio.h>
  11. int eyeCloseNumTab[] = {2,2,4,6,9,14,20,29,39,50,61,72,80,86,91,94,96,98,98,99,99,100,100,100,100,100,100,100,100,100, 100};
  12. int eyeCloseDurationTab[] = {2, 4, 9, 18, 32, 50, 68, 82, 91, 95, 98, 99, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100};
  13. int failFaceDurationTab[] = {2, 6, 14, 29, 50, 71, 86, 94, 98, 99, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100};
  14. int recoFatigueState(int thresh, int eyeCloseNum, int maxEyeCloseDuration, int failFaceNum, int maxFailFaceDuration)
  15. {
  16. int eyeCloseValue; // 眼睛闭合次数的贡献值
  17. int eyeCloseDurationValue; // 眼睛连续闭合次数的贡献值
  18. int failFaceValue; // 未检测到人脸的总次数的贡献值
  19. int failFaceDurationValue; // 连续未检测到人脸的贡献值
  20. int compreValue; // 综合贡献值
  21. // 查表计算四个指标的贡献值
  22. eyeCloseValue = eyeCloseNumTab[eyeCloseNum];
  23. eyeCloseDurationValue = eyeCloseDurationTab[maxEyeCloseDuration];
  24. failFaceValue = eyeCloseNumTab[failFaceNum];
  25. failFaceDurationValue = failFaceDurationTab[maxFailFaceDuration];
  26. // 综合贡献值
  27. compreValue = eyeCloseValue + eyeCloseDurationValue + failFaceValue + failFaceDurationValue;
  28. printf("\neyeCloseValue: %d\n", eyeCloseValue);
  29. printf("eyeCloseDurationValue: %d\n", eyeCloseDurationValue);
  30. printf("failFaceValue: %d\n", failFaceValue);
  31. printf("failFaceDurationValue: %d\n", failFaceDurationValue);
  32. printf("compreValue: %d\n\n", compreValue);
  33. return (compreValue >= thresh) ? 1 : 0;
  34. }

注意之处

  • 判断按是否处于疲劳驾驶状态的阈值 FATIGUETHRESHOLD 是自己设定的

改进之处

  • 让每个因素的贡献值函数更加适合、精确

  • 根据实验确定更精确的阈值

三、项目的限制

  • 基本只能使用于白天光线较好的时候,夜晚无法使用

  • 戴眼镜的情况无法使用

  • 低头情况下,人脸检测的效果很差

四、项目改进方向

  • 调试参数:使用类似级联滤波器的调试方法,即逐级调试,使得每一级的输出效果都是最佳的!

  • 将所有阈值定义为常量

  • 变量太多,有些变量可重复使用的,但是为了方便阅读,定了更多变量,所以转硬件的时候可以最大程度的利用变量,较少变量数量。另外,功能类似的变量可以考虑用结构体整合到一起!

  • 低头时人脸检测的准确率很低

  • 人眼状态识别时,闭眼的情况识别不准确,很多时候将闭眼识别为睁开状态,可以考虑自己一个睁眼和闭眼的模板数列,然后比较人眼积分投影数列与模板数列的相似度。

  • 从二值化时候就分开左右眼进行处理能适应更多特殊情况,比如左右脸亮度相差太大的情况!

  • 可转化为函数的部分:

    • 消除眉毛的部分,放到getEyePos模块中

    • 判断人眼睁闭状态中计算以人眼中心为中心的大致眼眶的模块,放到getEyePos模块中

    • 计算最小眼睛的矩形区域中的确定最小眼睛区域eyeRectTemp的模块,放到getEyePos模块中

    • 统计lMinEyeballImg中的1/2区域内黑像素的比例的模块,放到recoEyeState模块中

  • 模糊综合评价的模型可已选择突出主要因素的模型,指标的分数和权重可考虑用更客观的方式确定。

  • 对投影曲线进行递推滤波(消除毛刺影响)

    • 对于很暗的情况先灰度均衡化,然后非线性运算,用查表方式

    • 在缩小至最小眼球之前用中值滤波或者形态学处理,消除独立小黑块的影响

  • 对疲劳状态的判断:用数据分析的方法对采集的多组数据不断的进行分析,看数据是否有明显的上升趋势,从而判断驾驶员是否处于疲劳驾驶状态。另外,还可以考虑才采用概率论的假设检验的方法判断是否处于疲劳驾驶状态

  • 特殊情况

人眼区域的边界有大片黑块,造成人眼中心定位不准确,如何去除边界大块区域?

左右脸光照不均匀的情况二值化效果严重不准确

疲劳状态检测的特殊情况

1.检测过程中判断全是闭眼和检测不到人脸的情况,没有睁开眼的情况,导致maxEyeCloseDuration = 0;

2.眨眼与闭眼的频率很相近,即一次眨眼一次闭眼的情况,使得疲劳判断结果为正常!

3.当判断为全1的时候,程序运行出现内存读取错误!

分析:原因不明,但是肯定和lEyeballImg 和 EyeCloseDuration有关。重点查看EyeCloseDuration一直增加不跳出的时候,lEyeballImg处的程序如何运行。

上传的附件 cloud_download 基于人眼状态的疲劳驾驶识别系统.7z ( 10.39mb, 34次下载 )
error_outline 下载需要15点积分

发送私信

真正的强者不是要压倒一切,而是不被一切压倒

16
文章数
12
评论数
最近文章
eject