本篇博客主要是对前段时间数字图像课程大作业-疲劳检测所做工作的一次总结整理。主要涉及到的内容有1、基于图片的人脸、人眼检测;2、利用OPENCV实现本地视频与图片帧之间的相互转换;3、基于本地视频的人脸、人眼检测;4、操作笔记本摄像头,实现人脸、人眼检测。
源代码及haar检测器下载见http://download.csdn.net/detail/u011345885/9444335
1、基于图片的人脸、人眼检测
原理: OpenCV利用样本的Haar特征进行分类器训练,得到级联boosted分类器(CascadeClassification),可以检测图片中的眼睛(还支持的有人脸、嘴、鼻子、身体)。
具体操作步骤: 1、将分类器.xml文件放到源程序工程下与你的代码文件处于同一文件(这样在代码中定义路径时,可以免去其他硬盘路径,当然也可以放在任何位置,自己知道位置即可,在代码中做相应路径修改即可)。这些分类器.xml文件在opencv安装包文件下即可找到,以我的电脑为例:opencv-2.4.8->opencv->sources->data->haarcascades;也可以从此处下载:
然后就可以加载分类器进行,特征检测具体代码如下:
// face_detect.cpp : 定义控制台应用程序的入口点。
//
//#include "stdafx.h"
#include "opencv2/objdetect/objdetect.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/ml/ml.hpp"
#include <iostream>
#include <stdio.h>
using namespace std;
using namespace cv;
void detectAndDraw(Mat& img,
CascadeClassifier& cascade, CascadeClassifier& nestedCascade,
double scale);
String cascadeName = "./haarcascade_frontalface_alt2.xml";//人脸的训练数据
//String nestedCascadeName = "./haarcascade_eye_tree_eyeglasses.xml";//人眼的训练数据
String nestedCascadeName = "./haarcascade_eye.xml";//人眼的训练数据
int main(int argc, const char** argv)
{
Mat image;
CascadeClassifier cascade, nestedCascade;//创建级联分类器对象
double scale = 1.3;
//image = imread( "lena.jpg", 1 );//读入lena图片
image = imread("2.jpg", 1);
namedWindow("result", 1);//opencv2.0以后用namedWindow函数会自动销毁窗口
if (!cascade.load(cascadeName))//从指定的文件目录中加载级联分类器
{
cerr << "ERROR: Could not load classifier cascade唉唉出错了" << endl;
return 0;
}
if (!nestedCascade.load(nestedCascadeName))
{
cerr << "WARNING: Could not load classifier cascade for nested objects" << endl;
return 0;
}
if (!image.empty())//读取图片数据不能为空
{
detectAndDraw(image, cascade, nestedCascade, scale);
waitKey(0);
}
return 0;
}
void detectAndDraw(Mat& img,
CascadeClassifier& cascade, CascadeClassifier& nestedCascade,
double scale)
{
int i = 0;
double t = 0;
vector<Rect> faces;
const static Scalar colors[] = { CV_RGB(0, 0, 255),
CV_RGB(0, 128, 255),
CV_RGB(0, 255, 255),
CV_RGB(0, 255, 0),
CV_RGB(255, 128, 0),
CV_RGB(255, 255, 0),
CV_RGB(255, 0, 0),
CV_RGB(255, 0, 255) };//用不同的颜色表示不同的人脸
Mat gray, smallImg(cvRound(img.rows / scale), cvRound(img.cols / scale), CV_8UC1);//将图片缩小,加快检测速度
cvtColor(img, gray, CV_BGR2GRAY);//因为用的是类haar特征,所以都是基于灰度图像的,这里要转换成灰度图像
resize(gray, smallImg, smallImg.size(), 0, 0, INTER_LINEAR);//将尺寸缩小到1/scale,用线性插值
equalizeHist(smallImg, smallImg);//直方图均衡
t = (double)cvGetTickCount();//用来计算算法执行时间
//检测人脸
//detectMultiScale函数中smallImg表示的是要检测的输入图像为smallImg,faces表示检测到的人脸目标序列,1.1表示
//每次图像尺寸减小的比例为1.1,2表示每一个目标至少要被检测到3次才算是真的目标(因为周围的像素和不同的窗口大
//小都可以检测到人脸),CV_HAAR_SCALE_IMAGE表示不是缩放分类器来检测,而是缩放图像,Size(30, 30)为目标的
//最小最大尺寸
cascade.detectMultiScale(smallImg, faces,
1.1, 2, 0
//|CV_HAAR_FIND_BIGGEST_OBJECT
//|CV_HAAR_DO_ROUGH_SEARCH
| CV_HAAR_SCALE_IMAGE
,
Size(30, 30));
t = (double)cvGetTickCount() - t;//相减为算法执行的时间
printf("detection time = %g ms\n", t / ((double)cvGetTickFrequency()*1000.));
for (vector<Rect>::const_iterator r = faces.begin(); r != faces.end(); r++, i++)
{
Mat smallImgROI;
vector<Rect> nestedObjects;
Point center;
Scalar color = colors[i % 8];
int radius;
center.x = cvRound((r->x + r->width*0.5)*scale);//还原成原来的大小
center.y = cvRound((r->y + r->height*0.5)*scale);
radius = cvRound((r->width + r->height)*0.25*scale);
circle(img, center, radius, color, 3, 8, 0);
//检测人眼,在每幅人脸图上画出人眼
if (nestedCascade.empty())
continue;
smallImgROI = smallImg(*r);
//和上面的函数功能一样
nestedCascade.detectMultiScale(smallImgROI, nestedObjects,
1.1, 2, 0
//|CV_HAAR_FIND_BIGGEST_OBJECT
//|CV_HAAR_DO_ROUGH_SEARCH
//|CV_HAAR_DO_CANNY_PRUNING
| CV_HAAR_SCALE_IMAGE
,
Size(30, 30));
for (vector<Rect>::const_iterator nr = nestedObjects.begin(); nr != nestedObjects.end(); nr++)
{
center.x = cvRound((r->x + nr->x + nr->width*0.5)*scale);
center.y = cvRound((r->y + nr->y + nr->height*0.5)*scale);
radius = cvRound((nr->width + nr->height)*0.25*scale);
circle(img, center, radius, color, 3, 8, 0);//将眼睛也画出来,和对应人脸的图形是一样的
}
}
cv::imshow("result", img);
}
2.利用OPENCV实现本地视频与图片帧之间的相互转换
直接上代码:
// test3.cpp
//
// 该程序实现视频和图片的相互转换.
// Image_to_video()函数将一组图片合成AVI视频文件.
// Video_to_image()函数将AVI视频文件读入,将每一帧存储为jpg文件.
//
//#include "stdafx.h"
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <cv.h>
#include <highgui.h>
#define NUM_FRAME 300 //只处理前300帧,根据视频帧数可修改
void Video_to_image(char* filename)
{
printf("------------- video to image ... ----------------n");
//初始化一个视频文件捕捉器
CvCapture* capture = cvCaptureFromAVI(filename);
//获取视频信息
cvQueryFrame(capture);
int frameH = (int)cvGetCaptureProperty(capture, CV_CAP_PROP_FRAME_HEIGHT);
int frameW = (int)cvGetCaptureProperty(capture, CV_CAP_PROP_FRAME_WIDTH);
int fps = (int)cvGetCaptureProperty(capture, CV_CAP_PROP_FPS);
int numFrames = (int)cvGetCaptureProperty(capture, CV_CAP_PROP_FRAME_COUNT);
printf("tvideo height : %dntvideo width : %dntfps : %dntframe numbers : %dn", frameH, frameW, fps, numFrames);
//定义和初始化变量
int i = 0;
IplImage* img = 0;
char image_name[13];
cvNamedWindow("mainWin", CV_WINDOW_AUTOSIZE);
//读取和显示
while (1)
{
img = cvQueryFrame(capture); //获取一帧图片
cvShowImage("mainWin", img); //将其显示
char key = cvWaitKey(20);
sprintf(image_name, "%s%d%s", "image", ++i, ".jpg");//保存的图片名
cvSaveImage(image_name, img); //保存一帧图片
if (i == NUM_FRAME) break;
}
cvReleaseCapture(&capture);
cvDestroyWindow("mainWin");
}
void Image_to_video()
{
int i = 0;
IplImage* img = 0;
char image_name[13];
printf("------------- image to video ... ----------------n");
//初始化视频编写器,参数根据实际视频文件修改
CvVideoWriter *writer = 0;
int isColor = 1;
int fps = 30; // or 25
int frameW = 400; // 744 for firewire cameras
int frameH = 240; // 480 for firewire cameras
writer = cvCreateVideoWriter("out.avi", CV_FOURCC('X', 'V', 'I', 'D'), fps, cvSize(frameW, frameH), isColor);
printf("tvideo height : %dntvideo width : %dntfps : %dn", frameH, frameW, fps);
//创建窗口
cvNamedWindow("mainWin", CV_WINDOW_AUTOSIZE);
while (i<NUM_FRAME)
{
sprintf(image_name, "%s%d%s", "image", ++i, ".jpg");
img = cvLoadImage(image_name);
if (!img)
{
printf("Could not load image file...n");
exit(0);
}
cvShowImage("mainWin", img);
char key = cvWaitKey(20);
cvWriteFrame(writer, img);
}
cvReleaseVideoWriter(&writer);
cvDestroyWindow("mainWin");
}
int main(int argc, char *argv[])
{
char filename[13] = "tree.avi";
Video_to_image(filename); //视频转图片
Image_to_video(); //图片转视频
return 0;
}
3.基于本地视频的人脸、人眼检测,事实上是1和2的结合,将视频转换为一帧、一帧的图片帧,再进行人脸、人眼检测。
// face_detect.cpp : 定义控制台应用程序的入口点。
//该程序是读取本地文件视频并识别视频中人脸和眼睛,目前已经成功
//#include "stdafx.h"
#include "opencv2/objdetect/objdetect.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/ml/ml.hpp"
#include "cv.h"
#include <iostream>
#include <stdio.h>
using namespace std;
using namespace cv;
static CvMemStorage* storage = 0;
void detectAndDraw(IplImage* img,
CascadeClassifier& cascade, CascadeClassifier& nestedCascade,
double scale);
String cascadeName = "./haarcascade_frontalface_alt2.xml";//人脸的训练数据
//String nestedCascadeName = "./haarcascade_eye_tree_eyeglasses.xml";//人眼的训练数据
String nestedCascadeName = "./haarcascade_eye.xml";//人眼的训练数据
int eyesopen = 0;
int eyesclose = 0;
int main(int argc, const char** argv)
{
CvCapture *capture = 0;
IplImage *frame, *frame_copy = 0;
int optlen = strlen("--cascade=");
const char* input_name;
Mat image;
CascadeClassifier cascade, nestedCascade;//创建级联分类器对象
double scale = 4;
if (!cascade.load(cascadeName))//从指定的文件目录中加载级联分类器
{
cerr << "ERROR: Could not load classifier cascade唉唉出错了" << endl;
return 0;
}
if (!nestedCascade.load(nestedCascadeName))
{
cerr << "WARNING: Could not load classifier cascade for nested objects" << endl;
return 0;
}
storage = cvCreateMemStorage(0);
cvNamedWindow("result", 1);
//检测视频
capture = cvCaptureFromAVI("tree.avi");
if (capture)
{
for (;;)
{
if (!cvGrabFrame(capture))
break;
frame = cvRetrieveFrame(capture);
if (!frame)
break;
if (!frame_copy)
frame_copy = cvCreateImage(cvSize(frame->width, frame->height), IPL_DEPTH_8U, frame->nChannels);
if (frame->origin == IPL_ORIGIN_TL)
cvCopy(frame, frame_copy, 0);
else
cvFlip(frame, frame_copy, 0);
IplImage *equ = cvCreateImage(cvGetSize(frame_copy), 8, 1);
IplImage *gray = cvCreateImage(cvGetSize(frame_copy), 8, 1);
cvCvtColor(frame_copy, gray, CV_BGR2GRAY);
cvEqualizeHist(gray,equ);
detectAndDraw(frame_copy, cascade, nestedCascade, scale);
if (cvWaitKey(10) >= 0)
break;
}
cvReleaseImage(&frame_copy);
cvReleaseCapture(&capture);
}
//image = imread( "lena.jpg", 1 );//读入lena图片
//image = imread("lena.jpg", 1);
//namedWindow("result", 1);//opencv2.0以后用namedWindow函数会自动销毁窗口
//if (!image.empty())//读取图片数据不能为空
//{
// detectAndDraw(image, cascade, nestedCascade, scale);
// waitKey(0);
//}
//cout << "eye close:" << eyesclose<<endl<<"eye open:"<<eyesopen<<endl;
cvWaitKey(-1);
return 0;
}
void detectAndDraw(IplImage* img1,
CascadeClassifier& cascade, CascadeClassifier& nestedCascade,
double scale)
{
cv::Mat img(img1, 0);
int i = 0;
double t = 0;
vector<Rect> faces;
const static Scalar colors[] = { CV_RGB(0, 0, 255),
CV_RGB(0, 128, 255),
CV_RGB(0, 255, 255),
CV_RGB(0, 255, 0),
CV_RGB(255, 128, 0),
CV_RGB(255, 255, 0),
CV_RGB(255, 0, 0),
CV_RGB(255, 0, 255) };//用不同的颜色表示不同的人脸
Mat gray, smallImg(cvRound(img.rows / scale), cvRound(img.cols / scale), CV_8UC1);//将图片缩小,加快检测速度
cvtColor(img, gray, CV_BGR2GRAY);//因为用的是类haar特征,所以都是基于灰度图像的,这里要转换成灰度图像
resize(gray, smallImg, smallImg.size(), 0, 0, INTER_LINEAR);//将尺寸缩小到1/scale,用线性插值
equalizeHist(smallImg, smallImg);//直方图均衡
t = (double)cvGetTickCount();//用来计算算法执行时间
//检测人脸
//detectMultiScale函数中smallImg表示的是要检测的输入图像为smallImg,faces表示检测到的人脸目标序列,1.1表示
//每次图像尺寸减小的比例为1.1,2表示每一个目标至少要被检测到3次才算是真的目标(因为周围的像素和不同的窗口大
//小都可以检测到人脸),CV_HAAR_SCALE_IMAGE表示不是缩放分类器来检测,而是缩放图像,Size(30, 30)为目标的
//最小最大尺寸
cascade.detectMultiScale(smallImg, faces,
1.1, 2, 0
//|CV_HAAR_FIND_BIGGEST_OBJECT
//|CV_HAAR_DO_ROUGH_SEARCH
| CV_HAAR_SCALE_IMAGE
,
Size(30, 30));
t = (double)cvGetTickCount() - t;//相减为算法执行的时间
printf("detection time = %g ms\n", t / ((double)cvGetTickFrequency()*1000.));
for (vector<Rect>::const_iterator r = faces.begin(); r != faces.end(); r++, i++)
{
Mat smallImgROI;
vector<Rect> nestedObjects;
Point center;
Scalar color = colors[i % 8];
int radius;
center.x = cvRound((r->x + r->width*0.5)*scale);//还原成原来的大小
center.y = cvRound((r->y + r->height*0.5)*scale);
radius = cvRound((r->width + r->height)*0.25*scale);
circle(img, center, radius, color, 3, 8, 0);
//检测人眼,在每幅人脸图上画出人眼
if (nestedCascade.empty())
continue;
smallImgROI = smallImg(*r);
//和上面的函数功能一样
nestedCascade.detectMultiScale(smallImgROI, nestedObjects,
1.1, 2, 0
//|CV_HAAR_FIND_BIGGEST_OBJECT
//|CV_HAAR_DO_ROUGH_SEARCH
//|CV_HAAR_DO_CANNY_PRUNING
| CV_HAAR_SCALE_IMAGE
,
Size(30, 30));
if (nestedObjects.empty())
eyesclose++;
else
eyesopen++;
for (vector<Rect>::const_iterator nr = nestedObjects.begin(); nr != nestedObjects.end(); nr++)
{
center.x = cvRound((r->x + nr->x + nr->width*0.5)*scale);
center.y = cvRound((r->y + nr->y + nr->height*0.5)*scale);
radius = cvRound((nr->width + nr->height)*0.25*scale);
circle(img, center, radius, color, 3, 8, 0);//将眼睛也画出来,和对应人脸的图形是一样的
//eyesopen++;
}
}
cv::imshow("result", img);
}
4.opencv操作摄像头实现人脸和人眼的检测
//#include “stdafx.h”
#include
#include <opencv2/imgproc/imgproc.hpp> // Gaussian Blur
#include <opencv2/core/core.hpp> // Basic OpenCV structures (cv::Mat, Scalar)
#include <opencv2/highgui/highgui.hpp> // OpenCV window I/O
#include “opencv2/objdetect/objdetect.hpp”//人脸识别的接口
using namespace cv;//必须加入,否则无法检找到OPENCV的各个函数
using namespace std;
string face_cascade_name = “haarcascade_frontalface_alt2.xml”;
string eye_cascade_name = “./haarcascade_eye.xml”;
CascadeClassifier face_cascade;
CascadeClassifier eye_cascade;
string window_name = “疲劳检测”;
double scale = 2;//在检测本地视频时发现该值为4时,效果比较好,但在摄像头实时视频中取2值比较好
void detectAndDisplay(Mat frame){
std::vector
std::vector
Mat frame_gray,smallframe_gray(cvRound(frame.rows/scale),cvRound(frame.cols/scale),CV_8UC1);//将图片缩小,加快检测速度
cvtColor(frame, frame_gray, CV_BGR2GRAY);
equalizeHist(frame_gray, frame_gray);
resize(frame_gray, smallframe_gray, smallframe_gray.size(), 0, 0, INTER_LINEAR);//将尺寸缩小到1/scale,用线性差值
face_cascade.detectMultiScale(smallframe_gray, faces, 1.1, 2, 0 | CV_HAAR_SCALE_IMAGE, Size(30, 30));
for (int i = 0; i < faces.size(); i++){
Point center(cvRound((faces[i].x + faces[i].width*0.5)*scale), cvRound((faces[i].y + faces[i].height*0.5)*scale));
ellipse(frame, center, Size(faces[i].width*0.5*scale, faces[i].height*0.5*scale), 0, 0, 360, Scalar(255, 0, 255), 4, 8, 0);
eye_cascade.detectMultiScale(smallframe_gray, eyes, 1.1, 2, 0 | CV_HAAR_SCALE_IMAGE, Size(30, 30));
for (int i = 0; i < eyes.size(); i++){
Point center(cvRound((eyes[i].x + eyes[i].width*0.5)*scale), cvRound((eyes[i].y + eyes[i].height*0.5)*scale));
ellipse(frame, center, Size(eyes[i].width*0.5*scale, eyes[i].height*0.5*scale), 0, 0, 360, Scalar(255, 0, 255), 4, 8, 0);
}
}
imshow(window_name, frame);
}
int main()
{
//double scale = 4;
VideoCapture cap(0); // open the default camera
if (!cap.isOpened()) // check if we succeeded
return -1;
Mat edges;
//namedWindow("edges", 1);
if (!face_cascade.load(face_cascade_name)){
printf("[error] 无法加载face级联分类器文件!\n");
return -1;
}
if (!eye_cascade.load(eye_cascade_name)){
printf("[error] 无法加载eye级联分类器文件!\n");
return -1;
}
int nTick = 0;
for (;;)
{
if (!cap.isOpened())
{//等等摄像头打开
continue;
}
Mat frame;
nTick = getTickCount();
cap >> frame; // get a new frame from camera
if (frame.data == NULL)
{//等到捕获到数据
continue;
}
cvtColor(frame, edges, CV_BGR2BGRA);
detectAndDisplay(edges);
if (waitKey(30) >= 0) break;
}
return 0;
}