目录
获取完整代码请前往:GitHub - zhaohigh/Qt-chatrobut
目前还在学习中,需要练练手,就写了一个基于百度人脸识别的考勤系统。
百度人脸识别的功能很强大,也很完善了,所以就直接调用百度的人脸识别API,自己再QT完善一下界面就好啦。
一、百度智能云介绍
需要在百度智能云里领取人脸识别的权限和查看调用的接口信息。下面是百度智能云的连接:
百度智能云:百度智能云-云智一体深入产业
1、未注册的用户需要先注册才能使用,它是需要实名注册的。
2、登录进去需要领取人脸识别的权限:
领取的时候有许多选择,看需要哪些就可以领取使用。注意这个是有免费次数的用完了就需要付费使用了。
4、之后就是创建应用列表了,它会给API Key和Secret Key,这个就是获取Access_token来调用百度服务的,直接可以复制使用的。
5、然后可以在技术文档或者API调试里查看需要使用的AI服务的请求URL了。
二、代码
1、开始之前需要在.pro文件中添加多媒体和网络的类
2、因为我设计了几个界面,所以创建了几个类:
1、camera类
首先创建了一个camera的类,用来获取摄像头信息,并显示在设置好的label控件上。
camera.文件需要添加一些多媒体类的头文件,并定义相应的指针。
在构造函数中创建相应的对象。
Camera::Camera(QWidget *parent) : QWidget(parent)
{
//创建摄像头对象
camera = new QCamera(this);
//创建摄像头视图对象(父对象为Widget容器)
viewfinder = new QCameraViewfinder(this);
//设置图像捕获内容
imagecapture = new QCameraImageCapture(camera);
}
并设置一些槽函数,完成相应的功能。
//设置将摄像头内容添加到label标签上的槽函数
void Camera::setLabel(QLabel *label)
{
//设置于与label标签一样大
viewfinder->resize(label->size());
//设置摄像头内容到label控件
viewfinder->setParent(label);
//自动填充背景框(警告:背景框不能有样式表内容,否则将失效)
// viewfinder->autoFillBackground();
viewfinder->show();
}
//打开摄像头的槽函数
void Camera::startCamera()
{
//设置摄像头试图对象到摄像头
camera->setViewfinder(viewfinder);
camera->setCaptureMode(QCamera::CaptureStillImage);
imagecapture->capture();
camera->start();
}
//关闭摄像头的槽函数
void Camera::stopCamera()
{
camera->stop();
// camera->setViewfinder(nullptr);
}
//设置能够截屏的槽函数
QImage Camera::screenShort()
{
// 获取摄像头当前帧
QImage image = viewfinder->grab().toImage();
if(image.isNull()){
QMessageBox::warning(this,"警告","请重新识别或截图","确认");
}
return image;
}
2、widget类
在widget.h文件中需要添加camera的头文件,并定义。
在构造函数中获取Access_token,使得每次打开就能拉取新的Access_token(因为百度的Access_token的期限只有30天)。
#include "widget.h"
#include "ui_widget.h"
#include<QDebug>
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
this->setWindowTitle("人脸识别考勤");
this->setWindowIcon(QIcon(":/img/space.jpg"));
ui->label->setFixedSize(ui->widget->size());
//设置textEdit能够每次换行
ui->textEdit->insertPlainText("");
//设置按钮样式
camera.setBtnStyle(ui->faceBtn);
camera.setBtnStyle(ui->addUserBtn);
camera.setBtnStyle(ui->quitBtn);
//创建设置摄像头
camera.setLabel(ui->label);
//打开摄像头
camera.startCamera();
//获取百度人脸识别access_token
manager = new QNetworkAccessManager;
QByteArray url =QString("client_id=%1&client_secret=%2&grant_type=client_credentials")
.arg(client_id)//就是API Key,就直接复制过来就好啦
.arg(client_secret).toLatin1(); //这个是Secret Key
QNetworkRequest request;
request.setUrl(QUrl(baiduUrl+url));
connect(manager,&QNetworkAccessManager::finished,this,&Widget::Accesstokenreply);
manager->get(request);
}
/*获取Access_token,需要解析json格式*/
void Widget::Accesstokenreply(QNetworkReply *reply)
{
QByteArray byte = reply->readAll();
QByteArray bytearry= QString(byte).toUtf8();
QJsonDocument document= QJsonDocument::fromJson(bytearry);
if(document.isObject()){
QJsonObject json = document.object();
if(json.contains("access_token")){
QJsonValue jsonval = json.value("access_token");
access_token = jsonval.toString();
// qDebug()<<access_token;
}
}
}
到这里就可以利用Access_token直接调用人脸识别功能的一些URL使用啦。
我这里调用了人脸检测和人脸库的人脸注册(在第二个界面用来注册人脸)及人脸检索三个功能。
void Widget::Baiduface()
{
//发送图片给百度人脸检测,获取json信息
http = new QNetworkAccessManager;
QString Url= "https://aip.baidubce.com/rest/2.0/face/v3/detect?access_token=";
QNetworkRequest Request;
Request.setUrl(QUrl(Url+access_token));
//设置头部
Request.setRawHeader("Content-Type", "application/json");
//请求body参数(有一些非必选项看自己需要是否选择,看以查看百度的技术文档)
QJsonObject json;
json["image"]=ima;
json["image_type"]="BASE64";
//这里可以自己选择需要哪些内容,就请求哪些
json["face_field"]="age,beauty,expression,face_shape,gender,glasses,emotion";
json["max_face_num"]=2;
json["face_type"]="LIVE";
QByteArray data = QJsonDocument(json).toJson();
connect(http,&QNetworkAccessManager::finished,this,&Widget::RebackFacemessge);
http->post(Request,data);
//百度人脸库内检索,看是否存在
faceSearch = new QNetworkAccessManager;
QString url = "https://aip.baidubce.com/rest/2.0/face/v3/search?access_token=";
QNetworkRequest request;
request.setUrl(QUrl(url+access_token));
//设置头部
request.setRawHeader("Content-Type", "application/json");
//请求body参数(同样有非必选项)
QJsonObject json2;
json2["image"] = ima;
json2["image_type"] = "BASE64";
json2["group_id_list"] = "这是自己创建的组ID";
json2["quality_control"] = "NORMAL";
json2["match_threshold"] = 80;
QByteArray data2 = QJsonDocument(json2).toJson();
connect(faceSearch,&QNetworkAccessManager::finished,this,&Widget::RebackFaceSearch);
faceSearch->post(request,data2);
}
上面的ima是图片转成base64格式的,因为百度的图片信息是string类型的,所以需要自己声明定义一个槽函数用来将截取的人脸图片转成base64格式。
QString Widget::ImageToString(const QImage &image)
{
// 将 QImage 转换为 QString
QByteArray byteArray;
QBuffer buffer(&byteArray);
buffer.open(QIODevice::WriteOnly);
image.save(&buffer, "PNG"); // 保存为 PNG 格式的字节数组
QString imageString = QString(byteArray.toBase64());
// qDebug()<<"imagestring:"<<imageString;
buffer.close();
return imageString;
}
获取的json信息,根据自己需要解析内容并打印出来。
/*获取百度人脸检测结果*/
void Widget::RebackFacemessge(QNetworkReply *reply)
{
QByteArray byte = reply->readAll();
QByteArray bytearry = QString(byte).toUtf8();
QJsonDocument docunment = QJsonDocument::fromJson(bytearry);
if(docunment.isObject()){
QJsonObject json = docunment.object();
//可以先打印出json信息,再根据json内的信息进行解析
// qDebug()<<"json:"<<json;
if(json.contains("result")){
QJsonObject obj = json.take("result").toObject();
if(obj.contains("face_list")){
// qDebug()<<"obj:"<<obj;
QJsonArray array = obj.value("face_list").toArray();
// qDebug()<<"array"<<array;
//face_list是一个数组,需要循环遍历才能解析除所有内容,下面的中文可以根据自己的想法进行修改,应该很有趣
for(int i=0;i<array.size();i++){
QJsonObject obj1 = array.at(i).toObject();
if(obj1.contains("age")){
double age = obj1.value("age").toDouble();
ui->textEdit->append(QString("年龄:%1").arg(age));
}
if(obj1.contains("gender")){
QJsonObject obj2 = obj1.value("gender").toObject();
if(obj2.contains("type")){
QString type = obj2.value("type").toString();
if(type == "male"){
ui->textEdit->append("性别:男");
}else{
ui->textEdit->append("性别:女");
}
}
}
if(obj1.contains("beauty")){
double beauty = obj1.value("beauty").toDouble();
ui->textEdit->append(QString("颜值得分:%1").arg(beauty));
}
if(obj1.contains("emotion")){
QJsonObject obj2 = obj1.value("emotion").toObject();
if(obj2.contains("type")){
QString type = obj2.value("type").toString();
if(type == "sad"){
ui->textEdit->append("情绪:伤心");
}else if(type == "happy"){
ui->textEdit->append("情绪:高兴");
}else if(type == "neutral"){
ui->textEdit->append("情绪:不带感情的");
}else if(type == "angry"){
ui->textEdit->append("情绪:愤怒");
}else if(type == "disgust"){
ui->textEdit->append("情绪:厌恶");
}else if(type == "fear"){
ui->textEdit->append("情绪:恐惧");
}else if(type == "surprise"){
ui->textEdit->append("情绪:惊讶");
}else if(type == "pouty"){
ui->textEdit->append("情绪:撅嘴");
}else if(type == "grimace"){
ui->textEdit->append("情绪:鬼脸");
}
}
}
if(obj1.contains("glasses")){
QJsonObject obj2 = obj1.value("glasses").toObject();
if(obj2.contains("type")){
QString type = obj2.value("type").toString();
if(type == "none"){
ui->textEdit->append("没戴眼镜");
}else {
ui->textEdit->append("戴了眼镜");
}
}
}
if(obj1.contains("expression")){
QJsonObject obj2 = obj1.value("expression").toObject();
if(obj2.contains("type")){
QString type = obj2.value("type").toString();
if(type == "none"){
ui->textEdit->append("表情:无表情");
}else if(type == "smile"){
ui->textEdit->append("表情:微笑");
}else if(type == "laugh"){
ui->textEdit->append("表情:大笑");
}
}
}
if(obj1.contains("face_shape")){
QJsonObject obj2 = obj1.value("face_shape").toObject();
if(obj2.contains("type")){
QString type = obj2.value("type").toString();
if(type == "oval"){
ui->textEdit->append("脸型:椭圆脸");
}else if(type == "square"){
ui->textEdit->append("脸型:方形脸");
}else if(type == "triangle"){
ui->textEdit->append("脸型:尖脸");
}else if(type == "heart"){
ui->textEdit->append("脸型:鹅蛋脸");
}else if(type == "round"){
ui->textEdit->append("脸型:圆脸");
}
}
}
}
}
}
}
}
/*获取百度人脸库检索结果*/
void Widget::RebackFaceSearch(QNetworkReply *reply)
{
QByteArray byte = reply->readAll();
QByteArray bytearry = QString(byte).toUtf8();
QJsonDocument docunment = QJsonDocument::fromJson(bytearry);
if(docunment.isObject()){
QJsonObject json = docunment.object();
qDebug()<<"json:"<<json;
if(json.contains("error_msg")){
//这里是自己想要多的一些错误提示,所以才判断了很多,其实可以只判断成功与否
QString data = json.value("error_msg").toString();
if(data == "Open api daily request limit reached"){
QMessageBox::critical(this,"错误","今日识别次数已用完","确认");
flag=false;
}else if(data =="Open api request limit reached"){
QMessageBox::critical(this,"错误","请求速率过快,请重新识别","确认");
flag=false;
}/*else if(data =="Open api qps request limit reached"){
QMessageBox::critical(this,"错误","请求速率过快,请重新识别","确认");
flag=false;
}*/else if(data =="Open api total request limit reached"){
QMessageBox::critical(this,"错误","免费次数已用完,请付费开通","确认");
flag=false;
}else if(data =="Invalid parameter"){
QMessageBox::critical(this,"错误","无效接口","确认");
flag=false;
}else if(data =="match user is not found"){
QMessageBox::critical(this,"错误","用户未注册,请注册后识别","确认");
flag=false;
}
}
//设置了一个标签,初始值为true,如果出现错误提示就直接退出,不进行下面的解析了,因为有的错误会出现错误提示的同时,也会有结果返回
if(flag == false)
return;
if(json.contains("result")){
QJsonObject obj = json.value("result").toObject();
if(obj.contains("user_list")){
QJsonArray array = obj.value("user_list").toArray();
for(int i=0;i<array.size();i++){
QJsonObject obj2 = array.at(i).toObject();
if(obj2.contains("score")){
double score = obj2.value("score").toDouble();
if(score<80){
qDebug()<<"识别失败";
dialog.trueOrfalse("识别失败");
return;
}else {
qDebug()<<"识别成功";
dialog.trueOrfalse("识别成功");
}
}
if(obj2.contains("user_id")){
QString user = obj2.value("user_id").toString();
QStringList parts = user.split("_");
//qDebug()<<"用户名:"<<parts.at(0);
//qDebug()<<"工号:"<<parts.at(1);
//dialog是自己封装的弹出显示结果的类
dialog.setIoformation(QString("用户名:%1").arg(parts.at(0)));
dialog.setIoformation(QString("工号:%1").arg(parts.at(1)));
dialog.show();
}
}
}
}
}
}
这是一个截图按钮的控件。
/*截图*/
void Widget::on_faceBtn_clicked()
{
//清除image
image.fill(Qt::white);
// 调用函数获取摄像头当前帧
image = camera.screenShort();
//iamge转换成string类型
ima = ImageToString(image);
// qDebug()<<"ima"<<ima;
ui->label_image->clear();
//上传的截图放在label标签上
QPixmap pixmap = QPixmap::fromImage(image);
ui->label_image->setPixmap(pixmap);
ui->label_image->setScaledContents(true);
}
3、register类
这是第二个界面用来添加用户注册的功能。
在构造函数中,传入了一个Qstring类型的变量,用来传入最开始获取的Access_token就可以不用再次拉取了。
#include "register.h"
#include "ui_register.h"
#include<QDebug>
Register::Register(QString access_token, QWidget* parent ) :
QWidget(parent),
ui(new Ui::Register)
{
ui->setupUi(this);
this->setWindowTitle("添加人脸识别");
this->setWindowIcon(QIcon(":/img/space.jpg"));
token = access_token;
//创建设置摄像头
camera.setLabel(ui->label);
camera.startCamera();
//设置按钮样式
camera.setBtnStyle(ui->screenShortBtn);
camera.setBtnStyle(ui->screenShortBtn_2);
camera.setBtnStyle(ui->loginBtn);
camera.setBtnStyle(ui->rebackBtn);
}
这里同样也有截图和image类型的转成base64的转化。
这是注册用户人脸的按钮,也是最后一个请求URL了。
void Register::on_loginBtn_clicked()
{
//判断用户名和工号是否为空
if(ui->lineEdit_name->text().isEmpty() || ui->lineEdit_port->text().isEmpty()){
QMessageBox::warning(this,"警告","请输入姓名或账号","确认");
return;
}
//判断用户名是否字母
for(const QChar&c:ui->lineEdit_name->text()){
if(!c.isLetter()){
QMessageBox::warning(this,"非法操作","用户名格式不正确,请重新输入","确认");
return;
}
}
//判断工号是否是数字
bool isNumber=false;
ui->lineEdit_port->text().toInt(&isNumber);
if(isNumber == false){
QMessageBox::warning(this,"非法操作","工号格式不正确,请重新输入","确认");
return;
}
http = new QNetworkAccessManager;
//百度人脸识别注册url
QString url = "https://aip.baidubce.com/rest/2.0/face/v3/faceset/user/add?access_token=";
QNetworkRequest request;
request.setUrl(QUrl(url+token));
request.setRawHeader("Content-Type", "application/json");
QJsonObject json;
//与前两个一样,自己选择
json["image"]=imastring;
json["image_type"]="BASE64";
json["group_id"]="自己创建的组ID";
json["user_id"]=QString("%1_%2").arg(ui->lineEdit_name->text()).arg(ui->lineEdit_port->text().toInt());
json["quality_control"]="NORMAL";
QByteArray data = QJsonDocument(json).toJson();
connect(http,&QNetworkAccessManager::finished,this,&Register::RebackFacemessge);
http->post(request,data);
}
上面注册功能的输入信息自己还加了一些判断,因为百度人脸库注册的user_id要求只能数字、字母和下划线,这里自己就只允许用户名输入字母,工号输入数字,请求时自己在两者之间加一个下滑线,也方便到时候检索时返回结果解析出的user_id根据”_"来分割用户名和工号了。
这里获取的json解析的内容只有一些错误判断了,还是很好操作。
/*注册获取json信息*/
void Register::RebackFacemessge(QNetworkReply *reply)
{
//获取到信息,就清空用户输入的信息
ui->lineEdit_name->clear();
ui->lineEdit_port->clear();
QByteArray byte = reply->readAll();
QByteArray bytearry = QString(byte).toUtf8();
QJsonDocument docunment = QJsonDocument::fromJson(bytearry);
if(docunment.isObject()){
QJsonObject json = docunment.object();
//qDebug()<<"json"<<json;
if(json.contains("error_msg")){
QString data = json.value("error_msg").toString();
if(data == "SUCCESS"){
QMessageBox::information(this,"注册","恭喜!人脸库添加成功");
return;
}else if(data == "param[user_id] format error"){
QMessageBox::warning(this,"警告","用户名或账号格式不正确","确认");
return;
}else if(data == "param[quality_control]format error"){
QMessageBox::critical(this,"错误","人脸质量过低,请重新注册");
return;
}else if(data == "add face fail"){
QMessageBox::critical(this,"错误","人脸图片添加失败,请重新注册");
return;
}else if(data == "user is already exist"){
QMessageBox::critical(this,"错误","用户已存在,请不要重复注册");
return;
}else {
QMessageBox::critical(this,"错误","人脸库注册失败,请重新注册");
return;
}
}
}
}
4、dialog类
这里弹出的界面信息就比较简单了,只需要在检索时传入获取的内容并弹出界面就行了。在第一个界面定义,直接调用函数接口就可以了。
void Dialog::on_pushButton_clicked()
{
this->close();
}
void Dialog::trueOrfalse(const QString data)
{
if(data == "识别失败"){
ui->label_pixmap->setPixmap(QPixmap(":/img/fault.jpg"));
ui->textEdit->setText("识别失败");
return;
}else if(data == "识别成功"){
ui->label_pixmap->setPixmap(QPixmap(":/img/right.jpg"));
ui->textEdit->append("识别成功");
}
}
void Dialog::setIoformation(const QString data)
{
ui->textEdit->append(data);
}
void Dialog::startDialog()
{
this->show();
}
三、效果演示
注:他这个颜值得分算法肯定是不准确的,也还好不是本人亲自测试的(狗头保命)
四、改进
这次时间比较紧,还有许多可以改进的地方:
1、界面问题可以更美观一点,这个实在是丑得不行。
2、可以去掉手动截图发送识别的功能,可以增加活体检测,并自动截图识别返回信息。
3、可以增加语音功能,将识别成功与否的结果说出来。