行业知识
【基于深度学习的人脸识别】(Dlib+ResNet残差神经网络)——QT(C++)+Linux
1.基于深度学习的人脸识别模型
1.人脸识别模型
dlib_face_recognition_resnet_model_v1.dat 是基于深度学习的人脸识别模型,是dlib库中的一个重要组件。该模型的原理涉及到深度卷积神经网络(DCNN)和具体的人脸识别算法。
dlib 人脸识别采用了 Resnet 残差神经网络,识别精度高于普通神经网络,同样我们可以到官网去下载训练好的模型 dlib_face_recognition_ resnet_ model_v1. dat,通过net(接口返回 128 维人脸特征,然后再通过目标图像也同样得到128 维人脸特征,将两组特征进行对比即可判断出要识别的对象。
以下是一些详细的解释:
1. 深度卷积神经网络 (DCNN): dlib_face_recognition_resnet_model_v1.dat使用了深度学习中的卷积神经网络。深度卷积神经网络是一种强大的模型,能够自动学习图像中的特征。在人脸识别任务中,这些特征可能包括脸部的形状、眼睛、鼻子、嘴巴等。
2. ResNet 架构:
模型采用了残差网络(ResNet)的架构。ResNet是一种具有跳跃连接(skip connection)的深度神经网络结构,能够更有效地训练深层网络。这对于人脸识别等任务是有益的,因为它允许网络学习残差,即输入与期望输出之间的差异。
3. 人脸识别算法:
该模型在训练过程中学习了一种将输入图像映射到高维特征空间中的算法。这个高维向量就是人脸的描述符,包含了识别人脸的信息。通过比较两个人脸的描述符,可以判断它们之间的相似性。
4. 训练数据:
该模型是在大规模的人脸数据集上进行训练的,以学习能够泛化到不同人脸的特征。这样,模型在实际应用中能够较好地适应各种人脸。
2.模型下载
下载链接(文件名dlib_face_recognition_resnet_model_v1.dat)
下载到Ubuntu中
2.人脸录入模块
1.创建人脸录入模块
- 创建新界面ui
在原本人脸特征值识别模块项目上新建一个界面
这里我命名为Face_in
在主界面创建一个按钮实现跳转,并在face_in界面上创建一个返回按钮,接下来实现两界面的跳转
face_in.ui
mianwindow.ui
- 界面跳转逻辑实现
这里我们使用信号与槽机制实现界面跳转(以下只是简单示例)
//先在mainwindow.h中声明face_in界面和槽函数 Face_in *face_in; void backfromfacein(); //构造函数中实例化 face_in = new Face_in(); //人脸录入界面 face_in->setGeometry(this->geometry()); face_in->hide(); //人脸录入按钮点击事件void MainWindow::on_face_in_clicked(){cap.release(); //关闭摄像头timer->stop(); this->hide(); face_in->show();}//槽函数void MainWindow::backfromfacein(){ cap.open(0); timer->start(10); this->show(); face_in->hide();}//face_in.h中signals: void backtohome();private slots: void on_back_clicked();//返回主界面 void on_start_face_clicked();//开始录入人脸 void on_open_camera_clicked(); //开启摄像头 void updateframe();//画面流 private:VideoCapture cap;Mat frame;QTimer* timer;//face_in.cpp中void Face_in::on_back_clicked(){ timer->stop(); cap.release(); emit backtohome();}//开始采集人脸void Face_in::on_start_face_clicked(){ //后续人脸录入逻辑}//开启摄像头void Face_in::on_open_camera_clicked(){ cap.open(0); if (!cap.isOpened()) { qDebug() start(30); // 设置定时器间隔,单位毫秒}void Face_in::updateframe(){ if (cap.isOpened()) { cap >> frame; // 反转图像 flip(frame, frame, 1); // 转换到label上显示 QImage img(frame.data, frame.cols, frame.rows, frame.step, QImage::Format_RGB888); this->ui->label->setPixmap(QPixmap::fromImage(img.rgbSwapped())); this->ui->label->setFixedSize(frame.cols, frame.rows); }} 如果不了解QT信号与槽机制,可参考
2.人脸录入模块逻辑实现
我们利用摄像头对人像进行拍照并提取128D人脸特征值保存到人脸数据文件中,以便进行人脸识别
4.14:优化了人脸录入代码:录入时进行灰度化,均衡化处理,并且在采集时显示采集的照片,询问用户是否保存。并且将人脸录入和人脸识别拆开了(上文的界面跳转没有用了,仅供参考)
- 利用dlib获取人脸特征
#ifndef WIDGET_H#define WIDGET_H#include #include #include #include #include #include #include #include #include #include #include #include using namespace cv;using namespace dlib;using namespace std;// ----------------------------------------------------------------------------------------template using residual = add_prev1;template using residual_down = add_prev2;template using block = BN;template using ares = relu;template using ares_down = relu;template using alevel0 = ares_down;template using alevel1 = ares;template using alevel2 = ares;template using alevel3 = ares;template using alevel4 = ares;using anet_type = loss_metric>>>>>>;// ----------------------------------------------------------------------------------------std::vector jitter_image( const matrix& img );// ----------------------------------------------------------------------------------------QT_BEGIN_NAMESPACEnamespace Ui { class Widget; }QT_END_NAMESPACEclass Widget : public QWidget{ Q_OBJECTpublic: Widget(QWidget *parent = nullptr); ~Widget();private slots: void on_start_face_clicked(); void on_open_camera_clicked(); void updateframe(); //画面流 void on_close_camera_clicked(); void updateImageDisplay(const cv::Mat& image);//显示采集的图像private: Mat frame; VideoCapture cap; QTimer* timer; frontal_face_detector detector; //dlib的人脸检测器 shape_predictor sp; //预测器 anet_type net; // 创建dlib的人脸识别器 std::vector faces; //检测到的人脸 QImage currentImage;//存储当前采集的图像 Ui::Widget *ui;};#endif // WIDGET_H #include "widget.h"#include "ui_widget.h"Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget){ ui->setupUi(this); timer = new QTimer(this); //dlib的人脸关键点检测器 detector = get_frontal_face_detector(); deserialize("/home/tj/model/shape_predictor_68_face_landmarks.dat") >> sp; //dlib的人脸识别器 deserialize("/home/tj/model/dlib_face_recognition_resnet_model_v1.dat") >> net;}Widget::~Widget(){ delete ui;}//采集人脸按钮void Widget::on_start_face_clicked(){ timer->stop(); //先暂停视频采集 if(cap.isOpened()){ //确保摄像头打开才进行采集 //人脸录入逻辑实现 QString name = ui->name_input->text(); //获取人名字 if (name.isEmpty()) { QMessageBox::warning(this, "提示", "请输入人名!"); return; } Mat processedFrame = frame.clone(); cvtColor(processedFrame,processedFrame,CV_BGR2GRAY);//将彩色图转换从灰度图 equalizeHist(processedFrame,processedFrame);//进行均衡化处理 // 将OpenCV灰度图转换为dlib图像 dlib::cv_image dlib_img(processedFrame); // 获取人脸关键点 full_object_detection shape = sp(dlib_img, faces[0]); // 截取人脸部分并调整大小对齐 matrix face_chip; extract_image_chip(dlib_img, get_face_chip_details(shape, 150, 0.25), face_chip); // 提取人脸特征值 matrix face_descriptor = net(face_chip); // 转换为OpenCV格式 cv::Mat faceMat(face_chip.nr(), face_chip.nc(), CV_8UC1); // 灰度图,单通道 for (int r = 0; r x() + this->width() / 2 - msgBox.width() / 2; // 居中对齐 int y = this->y() + this->height() - msgBox.height(); // 底部对齐 // 将QMessageBox移动到窗口的底部中心 msgBox.move(x, y); int reply = msgBox.exec(); if (reply == QMessageBox::Yes) { // 用户选择了“是” //保存人脸图片到文件夹 string save_path = "/home/tj/faces/"; string photo_name = save_path + name.toStdString()+ + ".jpg"; imwrite(photo_name, faceMat); // 保存人脸特征值到文件 string file_name = save_path + name.toStdString() + ".dat"; ofstream outfile(file_name, ios::binary); serialize(face_descriptor, outfile); outfile.close(); QMessageBox::information(this, "提示", name + "保存成功!"); timer->start(30); //继续视频 } else { // 用户选择了“否” timer->start(30); //继续视频 return; } } //没有打开摄像头 else{ QMessageBox::information(this,"提示","请打开摄像头"); }}//打开摄像头按钮void Widget::on_open_camera_clicked(){ cap.open(0); if (!cap.isOpened()) { qDebug() start(30); // 设置定时器间隔,单位毫秒}void Widget::updateframe(){ cap >> frame; // 反转图像 flip(frame, frame, 1); // 将OpenCV图像转换为dlib图像 dlib::cv_image dlibImage(frame); // 使用人脸检测器检测人脸 faces = detector(dlibImage); // 对检测到的第人脸进行处理,有人脸才处理 if (!faces.empty()) { for(size_t i = 0; i ui->label->setPixmap(QPixmap::fromImage(img.rgbSwapped())); this->ui->label->setFixedSize(frame.cols, frame.rows);}//关闭摄像头void Widget::on_close_camera_clicked(){ cap.release(); timer->stop();}//显示采集的图像void Widget::updateImageDisplay(const Mat &image){ // 转换图像格式 QImage img(image.data, image.cols, image.rows, image.step, QImage::Format_Grayscale8); currentImage = img.copy(); // 使用copy确保图像数据的独立性 this->ui->label->setPixmap(QPixmap::fromImage(currentImage)); this->ui->label->setFixedSize(image.cols, image.rows);} serialize 和 deserialize 函数是 Dlib 库中用于序列化(将对象转换为字节流)和反序列化(将字节流还原为对象)的函数。这两个函数通常用于将对象保存到文件或从文件中加载对象。
serialize: 该函数用于将一个对象序列化为一个字节流,可以将这个字节流保存到文件、传输给其他系统等。在 Dlib 中,经过 serialize 处理的对象可以被保存为二进制文件。
deserialize: 该函数用于将一个字节流反序列化为原始对象。通过 deserialize,你可以还原之前使用 serialize序列化的对象。
这两个函数一起使用,使得你可以方便地保存和加载模型、数据、状态等信息。在本项目中, deserialize 函数用于加载训练好的人脸检测器、面部关键点检测器以及人脸识别器的模型
这里就成功将人脸特征保存至了指定目录下(代码做了详细注释)
3.人脸识别模块
人脸识别的主要流程:采集摄像头当前人脸数据生成128D人脸特征,再与人脸数据文件中的全部人脸特征进行对比,设置一个阈值,当两张人脸阈值小于指定阈值时,即判定属于同一个人
1.创建人脸识别模块
- dnn_face_recognition_ex.cpp代码参考dlib官网:参考
- 使用Dlib库定义的深度学习模型,基于残差网络(ResNet)结构的人脸识别模型。
// ----------------------------------------------------------------------------------------template using residual = add_prev1;template using residual_down = add_prev2;template using block = BN;template using ares = relu;template using ares_down = relu;template using alevel0 = ares_down;template using alevel1 = ares;template using alevel2 = ares;template using alevel3 = ares;template using alevel4 = ares;using anet_type = loss_metric>>>>>>;// ----------------------------------------------------------------------------------------
代码(上文也是用到了该网络)中定义了一系列模板(template)和类型别名(using),这些构建块用于构造一个深层神经网络
1.block 模板: 一个基本的残差块,包含卷积层、批归一化层和激活函数。这是构建整个网络的基本单元。
2.residual 和 residual_down 类型别名: 分别表示残差块和降采样残差块。residual_down 在 avg_pool层之后执行降采样,其目的是减小特征图的尺寸。
3.ares, ares_down 类型别名: 表示使用残差块或降采样残差块的激活层。
4.alevel0 到 alevel4 类型别名: 表示不同层次的网络结构,每个层次都包含多个残差块或降采样残差块。
5.anet_type 类型别名: 表示整个神经网络模型,其中包含五个层次的残差块和一些池化层,最终使用 loss_metric进行人脸识别的损失计算。
总体而言,这段代码(应该位于处理人脸识别类的头文件中)定义了一个深度学习模型,用于人脸识别,其中使用了残差网络结构来提高模型的训练和推断性能。这是基于Dlib库的一种高级抽象。
我们利用上篇文章中的人脸68位特征值检测类来进行人脸识别,大概流程为:从当前摄像头人脸中提取出特征值,遍历人脸库(上一步保存的),利用欧氏距离的阈值作为是否是同一个人的判别
2.人脸识别逻辑实现
- 创建人脸识别函数recognizeFace,该函数功能就是在程序提取出当前人脸的128D特征时对人脸库中的人脸进行遍历,找到最符合要求的人脸输出名字
//人脸识别函数string Faceprocess::recognizeFace(const matrix &face_chip){ // 从人脸形状中提取特征 matrix face_descriptor = net(face_chip); // 遍历已保存的人脸特征矩阵,计算特征之间的欧氏距离 double min_distance = std::numeric_limits::infinity(); std::string recognized_name = "null"; // 默认值为 "null" for (size_t i = 0; i 可以在构造函数中进行一次性的遍历人脸库文件夹,然后将所有人脸特征存储到矩阵(stored_descriptors)中。这样,后续的识别过程就只需要比对当前人脸与这个矩阵即可,避免在每一帧都重新读取文件。
1.遍历人脸特征文件夹中的所有文件,读取每个文件的人脸特征,然后将这些特征存储到 stored_descriptors 矩阵中。files 用于迭代这些文件,获取文件路径,读取特征,最后将特征添加到 stored_descriptors 中。
2.在每一帧中,检测到人脸,并通过人脸形状提取特征,得到 face_descriptor。
3.遍历 stored_descriptors 中已保存的人脸特征,计算当前人脸特征与已保存特征之间的欧氏距离。
4.如果距离小于阈值(0.6),则认为当前人脸与已保存的某个人脸匹配,返回匹配的人脸名称。否则,返回空字符串。
- 总体识别代码
这里由于recognizeFace(face_chip);进行人脸识别的函数涉及到深度学习模型执行,相对于画面帧的更新速度而言,它要慢许多,因此我们在此使用QT所提供的简单的异步(多线程)操作,用来执行我们的深度学习人脸识别函数。
这里使用QtConcurrent::run来进行多线程操作,我的思路是这样的:由于QtConcurrent::run会将参数中的函数放在线程池中进行执行,并不会干扰主UI线程的执行,那么,如果我们在循环中调用QtConcurrent::run就会导致不间断重复创建和销毁线程,导致资源的浪费以及识别缓慢等问题,因此我们希望在打开摄像头后,就只创建一次线程,不断进行异步人脸识别,直到我们关闭摄像头或者关闭程序时才停止这个线程,这时候我们就需要将人脸识别函数进行修改,并将face_chip作为全局变量供人脸识别函数使用,关键代码如下:
//人脸识别函数void Widget::recognizeFace(){ //只要没点击停止,就一直识别 while(start_face) { try { if(face_chip.size()){//有人脸 cout double distance = length(face_descriptor - stored_descriptors[i]); // 如果距离小于阈值,认为是同一个人 if (distance
下一篇:服务器显卡驱动怎么打?