此文章主要是學習的記錄。使用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工程待整理: