当前位置: 首页 > article >正文

人脸识别打卡系统--基于QT(附源码)

逃离舒适区


项目源代码放在我的仓库中,有需要自取

项目地址

https://gitcode.com/hujiahangdewa/Face_recognition.git

文章目录

  • 一、项目结构分析
  • 二、服务器的搭建
  • 三、客户端的搭建
  • 四、人脸识别库的申请
  • 五、基于人脸识别库的识别判断
  • 六、QT人脸识别----调用百度ai接口
  • 七、如何在Qt中添加图片素材
  • 八、项目总结


本项目需要准备到的工具:qt6.8.0、VMware17、Ubuntu24.10、百度AI人脸识别库的密钥、摄像头(笔记本电脑自带即可)

一、项目结构分析

在这里插入图片描述

  • 本项目选用Linux系统来搭建服务器,首先我们要明白一点就是在Linux系统中一切
    皆文件
  • 我们的客户端使用QT来搭建
  • 本项目中的客户端与服务器之间的通信采用TCP/IP协议
  • 人脸识别算法采用了百度AI中的人脸识别库(免费)

二、服务器的搭建

  1. 首先先检查我们的虚拟机的USB功能有没有启用
    windows+R调出这个窗口,输入以下命令
    在这里插入图片描述
    找到VMware USB Arbitration Service,将其启动
    在这里插入图片描述
    启动虚拟机,将摄像头连入虚拟机)
    在这里插入图片描述
    检查摄像头是否连接成功:唤起命令行,输入cheese即可检查
  2. 结构分析
    总的来说就是服务器端打开摄像头,将摄像头拍摄的数据传输到客户端中显示出来。
    接下来我们来看看关于摄像头的头文件的结构
#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

  1. 服务器的具体实现(代码)
#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是摄像头的一些驱动库
  1. 启动服务器
    在这里插入图片描述
    这即是服务器启动成功之后的界面,如若启动失败,在评论区留下错误原因。

三、客户端的搭建

  1. 创建项目

打开我们的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. 按钮槽函数的设计
    (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. 识别失败
  2. 识别成功
    //绑定信号与槽
    //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的资源文件

八、项目总结


http://www.kler.cn/a/515149.html

相关文章:

  • 5.SQLAlchemy对两张有关联关系表查询
  • 【AI编辑器】字节跳动推出AI IDE——Trae,专为中文开发者深度定制
  • 代码随想录day1
  • qml Timer详解
  • 在Ubuntu上安装RabbitMQ教程
  • 机器学习10-解读CNN代码Pytorch版
  • Django学习笔记(bootstrap的运用)-04
  • 【优选算法】5----有效三角形个数
  • python创建一个httpServer网页上传文件到httpServer
  • LightGBM算法
  • node安装与管理
  • Centos类型服务器等保测评整/etc/pam.d/system-auth
  • Typescript 多个泛型参数详细解读
  • HP 笔记本重新安装 Windows 11 无法启动
  • webrtc入门系列(五)amazon-kinesis-video-streams-webrtc-sdk-c编译
  • 【P2P】基于 Nebula 的 P2P 通信技术的虚拟局域网游戏设计方案
  • 低代码系统-产品架构案例介绍(四)
  • 【esp32小程序】小程序篇02——连接git
  • 大语言模型应用实践:性能与资源的权衡之道
  • Pytorch深度学习指南 卷I --编程基础(A Beginner‘s Guide) 第1章 一个简单的回归
  • Logo语言的网络编程
  • mac 电脑上安装adb命令
  • HackTheBox靶机:Sightless;NodeJS模板注入漏洞,盲XSS跨站脚本攻击漏洞实战
  • Chromium 132 编译指南 Mac 篇(四)- 获取源代码
  • 【Uniapp-Vue3】动态设置页面导航条的样式
  • 使用c#开发机器学习项目入门