一、声明
- 首先声明一点,这个客户端代码放出来的是win版本,需要移植到开发板的同学请自行配置环境
- 这个人脸考勤是跟哔站一个UP学的,但这个是自己重构的版本,需要思路的同学请前往哔站搜索原视频(搜索arm+qt+opencv),由于哔站很多人都发了相同的视频,所以我也不知道原作者是谁,这里就没发了
- 我这里不提供环境,只提供自己写的源码,需要环境的同学请移步哔站(害怕侵权)
- 如有构成侵权请告知 ,邮箱为2286087148@qq.com,这个不常看,发邮箱告知我能第一时间删除内容
二、UI界面
客户端的UI界面就一个label,用来将摄像头捕获到的图片视频放在这里,效果图如下
三、功能实现
客户端只负责采集数据,即检测到人脸后,将人脸发送给服务器进行处理,同时接收来自服务器的消息做一个弹窗显示,效果如下,其中bbb是人名,中间一段告知是上班打卡还是下班打卡,最后一段为时间,这里设计的比较简陋,因为主要是 实现服务器部分。
四、设计思路及原理
opencv可以检测人脸,那么客户端只需要将采集到的人脸数据发送给服务器,由服务器进行识别并进行打卡操作以及数据库的写入(服务器端会详细说明),所以在客户端只需要简单实现人脸检测即可,同时采用了一个类似于按键消抖的机制,就是得等人脸稳定下来后再进行发送,这样可以减少通信次数也避免了某个瞬间捕获到人脸从而进行打卡的错误操作。
五、源码展示
主界面类
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include <QThread>
#include <QImage>
#include <QPixmap>
#include <QDebug>
#include <opencv.hpp>
#include "tcpclient.h"
#include "facecapture.h"
QT_BEGIN_NAMESPACE
namespace Ui { class Dialog; }
QT_END_NAMESPACE
class Dialog : public QDialog
{
Q_OBJECT
public:
Dialog(QWidget *parent = nullptr);
~Dialog();
public slots:
//更新摄像头数据在ui界面上
void updataVideoDataOnUI(QPixmap mmp);
//来自服务器的消息弹窗
void receiveMsgFromServerToPopup(QString msg);
private:
Ui::Dialog *ui;
TcpClient* tcpClient;
FaceCapture* faceCapture;
};
#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"
Dialog::Dialog(QWidget *parent)
: QDialog(parent)
, ui(new Ui::Dialog)
{
ui->setupUi(this);
tcpClient = new TcpClient;
faceCapture = new FaceCapture(this);
tcpClient->start();
connect(faceCapture,&FaceCapture::captureFaceToUI,this,&Dialog::updataVideoDataOnUI);
connect(faceCapture,&FaceCapture::captureFaceToServer,tcpClient,&TcpClient::sendDataToServer);
connect(tcpClient,&TcpClient::receiveMsgFromServer,this,&Dialog::receiveMsgFromServerToPopup);
}
Dialog::~Dialog()
{
tcpClient->quit();
tcpClient->wait();
delete tcpClient;
delete ui;
}
//更新摄像头数据在ui界面上
void Dialog::updataVideoDataOnUI(QPixmap mmp)
{
//qDebug()<<"123456789";
ui->videoLabel->setPixmap(mmp);
}
//来自服务器的消息弹窗
void Dialog::receiveMsgFromServerToPopup(QString msg)
{
QDialog *popup = new QDialog(this, Qt::Popup | Qt::FramelessWindowHint);
QLabel *label = new QLabel(msg, popup);
QVBoxLayout *layout = new QVBoxLayout(popup);
layout->addWidget(label);
popup->resize(200, 200);
popup->setStyleSheet("QDialog { background-color: white; }");
QTimer::singleShot(3000, popup, &QDialog::close);
popup->exec();
}
捕获人脸类
facecapture.h
#ifndef FACECAPTURE_H
#define FACECAPTURE_H
#include <QObject>
#include <QDebug>
#include <QImage>
#include <QPixmap>
#include <QByteArray>
#include <QDataStream>
#include <opencv.hpp>
class FaceCapture : public QObject
{
Q_OBJECT
public:
explicit FaceCapture(QObject *parent = nullptr);
public slots:
//定时器事件
void timerEvent(QTimerEvent* e);
signals:
//将摄像头图像发送至UI界面
void captureFaceToUI(QPixmap mmp);
//将捕获的人脸发送给服务器进行识别
void captureFaceToServer(QByteArray sendData);
private:
cv::CascadeClassifier cascade;//haar--级联分类器
cv::VideoCapture cap;//摄像头
cv::Mat faceMat;//保存人类的数据
int flag;//标志是否是同一个人脸进入到识别区域
};
#endif // FACECAPTURE_H
facecapture.cpp
#include "facecapture.h"
FaceCapture::FaceCapture(QObject *parent) : QObject(parent)
{
//导入级联分类器文件
cascade.load("E:/Codes/qt-opencv_codes/opencv452/etc/haarcascades/haarcascade_frontalface_alt2.xml");
//打开摄像头
cap.open(0);
//启动定时器事件
startTimer(100);
}
void FaceCapture::timerEvent(QTimerEvent *e)
{
//采集数据
cv::Mat srcImage;
if(cap.grab()){
//读取一帧数据
cap.read(srcImage);
}
//检测人脸
//转化为灰度图
cv::Mat garyImage;
cv::cvtColor(srcImage,garyImage,cv::COLOR_BGR2GRAY);
//定义存储人脸的容器,检测到人脸就得到了一个人脸矩形框
std::vector<cv::Rect> faceRects;
//调用分类器中的方法,检测人脸(灰度图),返回结果会存在容器里面
cascade.detectMultiScale(garyImage,faceRects);
if(faceRects.size()>0 && flag>=0){//检测到了人脸
//只保留第一张人脸
cv::Rect firstFaceRect = faceRects.at(0);
//在原图上画矩形框,用来在界面上显示
cv::rectangle(srcImage,firstFaceRect,cv::Scalar(255,0,0));
if(flag > 2){
//把Mat数据编码成jpg的格式,用于发送给服务器
std::vector<uchar> buf;
cv::imencode(".jpg",srcImage,buf);
//将uchar类型转化为Qt中的QByteArray类型
QByteArray byte((const char*)buf.data(),buf.size());
qint64 backSize = byte.size();//提取图片大小
QByteArray sendData;
QDataStream stream(&sendData,QIODevice::WriteOnly);
stream.setVersion(QDataStream::Qt_5_12);
stream<<backSize<<byte;//先发送图片大小,再发送图片数据
//发送
emit captureFaceToServer(sendData);
flag = -5;
}
flag++;
}
if(faceRects.size() == 0){
flag=0;
}
//将Mat(BGR)数据转化为QT中的QImage数据(RGB)
if(srcImage.data == nullptr)
return;
cv::cvtColor(srcImage,srcImage,cv::COLOR_BGR2RGB);
QImage image(srcImage.data,srcImage.cols,srcImage.rows,srcImage.step1(),QImage::Format_RGB888);
QPixmap mmp = QPixmap::fromImage(image);
//将人脸数据在UI界面上显示
emit captureFaceToUI(mmp);
}
tcp客户端类
tcpclient.h
#ifndef TCPCLIENT_H
#define TCPCLIENT_H
#include <QObject>
#include <QTcpServer>
#include <QTcpSocket>
#include <QTimer>
#include <QThread>
#include <QByteArray>
class TcpClient : public QThread
{
Q_OBJECT
public:
explicit TcpClient(QObject *parent = nullptr);
//重写run方法
void run();
public slots:
//停止发送连接请求
void stopSendConnectRequest();
//开始发送连接请求
void startSendConnectRequest();
//定时器发送连接请求
void timerSendConnectRequest();
//接受到来自服务器的数据
void receiveDataFromServer();
//将数据发送给服务器
void sendDataToServer(QByteArray sendData);
signals:
void receiveMsgFromServer(QString msg);
private:
QTcpSocket tcpSocket;
QTimer timer;
};
#endif // TCPCLIENT_H
tcpclient.cpp
#include "tcpclient.h"
TcpClient::TcpClient(QObject *parent) : QThread(parent)
{
//连接成功会发出connected信号
connect(&tcpSocket,&QTcpSocket::connected,this, &TcpClient::stopSendConnectRequest);
//断开连接会发出disconnected信号
connect(&tcpSocket,&QTcpSocket::disconnected,this, &TcpClient::startSendConnectRequest);
//定时器事件驱动连接服务器
connect(&timer, &QTimer::timeout,this,&TcpClient::timerSendConnectRequest);
//关联接收数据的槽函数
connect(&tcpSocket, &QTcpSocket::readyRead,this, &TcpClient::receiveDataFromServer);
//每5s钟连接一次,直到连接成功就不在连接
timer.start(5000);//启动定时器
}
//重写run方法
void TcpClient::run()
{
exec();
}
//停止发送连接请求
void TcpClient::stopSendConnectRequest()
{
timer.stop();
qDebug()<<"成功连接服务器";
}
//开始发送连接请求
void TcpClient::startSendConnectRequest()
{
timer.start(5000);//启动定时器
qDebug()<<"断开连接";
}
//定时器发送连接请求
void TcpClient::timerSendConnectRequest()
{
//连接服务器
tcpSocket.connectToHost("192.168.149.1",9999);
qDebug()<<"正在连接服务器";
}
//将数据发送给服务器
void TcpClient::sendDataToServer(QByteArray sendData)
{
// 检查QTcpSocket的连接状态
if (tcpSocket.state() == QAbstractSocket::ConnectedState) {
// 已连接到服务器,可以发送数据
tcpSocket.write(sendData);
//tcpSocket.flush();
qDebug()<<"发送";
} else {
return ;
}
}
//接受到来自服务器的数据
void TcpClient::receiveDataFromServer()
{
QString msg = tcpSocket.readAll();
//告知主窗口,生成弹窗
emit receiveMsgFromServer(msg);
}
六、源码链接
这里我将服务器与客户端的源码都放出来,我也不知道什么时候会更新服务器代码,先放出来有需要可以自行下载
链接:https://pan.baidu.com/s/1u7_suwPfg4YwVmTC6SUMGQ?pwd=yuan
提取码:yuan
--来自百度网盘超级会员V5的分享
七、说明
本人是菜鸟,如有大神对 代码进行优化也可以联系我,QQ邮箱在最上面,本人非常乐意学习大佬的思路,主打一个听劝!