1.环境配置(博主环境):系统环境:win7旗舰 + Visual Studio 2012 + opencv2.4.9
博主的经历比较悲催,刚开始想在ARM11架构的OK6410上实现人脸检测的功能,很遗憾,在历经长时间的失败之后终于无法坚持下去而放弃,询问了一下实验室老师意见,建议还是先在PC上实现人脸识别的全部过程,然后还有余力的话可以尝试在cotex-a9上试一试。硬件环境搭建之操蛋,不堪回首的血泪史……
好了,言归正传,首先介绍一下博主本人的环境配置,win7 Ultimate+VS2012+opencv2.4.9,为什么标的这么仔细呢?博主实在是被前期在Ubuntu12.04,14.04,federal9,redhat系统下搭建环境对版本要求之苛刻所惊怕(PS:博主渣渣,大牛路过请无视……),俨然“一朝被蛇咬十年怕井绳”,环境配置的链接博主放下面(PS:建议参考的朋友们注意细节,自己也可以多找几篇对比一下):
*【OpenCV入门教程之一】 安装OpenCV:OpenCV 3.0、OpenCV 2.4.8、OpenCV 2.4.9 +VS 开发环境配置 [http://blog.csdn.net/poem_qianmo/article/details/19809337]
(http://blog.csdn.net/poem_qianmo/article/details/19809337%20%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE)*
2.Opencv简介
这个一百度一大片,但是博主还是充当一回话唠吧,因为可能有的人懒得查。
● 全称:Open Source Computer Vision Library。
● 运行平台:跨平台计算机视觉库,可运行在Linux、Windows和Mac OS上
● 函数构成:由一系列 C 函数和少量 C++ 类构成,同时提供了Python、Ruby、MATLAB等语言的接口,实现了图像处理和计算机视觉方面的很多通用算法。
● 编写脚本:基于C++语言编写,它的主要接口也是C++语言,但是依然保留了大量的C语言接口。(该库也有大量的Python, Java and MATLAB/OCTAVE (版本2.5)的接口。这些语言的API接口函数可以通过在线文档获得。如今也提供对于C#,Ch, Ruby的支持。)
3.人脸识别流程
● 步骤一:人脸获取,预处理
不同的人脸图像都能通过摄像镜头采集下来,比如静态图像、动态图像、不同的位置、不同表情等方面都可以得到很好的采集。对于人脸的图像预处理是基于人脸检测结果,对图像进行处理并最终服务于特征提取的过程。
正文中图举例如下:
● 步骤二:特征提取
人脸图像特征提取:人脸识别系统可使用的特征通常分为视觉特征、像素统计特征、人脸图像变换系数特征、人脸图像代数特征等。特征提取的方法归纳起来分为两大类:一种是基于知识的表征方法;另外一种是基于代数特征或统计学习的表征方法。
正文中图举例如下:
● 步骤三:匹配与识别
提取的人脸图像的特征数据与数据库中存储的特征模板进行搜索匹配,通过设定一个阈值,当相似度超过这一阈值,则把匹配得到的结果输出。人脸识别就是将待识别的人脸特征与已得到的人脸特征模板进行比较,根据相似程度对人脸的身份信息进行判断。
正文中图举例如下:
● 步骤三:输出结果
在对待识别的人脸与已得到的人脸特征模板进行比较,根据相似程度输出对人脸的身份信息进行判断结果。
正文中图举例如下:
4.人脸检测(博主会后续更新人脸的采集、标准化、匹配、识别,代码正在测试之中)
● First : Opencv检测原理
OpenCV中有检测人脸的函数(该函数还可以检测一些其他物体), 甚至还包含一些预先训练好的物体识别文件。
所以利用这些现成的东西就可以很快做出一个人脸检测的程序。
主要步骤为:
① . 加载分类器。
用cvLoad函数读入xml格式的文件。文件在OpenCV安装目录下的“Opencv/sources/data/haarcascades/”路径下。
haarcascade_frontalface_atl.xml
haarcascade_frontalface_atl2.xml
haarcascade_lefteye_2splits.xml
haarcascade_righteye_2splits.xml
try {
faceDetector.load("F:\\Program Files\\opencv\\sources\\data\\haarcascades\\haarcascade_frontalface_alt2.xml");
}
catch (cv::Exception e) { }
if ( faceDetector.empty() )
{
cerr << "ERROR: Couldn't load Detector ";
exit(1);
}
try {
eyeDetector1.load("F:\\Program Files\\opencv\\sources\\data\\haarcascades\\haarcascade_lefteye_2splits.xml");
eyeDetector2.load("F:\\Program Files\\opencv\\sources\\data\\haarcascades\\haarcascade_righteye_2splits.xml");
//("E:\\faces\\PCAModel.xml");//保存路径可自己设置,但注意用“\\”
}
catch (cv::Exception e) { }
② . 摄像头实时读取人脸图像:
if(!cap.isOpened())
{
AfxMessageBox("无法打开");
return;
}
cap>>mat;
IplImage img = mat;//另外添加
CDC* pDC = GetDlgItem(IDC_VSHOW)->GetDC();
HDC hDC = pDC->GetSafeHdc();
CRect rect;
GetDlgItem(IDC_VSHOW)->GetClientRect(&rect);
CvvImage cimg;
cimg.CopyOf( &img );// 复制图片
cimg.DrawToHDC(hDC, &rect);// 将图③绘制到显示控件的指定器区域内
ReleaseDC( pDC );
// 设置计时器,每10ms触发一次事件
/————————————-目标检测————————————-/
void detectObjectsCustom(const Mat &img, CascadeClassifier &cascade, vector &objects, int scaledWidth, int flags, Size minFeatureSize, float searchScaleFactor, int minNeighbors);
void detectLargestObject(const Mat &img, CascadeClassifier &cascade, Rect &largestObject, int scaledWidth);
void detectManyObjects(const Mat &img, CascadeClassifier &cascade, vector &objects, int scaledWidth);
/– end——————————————/
void detectBothEyes(const Mat &face, CascadeClassifier &eyeCascade1, CascadeClassifier &eyeCascade2,CascadeClassifier &eyeCascade3, Point &leftEye, Point &rightEye, Rect *searchedLeftEye, Rect *searchedRightEye);
Mat getPreprocessedFace(Mat &srcImg, int desiredFaceWidth, CascadeClassifier &faceCascade, CascadeClassifier &eyeCascade1, CascadeClassifier &eyeCascade2, bool doLeftAndRightSeparately, Rect *storeFaceRect, Point *storeLeftEye, Point *storeRightEye, Rect *searchedLeftEye, Rect *searchedRightEye);
void initDetector()
{
}
int main(int argc,char **argv)
{
CascadeCl● assifier faceDetector;
//CascadeClassifier eyeDetector1;
//CascadeClassifier eyeDetector2;//未初始化不用
CascadeClassifier eyeDetector1;
CascadeClassifier eyeDetector2;
CascadeClassifier eyeDetector3;
try{
//faceDetector.load("E:\\OpenCV-2.3.0\\data\\haarcascades\\haarcascade_frontalface_alt.xml");
faceDetector.load("D:\\opencv\\data\\lbpcascades\\lbpcascade_frontalface.xml");
//eyeDetector1.load("D:\\opencv\\data\\haarcascades\\haarcascade_eye.xml");
//eyeDetector2.load("D:\\opencv\\data\\haarcascades\\haarcascade_eye_tree_eyeglasses.xml");
eyeDetector1.load("D:\\opencv\\data\\haarcascades\\haarcascade_mcs_lefteye.xml");
eyeDetector2.load("D:\\opencv\\data\\haarcascades\\haarcascade_mcs_righteye.xml");
}catch (cv::Exception e){}
if(faceDetector.empty())
{
cerr<<"error:couldn't load face detector (";
cerr<<"lbpcascade_frontalface.xml)!"<(0, 2) += ex;
rot_mat.at(1, 2) += ey;
Mat warped = Mat(DESIRED_FACE_HEIGHT, DESIRED_FACE_WIDTH,CV_8U, Scalar(128));
warpAffine(img_rect, warped, rot_mat, warped.size());
//直方图均衡化,左均衡右均衡,全均衡
Mat faceImg_temp,faceImg(warped.rows,warped.cols,warped.type());
warped.copyTo(faceImg_temp);
cvtColor(faceImg_temp,faceImg,CV_BGR2GRAY);
imshow("faceImg",faceImg);
int w=faceImg.cols;
int h=faceImg.rows;
Mat wholeFace;
equalizeHist(faceImg,wholeFace);
int midX=w/2;
Mat leftSide=faceImg(Rect(0,0,midX,h));
Mat rightSide=faceImg(Rect(midX,0,w-midX,h));
equalizeHist(leftSide,leftSide);
equalizeHist(rightSide,rightSide);
for(int y=0;y(y,x);
}else if(x(y,x);
int wv=wholeFace.at(y,x);
float f=(x-w*1/4)/(float)(w/4);
v=cvRound((1.0f-f)*lv+(f)*wv);
}else if(x(y,x-midX);
int wv=wholeFace.at(y,x);
float f=(x-w*2/4)/(float)(w/4);
v=cvRound((1.0f-f)*wv+(f)*rv);
}else
{
v=rightSide.at(y,x-midX);
}
faceImg.at(y,x)=v;
}
}
//平滑图像
imshow("混合后 ",faceImg);
Mat filtered=Mat(warped.size(),CV_8U);
bilateralFilter(faceImg,filtered,0,20.0,2.0);
imshow("双边滤波后",filtered);
//椭圆形掩码
Mat mask=Mat(warped.size(),CV_8UC1,Scalar(255));
double dw=DESIRED_FACE_WIDTH;
double dh=DESIRED_FACE_HEIGHT;
Point faceCenter=Point(cvRound(dw*0.5),cvRound(dh*0.4));
Size size=Size(cvRound(dw*0.5),cvRound(dh*0.8));
ellipse(mask,faceCenter,size,0,0,360,Scalar(0),CV_FILLED);
filtered.setTo(Scalar(128),mask);
imshow("filtered",filtered);
imshow("warped",warped);
rectangle(img,Point(largestObject.x,largestObject.y),Point(largestObject.x+largestObject.width,largestObject.y+largestObject.height),Scalar(0,0,255),2,8);
rectangle(img_rect,Point(searchedLeftEye.x,searchedLeftEye.y),Point(searchedLeftEye.x+searchedLeftEye.width,searchedLeftEye.y+searchedLeftEye.height),Scalar(0,255,0),2,8);
rectangle(img_rect,Point(searchedRightEye.x,searchedRightEye.y),Point(searchedRightEye.x+searchedRightEye.width,searchedRightEye.y+searchedRightEye.height),Scalar(0,255,0),2,8);
//getPreprocessedFace
imshow("img_rect",img_rect);
imwrite("img_rect.jpg",img_rect);
imshow("img",img);
waitKey();
}
/*
1、采用给出的参数在图像中寻找目标,例如人脸
2、可以使用Haar级联器或者LBP级联器做人脸检测,或者甚至眼睛,鼻子,汽车检测
3、为了使检测更快,输入图像暂时被缩小到’scaledWidth’,因为寻找人脸200的尺度已经足够了。
*/
void detectObjectsCustom(const Mat &img, CascadeClassifier &cascade, vector &objects, int scaledWidth, int flags, Size minFeatureSize, float searchScaleFactor, int minNeighbors)
{
//如果输入的图像不是灰度图像,那么将BRG或者BGRA彩色图像转换为灰度图像
Mat gray;
if (img.channels() == 3) {
cvtColor(img, gray, CV_BGR2GRAY);
}
else if (img.channels() == 4) {
cvtColor(img, gray, CV_BGRA2GRAY);
}
else {
// 直接使用输入图像,既然它已经是灰度图像
gray = img;
}
// 可能的缩小图像,是检索更快
Mat inputImg;
float scale = img.cols / (float)scaledWidth;
if (img.cols > scaledWidth) {
// 缩小图像并保持同样的宽高比
int scaledHeight = cvRound(img.rows / scale);
resize(gray, inputImg, Size(scaledWidth, scaledHeight));
}
else {
// 直接使用输入图像,既然它已经小了
inputImg = gray;
}
//标准化亮度和对比度来改善暗的图像
Mat equalizedImg;
equalizeHist(inputImg, equalizedImg);
// 在小的灰色图像中检索目标
cascade.detectMultiScale(equalizedImg, objects, searchScaleFactor, minNeighbors, flags, minFeatureSize);
// 如果图像在检测之前暂时的被缩小了,则放大结果图像
if (img.cols > scaledWidth) {
for (int i = 0; i < (int)objects.size(); i++ ) {
objects[i].x = cvRound(objects[i].x * scale);
objects[i].y = cvRound(objects[i].y * scale);
objects[i].width = cvRound(objects[i].width * scale);
objects[i].height = cvRound(objects[i].height * scale);
}
}
//确保目标全部在图像内部,以防它在边界上
for (int i = 0; i < (int)objects.size(); i++ ) {
if (objects[i].x < 0)
objects[i].x = 0;
if (objects[i].y < 0)
objects[i].y = 0;
if (objects[i].x + objects[i].width > img.cols)
objects[i].x = img.cols - objects[i].width;
if (objects[i].y + objects[i].height > img.rows)
objects[i].y = img.rows - objects[i].height;
}
// 返回检测到的人脸矩形,存储在objects中
}
/*
1、仅寻找图像中的单个目标,例如最大的人脸,存储结果到largestObject
2、可以使用Haar级联器或者LBP级联器做人脸检测,或者甚至眼睛,鼻子,汽车检测
3、为了使检测更快,输入图像暂时被缩小到’scaledWidth’,因为寻找人脸200的尺度已经足够了。
4、注释:detectLargestObject()要比 detectManyObjects()快。
*/
void detectLargestObject(const Mat &img, CascadeClassifier &cascade, Rect &largestObject, int scaledWidth)
{
//仅寻找一个目标 (图像中最大的).
int flags = CV_HAAR_FIND_BIGGEST_OBJECT;// | CASCADE_DO_ROUGH_SEARCH;
// 最小的目标大小.
Size minFeatureSize = Size(20, 20);
// 寻找细节,尺度因子,必须比1大
float searchScaleFactor = 1.1f;
// 多少检测结果应当被滤掉,这依赖于你的检测系统是多坏,如果minNeighbors=2 ,大量的good or bad 被检测到。如果
// minNeighbors=6,意味着只good检测结果,但是一些将漏掉。即可靠性 VS 检测人脸数量
int minNeighbors = 4;
// 执行目标或者人脸检测,仅寻找一个目标(图像中最大的)
vector objects;
detectObjectsCustom(img, cascade, objects, scaledWidth, flags, minFeatureSize, searchScaleFactor, minNeighbors);
if (objects.size() > 0) {
// 返回仅检测到的目标
largestObject = (Rect)objects.at(0);
}
else {
// 返回一个无效的矩阵
largestObject = Rect(-1,-1,-1,-1);
}
}
void detectManyObjects(const Mat &img, CascadeClassifier &cascade, vector &objects, int scaledWidth)
{
// 寻找图像中的许多目标
int flags = CV_HAAR_SCALE_IMAGE;
// 最小的目标大小.
Size minFeatureSize = Size(20, 20);
// 寻找细节,尺度因子,必须比1大
float searchScaleFactor = 1.1f;
// 多少检测结果应当被滤掉,这依赖于你的检测系统是多坏,如果minNeighbors=2 ,大量的good or bad 被检测到。如果
// minNeighbors=6,意味着只good检测结果,但是一些将漏掉。即可靠性 VS 检测人脸数量
int minNeighbors = 4;
// 执行目标或者人脸检测,寻找图像中的许多目标
detectObjectsCustom(img, cascade, objects, scaledWidth, flags, minFeatureSize, searchScaleFactor, minNeighbors);
}
/*
1、在给出的人脸图像中寻找双眼,返回左眼和右眼的中心,如果当找不到人眼时,或者设置为Point(-1,-1)
2、注意如果你想用两个不同的级联器寻找人眼,你可以传递第二个人眼检测器,例如如果你使用的一个常规人眼检测器和带眼镜的人眼检测器一样好,或者左眼检测器和右眼检测器一样好,
或者如果你不想第二个检测器,仅传一个未初始化级联检测器。
3、如果需要的话,也可以存储检测到的左眼和右眼的区域
*/
void detectBothEyes(const Mat &face, CascadeClassifier &eyeCascade1, CascadeClassifier &eyeCascade2, CascadeClassifier &eyeCascade3,Point &leftEye, Point &rightEye, Rect *searchedLeftEye, Rect *searchedRightEye)
{
//跳过人脸边界,因为它们经常是头发和耳朵,这不是我们关心的
/*
// For “2splits.xml”: Finds both eyes in roughly 60% of detected faces, also detects closed eyes.
const float EYE_SX = 0.12f;
const float EYE_SY = 0.17f;
const float EYE_SW = 0.37f;
const float EYE_SH = 0.36f;
*/
// For mcs.xml: Finds both eyes in roughly 80% of detected faces, also detects closed eyes.
const float EYE_SX = 0.10f;
const float EYE_SY = 0.19f;
const float EYE_SW = 0.40f;
const float EYE_SH = 0.36f;
// For default eye.xml or eyeglasses.xml: Finds both eyes in roughly 40% of detected faces, but does not detect closed eyes.
//haarcascade_eye.xml检测器在由下面确定的人脸区域内搜索最优。
/*
const float EYE_SX = 0.16f;//x
const float EYE_SY = 0.26f;//y
const float EYE_SW = 0.30f;//width
const float EYE_SH = 0.28f;//height
*/
int leftX = cvRound(face.cols * EYE_SX);
int topY = cvRound(face.rows * EYE_SY);
int widthX = cvRound(face.cols * EYE_SW);
int heightY = cvRound(face.rows * EYE_SH);
int rightX = cvRound(face.cols * (1.0-EYE_SX-EYE_SW) ); // 右眼的开始区域
Mat topLeftOfFace = face(Rect(leftX, topY, widthX, heightY));
Mat topRightOfFace = face(Rect(rightX, topY, widthX, heightY));
Rect leftEyeRect, rightEyeRect;
// 如果需要的话,然后搜索到的窗口给调用者
if (searchedLeftEye)
*searchedLeftEye = Rect(leftX, topY, widthX, heightY);
if (searchedRightEye)
*searchedRightEye = Rect(rightX, topY, widthX, heightY);
// 寻找左区域,然后右区域使用第一个人眼检测器
detectLargestObject(topLeftOfFace, eyeCascade1, leftEyeRect, topLeftOfFace.cols);
detectLargestObject(topRightOfFace, eyeCascade2, rightEyeRect, topRightOfFace.cols);
// 如果人眼没有检测到,尝试另外一个不同的级联检测器
if (leftEyeRect.width <= 0 && !eyeCascade3.empty()) {
detectLargestObject(topLeftOfFace, eyeCascade3, leftEyeRect, topLeftOfFace.cols);
//if (leftEyeRect.width > 0)
// cout << "2nd eye detector LEFT SUCCESS" << endl;
//else
// cout << "2nd eye detector LEFT failed" << endl;
}
//else
// cout << "1st eye detector LEFT SUCCESS" << endl;
// 如果人眼没有检测到,尝试另外一个不同的级联检测器
if (rightEyeRect.width <= 0 && !eyeCascade3.empty()) {
detectLargestObject(topRightOfFace, eyeCascade3, rightEyeRect, topRightOfFace.cols);
//if (rightEyeRect.width > 0)
// cout << "2nd eye detector RIGHT SUCCESS" << endl;
//else
// cout << "2nd eye detector RIGHT failed" << endl;
}
//else
// cout << "1st eye detector RIGHT SUCCESS" << endl;
if (leftEyeRect.width > 0) { // 检查眼是否被检测到
leftEyeRect.x += leftX; //矫正左眼矩形,因为人脸边界被去除掉了
leftEyeRect.y += topY;
leftEye = Point(leftEyeRect.x + leftEyeRect.width/2, leftEyeRect.y + leftEyeRect.height/2);
}
else {
leftEye = Point(-1, -1); // 返回一个无效的点
}
if (rightEyeRect.width > 0) { //检查眼是否被检测到
rightEyeRect.x += rightX; // 矫正左眼矩形,因为它从图像的右边界开始
rightEyeRect.y += topY; // 矫正右眼矩形,因为人脸边界被去除掉了
rightEye = Point(rightEyeRect.x + rightEyeRect.width/2, rightEyeRect.y + rightEyeRect.height/2);
}
else {
rightEye = Point(-1, -----------
1); // 返回一个无效的点
}
}
“`
参考文献———
[1]: 李武军, 王崇骏, 张炜,陈世福. 人脸识别研究综述,模式识别与人工智能,2006,(20)2: 470-475.
[2]: W.ZHAO Face Recognition:A Literature Survey ACM Computing Surveys,Vol.35 ,No,4December 2003,pp.399-458.
[3]: Y.kaya and K.Kobayashi,”A basic study on human face recognition[J] ”,Frontiers of patern Recognition cs Watanabe,Ed,2000:265-269
[4]: Johnson C, Seidel J, and Sofer A. Interior point methodology for 3-D PET reconstruction[J]. IEEE Transactions on Medical Imaging,
2000, 19(4): 271-285.
[5]: Yang MH, Kriegman D, Ahuja N. Detecting faces in images: A survey. IEEE Trans Pattern Analysis and Machine Intelligence,
2002, 24 (1): 34~58.