根据B站@井上远子_的“基于Qt的图像识别项目”写的测试代码。该代码为连续检测,但后来新申请的百度智能云账号赠送的调用次数有限制,连续检测可能很短的时间内就把一个月的赠送量用完了,建议可以修改为非连续调用,比如使用UI上的按钮单次触发。
1、UI
2、imageTest.pro
QT += core gui multimedia multimediawidgets network
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
main.cpp \
imagetest.cpp \
worker.cpp
HEADERS += \
imagetest.h \
worker.h
FORMS += \
imagetest.ui
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
3、imagetest.h
#ifndef IMAGETEST_H
#define IMAGETEST_H
#include <QWidget>
#include <QCamera>
#include <QCameraViewfinder>
#include <QCameraImageCapture>
#include <QTimer>
#include <QNetworkAccessManager>
#include <QUrl>
#include <QUrlQuery>
#include <QSslConfiguration>
#include <QNetworkReply>
#include <QJsonParseError>
#include <QJsonDocument>
#include <QJsonObject>
#include <QBuffer>
#include <QJsonArray>
#include <QThread>
#include "worker.h"
#include <QPainter>
#include <QCameraInfo>
QT_BEGIN_NAMESPACE
namespace Ui { class ImageTest; }
QT_END_NAMESPACE
class ImageTest : public QWidget
{
Q_OBJECT
public:
ImageTest(QWidget *parent = nullptr);
~ImageTest();
signals:
void begainWork(QImage img, QThread *childThread); // 通知子线程干活
public slots:
void showCamera(int id, QImage preview); // 接收拍好照片的槽函数
void takePicture(); // 拍照按钮槽函数
void tokenReply(QNetworkReply *reply); // 网络请求回复槽函数
void beginFaceDetect(QByteArray postData, QThread *overThread); // 开始人脸识别
void imgReply(QNetworkReply *reply); // 图像识别网络请求回复槽函数
void prePostData();
void pickCamera(int idx);
private:
Ui::ImageTest *ui;
QCamera* camera; // 相机对象
QCameraViewfinder* finder; // 相机取景器对象
QCameraImageCapture* imageCapture; // 图像捕捉对象
QTimer* refreshTimer; // 连续拍照定时器
QTimer* netTimer; // 网络是被请求定时器
QImage img;
QNetworkAccessManager* tokenManager; // token获取网络访问管理
QNetworkAccessManager* imgManager; // 图像识别网络访问管理
QSslConfiguration sslConfig; // ssl配置
QString accessToken;
QThread *childThread;
double faceTop;
double faceLeft;
double faceWidth;
double faceHeight;
double age;
QString gender;
int mask;
double beauty;
QList<QCameraInfo> cameraInfoList;
int latestTime;
};
#endif // IMAGETEST_H
4、imagetest.c
#include "imagetest.h"
#include "ui_imagetest.h"
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QNetworkAccessManager>
ImageTest::ImageTest(QWidget *parent)
: QWidget(parent)
, ui(new Ui::ImageTest)
{
ui->setupUi(this);
// 获取所有可用摄像头信息
cameraInfoList = QCameraInfo::availableCameras();
// 遍历输出相机信息
for (const QCameraInfo &tmpCam:cameraInfoList)
{
qDebug() << tmpCam.deviceName() << "|||" << tmpCam.description();
ui->comboBox->addItem(tmpCam.description());
}
connect(ui->comboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ImageTest::pickCamera);
/*
* 1 先有一个相机
* 2 通过相机的取景器实时观察相机镜头内的图像
* 3 通过图像捕捉,拍摄图像
*/
camera = new QCamera(); // 相机对象
finder = new QCameraViewfinder(); // 相机取景器对象
imageCapture = new QCameraImageCapture(camera); // 相机对应的图像捕捉对象
camera->setViewfinder(finder); // 设置相机的取景器
camera->setCaptureMode(QCamera::CaptureStillImage); // 设置图像捕捉模式:捕捉静态图像
imageCapture->setCaptureDestination(QCameraImageCapture::CaptureToBuffer); // 设置捕捉的图片存到缓存
// 拍照好之后QCameraImageCapture会发出一个拍照成功的信号,用一个槽函数接收信号,取出图片
connect(imageCapture, &QCameraImageCapture::imageCaptured, this, &ImageTest::showCamera);
// 识别按钮信号槽
connect(ui->pushButton, &QPushButton::clicked, this, &ImageTest::prePostData);
camera->start(); // 启动相机
// 进行界面布局
this->resize(1000, 600); // 整体界面大小
QVBoxLayout* vboxl = new QVBoxLayout; // 左边的垂直布局
vboxl->addWidget(ui->label); // 将UI中的label加入到左边垂直布局
vboxl->addWidget(ui->pushButton); // 将UI中的pushButton加入到左边垂直布局
QVBoxLayout* vboxr = new QVBoxLayout; // 右边的垂直布局
vboxr->addWidget(ui->comboBox); // 相机下拉列表
vboxr->addWidget(finder); // 取景器加入到右边垂直布局
vboxr->addWidget(ui->textBrowser); // 将UI中的textBrowser加入到右边垂直布局
QHBoxLayout* hbox = new QHBoxLayout(this); // 整体水平布局
hbox->addLayout(vboxl); // 左边的垂直布局加入到水平布局中
hbox->addLayout(vboxr); // 右边的垂直布局加入到水平布局中
this->setLayout(hbox); // 将hbox设置为界面布局
// 利用定时器不断刷新拍照界面
refreshTimer = new QTimer(this);
connect(refreshTimer, &QTimer::timeout, this, &ImageTest::takePicture);
refreshTimer->start(50); // 启动定时器,周期ms
// 利用定时器不断镜像人脸识别请求
netTimer = new QTimer(this);
connect(netTimer, &QTimer::timeout, this, &ImageTest::prePostData);
// 网络访问,连接百度AI,用于图像识别
tokenManager = new QNetworkAccessManager(this);
// 网络请求返回信息处理
connect(tokenManager, &QNetworkAccessManager::finished, this, &ImageTest::tokenReply);
// 图像识别网络请求
imgManager = new QNetworkAccessManager(this);
connect(imgManager, &QNetworkAccessManager::finished, this, &ImageTest::imgReply);
// 支持的协议,不支持https时需要安装库
// Qt5.12: libcrypto-1_1-x64.dll libssl-1_1-x64.dll
// 系统中找到后复制到 F:\Qt\Qt5.12.9\5.12.9\mingw73_64\bin
qDebug() << tokenManager->supportedSchemes();
// 定义url
QUrl url("https://aip.baidubce.com/oauth/2.0/token");
qDebug() << url;
// 键值队
QUrlQuery query;
query.addQueryItem("grant_type", "client_credentials");
query.addQueryItem("client_id", "<你自己的client_id>"); // 替换成你自己的client_id
query.addQueryItem("client_secret", "<你自己的client_secret>"); // 替换成你自己的client_secret
// url添加键值队
url.setQuery(query);
qDebug() << url;
///ssl支持检查
if(QSslSocket::supportsSsl())
{
qDebug() << "支持";
}
else
{
qDebug() << "不支持";
}
// ssl配置
sslConfig = QSslConfiguration::defaultConfiguration(); // 使用默认配置
sslConfig.setPeerVerifyMode(QSslSocket::QueryPeer); // 模式
sslConfig.setProtocol(QSsl::TlsV1_2); // 协议
// 请求配置
QNetworkRequest req;
req.setUrl(url); // 设置url
req.setSslConfiguration(sslConfig); // 设置ssl配置信息
// 发送get请求
tokenManager->get(req);
}
ImageTest::~ImageTest()
{
delete ui;
}
// 接收处理拍好的照片
void ImageTest::showCamera(int id, QImage img)
{
Q_UNUSED(id)
this->img = img;
// 绘制人脸框
QPainter p(&img); // 创建绘图
p.setPen(Qt::red); // 画笔颜色
p.drawRect(faceLeft, faceTop, faceWidth, faceHeight); // 绘制矩形
// 识别的信息显示在人脸旁边
QFont font = p.font();
font.setPixelSize(30);
p.setFont(font);
p.drawText(faceLeft+faceWidth+5, faceTop, QString("年龄:").append(QString::number(age)));
p.drawText(faceLeft+faceWidth+5, faceTop+40, QString("性别:").append(gender));
p.drawText(faceLeft+faceWidth+5, faceTop+80, QString("口罩:").append(mask==0?"没带口罩":"戴口罩了"));
p.drawText(faceLeft+faceWidth+5, faceTop+120, QString("颜值:").append(QString::number(beauty)));
ui->label->setPixmap(QPixmap::fromImage(img)); // 将拍好的图片转化成像素格式在label中显示
}
// 拍照
void ImageTest::takePicture()
{
imageCapture->capture();
}
void ImageTest::tokenReply(QNetworkReply *reply)
{
// 错误处理
if (reply->error() != QNetworkReply::NoError)
{
qDebug() << reply->errorString();
return;
}
// 正常应答
const QByteArray reply_data = reply->readAll();
// json解析
QJsonParseError jsonErr;
QJsonDocument doc = QJsonDocument::fromJson(reply_data, &jsonErr);
if (jsonErr.error == QJsonParseError::NoError)
{
// 解析成功
QJsonObject obj = doc.object(); // 获得JSON对象
if (obj.contains("access_token"))
{
accessToken = obj.take("access_token").toString();
}
ui->textBrowser->setText(accessToken);
}
else
{
qDebug() << "JSON ERR:" << jsonErr.errorString();
}
reply->deleteLater();
netTimer->start(1000); // 定时器不断触发识别请求
// prePostData(); // 连续循环检测 // 连续循环检测时取消改行注释
}
void ImageTest::prePostData()
{
// 创建线程
// 创建工人
// 把工人送进子线程
// 启动子线程
// 给工人发通知干活
childThread = new QThread(this);
Worker *worker = new Worker;
worker->moveToThread(childThread);
// 通知线程干活
connect(this, &ImageTest::begainWork, worker, &Worker::doWork);
// 干完活的结果通知主线程
connect(worker, &Worker::resultReady, this, &ImageTest::beginFaceDetect);
// 子线程结束后删除子线程工人
connect(childThread, &QThread::finished, worker, &Worker::deleteLater);
childThread->start();
emit begainWork(img, childThread);
}
// 相机切换槽函数
void ImageTest::pickCamera(int idx)
{
qDebug() << cameraInfoList.at(idx).description();
refreshTimer->stop(); // 刷新画面的计时器先停止
camera->stop(); // 之前的相机先关掉
// 创建新相机
camera = new QCamera(cameraInfoList.at(idx));
imageCapture = new QCameraImageCapture(camera);
connect(imageCapture, &QCameraImageCapture::imageCaptured, this, &ImageTest::showCamera);
imageCapture->setCaptureDestination(QCameraImageCapture::CaptureToBuffer);
camera->setCaptureMode(QCamera::CaptureStillImage);
camera->setViewfinder(finder);
camera->start();
refreshTimer->start(100);
}
void ImageTest::beginFaceDetect(QByteArray postData, QThread *overThread)
{
// 关闭子线程
// 组装图像识别请求
// 用post发送数据给百度AI
overThread->exit(); // 结束线程
overThread->wait(); // 等待线程结束
if (childThread->isFinished())
{
qDebug() << "子线程结束了";
}
else
{
qDebug() << "子线程没结束";
}
// 3 组装图像识别请求
QUrl url("https://aip.baidubce.com/rest/2.0/face/v3/detect");
QUrlQuery query;
query.addQueryItem("access_token", accessToken); // URL参数
url.setQuery(query);
// ssl在初始化时已配置
// 组装请求
QNetworkRequest req;
req.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json")); // 请求头
req.setUrl(url); // url
req.setSslConfiguration(sslConfig); // ssl
// 发送post网络请求
imgManager->post(req, postData);
}
void ImageTest::imgReply(QNetworkReply *reply)
{
// 错误处理
if (reply->error() != QNetworkReply::NoError)
{
qDebug() << reply->errorString();
return;
}
// 正常应答
const QByteArray replyData = reply->readAll();
qDebug() << replyData;
// json解析
QJsonParseError jsonErr;
QJsonDocument doc = QJsonDocument::fromJson(replyData, &jsonErr);
// 解析成功
if (jsonErr.error == QJsonParseError::NoError)
{
QString faceInfo; // 用于存放解析出的人脸信息
// 取出最外层json
QJsonObject obj = doc.object(); // 获得JSON对象
if(obj.contains("timestamp")) // 获取时间用于判断是否最新一次人脸检测
{
int tmpTime = obj.take("timestamp").toInt();
if (tmpTime < latestTime) // 旧包,不处理(可能是网络等原因旧包后到达)
{
return;
}
else
{
latestTime = tmpTime;
}
}
if (obj.contains("result")) // 获取result内容
{
QJsonObject resultObj = obj.take("result").toObject(); // 将result转换为jsonObj
// 取出人脸列表
if (resultObj.contains("face_list"))
{
QJsonArray faceList = resultObj.take("face_list").toArray(); // 将face_list转换为QJsonArray
QJsonObject faceObject = faceList.at(0).toObject(); // 取出数组中第一张人脸信息
// 取出人脸定位信息
if (faceObject.contains("location"))
{
QJsonObject locObj = faceObject.take("location").toObject();
if (locObj.contains("left"))
{
faceLeft = locObj.take("left").toDouble();
}
if (locObj.contains("top"))
{
faceTop = locObj.take("top").toDouble();
}
if (locObj.contains("width"))
{
faceWidth = locObj.take("width").toDouble();
}
if (locObj.contains("height"))
{
faceHeight = locObj.take("height").toDouble();
}
}
// 取出年龄
if(faceObject.contains("age"))
{
age = faceObject.take("age").toDouble();
faceInfo.append("年龄:").append(QString::number(age)).append("\r\n"); // 年龄信息存入
qDebug() << "age";
}
// 取出性别
if(faceObject.contains("gender"))
{
QJsonObject genderObj = faceObject.take("gender").toObject();
if (genderObj.contains("type"))
{
gender = genderObj.take("type").toString();
faceInfo.append("性别:").append(gender).append("\r\n"); // 性别信息存入
qDebug() << "gender";
}
}
// 取出表情
if(faceObject.contains("emotion"))
{
QJsonObject emotionObj = faceObject.take("emotion").toObject();
if (emotionObj.contains("type"))
{
QString emotion = emotionObj.take("type").toString();
faceInfo.append("表情:").append(emotion).append("\r\n"); // 表情信息存入
qDebug() << "emotion";
}
}
// 取出是否戴口罩
if(faceObject.contains("mask"))
{
QJsonObject maskObj = faceObject.take("mask").toObject();
if (maskObj.contains("type"))
{
mask = maskObj.take("type").toInt();
faceInfo.append("口罩:").append(mask==0?"没带口罩":"戴口罩了").append("\r\n"); // 表情信息存入
qDebug() << "mask";
}
}
// 取出颜值
if(faceObject.contains("beauty"))
{
beauty = faceObject.take("beauty").toDouble();
faceInfo.append("颜值:").append(QString::number(beauty)).append("\r\n"); // 年龄信息存入
qDebug() << "beauty";
}
}
}
// 信息框中显示
ui->textBrowser->setText(faceInfo);
qDebug() << faceInfo;
}
else
{
qDebug() << "JSON ERR:" << jsonErr.errorString();
}
reply->deleteLater();
// prePostData(); // 连续循环检测 // 连续循环检测时取消改行注释
}
5、worker.h
#ifndef WORKER_H
#define WORKER_H
#include <QObject>
#include <QImage>
#include <QThread>
class Worker : public QObject
{
Q_OBJECT
public:
explicit Worker(QObject *parent = nullptr);
public slots:
void doWork(QImage img, QThread *childThread);
signals:
void resultReady(QByteArray pd, QThread *childThread);
};
#endif // WORKER_H
6、worker.cpp
#include "worker.h"
#include <QBuffer>
#include <QJsonObject>
#include <QJsonDocument>
Worker::Worker(QObject *parent) : QObject(parent)
{
}
void Worker::doWork(QImage img, QThread *childThread)
{
// 1 转成base64编码图片
QByteArray ba; // 字节数组
QBuffer buff(&ba); // 为字节数组提供一个buff,类似文件IO,内存操作
img.save(&buff, "png"); // 以png格式保存图片
QString b64str = ba.toBase64(); // 将图片转换为Base64编码
// qDebug() << b64str;
// 百度要求JSON格式post发送
// 2 请求体body参数设置
QJsonObject postJson;
QJsonDocument doc;
// 插入键值对
postJson.insert("image", b64str);
postJson.insert("image_type", "BASE64");
postJson.insert("face_field", "age,expression,face_shape,gender,glasses,emotion,face_type,mask,beauty");
doc.setObject(postJson); // 放入doc内
QByteArray postData = doc.toJson(QJsonDocument::Compact); // 打包转换为json字节数组
emit resultReady(postData, childThread); // 通知主线程
}