人脸识别打卡系统--基于QT(附源码)
逃离舒适区
项目源代码放在我的仓库中,有需要自取
项目地址
https://gitcode.com/hujiahangdewa/Face_recognition.git
文章目录
- 一、项目结构分析
- 二、服务器的搭建
- 三、客户端的搭建
- 四、人脸识别库的申请
- 五、基于人脸识别库的识别判断
- 六、QT人脸识别----调用百度ai接口
- 七、如何在Qt中添加图片素材
- 八、项目总结
本项目需要准备到的工具:qt6.8.0、VMware17、Ubuntu24.10、百度AI人脸识别库的密钥、摄像头(笔记本电脑自带即可)
一、项目结构分析
- 本项目选用Linux系统来搭建服务器,首先我们要明白一点就是在Linux系统中一切
皆文件 - 我们的客户端使用QT来搭建
- 本项目中的客户端与服务器之间的通信采用TCP/IP协议
- 人脸识别算法采用了百度AI中的人脸识别库(免费)
二、服务器的搭建
- 首先先检查我们的虚拟机的USB功能有没有启用
windows+R调出这个窗口,输入以下命令
找到VMware USB Arbitration Service,将其启动
启动虚拟机,将摄像头连入虚拟机)
检查摄像头是否连接成功:唤起命令行,输入cheese即可检查 - 结构分析
总的来说就是服务器端打开摄像头,将摄像头拍摄的数据传输到客户端中显示出来。
接下来我们来看看关于摄像头的头文件的结构
#ifndef CAMERA_H
#define CAMERA_H
#include <stddef.h> // for size_t
#define BUFFER_COUNT 4
struct camera
{
void *start;
size_t length;
};
struct buffer_info
{
int index;
char *pic_buf;
int length;
};
extern struct camera buffer[BUFFER_COUNT];
extern struct v4l2_buffer buf;
extern struct buffer_info buf_info;
/**
* @brief 初始化摄像头
*
* @param pathname 摄像头设备的路径。
* @return 成功时返回文件描述符,失败时返回 -1。
*/
int camera_init(const char *pathname);
/**
* @brief 启动摄像头采集
*
* @param fd 摄像头设备的文件描述符。
* @return 成功时返回 0,失败时返回 -1。
*/
int camera_start(int fd);
/**
* @brief 获取一帧数据
*
* @param fd 摄像头设备的文件描述符。
* @param pic_buf 指向图像数据的指针。
* @param length 图像数据的长度。
* @return 成功时返回 0,失败时返回 -1。
*/
int camera_get(int fd, char **pic_buf, int *length);
/**
* @brief 将已处理的缓冲区重新放入缓存队列
*
* @param fd 摄像头设备的文件描述符。
* @return 成功时返回 0,失败时返回 -1。
*/
int camera_input(int fd);
/**
* @brief 停止摄像头流
*
* @param fd 摄像头设备的文件描述符。
*/
void camera_stop(int fd);
#endif // CAMERA_H
- 服务器的具体实现(代码)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "camera.h"
int main(int argc, char *argv[])
{
// 创建socket套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("socket");
return -1;
}
printf("create socket success!\n");
// 设置地址信息
struct sockaddr_in addr;
addr.sin_family = AF_INET; // 地址协议族IPv4
addr.sin_port = htons(65432); // 端口号
addr.sin_addr.s_addr = inet_addr("0.0.0.0"); // IP地址
socklen_t addrlen = sizeof(addr);
// bind绑定地址信息 端口号
int bind_info = bind(sockfd, (const struct sockaddr *)&addr, addrlen);
if (bind_info < 0)
{
perror("bind");
return -1;
}
printf("bind socket success!\n");
// listen监听
if (listen(sockfd, 5) < 0)
{
perror("listen");
return -1;
}
printf("listen socket success!\n");
// 摄像头的初始化
int camera_fd = camera_init("/dev/video0");
if (camera_fd < 0)
{
printf("camera init failed\n");
return -1;
}
printf("camera_init success\n");
// 服务器搭建完毕,保证服务器不停
while (1)
{
struct sockaddr_in c_addr;
socklen_t c_addrlen = sizeof(c_addr);
int conn_fd = accept(sockfd, (struct sockaddr *)&c_addr, &c_addrlen);
if (conn_fd < 0)
{
perror("accept");
continue;
}
printf("client connect success! conn_fd:%d\n", conn_fd);
// 开启摄像头----------->摄像头开始获取数据
if (camera_start(camera_fd) < 0)
{
printf("camera start failed\n");
return -1;
}
// 连接成功,保证服务程序持续运行
while (1)
{
// 服务程序--->发送视频信息
// 采集(获取)一帧图像
char *pic_buf = NULL; // 定义存放图像数据的指针
int length = 0; // 定义保存图像大小的变量
if (camera_get(camera_fd, &pic_buf, &length) < 0)
{
printf("camera get failed\n");
continue;
}
// 1.先发送图像的大小
char len[8] = {0};
sprintf(len, "%d", length);
write(conn_fd, len, 8);
// 2.发送图像的数据
write(conn_fd, pic_buf, length);
// 清除存放图像的缓冲区
if (camera_input(camera_fd) < 0)
{
printf("camera_input close failed\n");
continue;
}
}
}
// 摄像头停止工作
camera_stop(camera_fd);
// 关闭文件描述符
close(camera_fd);
// 关闭通信的套接字
close(sockfd);
return 0;
}
以上服务器代码中的第55行中,由于设备的不同,我们需要进行更换,如果以上代码出现摄像头无法显示的问题,将/dev/video0修改为dev/video1,即可解决。
接下来编译这个服务器文件,使用gcc来编译
gcc server.c -o app -L ./ -lcamera
其中lcamera是摄像头的一些驱动库
- 启动服务器
这即是服务器启动成功之后的界面,如若启动失败,在评论区留下错误原因。
三、客户端的搭建
- 创建项目
打开我们的QT软件,创建一个qt项目,具体如何创建可参考这个文章https://blog.csdn.net/hujiahangdewa/article/details/143915238?spm=1001.2014.3001.5501
我们需要在项目文件的.pro文件中添加以下模块,这里面含有tcp/ip协议的构造函数
QT += network
2.设计ui界面
点击左侧的widget.ui即可进入到ui界面的设计
从左侧的控件栏中拖出三个按钮(Push Button),根据需要可以进行更名如下:
那我们用什么来显示摄像头传输给我们的图像数据呢?---------使用label或者TextEdit
这里我们就使用label来显示我们的图像数据
我们还需要两个文本框来输入我们服务器的IP地址和端口号
在控件栏中拖出两个LineEdit即可,如下
ui界面的简单布局就是这样,美化的事情可以后期再做。
接下来我们要理解一个很重要的一个QT的核心理念就是----信号与槽
当我们点击登录按钮就是一个鼠标的点击事件。
- 按钮槽函数的设计
(1)登录按钮的槽函数
我们先选中登录按钮,右键,转到槽,在弹出的提示框中选择clicked()
void Widget::on_pushButton_clicked()
{
QString IP=ui->lineEdit->text(); //获取IP地址
int PORT=ui->lineEdit_2->text().toInt(); //获取端口号
client->connectToHost(IP,PORT);//连接到服务器,前提是服务器是启动状态
}
接下来就是当我们登录成功之后,在label框中实时显示我们摄像头拍摄的数据
我们来到widget.cpp这个文件中,新建一个recv函数
void Widget::recv()
{
static int flag=0;
//第一次读取图像的大小
//第二次读取图像的数据
if(flag==0)
{
//判断数据是否有效
if(client->bytesAvailable()<8)
{
return;
}
length = client->read(8).toInt();
flag=1;
}
else if(flag==1)
{
//判断数据是否有效
if(client->bytesAvailable()<length)//说明图片没有传完
{
return;
}
pic = client->read(length);
QPixmap pix;//定义一个图片类型变量
pix.loadFromData(pic);
//显示图像
ui->label_3->setPixmap(pix);
ui->label_3->setScaledContents(true);
flag=0;
}
}
然后我们需要在Widget的构造函数中进行信号和槽的绑定
//实例化客户端对象,通信协议
client=new QTcpSocket;
//接收信号与接收槽函数的绑定
//信号对象 信号 槽函数对象 对象
connect(client, &QTcpSocket::readyRead, this, &Widget::recv);
同时启动服务器和客户端,在客户端输入服务器的IP和端口号,登陆成功之后,我们的label框中会显示摄像头拍摄到的数据。如下
(2)注册人脸信息按钮的槽函数
//人脸信息的注册
void Widget::on_pushButton_3_clicked()
{
QPixmap pix;//定义一个图片类型变量
pix.loadFromData(pic);
//显示图像
ui->label_4->setPixmap(pix);
ui->label_4->setScaledContents(true);
//上传的人脸信息
QImage image;
image.loadFromData(pic);
//获取用户的组
QString group_id = ui->lineEdit_3->text();
//获取用户的ID
QString user_id = ui->lineEdit_4->text();
//进行人脸信息的注册
AI->insert_user(image,group_id,user_id);
}
(3)打卡按钮的槽函数
//打卡的槽函数
void Widget::on_pushButton_2_clicked()
{
QPixmap pix;//定义一个图片类型变量
pix.loadFromData(pic);
//显示图像
ui->label_4->setPixmap(pix);
ui->label_4->setScaledContents(true);
//上传的人脸信息
QImage image;
image.loadFromData(pic);
//获取用户的组
QString group_id = ui->lineEdit_3->text();
//进行人脸检测
AI->search_face(image,group_id);
}
//绑定信号与槽
//1.绑定识别成功的信号与槽
connect(AI, &face::recv_nub, this, &Widget::face_success);
//人脸识别成功的槽函数
void Widget::face_success(QString user_id,double number)
{
QString data = "打卡成功,欢迎" + user_id +"业主回家!"+" "+"相似度为:"+QString::number(number);
ui->textEdit->setText(data);
}
四、人脸识别库的申请
接下来我们将教大家如何申请
首先进入到百度AI的控制台
https://console.bce.baidu.com/#/index/overview
在这个界面中,在人工智能的那一列找到人脸识别,点击应用列表,点击创建应用,根据提示完成创建。然后在返回到应用列表界面,
这里的API KEY和Secret Key很重要,是我们调用百度ai的密钥
返回到我们的qt软件
来到widget.cpp,在widget的构造函数中初始化百度ai,以及输入密钥
//实例化一个百度ai对象
AI = new face("API_KEY","SECRET_KEY"); //API_KEY SECRET_KEY
五、基于人脸识别库的识别判断
- 识别失败
- 识别成功
//绑定信号与槽
//1.绑定识别成功的信号与槽
connect(AI, &face::recv_nub, this, &Widget::face_success);
//2.绑定识别失败的信号与槽
connect(AI, &face::recv_dis, this, &Widget::face_failure); //连接人脸识别失败信号到新的槽函数
connect(AI, &face::recv_error_msg, this, &Widget::showErrorMsg); // 连接接收错误信息信号到显示错误信息的槽函数
//人脸识别成功的槽函数
void Widget::face_success(QString user_id,double number)
{
QString data = "打卡成功,欢迎" + user_id +"业主回家!"+" "+"相似度为:"+QString::number(number);
ui->textEdit->setText(data);
}
void Widget::face_failure()
{
QMessageBox::warning(this, "打卡失败", "人脸识别未通过,请重新尝试打卡。");
}
//显示错误信息的槽函数,弹出提示框显示具体错误消息
void Widget::showErrorMsg(QString errorMsg)
{
QMessageBox::critical(this, "错误", "发生错误:" + errorMsg);
}
六、QT人脸识别----调用百度ai接口
我们注册的人脸信息将会保存在百度AI的人脸库中,每隔一个月会消除一次。
以下代码就是调用这个接口的具体实现
#ifndef FACE_H
#define FACE_H
#include <QObject>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QImage>
#include <QBuffer>
#include <QDebug>
#include <QPixmap>
class face : public QObject
{
Q_OBJECT
public:
explicit face(QObject *parent = nullptr);
//构造face类,需要传递api_key值和secret_key
face(QString,QString);
/*********************************************************************************************
* 进行人脸库搜索
* 参数说明:
* 1.图像内容为Qimage类型
* 2.查询的用户组
*********************************************************************************************/
void search_face(QImage,QString);
/*********************************************************************************************
* 添加用户信息,即实现用户人脸注册,当用户存在时添加用户图片,用户不存在添加用户和图片
* 参数说明:
* 1.图像内容为Qimage类型
* 2.添加的用户组
* 3.添加到的用户名id
*********************************************************************************************/
void insert_user(QImage,QString,QString);
/*********************************************************************************************
* 添加用户组
* 参数说明:
* 添加的用户
*********************************************************************************************/
void insert_group(QString);
/*********************************************************************************************
*获取access_token值
* api_key和secret_key来自于构造该对象时传的参数
*********************************************************************************************/
void get_access_token();
signals:
/*********************************************************************************************
* 该对象的信号,触发该信号返回人脸搜索的用户名id和对比度得分
* 触发条件,人脸对比与用户信息阈值超过80则触发
*********************************************************************************************/
void recv_nub(QString,double);
/*********************************************************************************************
* 触发条件,人脸对比与用户信息阈值低于80则触发
*********************************************************************************************/
void recv_dis();
//用于返回错误码的信号,该信号触发返回错误信息
void recv_error_msg(QString);
public slots:
//接收由networkaccessmanager发起的网络请求的网络返回数据,并解析
void recv_json(QNetworkReply*);
private:
QNetworkAccessManager *my_access_manager;
QString api_key;
QString secret_key;
QString access_token;
QString user_id;
double score;
};
#endif // FACE_H
#include "face.h"
face::face(QObject *parent) : QObject(parent)
{
}
face::face(QString ak,QString sk)
{
api_key = ak;
secret_key = sk;
//实例化网络请求管理工具
my_access_manager = new QNetworkAccessManager;
//关联信号与槽
connect(my_access_manager,SIGNAL(finished(QNetworkReply*)),this,SLOT(recv_json(QNetworkReply*)));
get_access_token();
}
//获取access_token值,发起网络请求
void face::get_access_token()
{
QNetworkRequest req;
req.setUrl(QUrl(QString("https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=%0&client_secret=%1").arg(api_key).arg(secret_key)));
req.setHeader(QNetworkRequest::ContentTypeHeader,"Content-Type:application/json");
QByteArray buf;
my_access_manager->post(req,buf);
}
//发起人脸检测的网络请求
void face::search_face(QImage image,QString group_id)
{
//将接收到的image类型转化为qpixmap类
QPixmap pic = QPixmap::fromImage(image);
//存储图像数据到字节数组内
QByteArray buf;
QBuffer b(&buf);
b.open(QIODevice::ReadWrite);
pic.save(&b,"jpg");
b.close();
//封装百度api请求需要的json格式类型数据
QJsonObject obj;
//图像数据格式要求base64格式
obj.insert("image",QString(buf.toBase64()));
obj.insert("image_type","BASE64");
obj.insert("group_id_list",group_id);
//实例化网络请求
QNetworkRequest req;
//表明网络请求发起的请求地址
req.setUrl(QUrl(QString("https://aip.baidubce.com/rest/2.0/face/v3/search?access_token=%0").arg(access_token)));
//设置网络请求头
req.setHeader(QNetworkRequest::ContentTypeHeader,"Content-Type:application/json");
//发起post网络请求
my_access_manager->post(req,QJsonDocument(obj).toJson());
}
//发起添加用户的网络请求
void face::insert_user(QImage image,QString group_id,QString user_id)
{
QPixmap pic = QPixmap::fromImage(image);
QByteArray buf;
QBuffer b(&buf);
b.open(QIODevice::ReadWrite);
pic.save(&b,"jpg");
b.close();
//封装数据内添加需要增加到的用户组,用户id以及该用户的图像数据
QJsonObject obj;
obj.insert("image",QString(buf.toBase64()));
obj.insert("image_type","BASE64");
obj.insert("group_id",group_id);
obj.insert("user_id",user_id);
obj.insert("action_type","APPEND");
QNetworkRequest req;
req.setUrl(QUrl(QString("https://aip.baidubce.com/rest/2.0/face/v3/faceset/user/add?access_token=%0").arg(access_token)));
req.setHeader(QNetworkRequest::ContentTypeHeader,"Content-Type:application/json");
my_access_manager->post(req,QJsonDocument(obj).toJson());
}
//发起添加用户组的网络请求
void face::insert_group(QString group_id)
{
//封装组id
QJsonObject obj;
obj.insert("group_id",group_id);
QNetworkRequest req;
req.setUrl(QUrl(QString("https://aip.baidubce.com/rest/2.0/face/v3/faceset/group/add?access_token=%0").arg(access_token)));
req.setHeader(QNetworkRequest::ContentTypeHeader,"Content-Type:application/json");
my_access_manager->post(req,QJsonDocument(obj).toJson());
}
//解析接收到的网络请求的返回数据
void face::recv_json(QNetworkReply *rep)
{
//读取网络请求后的返回数据
QByteArray replay = rep->readAll();
//将数据转化为json文档
QJsonDocument doc = QJsonDocument::fromJson(replay);
if(doc.isObject())
{
//将json文档转化为json对象
QJsonObject replay_obj = doc.object();
//判定json数据内所包含的字样,并获取字样对应的数据值
if(replay_obj.contains("result"))
{
QJsonValue val = replay_obj.value("result");
if(val.isObject())
{
QJsonObject replay_obj = val.toObject();
if(replay_obj.contains("user_list"))
{
QJsonValue val = replay_obj.value("user_list");
if(val.isArray())
{
QJsonArray arr = val.toArray();
QJsonValue val = arr.at(0);
if(val.isObject())
{
QJsonObject replay_obj = val.toObject();
if(replay_obj.contains("user_id"))//解析用户id
{
user_id = replay_obj.value("user_id").toString();
}
if(replay_obj.contains("score"))//解析人脸比对的阈值
{
score = replay_obj.value("score").toDouble();
if(score > 80)//比对得分超过80认为是同一个人,可以触发信号返回用户id和对比值
{
qDebug()<<score;
emit this->recv_nub(user_id,score);
}
else
{
emit this->recv_dis();
}
}
}
}
}
}
}
//解析access_token值
if(replay_obj.contains("access_token"))
{
access_token = replay_obj.value("access_token").toString();
}
//解析错误码
if(replay_obj.contains("error_msg"))
{
QString error_msg = replay_obj.value("error_msg").toString();
if(error_msg!="SUCCESS")
{
//触发信号返回错误信息
emit this->recv_error_msg(error_msg);
}
}
//解析access_token值的错误码
else if(replay_obj.contains("error_description"))
{
QString error_description = replay_obj.value("error_description").toString();
emit this->recv_error_msg(error_description);
}
}
}
完成以上之后,我们的项目基本完成,剩下的就是美化工作,我们可以根据自己的喜好来进行美化。
七、如何在Qt中添加图片素材
在这个界面中选择Qt,然后选择Qt Resource File
取一个名字,存放位置建议默认,也可根据情况而修改,接下来的设置默认就可。
完成以上步骤之后,会出现这样一个界面,点击添加前缀
建议是设置成以下这样
然后点击添加文件,我们就可以自由的添加图片文件了。我们的文件列表中会出现一个.prc的资源文件