基于OpenCV4.1.0实现静态图片人脸检测

1,830 阅读8分钟

摘要:今天花了一天的时间完成了静态图片中的人脸检测,这是对计算机视觉的第一次直观体验,颇有成就感。这篇文章详细的解释了源程序每一行语句的作用,主要还是理清自己大脑的思路。话不多说,直接开撸!

源代码

注:本文所用开发环境为VS2019 Community + OpenCV4.1.0,需提前配置好环境,如需帮助,请关注公众号【鹿谈】,看我的第一篇推文。

//FaceRec.cpp

#include<opencv2/opencv.hpp>
//#include<opencv2/objdetect/objdetect.hpp>
//#include<opencv2/highgui/highgui.hpp>
//#include<opencv2/imgproc/imgproc.hpp>
#include <opencv2\imgproc\types_c.h>

using namespace std;
using namespace cv;

CascadeClassifier faceCascade;                  //人脸检测的类

int main()
{     faceCascade.load("D:/openCV/opencv/sources/data/haarcascades/haarcascade_frontalface_alt2.xml");   //加载分类器,注意文件路径

	Mat img = imread("3ming.jpg");    //图片放在与FaceRec.cpp同级目录中
	Mat imgGray;
	vector<Rect> faces;

	if (img.empty())
	{
		return 1;
	}

	if (img.channels() == 3)
	{
		cvtColor(img, imgGray, CV_RGB2GRAY);
	}
	else
	{
		imgGray = img;
	}

	faceCascade.detectMultiScale(imgGray, faces, 1.2, 6, 0, Size(0, 0));   //检测人脸

	if (faces.size() > 0)
	{
		for (int i = 0; i < faces.size(); i++)
		{
			rectangle(img, Point(faces[i].x, faces[i].y), Point(faces[i].x + faces[i].width, faces[i].y + faces[i].height),
				Scalar(0, 0, 255), 4, 8);    //框出人脸位置
		}
	}

	imshow("FacesOf3ming", img);

	waitKey(0);
	return 0;
}

注:本例用的明家三兄弟的照片。最好找正脸的照片,放在FaceRec.cpp同级目下即可,代码中的图片名换成自己的。

代码详细解析

头文件部分

#include<opencv2/opencv.hpp>
//#include<opencv2/objdetect/objdetect.hpp>
//#include<opencv2/highgui/highgui.hpp>
//#include<opencv2/imgproc/imgproc.hpp>
#include <opencv2\imgproc\types_c.h>

找到OpenCV的安装目录,打开D:\openCV\opencv\sources\include\opencv2,会看到opencv.hpp文件,我们用记事本打开它,可以发现里边包含了很多OpenCV的头文件,我们注释掉的三个头文件也包含于内。所以我们可以用#include<opencv2/opencv.hpp>一句代替下边注释掉的三行头文件,有效简化代码。而最后的#include <opencv2\imgproc\types_c.h>位于D:\openCV\opencv\build\include\opencv2\imgproc内,不包含在opencv.hpp之内,所以这句#include <opencv2\imgproc\types_c.h>要加上。那么为什么用到了这些头文件呢?

1.把#include <opencv2\imgproc\types_c.h>注释掉后,会提示未定义标识符"CV_RGB2GRAY",估计该标志符位于<opencv2\imgproc\types_c.h>这个头文件。

2.跟1同样的方法,cvtColor()函数和Rectangl()函数应该位于<opencv2/imgproc/imgproc.hpp>

3.同上,imread()imshow()waitkey()位于<opencv2/highgui/highgui.hpp>

4.同上,CascadeClassifier类应该位于<opencv2/objdetect/objdetect.hpp>

命名空间部分

using namespace std;
using namespace cv;

using namespace std;不用多说,C++标准程序库的所有标识符都被声明在std命名空间内,经常用到的cincoutendl等标识符皆如此,所以基本上所有的C++程序都有这句指令。而本段函数中的vector标识符就属于std命名空间,故有此指令。

对于using namespace cv;OpenCV官方文档如是说:All the OpenCV classes and functions are placed into the cv namespace. Therefore, to access this functionality from your code, use the cv:: specifier or using namespace cv; directive. (翻译:所有的OpenCV类和函数都放在cv命名空间中。因此,要从代码中访问此功能,请使用说明cv::符或using namespace cv;指令),故有此指令。

级联分类器类CascadeClassifier

CascadeClassifier faceCascade;  //定义一个CascadeClassifier类的对象faceCascade

CascadeClassifier:Cascade classifier class for object detection(用于目标检测的级联分类器类)。也就是说,CascadeClassifier是一个。那么什么是级联分类器类呢?

理解级联分类器

分类器: 判别某个事物是否属于某种分类的器件。它有两种结果: 。 级联分类器: 可以理解为将N个单类的分类器串联起来。如果一个事物能属于这一系列串联起来的的所有分类器,则最终结果就为,若有一项不符,则判定为

比如人脸,它有很多属性,我们将每个属性做一成个分类器,如果一个模型符合了我们定义的人脸的所有属性,则我们人为这个模型就是一个人脸。那么这些属性是指什么呢? 比如人脸需要有两条眉毛,两只眼睛,一个鼻子,一张嘴,一个大概U形状的下巴或者是轮廓等等。

(引用自arvik的博文opencv类简单分析: CascadeClassifier

主函数部分

faceCascade.load("D:/openCV/opencv/sources/data/haarcascades/haarcascade_frontalface_alt2.xml");   //加载分类器,注意文件路径

Mat img = imread("3ming.jpg");    //图片放在与FaceRec.cpp同级目录中
Mat imgGray;
vector<Rect> faces;
  • faceCascade.load("D:/openCV/opencv/sources/data/haarcascades/haarcascade_frontalface_alt2.xml");

    调用对象faceCascadeload()函数,其参数为.xml文件的绝对路径。

    load()函数的作用是通过load一个xml文件来初始化对象faceCascade.xml是一些分类器的格式,我们这里用到的haarcascade_frontalface_alt2.xml是OpenCV内置的已经训练好的诸多分类器中的一个,它的作用是Haar特征人脸检测。

    .xml的路径要写正确,严格按照上面的格式。电脑上复制路径是右斜杠'\',但在程序里要用左斜杠'/'。

  • Mat img = imread("3ming.jpg");

    定义一个Mat类的对象img,并用imread()函数加载指定文件3ming.jpg赋值给对象img

    Mat是一个:OpenCV库C++实现的核心类。

    Mat 类由两个数据部分组成:矩阵头(包含矩阵尺寸,存储方法,存储地址等信息)和一个指向存储所有像素值的矩阵(根据所选存储方法的不同矩阵可以是不同的维数)的指针。

    Mat 类首先要知道的是不必再手动(1)为其开辟空间(2)在不需要时立即将空间释放。即自动内存管理。

    imread()函数的功能是载入一张图片,参数为图片的路径。

  • Mat imgGray;

    定义一个Mat类的对象imgGray

  • vector<Rect> faces;

    vector定义一个元素类型为Rect类的动态数组faces

    vector是C++标准库提供的被封装的动态数组,它不是一个类,而是一个类模板。用vector定义的数组对象的所有元素都会被初始化。如果数组元素为基本类型,则以初始化;如果数组元素为类类型,则调用默认构造函数初始化。此处,数组元素为Rect类类型,故将调用Rect类的默认构造函数初始化数组。

    Rect类,矩形类。它有四个公共属性,Rect(int_x,int_y,int_width,int_height),(x,y)为左上角顶点的坐标,width为宽度,height为高度。

if (img.empty())   {return 1;}

调用Mat类对象img的成员函数empty()来检查3ming.jpg是否载入成功,如成功,则返回1。

if (img.channels() == 3)  {cvtColor(img, imgGray, CV_RGB2GRAY);}
else  {imgGray = img;}

调用Mat类对象img的成员函数channels(),其会返回矩阵通道的数量。如果矩阵通道数量为3,则调用cvtColor()函数,将输入的img从转换为imgGray输出,转换模式为CV_RGB2GRAY,即将3通道的彩色影像转换为灰度图输出;如果矩阵通道不为3,则直接将img赋给imgGray

cvtColor()函数:Opencv里的颜色空间转换函数,可以实现RGB颜色向HSV,HSI等颜色空间的转换,也可以转换为灰度图。第一个参数为输入,第二个参数为输出,第三个参数为转换模式。那么为什么将RGB图像转为灰度图呢?

1.三通道转为单通道后,运算量大大减少。

2.自然界中,颜色本身非常容易受到光照的影响,RGB变化很大,反而梯度信息能提供更本质的信息。

3.Opencv的很多函数只支持单通道。

	faceCascade.detectMultiScale(imgGray, faces, 1.2, 6, 0, Size(0, 0));

调用CascadeClassifier类对象faceCascade的成员函数detectMultiScale(),对输入的灰度图imgGray进行检测,将检测到的人脸的矩形框向量组存入faces,尺度参数为1.2,每一个目标至少要被检测到6次才算是人脸,flag为0,最小可能的对象大小为Size(0,0),小于该值的对象将被忽略。

detectMultiScale()函数:在输入的图片中检测不同大小的目标,检测到的目标作为矩形列表返回。

if (faces.size() > 0){
		for (int i = 0; i < faces.size(); i++){
			rectangle(img, Point(faces[i].x, faces[i].y), Point(faces[i].x + faces[i].width, faces[i].y + faces[i].height),
				Scalar(0, 0, 255), 4, 8);    //框出人脸位置
		}
}

调用数组元素为Rect类类型的动态数组faces的成员函数size(),如果face.size()大于0,即矩阵数组不为空,也就是说检测到目标了,则执行for循环,将检测到的目标框起来。

rectangle()函数:绘制简单、指定粗细或带填充的矩形。

Rectangle( CvArr* img, CvPoint pt1, CvPoint pt2, CvScalar color, int thickness=1, int line_type=8, int shift=0 );

1.第一个参数为img,也就是指定在哪个图上画框,对于本例则为img

2.第二个参数为矩形的左上角顶点的坐标,对应本例为Point(faces[i].x, faces[i].y)

3.第三个参数为矩形对角线上的另一个顶点,对应本例为Point(faces[i].x + faces[i].width, faces[i].y + faces[i].height)

4.第四个参数为线条颜色,Scalar(B,G,R)有三个参数,分别对应Blue、Green、Red,本例将框设置为红色,即Scalar(0,0,255)。

5.第五个参数为线条粗细,数越大线条越粗,本例设置为4。

6.第六个参数似乎是线条类型。有待考证。

imshow("FacesOf3ming", img);
waitKey(0);
return 0;

执行完循环,检测到的目标已全部框出,调用imshow()函数,将框完人脸的图片img显示出来。

imshow()函数有两个参数,第一个是显示图片窗口的名称,第二个是储存图像数据的对象。

waitkey(0)函数在显示图像时具有延时的作用,单位为毫秒。若参数为0,则图像不会自动关闭,会无限等待下去直到有按键按下。

程序运行结束,return 0

程序运行结果

可以看到,明家三兄弟的脸已经被红框框起来了。静态图片中人脸检测成功实现。

总结

本来想写很多,却又写不出了。就希望自己能学到想学的知识,氧氧能快快乐乐的吧。