官方参考文档:
阿里云人脸活体检测api文档:调用DetectLivingFace进行人脸活体检测_视觉智能开放平台(VIAPI)-阿里云帮助中心
阿里云视觉智能平台公共请求参数文档:人体人脸分析API接口的公共请求参数_视觉智能开放平台(VIAPI)-阿里云帮助中心
这个公共请求参数文档里面,官方给了完整的java代码,用于请求人脸活体检测接口,但是阿里云官方对c++的支持很弱,没有c++相关的示例,这篇文章就是根据这个官方java示例,给出qt/c++的代码
先给出完整qt代码
头文件:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QImage>
#include <QNetworkAccessManager>
#include <QNetworkReply>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_imgBtn_clicked();
private:
Ui::MainWindow *ui;
QNetworkAccessManager *_manager = nullptr;
QUrl url;
void doAliDetectLivingFace();
QString specialUrlEncode(const QString &value);
QString sign(const QString &accessSecret, const QString &stringToSign);
QString sign2(const QString &accessSecret, const QString &stringToSign);
private slots:
void finishedReplay();
void downloadProgress(qint64 bytesSent, qint64 bytesTotal);
void slotError(QNetworkReply::NetworkError net_error);
};
#endif // MAINWINDOW_H
cpp 文件:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QBuffer>
#include <QFileInfo>
#include <QCryptographicHash>
#include <QDebug>
#include <QFileDialog>
#include <QUrlQuery>
#include <QUuid>
#include <QMessageAuthenticationCode>
#include <QTimeZone>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
_manager =new QNetworkAccessManager(this);
doAliDetectLivingFace();
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::finishedReplay()
{
QNetworkReply *reply = dynamic_cast<QNetworkReply*>(sender());
QByteArray bytes = reply->readAll();
qDebug()<<"finished:\n";
QString html_text = bytes;
qDebug()<<"get ready,read size:"<<html_text.size();
qDebug()<< "ret_html_text:\n"<<html_text<<"\n";
reply->deleteLater();
}
void MainWindow::downloadProgress(qint64 bytesSent, qint64 bytesTotal)
{
qDebug()<< "\ndownloadProgress done:\n";
qDebug() << "bytesSent: " << bytesSent<< " " << "bytesTocal: " << bytesTotal;
}
void MainWindow::slotError(QNetworkReply::NetworkError net_error)
{
qDebug()<< "slotError:"<<net_error;
}
void MainWindow::doAliDetectLivingFace()
{
/*
最终url:
http://facebody.cn-shanghai.aliyuncs.com/?Signature=olXJ3UlVH4bSG1OHrg3kQWE0lrE%3D
&AccessKeyId=LTAI5tFopUsaPTXZCvSdrkpQ
&Action=DetectLivingFace
&Format=JSON
&RegionId=cn-shanghai
&SignatureMethod=HMAC-SHA1
&SignatureNonce=2a90f9ef-9d6d-438e-b20f-5106653654d2
&SignatureVersion=1.0
&Tasks.1.ImageURL=http%3A%2F%2Fviapi-test.oss-cn-shanghai.aliyuncs.com%2Fviapi-3.0domepic%2Ffacebody%2FDetectLivingFace%2FDetectLivingFace11.jpg
&Tasks.2.ImageURL=http%3A%2F%2Fviapi-test.oss-cn-shanghai.aliyuncs.com%2Fviapi-3.0domepic%2Ffacebody%2FDetectLivingFace%2FDetectLivingFace13.jpg
&Timestamp=2024-10-31T09%3A03%3A08Z
&Version=2019-12-30
*/
QDateTime currentTime = QDateTime::currentDateTime();
QDateTime eightHoursAgo = currentTime.addSecs(-8 * 3600);
QTimeZone gmtTimeZone("GMT");
eightHoursAgo.setTimeZone(gmtTimeZone);
QString currentTimeStr = eightHoursAgo.toString("yyyy-MM-dd'T'HH:mm:ss'Z'");
// 这里换成自己阿里云账户的accessKeyId和accessKeySecret
QString accessKeyId = "***************";
QString accessKeySecret = "***************";
QMap<QString, QString> urlQueryMap;
urlQueryMap.insert("Tasks.1.ImageURL", "http://viapi-test.oss-cn-shanghai.aliyuncs.com/viapi-3.0domepic/facebody/DetectLivingFace/DetectLivingFace11.jpg");
urlQueryMap.insert("Tasks.2.ImageURL", "http://viapi-test.oss-cn-shanghai.aliyuncs.com/viapi-3.0domepic/facebody/DetectLivingFace/DetectLivingFace13.jpg");
urlQueryMap.insert("SignatureMethod", "HMAC-SHA1");
QUuid uuid = QUuid::createUuid();
QString uuidString = uuid.toString();
uuidString = uuidString.mid(1, uuidString.size() -2);
// urlQueryMap.insert("SignatureNonce", "4449ddb6-d72a-4e56-899c-c33365a8caf2");
urlQueryMap.insert("SignatureNonce", uuidString);
urlQueryMap.insert("AccessKeyId", accessKeyId);
urlQueryMap.insert("SignatureVersion", "1.0");
// urlQueryMap.insert("Timestamp", "2024-10-28T02:02:26Z");
urlQueryMap.insert("Timestamp", currentTimeStr);
urlQueryMap.insert("Format", "JSON");
urlQueryMap.insert("RegionId", "cn-shanghai");
urlQueryMap.insert("Version", "2019-12-30");
urlQueryMap.insert("Action", "DetectLivingFace");
// urlQueryMap.insert("ImageURL", "https://ainemo-testdev.oss-cn-beijing.aliyuncs.com/wenkeTest/1.png");
if (urlQueryMap.contains("Signature")) {
urlQueryMap.remove("Signature");
}
QString sortQueryStringTmp;
for(auto it = urlQueryMap.begin(); it != urlQueryMap.end(); ++it)
{
sortQueryStringTmp.append("&").append(specialUrlEncode(it.key())).append("=").append(specialUrlEncode(it.value()));
}
QString sortedQueryString = sortQueryStringTmp.mid(1);
QString stringToSign;
stringToSign.append("POST").append("&");
stringToSign.append(specialUrlEncode("/")).append("&");
stringToSign.append(specialUrlEncode(sortedQueryString));
// QString signStr = QString::fromStdString(SignUtil::sign(QString(accessKeySecret + "&").toStdString(), stringToSign.toStdString()));
// signStr = signStr.left(signStr.size() - 1);
QString signStr = sign(accessKeySecret + "&", stringToSign);
QString signature = specialUrlEncode(signStr);
QString urlStr = QString("http://facebody.cn-shanghai.aliyuncs.com/?Signature=%1&%2").arg(signature, sortedQueryString);
// qDebug() << "timeStamp is " << "2024-10-28T02:02:26Z";
qDebug() <<"sortedQueryString is " << sortedQueryString;
qDebug() <<"stringToSign is " << stringToSign;
qDebug() <<"signStr is " << signStr;
qDebug() <<"signature is " << signature;
qDebug() << "url str is " << urlStr;
QNetworkRequest request;
QString accept = "application/json";
QString content_type = "application/json";
request.setRawHeader(QByteArray("accept"), accept.toLocal8Bit());
request.setHeader(QNetworkRequest::ContentTypeHeader,content_type);
request.setUrl(QUrl(urlStr));
QNetworkReply *reply = _manager->post(request, "");
connect(reply, SIGNAL(finished()), this, SLOT(finishedReplay()));
connect(reply, SIGNAL(error(QNetworkReply::NetworkError)),
this, SLOT(slotError(QNetworkReply::NetworkError)));
connect(reply,SIGNAL(downloadProgress(qint64,qint64)),
this,SLOT(downloadProgress(qint64,qint64)));
}
QString MainWindow::specialUrlEncode(const QString &value)
{
QString encodedValue = QUrl::toPercentEncoding(value, "", "");
encodedValue.replace("+", "%20");
encodedValue.replace("*", "%2A");
encodedValue.replace("%7E", "~");
return encodedValue;
}
QString MainWindow::sign(const QString &accessSecret, const QString &stringToSign)
{
QByteArray key = accessSecret.toUtf8();
QByteArray data = stringToSign.toUtf8();
int blockSize = 64; // HMAC-SHA1 block size
if (key.length() > blockSize) {
key = QCryptographicHash::hash(key, QCryptographicHash::Sha1);
} else if (key.length() < blockSize) {
key = key.leftJustified(blockSize, '\0');
}
QByteArray ipad(blockSize, 0x36);
QByteArray opad(blockSize, 0x5c);
// XOR key with inner and outer padding
for (int i = 0; i < blockSize; i++) {
ipad[i] = ipad[i] ^ key.at(i);
opad[i] = opad[i] ^ key.at(i);
}
QByteArray innerHash = QCryptographicHash::hash(ipad + data, QCryptographicHash::Sha1);
QByteArray finalData = opad + innerHash;
QByteArray signData = QCryptographicHash::hash(finalData, QCryptographicHash::Sha1);
// Encode the result in Base64
QByteArray base64SignData = signData.toBase64();
return QString(base64SignData);
}
QString MainWindow::sign2(const QString &accessSecret, const QString &stringToSign)
{
QByteArray key = accessSecret.toUtf8();
QByteArray data = stringToSign.toUtf8();
// Calculate HMAC-SHA1
QByteArray signData = QMessageAuthenticationCode::hash(data, key, QCryptographicHash::Sha1);
// Encode the result in Base64
QByteArray base64SignData = QByteArray(signData).toBase64();
return QString(base64SignData);
}
void MainWindow::on_imgBtn_clicked()
{
QString imgPath = QFileDialog::getOpenFileName(this, "select image", "", tr("Images (*.png *.xpm *.jpg)"));
doAliDetectLivingFace();
ui->imgLineEdit->setText(imgPath);
}
关键就是doAliDetectLivingFace函数,里面有几个细节:
1、时间的获取需要比当前时间早8个小时
2、注意stringToSign获取细节,url的query需要按字母顺序排序好,同时需要经specialUrlEncode,这一块自己仔细看代码
3、获取认证字符串的函数sign、sign2都可以使用,是一样的结果,其中sign函数呈现了更多细节
另外,考虑到有些开发者只熟悉原生c++,不熟悉qt,而上面sign函数是关键,其它地方都好写出替代代码,所以这里给出用openssl实现的sign函数,qt的开发者可以不管这个
std::string SignUtil::sign(const std::string &accessSecret, const std::string &stringToSign)
{
unsigned char hash[EVP_MAX_MD_SIZE];
unsigned int hashLen = 0;
// 初始化HMAC上下文
HMAC_CTX* hmacCtx = HMAC_CTX_new();
HMAC_Init_ex(hmacCtx, accessSecret.c_str(), accessSecret.length(), EVP_sha1(), NULL);
// 更新HMAC上下文
HMAC_Update(hmacCtx, (const unsigned char *)stringToSign.c_str(), stringToSign.length());
// 完成HMAC计算
HMAC_Final(hmacCtx, hash, &hashLen);
// 释放HMAC上下文
HMAC_CTX_free(hmacCtx);
// 创建一个Base64编码器
BIO* bio = BIO_new(BIO_s_mem());
BIO* b64 = BIO_new(BIO_f_base64());
bio = BIO_push(b64, bio);
// 将哈希值写入BIO
BIO_write(bio, hash, hashLen);
BIO_flush(bio);
// 从BIO读取Base64编码后的字符串
BUF_MEM* buffer;
BIO_get_mem_ptr(bio, &buffer);
std::string base64Encoded(buffer->data, buffer->length);
// 清理BIO
BIO_free_all(bio);
return base64Encoded;
}