前言:
什么是OpenCV?可能还有人不清楚吧,简单地说,OpenCV——Open Source Computer Vision Library,即开源计算机视觉库,它是基于C语言和部分C++语言来开发,可用于计算机视觉、图像处理以及模式识别和跟踪。
由于网上有很多Opencv源码编译的教程,这里我就直接拿编译好的Opencv在QT Create使用,首先先强调一下我使用的环境,针对的不同环境选择的版本也不同
我的电脑配置是Win10、qt-opensource-windows-x86-5.12.0,选择的opencv是64位的opencv3.4.4
将opencv的bin目录找到 :D:\opencv64\x64\mingw\bin
【P.S以上内容根据安装的Opencv库的路径,自行修改】
点开电脑的属性——>高级系统变量——>环境变量——>系统变量的Path——>点击新建——>将bin的目录添加进去
记住一定要点击确定,完成后,建议重启电脑
创建一个Qt桌面项目;
在.pro文件的底部添加
在main.cpp的代码
如果图片正常显示出来,那么恭喜你,opencv库在QT配置成功
Opecv在QT中配置成功,接下来我们就来实现人脸识别,人脸识别分三个步骤:1、图片采集和预处理 2、训练模型 3、将训练好的模型进行人脸识别
(1)下载数据集
本次用的数据集是opencv给出的教程里面的第一个数据集:The AT&T Facedatabase。又称ORL人脸数据库,40个人,每人10张照片。照片在不同时间、不同光照、不同表情(睁眼闭眼、笑或者不笑)、不同人脸细节(戴眼镜或者不戴眼镜)下采集。所有的图像都在一个黑暗均匀的背景下采集的,正面竖直人脸(有些有有轻微旋转)。
下载下来的文件名是 att_faces ,里面一共有40个文件夹,每个文件夹里面有10张图片。且每张图片都进行了预处理。
(2)采集自己的图片,对图片进行预处理
想要识别自己,单有别人的数据集还是不行的,还需要自己人脸的照片才行。这就需要我们收采集自己的照片,对采集的照片进行预处理,然后和上面的那个数据集一起来训练模型
使用Opencv官方训练好的人脸检测分类器(haarcascade_frontalface_alt2.xml)对采集的图片进行预处理。这个人脸识别分类器的路径是:D:\opencv64\etc\haarcascades\haarcascade_frontalface_alt2.xml,将此文件拷贝到工程目录文件下。
源代码:
#include
#include
#include
#include
using namespace std;
using namespace cv;
int main()
{
CascadeClassifier cascada;
//将opencv官方训练好的人脸识别分类器拷贝到自己的工程目录中
cascada.load("F:\\video\\pic\\haarcascade_frontalface_alt2.xml");
VideoCapture cap(0); //0表示电脑自带的,如果用一个外接摄像头,将0变成1
Mat frame, myFace;
int pic_num = 1;
while (1) {
//摄像头读图像
cap >> frame;
vector faces;//vector容器存检测到的faces
Mat frame_gray;
cvtColor(frame, frame_gray, COLOR_BGR2GRAY);//转灰度化,减少运算
cascada.detectMultiScale(frame_gray, faces, 1.1, 4, CV_HAAR_DO_ROUGH_SEARCH, Size(70, 70), Size(1000, 1000));
printf("检测到人脸个数:%d\n", faces.size());
//识别到的脸用矩形圈出
for (int i = 0; i < faces.size(); i++)
{
rectangle(frame, faces[i], Scalar(255, 0, 0), 2, 8, 0);
}
//当只有一个人脸时,开始拍照
if (faces.size() == 1)
{
Mat faceROI = frame_gray(faces[0]);//在灰度图中将圈出的脸所在区域裁剪出
//cout << faces[0].x << endl;//测试下face[0].x
resize(faceROI, myFace, Size(92, 112));//将兴趣域size为92*112
putText(frame, to_string(pic_num), faces[0].tl(), 3, 1.2, (0, 0, 225), 2, 0);//在 faces[0].tl()的左上角上面写序号
string filename = format("F:\\video\\%d.jpg", pic_num); //图片的存放位置,frmat的用法跟QString差不对
imwrite(filename, myFace);//存在当前目录下
imshow(filename, myFace);//显示下size后的脸
waitKey(500);//等待500us
destroyWindow(filename);//:销毁指定的窗口
pic_num++;//序号加1
if (pic_num == 11)
{
return 0;//当序号为11时退出循环,一共拍10张照片
}
}
int c = waitKey(10);
if ((char)c == 27) { break; } //10us内输入esc则退出循环
imshow("frame", frame);//显示视频流
waitKey(100);//等待100us
}
return 0;
}
至此,我们就得到和ORL人脸数据库人脸大小一致的自己的人脸数据集(10张图片)。然后我们把自己的作为第41个人,在我们下载的人脸文件夹(att_faces)下建立一个s41的子文件夹,把自己的人脸数据放进去。就成了这样下面这样,最后一个文件夹里面是我自己的头像照片
opencv3的人脸识别库等contrib模块已经不再内置了。所以要想使用这个功能的话就必须下载contrib模块https://github.com/opencv/opencv_contrib
下载完成后,解压找到\modules\face 然后include里面的就是opencv2,打开后就是face和face.hpp头文件,src里面是源文件
将include下的opencv2里面的face文件夹和face.hpp,以及src 导入到训练模型工程中(训练模型项目源码在下面),这时候运行的话会出现一系列问题,主要是文件中的包含关系的问题,改一下就行了,下面是我训练模型的源码,源码中已经改好了文件中的包含关系
链接:https://pan.baidu.com/s/1sR_voL2cD05y_1vdNObVug
提取码:v01i
有了源码后还是需要往下看的,不然里面有很多文件不明白是怎么生成的
数据集现在准备好了,我们写人脸模型训练程序的时候需要读取人脸和人脸对应的标签,直接读取显然是低效的,所以我们用csv文件读取。csv文件中包含两方面的内容,一是每一张图片的路径,二是每一个人脸对应的标签,幸好opencv教程里面为我们提供了自动生成csv文件的脚本(create_csv.py)。不然的话自己制作一个有410数据对的csv格式的数据还是挺费事的。脚本就是刚刚在github的下载的文件中,路径为:opencv_contrib-4.1.1\opencv_contrib-4.1.1\modules\face\samples\etc\create_csv.py
打开后是我们修改一下这个代码,BASE_PATH手动的改成自己的数据集路径,改完后代码如下:
#!/usr/bin/env python
import sys
import os.path
# This is a tiny script to help you creating a CSV file from a face
# database with a similar hierarchie:
#
# philipp@mango:~/facerec/data/at$ tree
# .
# |-- README
# |-- s1
# | |-- 1.pgm
# | |-- ...
# | |-- 10.pgm
# |-- s2
# | |-- 1.pgm
# | |-- ...
# | |-- 10.pgm
# ...
# |-- s40
# | |-- 1.pgm
# | |-- ...
# | |-- 10.pgm
#
if __name__ == "__main__":
#if len(sys.argv) != 2:
# print("usage: create_csv ")
# sys.exit(1)
BASE_PATH = "F:/video/att_faces" //数据集的路径
SEPARATOR = ";"
fh = open("F:/video/att_faces/at.txt",'w') //生成csv格式数据的路径
label = 0
for dirname, dirnames, filenames in os.walk(BASE_PATH):
for subdirname in dirnames:
subject_path = os.path.join(dirname, subdirname)
for filename in os.listdir(subject_path):
abs_path = "%s/%s" % (subject_path, filename)
print("%s%s%d" % (abs_path, SEPARATOR, label))
fh.write(abs_path)
fh.write(SEPARATOR)
fh.write(str(label))
fh.write("\n")
label = label + 1
fh.close()
至此,前期准备都做好了,接下来就是训练模型了,训练模型用到了opencv的Facerecognizer类。opencv中所有的人脸识别模型都是来源于这个类
FaceRecognizer这个类目前包含三种人脸识别方法:基于PCA变换的人脸识别(EigenFaceRecognizer)、基于Fisher变换的人脸识别(FisherFaceRecognizer)、基于局部二值模式的人脸识别(LBPHFaceRecognizer)。对于像我这样的人脸识别初学者,对人脸识别理论了解得不是很透彻,但并不影响对函数的使用,接下来就分别训练这三种人脸模型。这个时候就能体现Facerecognizer类的强大了。因为每一种模型的训练只需要三行代码
Ptr model = face::EigenFaceRecognizer::create();
model->train(images, labels);
model->save("MyFacePCAModel.xml");//保存路径可自己设置,但注意用“\\”
Ptr model1 = face::FisherFaceRecognizer::create();
model1->train(images, labels);
model1->save("MyFaceFisherModel.xml");
Ptr model2 = face::LBPHFaceRecognizer::create();
model2->train(images, labels);
model2->save("MyFaceLBPHModel.xml");
训练模型的源代码
每个函数的作用都写再代码上,大家只需要合并这三个函数即可成功训练模型
#include
#include "face.hpp" //添加到工程目录的
#include
#include
#include
#include
#include
#include
using namespace std;
using namespace cv;
static Mat norm_0_255(InputArray _src) {
Mat src = _src.getMat();
// 创建和返回一个归一化后的图像矩阵:
Mat dst;
switch (src.channels()) {
case 1:
cv::normalize(_src, dst, 0, 255, NORM_MINMAX, CV_8UC1);
break;
case 3:
cv::normalize(_src, dst, 0, 255, NORM_MINMAX, CV_8UC3);
break;
default:
src.copyTo(dst);
break;
}
return dst;
}
//使用CSV文件去读图像和标签,主要使用stringstream和getline方法
static void read_csv(const string& filename, vector& images, vector& labels, char separator = ';') {
ifstream file(filename.c_str(), ifstream::in);
if (!file)
{
string error_message = "No valid input file was given, please check the given filename.";
CV_Error(CV_StsBadArg, error_message);
}
string line, path, classlabel;
while (getline(file, line)) //从文本文件中读取一行字符,未指定限定符默认限定符为“/n”
{
stringstream liness(line);//这里采用stringstream主要作用是做字符串的分割
getline(liness, path, separator);//读入图片文件路径以分好作为限定符
getline(liness, classlabel);//读入图片标签,默认限定符
if (!path.empty() && !classlabel.empty()) //如果读取成功,则将图片和对应标签压入对应容器中
{
images.push_back(imread(path, 0));
labels.push_back(atoi(classlabel.c_str()));
}
}
}
int main()
{
//读取你的CSV文件路径.
string fn_csv = "F:\\video\\ccc\\at.txt";
// 2个容器来存放图像数据和对应的标签
vector images;
vector labels;
// 读取数据. 如果文件不合法就会出错
// 输入的文件名已经有了.
try
{
read_csv(fn_csv, images, labels); //从csv文件中批量读取训练数据
}
catch (cv::Exception& e)
{
cerr << "Error opening file \"" << fn_csv << "\". Reason: " << e.msg << endl;
// 文件有问题,我们啥也做不了了,退出了
exit(1);
}
// 如果没有读取到足够图片,也退出.
if (images.size() <= 1) {
string error_message = "This demo needs at least 2 images to work. Please add more images to your data set!";
CV_Error(CV_StsError, error_message);
}
for (int i = 0; i < images.size(); i++)
{
//cout< model = face::EigenFaceRecognizer::create();
model->train(images, labels);
model->save("MyFacePCAModel.xml");//保存路径可自己设置,但注意用“\\”
Ptr model1 = face::FisherFaceRecognizer::create();
model1->train(images, labels);
model1->save("MyFaceFisherModel.xml");
Ptr model2 = face::LBPHFaceRecognizer::create();
model2->train(images, labels);
model2->save("MyFaceLBPHModel.xml");
// 下面对测试图像进行预测,predictedLabel是预测标签结果
//注意predict()入口参数必须为单通道灰度图像,如果图像类型不符,需要先进行转换
//predict()函数返回一个整形变量作为识别标签
int predictedLabel = model->predict(testSample);//加载分类器
int predictedLabel1 = model1->predict(testSample);
int predictedLabel2 = model2->predict(testSample);
// 还有一种调用方式,可以获取结果同时得到阈值:
// int predictedLabel = -1;
// double confidence = 0.0;
// model->predict(testSample, predictedLabel, confidence);
string result_message = format("Predicted class = %d / Actual class = %d.", predictedLabel, testLabel);
string result_message1 = format("Predicted class = %d / Actual class = %d.", predictedLabel1, testLabel);
string result_message2 = format("Predicted class = %d / Actual class = %d.", predictedLabel2, testLabel);
cout << result_message << endl;
cout << result_message1 << endl;
cout << result_message2 << endl;
getchar();
//waitKey(0);
return 0;
}
成功训练模型后,会生成三个xml文件,这三个xml文件对应的是不同的人脸识别方法
现在我们有训练好的模型,只需要把模型拿出来用就可以进行人脸识别。这里我拿出的是基于Fisher变换的人脸识别训练的模来进行人脸识别。 人脸识别的过程:1、打开摄像头 2、加载人脸检测分类器(haarcascade_frontalface_alt2.xml)和人脸模型 MyFaceFisherModel.xml 3、检测人脸 4、把检测到的人脸与人脸模型里面的对比,找出这是谁的脸。 5、如果人脸是自己拍照的人脸,显示自己的名字。
利用训练好的模型进行人脸识别代码
#include
#include
#include
#include
#include
#include
#include "face.hpp"
using namespace std;
using namespace cv;
RNG g_rng(12345);
Ptr model;
int Predict(Mat src_image) //识别图片
{
Mat face_test;
int predict = 0;
//截取的ROI人脸尺寸调整
if (src_image.rows >= 120)
{
//改变图像大小,使用双线性差值
resize(src_image, face_test, Size(92, 112));
}
//判断是否正确检测ROI
if (!face_test.empty())
{
//测试图像应该是灰度图
predict = model->predict(face_test);
}
cout << predict << endl;
return predict;
}
int main()
{
VideoCapture cap(0); //打开默认摄像头
if (!cap.isOpened())
{
return -1;
}
Mat frame;
Mat gray;
//这个分类器是人脸检测所用
CascadeClassifier cascade;
bool stop = false;
//训练好的文件名称,放置在可执行文件同目录下
cascade.load("haarcascade_frontalface_alt2.xml");
model = face::FisherFaceRecognizer::create();
//1.加载训练好的模型
model->read("MyFaceFisherModel.xml");// opencv2用load
while (1)
{
cap >> frame;
vector faces(0);//建立用于存放人脸的向量容器
cvtColor(frame, gray, CV_RGB2GRAY);//测试图像必须为灰度图
equalizeHist(gray, gray); //变换后的图像进行直方图均值化处理
//检测人脸
cascade.detectMultiScale(gray, faces,
1.1, 4, 0
//|CV_HAAR_FIND_BIGGEST_OBJECT
| CV_HAAR_DO_ROUGH_SEARCH,
//| CV_HAAR_SCALE_IMAGE,
Size(30, 30), Size(500, 500));
Mat* pImage_roi = new Mat[faces.size()]; //定以数组
Mat face;
Point text_lb;//文本写在的位置
//框出人脸
string str;
for (int i = 0; i < faces.size(); i++)
{
pImage_roi[i] = gray(faces[i]); //将所有的脸部保存起来
text_lb = Point(faces[i].x, faces[i].y);
if (pImage_roi[i].empty())
continue;
cout<
结果:(大功告成)
能成功完成人脸识别,特别感谢
https://blog.csdn.net/qq_37791134/article/details/81385848 ——基于OpenCV3实现人脸识别(实践篇)
https://blog.csdn.net/sinat_37185987/article/details/82956367 ——opencv3人脸识别问题解决
https://blog.csdn.net/xingchenbingbuyu/article/details/51386949 ——OpenCV实践之路——人脸识别之一数据收集和预处理