目录
一、百度智能云介绍
二、代码
1、camera类
2、widget类
3、register类
4、dialog类
三、效果演示
四、改进
获取完整代码请前往: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_tokenmanager = 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 KeyQNetworkRequest 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 转换为 QStringQByteArray 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()
{//清除imageimage.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;//百度人脸识别注册urlQString 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、可以增加语音功能,将识别成功与否的结果说出来。