您的位置:首页 > 运维架构

OpenCV 实践——人脸检测与人脸图像提取

2017-10-27 22:01 453 查看
    人脸对比是现在比较常用的功能,比如出租车司机人脸与司机驾照照片对比,门禁系统中进入者的人脸与人脸库中的人脸进行对比。要实现人脸对比,首先要实现的是人脸检测,在摄像头拍摄到的一张图片中,正确的检测到人脸的位置,并且将人脸提取出来。考虑到免费开源,OpenCV 就可以很好的实现这个功能。OpenCVC 在linux 的安装可以参考前面的博客:ubuntu
16.04 OpenCV3.2.0完全编译安装

(一)人脸检测的实现:

    下面的代码由OpenCVC实例改进而来,它能够实现人脸检测和人眼睛的检测,将检测到的结果用圆圈圈出来。代码如下:

/*=============================================================================
#     FileName: facedetect.cpp
#         Desc: detect faces and eyes by opencv
#       Author: Licaibiao
#      Version:
#   LastChange: 2017-10-28
#      History:
=============================================================================*/
#include "opencv2/objdetect.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>

using namespace std;
using namespace cv;

/* opencv 的训练数据,由opencv 提供 */
#define CASCADENAME			"./data/haarcascade_frontalface_alt2.xml"
#define NESTEDCASCADENAME	"./data/haarcascade_eye_tree_eyeglasses.xml"
/* 用作人脸检测的图片 */
#define DETECT_IMAGE		"./image/001.jpg"

void detectAndDraw( Mat& img, CascadeClassifier& cascade,
CascadeClassifier& nestedCascade, double scale, bool tryflip )
{
double t = 0;
vector<Rect> faces, faces2;
/* 定义七种颜色用于人脸标记 */
const static Scalar colors[] =
{
Scalar(255,0,0),
Scalar(255,128,0),
Scalar(255,255,0),
Scalar(0,255,0),
Scalar(0,128,255),
Scalar(0,255,255),
Scalar(0,0,255),
Scalar(255,0,255)
};

Mat gray, smallImg;

/* 因为用的是类haar特征,所以都是基于灰度图像的,这里要转换成灰度图像 */
cvtColor( img, gray, COLOR_BGR2GRAY );

/* 将图片缩小,加快检测速度 */
double fx = 1 / scale;
/* 将尺寸缩小到1/scale, 用线性插值 */
resize( gray, smallImg, Size(), fx, fx, INTER_LINEAR );
/* 直方图均衡 */
equalizeHist( smallImg, smallImg );

/* 用来计算算法执行时间 */
t = (double)getTickCount();

/*人脸检测
smallImg:输入的原图
faces	:表示检测到的人脸目标序列
1.1		:每次图像尺寸减小的比例为1.1
2		:每一个目标至少要被检测到3次才算是真的目标
CV_HAAR_SCALE_IMAGE:表示不是缩放分类器来检测,而是缩放图像
Size(30, 30) 目标的最大最小尺寸
*/
cascade.detectMultiScale( smallImg, faces, 1.1, 2, CASCADE_SCALE_IMAGE, Size(30, 30) );
if( tryflip )
{
flip(smallImg, smallImg, 1);
cascade.detectMultiScale( smallImg, faces2,1.1, 2, 0|CASCADE_SCALE_IMAGE,Size(30, 30) );
for( vector<Rect>::const_iterator r = faces2.begin(); r != faces2.end(); ++r )
{
faces.push_back(Rect(smallImg.cols - r->x - r->width, r->y, r->width, r->height));
}
}

/* 相减为算法执行的时间 */
t = (double)getTickCount() - t;
printf( "detection time = %g ms\n", t*1000/getTickFrequency());

for ( size_t i = 0; i < faces.size(); i++ )
{
Rect r = faces[i];
Mat smallImgROI;
vector<Rect> nestedObjects;
Point center;
Scalar color = colors[i%8];
int radius;

/* 人脸长宽比例,在0.75-1.3 间画圆,其他范围画矩形 */
double aspect_ratio = (double)r.width/r.height;
if( 0.75 < aspect_ratio && aspect_ratio < 1.3 )
{
/*还原原来尺寸 计算圆心和圆半径 */
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 );
}
else
{
/* 画出检测区域,画矩形 */
rectangle( img, cvPoint(cvRound(r.x*scale), cvRound(r.y*scale)),
cvPoint(cvRound((r.x + r.width-1)*scale), cvRound((r.y + r.height-1)*scale)), color, 3, 8, 0);
}

/* 检测到人眼,在人脸上画出人眼 */
if( nestedCascade.empty())
{
continue;
}

smallImgROI = smallImg( r );

/* 人眼检测 */
nestedCascade.detectMultiScale( smallImgROI, nestedObjects, 1.1, 2, CASCADE_SCALE_IMAGE, Size(30, 30) );
for ( size_t j = 0; j < nestedObjects.size(); j++ )
{
Rect nr = nestedObjects[j];
/*还原原来尺寸 计算圆心和圆半径 */
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 );
}
}
/* 显示图像 img */
imshow( "result", img );
}

int main( int argc, const char** argv )
{
Mat frame, image;
bool tryflip;
CascadeClassifier cascade, nestedCascade;
double scale = 1.3;

/* 加载分类器 */
if ( !nestedCascade.load( NESTEDCASCADENAME ) )
{
cerr << "WARNING: Could not load classifier cascade for nested objects" << endl;
}
if( !cascade.load( CASCADENAME ) )
{
cerr << "ERROR: Could not load classifier cascade" << endl;
return -1;
}

/* 加载图片 */
image = imread(DETECT_IMAGE, 1 );
if(image.empty())
{
cout << "Couldn't read iamge" << DETECT_IMAGE  <<  endl;

}

cout << "Detecting face(s) in " << DETECT_IMAGE << endl;

/* 检测人脸及眼睛并画出检测到的区域 */
if( !image.empty() )
{
detectAndDraw( image, cascade, nestedCascade, scale, tryflip );
waitKey(0);
}
return 0;
}


要编译运行上面的代码需要注意几个地方

(1)haarcascade_frontalface_alt2.xml 和haarcascade_eye_tree_eyeglasses.xml 是opencv 中提供的文件。

(2)由于opencv源码安装的时候需要下载一些文件,网速慢的地方安装容易失败。如果使用sudo apt-get install libopencv-dev python-opencv 命令直接安装的,编译的时候需要带参数`pkg-config --cflags --libs opencv`

编译运行结果:

biao@ubuntu:~/Opencv/Face_Detect$ make clean
rm -f /home/biao/Opencv/Face_Detect/out/example
biao@ubuntu:~/Opencv/Face_Detect$ make
g++ facedetect.cpp -o /home/biao/Opencv/Face_Detect/out/example -L/home/biao/Opencv/Face_Detect/lib -I/home/biao/Opencv/Face_Detect/include/opencv -I/home/biao/Opencv/Face_Detect/include -lopencv_objdetect  -lopencv_videoio  -lopencv_imgcodecs  -lopencv_imgproc   -lopencv_core -lopencv_calib3d -lopencv_features2d -lopencv_flann -lopencv_highgui -lopencv_ml -lopencv_photo -lopencv_shape -lopencv_stitching -lopencv_superres -lopencv_video -lopencv_videostab -lpthread -lrt
biao@ubuntu:~/Opencv/Face_Detect$ ./test
Detecting face(s) in ./image/001.jpg
detection time = 376.387 ms
biao@ubuntu:~/Opencv/Face_Detect$

    可以看到图片中的人脸和眼睛被圈出来了(图片像素较低,看着怪怪的,哈哈~~)



    实际应用中并不是每一张图片都能像上面的宋美女和宋帅哥那样正看着镜头,图像背景环境也可能更加的复杂,比如下面的这张在下的抠鼻照,它就将我背后的那张纸判断为了一张人脸:



    
    对于上面的这种情况,如果要做人脸对比,那么我们只需要从图片中提取一张人脸,为了提高检测识别率,我们可以在OpenCV检测到的几张人脸中,提取那些有检测到眼睛的人脸,如果同时又多张人脸都有检测到眼睛,那么我们还可以通过检测到人脸的大小来筛选最合适的一个人脸,下面的代码主要实现的就是这个功能。

/*=============================================================================
#     FileName: facecheck.cpp
#         Desc: detect faces and eyes by opencv ,and then cut the face
#       Author: Licaibiao
#      Version:
#   LastChange: 2017-10-31
#      History:
=============================================================================*/
#include "opencv2/objdetect.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>

using namespace std;
using namespace cv;

#define CASCADENAME			"./data/haarcascade_frontalface_alt2.xml"
#define NESTEDCASCADENAME	"./data/haarcascade_eye_tree_eyeglasses.xml"
#define FACEPHOTO_FACENAME  "./image/result.jpg"
#define DETECT_IMAGE		"./image/biao.jpg"

int main(void)
{
CascadeClassifier cascade, nestedCascade;
VideoCapture capture;
Mat frame, image;
string inputName;
int ret = 0 ;
bool tryflip;
double scale = 1.3;

/* 加载分类器 */
if( !cascade.load(CASCADENAME) )
{
cerr << "ERROR: Could not load classifier cascade" << endl;
return -1;
}else
{
printf("[%s:%d] cascade.load ok !!! \n", __func__, __LINE__);
}

if( !nestedCascade.load(NESTEDCASCADENAME) )
{
cerr << "ERROR: Could not load classifier cascade" << endl;
return -1;
}
/* 加载图片 */
image = imread(DETECT_IMAGE, 1 );
if( !image.empty() )
{
double t = 0;
double fx = 1 / scale;
size_t i = 0;
Rect  r ;
vector<Rect> faces;
Mat gray, smallImg,img1;;

/*显示原始图片*/
imshow( "original", image);

/* 转换为灰度图片 */
cvtColor( image, gray, COLOR_BGR2GRAY );

/* 将尺寸缩小到1/scale, 用线性插值 */
resize( gray, smallImg, Size(), fx, fx, INTER_LINEAR );

/* 直方图均衡 */
equalizeHist( smallImg, smallImg );

/* 检测人脸 */
cascade.detectMultiScale( smallImg, faces, 1.1, 2, CASCADE_SCALE_IMAGE, Size(30, 30) );

/* 如果检测到多张人脸,再检测是否有眼睛。裁剪有眼睛的区域 */
if(faces.size() > 1)
{
/* 选择有检测到眼睛的区域 */
for(i = 0; i < faces.size(); i++)
{
r = faces[i];
Mat smallImgROI;
vector<Rect> eyes;

smallImgROI = smallImg(r);
printf("debug detect face num i = %d \n",i);

/* 检测眼睛 */
nestedCascade.detectMultiScale( smallImgROI, eyes, 1.1, 2, CASCADE_SCALE_IMAGE, Size(30, 30) );

/* 有检测到眼睛 */
if(eyes.size() > 0)
{
printf("debug detect eyes i = %d  num = %d \n",i ,eyes.size());
break ;
}

/* 没有检测到眼睛 */
else if(((i + 1 ) == faces.size())&&(0 == eyes.size()))
{
printf("[%s:%d] have detect face but no eyes  !!! \n", __func__, __LINE__);
return -1;
}
}
}
/* 检测到一张脸的时候,直接取该脸 */
else if(1 == faces.size())
{
r = faces[0] ;
printf("[%s:%d] use face num 0 \n", __func__, __LINE__);

}
/* 没有检测到眼睛 */
else
{
printf("none face detct \n");
return -1;
}

/*检测到脸的面积大于0 表示有检测到合格的脸*/
if(0 < r.area())
{
/* 调整检测大小 */
Mat roi= image(Rect(r.x*(scale-0.1), r.y*(scale-0.1), r.width*(scale+0.2),r.height*(scale+0.3)));

roi.copyTo(img1);

/* 将检测到的脸保存为图片*/
ret = imwrite(FACEPHOTO_FACENAME,img1);
printf("[%s:%d] creat detect face image ret = %d !!! \n", __func__, __LINE__, ret);
}else
{
printf("[%s:%d] no detect right face  !!! \n", __func__, __LINE__);
return -1;
}
/* 显示检测到的脸 */
imshow( "result", img1 );
}
waitKey(0);
return 0;
}


实际效果图如下:



同时在image 目录下生成result.jpg 图片:

biao@ubuntu:~/Opencv/Face_Detect/image$ ls
001.jpg  biao.jpg  result.jpg  Roi.jpg
biao@ubuntu:~/Opencv/Face_Detect/image$


    完整的代码可以这里下载:人脸检测与人脸图像提取(包含OpenCV 所有的头文件,库文件,训练文件)

biao@ubuntu:~/Opencv/Face_Detect$ tree -L 1
.
├── data
├── facecheck.cpp
├── facedetect.cpp
├── image
├── inlcude
├── lib
├── Makefile
├── out
└── test

5 directories, 4 files


    今天是双宋婚礼,两个都是比较喜欢的演员,就上张他两的合照以示祝福吧~~~

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息