成果展示放在前面

代码下载链接https://download.csdn.net/download/qq_37071435/12277061

打包好的软件链接https://download.csdn.net/download/qq_37071435/12270477

opencv4.2+contribute https://download.csdn.net/download/qq_37071435/12265358

注意:打包的软件中没有更新中文名的输入,只支持英文,环境为vs2019+opencv4.2+qt

主要功能:利用vs的qt扩展模块,编写了GUI,通过按键,可以实现用户自己进行拍照,将自己的照片存入照片库中,通过按键进行训练,然后打开摄像头,进行人脸识别。

主要参考文章:

https://blog.csdn.net/qq_37791134/article/details/81385848?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

主要程序还是参考的这篇博客,真的学到了很多。

在人脸显示名字的时候调用的putText()函数,只能显示英文,所以在csdn中搜索到了这篇博客,直接解决了问题

https://blog.csdn.net/weixinhum/article/details/84074594

下面依次讲解各个函数

首先是读取目标文件夹下的文件目录

如图,face_database为我放置人脸照片和at.txt文件的目标文件夹(下面称其为目标文件夹),该文件夹下的子文件夹的名字从0开始,都为正整数,依次增加,其中任意一个子文件夹中都有十张人脸照片(除了0),这里我用的是.pgm格式的照片,当然.jpg格式也可以.

at.txt文件是在后边训练时所用到的一个存放了所有图像路径的文本文件,可以看到,里面每一行的格式为:图片路径;label;人名。

label与所在的文件夹的名字相同。我在label后边加了一个人名,是为了方便在识别人脸的时候可以从文件中读取对应的名字,这里只填了一个标签,右下角被黄色荧光笔涂抹的地方可以看见"ANSI",这个表明该文本的编码与本机默认编码相同,一般文本默认的编码方式是UTF8,所以要注意改成ANSI。点击文件->另存为,r然后更改编码方式,覆盖原文件就可以了。

at.txt文件其实叫做csv文件,但是它的本质就如我们所见,是一个可写的文本文件,我刚开始也查了很多资料去生成他,有各种方式,但最后我选择了比较笨的自己写入,通过调用C++的fstream来写入我想要的格式。

主要思路是:读取文件夹的名字,生成label。因为文件夹的名字是正整数,所以最后一个文件夹的名字就是含有照片文件夹的数量(文件夹名为0的文件夹内不含照片,它的存在只是起到方便累加的作用)

这里先把所有函数的声明放在这里,避免在下面函数介绍的时候,不知道某个函数的作用。

//GUI
class facerecognizer_gui : public QMainWindow
{Q_OBJECTpublic:facerecognizer_gui(QWidget *parent = Q_NULLPTR);~facerecognizer_gui();private:Ui::facerecognizer_guiClass ui;Mat image;QLabel* label_3;//输入的人名TEXTEDITQTextEdit* EnterLabel;
private slots://若函数为on_名字_clicked() 会触发两次 注意void ShowButton_clicked();//人脸识别void TakePhoto_clicked();//照相void Train_clicked();//训练void CloseCarmera_clicked();//关闭摄像头void closeEvent(QCloseEvent* e);//关闭窗口事件
};//Label显示Mat图像
void LabelDisplayMat(QLabel* label, cv::Mat& mat);
//从at.txt中读取信息,初始化LabelName
void InitLabelName();//获得目标文件夹下的所有文件夹名 存入向量中
void getSubdirs(std::string path, std::vector<std::string>& files);
//sort的bool参数
bool sort_fun(const string& p1, const string& p2);
//返回目标文件夹下的文件夹名中数字最大的值
int back_file_number();
//outi:为新建文件夹的的名字
//label:标签 int类型
void write_at(int outi, int label);
//string 转化成int型
int StringToInt(string a);
//读取at.txt
void read_csv(const string& filename, vector<Mat>& images,vector<int>& labels, char separator);
//进行训练 生成xml文件
int TrainAndXml(void);
//识别图片 返回识别的序号
int Predict(Mat src_image);

这里是读取目标文件夹下的各个文件夹名字所借鉴的博客

https://blog.csdn.net/leo_888/article/details/80681184?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

void getSubdirs(std::string path, std::vector<std::string>& files)
{long long hFile = 0;//注意如果是long handle则在64位下会出现异常struct _finddata_t fileinfo;std::string p;if ((hFile = _findfirst(p.assign(path).append("/*").c_str(), &fileinfo)) != -1){do{if ((fileinfo.attrib & _A_SUBDIR)){if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)files.push_back(fileinfo.name);}} while (_findnext(hFile, &fileinfo) == 0);_findclose(hFile);}
}

下面为为了生成at.txt的各个函数 ,主要思路是,读取目标文件夹下的文件夹名,将其转化为int,进行比较,取最大值,便可知道目标文件夹下有过少个含有照片的文件夹了。

//sort的bool参数
bool sort_fun(const string& p1, const string& p2)
{stringstream stream;int number1 = 0, number2 = 0;stream << p1; stream >> number1; stream.clear();stream << p2; stream >> number2; stream.clear();return number1 > number2;//降序排列
}
//返回目标文件夹下的文件夹名中数字最大的值
int back_file_number()
{string inPath = "Resources/face_database/";vector<string> filename;//获取目标文件夹下的文件夹名的vectorgetSubdirs(inPath, filename);//排序,取最大值,即目标文件夹下所含有的文件夹的数量sort(filename.begin(), filename.end(), sort_fun);string out = *filename.begin();return StringToInt(out);
}
//outi:为新建文件夹的的名字
//label:标签 int类型
//为at.txt写入内容
void write_at(int outi, int enterlabel)
{int i = 1;ofstream outfile("Resources/face_database/at.txt", ofstream::app);string temp = "Resources\\face_database";string dirName = temp + "\\" + to_string(outi);_mkdir(dirName.c_str());//_mkdir()创建成功返回0,失败返回-1。//分别写入十张照片的路径,label与姓名while (i != 11){outfile << temp << "\\" << outi << "\\" << i << ".pgm;" << enterlabel << ";" <<         LabelName[outi] << endl;i++;}outfile.close();
}
//利用stringstream来转换string成int
int StringToInt(string a)
{int ai = 0;stringstream stream;stream << a;stream >> ai;stream.clear();return ai;
}

通过上面的函数,我们的at.txt文件就写好了。熟悉了照片的存储路径与方式,下面还需要先了解一下一些全局变量,就可以进行拍照存储了。LabelName会在人脸识别函数前讲解。

//LabelName key存放序号 value存放人名
map<int,string> LabelName;
int map_size = 0;//map_size为map中含有的数量
//导入其他程序中的引用
/**.xml文件位置 */
String face_cascade_name = "Resources/haarcascades/haarcascade_frontalface_default.xml";
String eyes_cascade_name = "Resources/haarcascades/haarcascade_eye_tree_eyeglasses.xml";
String window_name = "Capture - Face detection";
String haarcascade_frontalface_alt2 = "Resources/haarcascades/haarcascade_frontalface_alt2.xml";RNG g_rng(12345);
Ptr<FaceRecognizer> model;
int NumberOfPeople = 1;
//检测是否关闭摄像头
//1 为关闭 0 为打开
int flag = 0;
//全程检测摄像头的状态
VideoCapture cap;

下面使拍照的函数,该函数是通过TakePhoto按键触发的,对于qt按键触发的函数,可以自己命名也可以用默认的,这里是自己命名的,需要注意,要将该函数,在ui界面中加入槽函数中。

void facerecognizer_gui::TakePhoto_clicked()
{//获取textEdit中的文本QString strTxtEdt = ui.name->toPlainText();string LabelString;//如果再拍照前没有输入名字的话,会跳出警告窗口if (strTxtEdt == ""){QMessageBox::warning(NULL, "warning", "if you do not enter your name,sorry you will stay here",QMessageBox::Yes);}else if (!cap.isOpened())//检测摄像头是否开着,避免与人脸检测同时使用摄像头{//获取输入的人名LabelString = strTxtEdt.toStdString();//避免拍照未完成就连续请求拍照,使拍照键无法使用,直到拍完十张后才恢复ui.TakePhoto->setEnabled(false);//按下按键后改变按键上的图案QIcon icon;icon.addFile(tr("Resources/takephoto_down.jpg"));ui.TakePhoto->setIcon(icon);ui.TakePhoto->setIconSize(QSize(100, 30));//a 记录拍照的状态,在GUI上显示QString a;//要建立文件夹的名字int outi = back_file_number() + 1;//将label与名字存入map中 方便后边查询LabelName[outi] = LabelString;map_size++;//建立文件夹并更新at.txtwrite_at(outi, outi);//加载模型 检测人脸 进行拍照CascadeClassifier cascada;cascada.load(haarcascade_frontalface_alt2);cap.open(0);Mat frame, myFace;int pic_num = 1;a = "taking";ui.label_3->setText(a);while (1) {//摄像头读图像cap >> frame;vector<Rect> faces;//vector容器存检测到的facesMat frame_gray;cvtColor(frame, frame_gray, COLOR_BGR2GRAY);//转灰度化,减少运算cascada.detectMultiScale(frame_gray, faces, 1.1, 4, 0);printf("检测到人脸个数:%d\n", (int)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]);//在灰度图中将圈出的脸所在区域裁剪出//faces[0]即为检测到的唯一的人脸的矩阵cv::resize(faceROI, myFace, Size(92, 112));//将faceROI裁为92*112赋值给myFaceputText(frame, to_string(pic_num), faces[0].tl(), 3, 1.2, (0, 0, 225), 2, 0);//在 faces[0].tl()的左上角上面写序号string filename = format("Resources/face_database/%d/%d.pgm", outi, pic_num);imwrite(filename, myFace);//存在当前目录下LabelDisplayMat(ui.photo, myFace);waitKey(500);//等待500uspic_num++;//序号加1if (pic_num == 11){break;//当序号为11时退出循环}}}a = "over";ui.label_3->setText(a);icon.addFile(tr("Resources/takephoto.jpg"));ui.TakePhoto->setIcon(icon);ui.TakePhoto->setIconSize(QSize(100, 30));ui.photo->setPixmap(QPixmap(tr("Resources/photoback.jpg")));//关闭摄像头cap.release();//回复按键ui.TakePhoto->setEnabled(true);}}

从函数可以知道,处理过后的照片大小为112*92像素 的灰度图。

经过上述的步骤,我们的照片也照好了,下一步该进行训练了。

//按键触发的槽函数
void facerecognizer_gui::Train_clicked()
{//a用来记录训练的状态QString a;a = "training";ui.label_6->setText(a);ui.Train->setEnabled(false);QIcon icon;icon.addFile(tr("Resources/train_down.jpg"));ui.Train->setIcon(icon);ui.Train->setIconSize(QSize(100, 30));//调用训练函数TrainAndXml();a = "over";ui.label_6->setText(a);icon.addFile(tr("Resources/train.jpg"));ui.Train->setIcon(icon);ui.Train->setIconSize(QSize(100, 30));ui.Train->setEnabled(true);
}
//训练函数
int TrainAndXml()
{//读取你的CSV文件路径.   //string fn_csv = "H:/face_database/at.txt";string fn_csv = "Resources/face_database/at.txt";// 2个容器来存放图像数据和对应的标签  vector<Mat> images;vector<int> 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);}//加载特征脸训练模型Ptr<BasicFaceRecognizer> model = EigenFaceRecognizer::create();model->train(images, labels);model->save("Resources/xml_file/PCA_train.xml");// 下面对测试图像进行预测,predictedLabel是预测标签结果  //注意predict()入口参数必须为单通道灰度图像,如果图像类型不符,需要先进行转换//predict()函数返回一个整形变量作为识别标签return 1;
}

其中读取at.txt文件的函数read_csv();注意对at.txt文件中的图片路径和abel的读取

void read_csv(const string& filename, vector<Mat>& images, vector<int>& labels, char separator = ';')
{std::ifstream file(filename.c_str(), ifstream::in);//c_str()函数可用可不用,无需返回一个标准C类型的字符串//检测文件是否打开if (!file){string error_message = "No valid input file was given, please check the given filename.";CV_Error(CV_StsBadArg, error_message);}//line:整行读入//path:文件夹路径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()));}}
}

终于到了最后一步人脸识别了,由于名字也存储在at.txt文件中,所以在每次人脸识别之前,我们一定要从at.txt文件中读取相应label多对应的人名,所以我们声明了一个map类型的变量LabelName来存放label和对性的人名,并写了一个函数来初始化他,并在GUI的构造函数中调用。

void InitLabelName()
{ifstream file("Resources/face_database/at.txt", ifstream::in);string line;while (getline(file, line)) //从文本文件中读取一行字符,未指定限定符默认限定符为“/n”{//;作为分隔符char separator = ';';string path;string classlabel;string name;stringstream liness(line);//这里采用stringstream主要作用是做字符串的分割getline(liness, path, separator);//读入图片文件路径以分好作为限定符getline(liness, classlabel,separator);//读入图片标签,默认限定符getline(liness, name);if (!path.empty() && !classlabel.empty()) //如果读取成功,则将图片和对应标签压入对应容器中{//将Label和name加入字典LabelName[StringToInt(classlabel)] = name;map_size++;}}file.close();
}

LabelName介绍完之后,我们便可以开始进行人脸识别函数的介绍了。

//showbutton为开始人脸识别的按键的名字
void facerecognizer_gui::ShowButton_clicked()
{if (!cap.isOpened())//避免了与拍照同时进行{ui.ShowButton->setEnabled(false);QIcon icon;icon.addFile(tr("Resources/open_camera_down.jpg"));ui.ShowButton->setIcon(icon);ui.ShowButton->setIconSize(QSize(100, 30));icon.addFile(tr("Resources/close_camera.jpg"));ui.CloseCamera->setIcon(icon);ui.CloseCamera->setIconSize(QSize(100, 30));cap.open(0);    //打开默认摄像头  if (!cap.isOpened()) {}Mat frame;Mat gray;//这个分类器是人脸检测所用CascadeClassifier cascade;bool stop = false;//训练好的文件名称,放置在可执行文件同目录下  cascade.load(haarcascade_frontalface_alt2);//感觉用lbpcascade_frontalface效果没有它好,注意哈!要是正脸model = FisherFaceRecognizer::create();//1.加载训练好的分类器model->read("Resources/xml_file/PCA_train.xml");// opencv2用read//3.利用摄像头采集人脸并识别while (!flag){cap >> frame;vector<Rect> faces(0);//建立用于存放人脸的向量容器cvtColor(frame, gray, CV_RGB2GRAY);//转化为灰度图equalizeHist(gray, gray); //变换后的图像进行直方图均值化处理  //检测人脸cascade.detectMultiScale(gray, faces, 1.1, 4, 0);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;//获取对应的labelint key = Predict(pImage_roi[i]);if (LabelName.find(key) != LabelName.end())str = LabelName[key];else str = "陌生人";Scalar color = Scalar(g_rng.uniform(0, 255), g_rng.uniform(0, 255), g_rng.uniform(0, 255));//所取的颜色任意值rectangle(frame, Point(faces[i].x, faces[i].y), Point(faces[i].x + faces[i].width, faces[i].y + faces[i].height), color, 1, 8);//放入缓存//putText(frame, str, text_lb, FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255));//添加文字putTextZH(frame, str.c_str(), text_lb, Scalar(255, 0, 0), 20, "华文行楷");}delete[]pImage_roi;waitKey(200);LabelDisplayMat(ui.label, frame);if (flag){flag = 0;icon.addFile(tr("Resources/open_camera.jpg"));ui.ShowButton->setIcon(icon);ui.ShowButton->setIconSize(QSize(100, 30));ui.label->setPixmap(QPixmap(tr("Resources/cameraback.jpg")));break;}}if (cap.isOpened())cap.release();//摄像头关闭ui.ShowButton->setEnabled(true);}}

其中Predict函数,返回的是int的label,通过map我们便可以直接通过键值,找到label对应的人名然后进行输出了,LabelName[label]。

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));}//判断是否正确检测ROIif (!face_test.empty()){//测试图像应该是灰度图  predict = model->predict(face_test);}cout << predict << endl;return predict;
}

以上为程序中的主要函数的介绍。

如果有问题欢迎一起探讨,我也是通过阅读大牛的文章一点一点学习的,希望能共同进步。

如果文章中有错误,欢迎指出,一定及时修改。

vs+qt 人脸识别GUI相关推荐

  1. PCA人脸识别GUI(ORL+Yale人脸库)

    该系统为基于MATLAB平台的PCA的人脸识别系统,可识别ORL和YALE人脸库,方法实现统一,包括GUI界面.另外可二次开发成摄像头的实时人脸系统,识别出库外人脸,可做成门禁系统,考勤系统,打卡签到 ...

  2. Linux毕业设计:基于OpenCV和QT库实现的人脸识别考勤/门禁系统(arm嵌入式ubuntu)

    本文介绍:Linux上以opencv和qt库实现的人脸识别系统,可应用于考勤.门禁等场景,具有人脸录入.删除.人脸检测.识别.用户管理等完整功能.可运行于ARM嵌入式linux.ubuntu即纯软件. ...

  3. 基于百度云的人脸识别打卡系统设计

    ***QT人脸识别打卡系统设计*** 系统设计需求 计划做一个基于人脸识别的打卡系统,软件开发平台借助QT软件,人脸识别的模型直接调用百度人脸识别算法,使用在线API的调用,完成人脸识别分析.人脸库是 ...

  4. 基于SeetaFace+VS2017+Qt的人脸识别

    1 目的 目前计算机视觉技术已经比较成熟,相关的开源项目与算法很多,可以将这些开源算法进行整合,进而做成一个小项目,以供日后学习与研究.本实验主要将利用人脸识别开源项目SeetaFace,结合使用Op ...

  5. 基于QT的人脸识别考勤管理系统【二】

    前言: 上一篇我们实现了考勤管理系统的用户考勤打卡系统https://blog.csdn.net/qq_42449351/article/details/99716413,这一篇我将为大家带来这个系统 ...

  6. 基于人脸识别的课堂签到管理系统(一)---环境设置以及简单的QT界面设计

    基于人脸识别的课堂签到管理系统(一)---环境设置以及简单的QT界面设计 一.前言 二.Pycharm安装与环境配置 2.1 Pycharm安装配置 2.2 Pycharm环境配置 三.QT界面设计 ...

  7. ubuntu下用Qt实现人脸识别之检测人脸并绘制人脸框(三)

    ubuntu下用Qt实现人脸识别之检测人脸并绘制人脸框(三) 要检测出人脸并且还要识别出这个人是谁,就得用到人脸算法,这个算法如果你足够牛X的话可以自己写出来,当然,如果像我一样是个小菜鸟的话就得领悟 ...

  8. 基于Qt Creator的OpenCV人脸识别签到项目系列教程(一) - 人脸信息采集

    实现人脸信息采集需要下载XML训练文件:https://download.csdn.net/download/m0_66984268/80272590 源代码: .pro文件 QT += core g ...

  9. 基于Matlab人脸识别签到系统(GUI界面)

    文件大小:5.3M 代码行数:298行(主程序) 开发环境:Matlab2016.2017.2018.2020.2021 点击下载:点击下载 简要概述:基于Matlab人脸识别签到系统(GUI界面) ...

  10. 基于QT的人脸识别考勤管理系统【一】

    前言: 上篇我们已经用opencv实现了人脸识别https://blog.csdn.net/qq_42449351/article/details/99052241,现在我们就用人脸识别来做一个考勤管 ...

最新文章

  1. 百个JavaScript函数以及基础写法汇总
  2. linux系统无线驱动在哪下载,在linux上怎么安装无线网卡驱动?
  3. 换硬币c语言编程_为什么大多数程序员都会学C语言的5大原因!什么原因让你学不会?...
  4. SM04 在线用户管理(踢人事务)
  5. Linux之GDB调试命令
  6. kernel开启启动log_MySql启动数据库设置初始密码
  7. codeforces 796A Buying A House
  8. python数据结构剑指offer-反转链表
  9. 战疫,微软资深高管的十余年远程办公管理经验
  10. html form 返回的数据类型,HTML表单
  11. MySQL FAQs (持续更新中)
  12. BZOJ 1833: [ZJOI2010]count 数字计数
  13. CentOS 6.5忘记root密码,怎么办?
  14. 吃鸡服务器维护公告10月19日,绝地求生10月30日维护公告更新内容 今天吃鸡几点开服时间...
  15. 工程经济—技术方案经济效果评价
  16. 【linux】按键盘Backspace键终端出现^H
  17. 北京邮电计算机课程表,北京邮电大学课表管理规定
  18. Good Luck in CET-4 Everybody!(sg函数模板)
  19. Mapguide配置心得
  20. OpenCV C++案例实战六《绿幕视频背景替换》

热门文章

  1. 魔方矩阵c语言,C语言检验并打印魔方矩阵,检验并打印魔方矩阵,用C语言,求大神尽快解决...
  2. matlab魔方矩阵有哪些,关于matlab 魔方矩阵的1、用前100个自然数创建一个10阶的魔方矩阵,求出每行、每列、所有对角线元素的和,再将矩阵...
  3. yuv422,yuv420,yuv444的区别
  4. **记录在win10电脑上使用Duet display的一次经历**
  5. stm32—光敏电阻传感器的初步使用
  6. C# 大华相机图像采集
  7. 无线RTU 数据采集 远程测控
  8. layui JS 中文乱码解决办法
  9. matlab3阶幺矩阵,Matlab操作矩阵的相关方法
  10. PHP中json对象转数组过程中去除字符串中的换行与数字方法