此文章主要是学习的记录。使用opencv的版本是 3.4.6。实现了图片的人脸检测及人的眼睛、鼻子和嘴巴的检测。里面使用的窗口显示相关的代码都是opencv的函数。
人脸检测
openCV的人脸识别主要通过Haar特征分类器实现的,haar特征分类器是一个xml文件,文件描述了检测物体的Haar特征值。Haar分类器需要通过大量的数据来训练,opecv的安装路径里面有已经预先训练好的分类器,路径为opencv\sources\data\haarcascades。看到其中的正脸的分类器如下:
此技术虽然适用于人脸检测,但不限于人脸检测,还可用于其他物体的检测(对对应的物体收集数据进行训练,得到其xml文件)。当然也可以对人脸识别进行更多的数据训练,提高人脸的检测精度。
这个是只检测脸部的demo,因为这里是使用Qt创建的工程,图片的相对路径是Makefile的路径。
代码如下:
//只检测脸部的demo,直接调用
bool detectFace()
{
/********************************** 1.打开图片 *************************************/
string str = "face4.jpg";
Mat img = imread(str);
Mat imgGray;
//转灰度图像
cvtColor(img, imgGray, COLOR_BGR2GRAY);
//显示窗口
namedWindow("display");
imshow("display", img);
/*********************************** 2.加载检测器 ******************************/
// 建立级联分类器
// 加载训练好的 人脸检测器(.xml)
CascadeClassifier cascade_face;
const string path_face = "xml\\haarcascade_frontalface_alt.xml";
if (!cascade_face.load(path_face))
{
cout << "cascade load failed!\n";
return -1;
}
//计时
double t = 0;
t = (double)getTickCount();
/*********************************** 3.人脸检测 ******************************/
vector<Rect> faces(0);
cascade_face.detectMultiScale(imgGray, //输入的原图
faces, //表示检测到的人脸目标序列,输出的结果
1.1, //每次图像尺寸减小的比例为1.1
3, //每一个目标至少要被检测到3次才算是真的目标
0 , // 默认
Size(30, 30) //最小的检测区域,根据使用场景来确定大小,比如太小的脸放弃
);
cout << "detect face number is :" << faces.size() << endl;
/******************************** 4.显示人脸矩形框 ******************************/
if (faces.size() > 0)
{
//对脸部进行循环
for (size_t i = 0;i < faces.size();i++)
{
cout<<"face "<<i<<" area x = "<<faces[i].x<<" area y = "<<faces[i].y
<<" area width = "<<faces[i].width<<" area height = "<<faces[i].height<<endl;
rectangle(img, faces[i], Scalar(150, 0, 0), 3, 8, 0);
}
}
else
{
cout << "cant not get faces " << endl;
}
t = (double)getTickCount() - t; //getTickCount(): Returns the number of ticks per second.
cout<< t * 1000 / getTickFrequency() << "ms " << endl;
//画完人脸框的图片再显示
imshow("display", img);
while(waitKey(0)!='q') ;
destroyWindow("display");
}
int main(int argc,char *argv[])
{
detectFace();
return 0;
}
结果如下,因为背景不算复杂,所以检测结果很准确。如果背景复杂的话就很可能检测到错误的人脸或漏掉人脸。
人脸及眼睛、鼻子、嘴巴的检测
如果检测的脸部区域比较大,且检测的是正脸,为了提高识别率,可以通过检测到人脸再检测判断是否有人眼睛、鼻子、嘴来加以判断。在opencv的安装路径下只能找到了脸和眼睛的分类器,而鼻子、嘴的分类器可以到opencv_contrib那里去找:https://github.com/opencv/opencv_contrib
关于opencv_contrib:
2014年8月3.0 alpha发布,除大部分方法都使用OpenCL加速外,3.x默认包含以及使用IPP,同时,matlab bindings、Face Recognition、SIFT、SURF、 text detector、motion templates & simple flow 等都移到了opencv_contrib下(opencv_contrib不仅存放了尚未稳定的代码,同时也存放了涉及专利保护的技术实现),大量涌现的新方法也包含在其中。
在contrib中的face模块里可以找到更多的分类器,比如嘴巴的路径是:
modules/face/data/cascades/haarcascade_mcs_mouth.xml
检测的方法还是和上面类似的。主要是先检测人脸,然后在人脸的特定区域里面检测 眼睛、鼻子和嘴巴,如果 眼睛、鼻子和嘴巴都存在,那是人脸的概率就特别大了。
demo里面对眼睛、鼻子和嘴巴的检测做了些简单的限制,这个应该还有进一步细化研究。
代码:
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;
CascadeClassifier cascade_face;
CascadeClassifier cascade_eye;
CascadeClassifier cascade_mouth;
CascadeClassifier cascade_nose;
bool detectFaceDetail(Mat& img);
int main(int argc,char *argv[])
{
/*********************************** 1.加载检测器 ******************************/
// 建立级联分类器
// 加载训练好的 人脸检测器(.xml)
const string path_face = "xml\\haarcascade_frontalface_alt.xml";
if (!cascade_face.load(path_face))
{
cout << "cascade load failed!\n";
return -1;
}
// 加载训练好的 眼睛检测器(.xml)
const string path_eye = "xml\\haarcascade_eye.xml";
if (!cascade_eye.load(path_eye))
{
cout << "cascade_eye load failed!\n";
return -1;
}
// 加载训练好的 嘴巴检测器(.xml)
const string path_mouth = "xml\\haarcascade_mcs_mouth.xml";
if (!cascade_mouth.load(path_mouth))
{
cout << "cascade_mouth load failed!\n";
return -1;
}
// 加载训练好的 鼻子检测器(.xml)
const string path_nose = "xml\\haarcascade_mcs_nose.xml";
if ( ! cascade_nose.load(path_nose))
{
cout << "cascade_mouth load failed!\n";
return -1;
}
//单个图片检测
string str = "face4.jpg";
Mat img = imread(str);//放到makefile文件下
namedWindow("display");
imshow("display", img);
detectFaceDetail(img);
destroyWindow("display");
namedWindow("face_detect");
imshow("face_detect", img);
while(waitKey(0)!='k') ;
destroyWindow("face_detect");
return 0;
}
//检测脸部、眼睛、鼻子、嘴巴,传入需要检测的图像
bool detectFaceDetail(Mat& img)
{
bool getFace = false;
Mat imgGray;
/* 因为用的是类haar特征,所以都是基于灰度图像的,这里要转换成灰度图像 */
cvtColor(img, imgGray, COLOR_BGR2GRAY);
//计时
double t = 0;
t = (double)getTickCount();
/*********************************** 2.人脸检测 ******************************/
vector<Rect> faces(0);
cascade_face.detectMultiScale(imgGray,
faces,
1.1,
3,
0 ,
Size(30, 30) //过大则检测不到人脸,根据使用场景来确定大小,比如太小的脸放弃
);
cout << "detect face number is :" << faces.size() << endl;
/******************************** 3.显示人脸矩形框 ******************************/
if (faces.size() > 0)
{
//对脸部进行循环
for (size_t i = 0;i < faces.size();i++)
{
cout<<"face "<<i<<" area x = "<<faces[i].x<<" area y = "<<faces[i].y
<<" area width = "<<faces[i].width<<" area height = "<<faces[i].height<<endl;
rectangle(img, faces[i], Scalar(150, 0, 0), 3, 8, 0);
bool bGetEyes = false;
bool bGetNose = false;
bool bGetMouth = false;
//脸部区域
Mat faceROIGray = imgGray(faces[i]);
Mat faceROI = img(faces[i]);
//眼睛识别
vector<Rect> eyes;
cascade_eye.detectMultiScale(faceROIGray, eyes, 1.1, 3, 0 ,Size(10, 10));
if(eyes.size() > 0)
{
cout << "detect eye number is :" << eyes.size() << endl;
int iCoutEye = 0;
for(size_t j = 0; j<eyes.size(); j++)
{
//现在限制一下眼睛的位置,可以进一步细化
if(eyes[j].y+eyes[j].height < faces[i].height/3*2 && eyes[j].width<faces[i].width/2){
rectangle(faceROI, eyes[j], Scalar(150, 150, 0), 3, 8, 0);
iCoutEye++;
cout<<"eyes "<<j<<" area x = "<<eyes[j].x<<" area y = "<<eyes[j].y
<<" area width = "<<eyes[j].width<<" area height = "<<eyes[j].height<<endl;
}
}
//检测到两个眼睛
if(iCoutEye >= 2){
bGetEyes = true;
}
}
//鼻子识别
vector<Rect> noses;
cascade_nose.detectMultiScale(faceROIGray, noses, 1.1, 3, 0 ,Size(30, 30));
if(noses.size() > 0)
{
cout << "detect nose number is :" << noses.size() << endl;
for(size_t j = 0; j<noses.size(); j++)
{
//现在限制一下鼻子的位置,可以进一步细化,比如要求正脸正对摄像头就可以对鼻子的中心点做些限制
if(noses[j].y > faces[i].height/4){
bGetNose = true;
rectangle(faceROI, noses[j], Scalar(150, 200, 200), 3, 8, 0);
cout<<"eyes "<<j<<" area x = "<<noses[j].x<<" area y = "<<noses[j].y
<<" area width = "<<noses[j].width<<" area height = "<<noses[j].height<<endl;
}
}
}
//嘴巴识别
vector<Rect> mouths;
cascade_mouth.detectMultiScale(faceROIGray, mouths, 1.1, 3, 0 | CASCADE_SCALE_IMAGE ,Size(30, 30));
if(mouths.size() > 0)
{
cout << "detect eye number is :" << mouths.size() << endl;
for(size_t j = 0; j<mouths.size(); j++)
{
// Point mouth_center(faces[i].x + mouths[0].x + mouths[0].width / 2, faces[i].y + mouths[0].y + mouths[0].height / 2); //嘴巴的中心
//现在限制一下嘴巴的位置,可以进一步细化
if(mouths[j].y > faces[i].height/3)
{
bGetMouth = true;
rectangle(faceROI, mouths[j], Scalar(150, 150, 150), 3, 8, 0);
cout<<"eyes "<<j<<" area x = "<<mouths[j].x<<" area y = "<<mouths[j].y
<<" area width = "<<mouths[j].width<<" area height = "<<mouths[j].height<<endl;
}
}
}
//到时只有检测到眼睛、鼻子、嘴巴才把人脸框画出来,其它的都不画
if(bGetEyes && bGetMouth && bGetNose){
getFace = true;
// rectangle(img, faces[i], Scalar(150, 0, 0), 3, 8, 0);
}
}// end face while
}
else
{
cout << "cant not get faces " << endl;
}
t = (double)getTickCount() - t; //getTickCount(): Returns the number of ticks per second.
cout<< t * 1000 / getTickFrequency() << "ms " << endl;
return getFace;
}
结果如下(上面的图片脸好像太小了,换一张):
demo工程: