基于OpenCV4.1.0实现静态图片人脸检测
摘要:今天花了一天的时间完成了静态图片中的人脸检测,这是对计算机视觉的第一次直观体验,颇有成就感。这篇文章详细的解释了源程序每一行语句的作用,主要还是理清自己大脑的思路。话不多说,直接开撸!
源代码
注:本文所用开发环境为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
命名空间内,经常用到的cin
、cout
、endl
等标识符皆如此,所以基本上所有的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");
调用对象
faceCascade
的load()
函数,其参数为.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
。
程序运行结果
可以看到,明家三兄弟的脸已经被红框框起来了。静态图片中人脸检测成功实现。
总结
本来想写很多,却又写不出了。就希望自己能学到想学的知识,氧氧能快快乐乐的吧。