Qt编写百度离线版人脸识别+比对+活体检测
Posted feiyangqingyun
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Qt编写百度离线版人脸识别+比对+活体检测相关的知识,希望对你有一定的参考价值。
在AI技术发展迅猛的今天,很多设备都希望加上人脸识别功能,好像不加上点人脸识别功能感觉不够高大上,都往人脸识别这边靠,手机刷脸解锁,刷脸支付,刷脸开门,刷脸金融,刷脸安防,是不是以后还可以刷脸匹配男女交友?
很多人认为人脸识别直接用opencv做,其实那只是极其基础的识别个人脸,然并卵,好比学C++写了个hello类似。拿到人脸区域图片只是万里长征的第一步,真正能够起作用的是人脸特征值的提取,然后用于搜索和查找人脸,比如两张图片比较相似度,从一堆人脸库中找到最相似的人脸,对当前人脸识别是否是活体等。
对于可以接入外网的设备,可以直接通过在线api的http请求方式获得结果,但是有很多应用场景是离线的,或者说不通外网,只能局域网使用,为了安全性考虑,这个时候就要求所有的人脸处理在本地完成,本篇文章采用的百度离线SDK作为解决方案。可以去官网申请,默认有6个免费的密钥使用三个月,需要与本地设备的指纹信息匹配,感兴趣的同学可以自行去官网下载SDK。
百度离线人脸识别SDK文件比较大,光模型文件就645MB,估计这也许是识别率比较高的一方面原因吧,不断训练得出的模型库,本篇文章只放出Qt封装部分源码。官网对应的使用说明还是非常详细的,只要是学过编程的人就可以看懂。
第一步:初始化SDK
第二步:执行动作,比如查找人脸、图片比对、特征值比对等
完整头文件代码:
#ifndef FACEBAIDULOCAL_H #define FACEBAIDULOCAL_H /** * 百度离线版人脸识别+人脸比对等功能类 作者:feiyangqingyun(QQ:517216493) 2018-8-30 * 1:支持活体检测 * 2:可设置最大队列中的图片数量 * 3:多线程处理,通过type控制当前处理类型 * 4:支持单张图片检索相似度最高的图片 * 5:支持指定目录图片生成特征文件 * 6:支持两张图片比对方式 * 7:可设置是否快速查找 * 8:可设置是否统计用时 */ #include <QtCore> #include <QtGui> #if (QT_VERSION > QT_VERSION_CHECK(5,0,0)) #include <QtWidgets> #endif #include "baidu_face_api.h" class FaceBaiDuLocal : public QThread { Q_OBJECT public: static FaceBaiDuLocal *Instance(); explicit FaceBaiDuLocal(QObject *parent = 0); ~FaceBaiDuLocal(); protected: void run(); private: static QScopedPointer<FaceBaiDuLocal> self; BaiduFaceApi *api; std::vector<TrackFaceInfo> *faces; QMutex mutex; //锁对象 bool stopped; //线程停止标志位 int maxCount; //最大图片张数 int type; //当前处理类型 int percent; //最小人脸百分比 int delayms; //减去毫秒数,用于造假 bool findFast; //是否快速模式 bool countTime; //统计用时 bool busy; //是否正忙 QList<QString> flags; //等待处理的图像队列的名称 QList<QImage> imgs; //等待处理的图像队列 QList<QImage> imgs2; //等待处理的比对图像队列 QString sdkPath; //SDK目录 QString imgDir; //图片目录 QImage oneImg; //单张图片比对找出最大特征图像 QList<QString> imgNames; //图像队列 QList<QList<float> > features; //特征队列 signals: //人脸区域坐标返回 void receiveFaceRect(const QString &flag, const QRect &rect, int msec); //获取人脸区域坐标失败 void receiveFaceRectFail(const QString &flag); //人脸特征返回 void receiveFaceFeature(const QString &flag, const QList<float> &feature, int msec); //获取人脸特征失败 void receiveFaceFeatureFail(const QString &flag); //人脸比对结果返回 void receiveFaceCompare(const QString &flag, float result, int msec); //人脸比对失败 void receiveFaceCompareFail(const QString &flag); //单张图片检索最大相似度结果返回 void receiveFaceCompareOne(const QString &flag, const QImage &srcImg, const QString &targetName, float result); //所有人脸特征提取完毕 void receiveFaceFeatureFinsh(); //活体检测返回 void receiveFaceLive(const QString &flag, float result, int msec); //活体检测失败 void receiveFaceLiveFail(const QString &flag); public slots: //初始化SDK void init(); //停止处理线程 void stop(); //获取当前是否忙 bool getBusy(); //设置图片队列最大张数 void setMaxCount(int maxCount); //设置当前处理类型 void setType(int type); //设置最小人脸百分比 void setPercent(int percent); //设置减去毫秒数 void setDelayms(int delayms); //设置是否快速模式 void setFindFast(bool findFast); //设置是否统计用时 void setCountTime(bool countTime); //设置是否忙 void setBusy(bool busy); //设置SDK目录 void setSDKPath(const QString &sdkPath); //设置要将图片提取出特征的目录 void setImgDir(const QString &imgDir); //设置单张需要检索的图片 void setOneImg(const QString &flag, const QImage &oneImg); //往队列中追加单张图片等待处理 void append(const QString &flag, const QImage &img); //往队列中追加两张图片等待比对 void append(const QString &flag, const QImage &img, const QImage &img2); //自动加载目录下的所有图片的特征 void getFaceFeatures(const QString &imgDir); //获取人脸区域 bool getFaceRect(const QString &flag, const QImage &img, QRect &rect, int &msec); //活体检测 bool getFaceLive(const QString &flag, const QImage &img, float &result, int &msec); //获取人脸特征 bool getFaceFeature(const QString &flag, const QImage &img, QList<float> &feature, int &msec); //人脸比对,传入两张照片特征 float getFaceCompare(const QString &flag, const QList<float> &feature1, const QList<float> &feature2); //人脸比对,传入两张照片 bool getFaceCompare(const QString &flag, const QImage &img1, const QImage &img2, float &result, int &msec); //从一堆图片中找到最像的一张图片 void getFaceOne(const QString &flag, const QImage &img, QString &targetName, float &result); //指定特征找到照片 void getFaceOne(const QString &flag, const QList<float> &feature, QString &targetName, float &result); //添加人脸 void appendFace(const QString &flag, const QImage &img, const QString &txtFile); //删除人脸 void deleteFace(const QString &flag); }; #endif // FACEBAIDULOCAL_H
完整实现文件代码:
#include "facebaidulocal.h" #define TIMEMS qPrintable(QTime::currentTime().toString("HH:mm:ss zzz")) QByteArray getImageData(const QImage &image) { QByteArray imageData; QBuffer buffer(&imageData); image.save(&buffer, "JPG"); imageData = imageData.toBase64(); return imageData; } QScopedPointer<FaceBaiDuLocal> FaceBaiDuLocal::self; FaceBaiDuLocal *FaceBaiDuLocal::Instance() { if (self.isNull()) { QMutex mutex; QMutexLocker locker(&mutex); if (self.isNull()) { self.reset(new FaceBaiDuLocal); } } return self.data(); } FaceBaiDuLocal::FaceBaiDuLocal(QObject *parent) : QThread(parent) { //注册信号中未知的数据类型 qRegisterMetaType<QList<float> >("QList<float>"); stopped = false; maxCount = 100; type = 1; percent = 8; delayms = 0; findFast = false; countTime = true; busy = false; sdkPath = qApp->applicationDirPath() + "/facesdk"; imgDir = ""; oneImg = QImage(); api = new BaiduFaceApi; faces = new std::vector<TrackFaceInfo>(); } FaceBaiDuLocal::~FaceBaiDuLocal() { delete api; this->stop(); this->wait(1000); } void FaceBaiDuLocal::run() { this->init(); while(!stopped) { int count = flags.count(); if (count > 0) { QMutexLocker lock(&mutex); busy = true; if (type == 0) { QString flag = flags.takeFirst(); QImage img = imgs.takeFirst(); QRect rect; int msec; if (getFaceRect(flag, img, rect, msec)) { emit receiveFaceRect(flag, rect, msec); } else { emit receiveFaceRectFail(flag); } } else if (type == 1) { QString flag = flags.takeFirst(); QImage img = imgs.takeFirst(); QList<float> feature; int msec; if (getFaceFeature(flag, img, feature, msec)) { emit receiveFaceFeature(flag, feature, msec); } else { emit receiveFaceFeatureFail(flag); } } else if (type == 2) { QString flag = flags.takeFirst(); QImage img1 = imgs.takeFirst(); QImage img2 = imgs2.takeFirst(); float result; int msec; if (getFaceCompare(flag, img1, img2, result, msec)) { emit receiveFaceCompare(flag, result, msec); } else { emit receiveFaceCompareFail(flag); } } else if (type == 3) { flags.takeFirst(); getFaceFeatures(imgDir); } else if (type == 4) { QString flag = flags.takeFirst(); QString targetName; float result; getFaceOne(flag, oneImg, targetName, result); if (!targetName.isEmpty()) { emit receiveFaceCompareOne(flag, oneImg, targetName, result); } } else if (type == 5) { QString flag = flags.takeFirst(); QImage img = imgs.takeFirst(); float result; int msec; if (getFaceLive(flag, img, result, msec)) { emit receiveFaceLive(flag, result, msec); } else { emit receiveFaceLiveFail(flag); } } } msleep(100); busy = false; } stopped = false; } void FaceBaiDuLocal::init() { int res = api->sdk_init(); res = api->is_auth(); if(res != 1) { qDebug() << TIMEMS << QString("init sdk error: %1").arg(res); return; } else { //设置最小人脸,默认30 api->set_min_face_size(percent); //设置光照阈值,默认40 api->set_illum_thr(20); //设置角度阈值,默认15 //api->set_eulur_angle_thr(30, 30, 30); qDebug() << TIMEMS << "init sdk ok"; } } void FaceBaiDuLocal::stop() { stopped = true; } bool FaceBaiDuLocal::getBusy() { return this->busy; } void FaceBaiDuLocal::setMaxCount(int maxCount) { if (maxCount <= 1000) { this->maxCount = maxCount; } } void FaceBaiDuLocal::setType(int type) { if (this->type != type) { this->type = type; this->flags.clear(); this->imgs.clear(); this->imgs2.clear(); } } void FaceBaiDuLocal::setPercent(int percent) { this->percent = percent; } void FaceBaiDuLocal::setDelayms(int delayms) { this->delayms = delayms; } void FaceBaiDuLocal::setFindFast(bool findFast) { this->findFast = findFast; } void FaceBaiDuLocal::setCountTime(bool countTime) { this->countTime = countTime; } void FaceBaiDuLocal::setBusy(bool busy) { this->busy = busy; } void FaceBaiDuLocal::setSDKPath(const QString &sdkPath) { this->sdkPath = sdkPath; } void FaceBaiDuLocal::setImgDir(const QString &imgDir) { this->imgDir = imgDir; this->flags.clear(); this->flags.append("imgDir"); this->type = 3; } void FaceBaiDuLocal::setOneImg(const QString &flag, const QImage &oneImg) { setType(4); //需要将图片重新拷贝一个,否则当原图像改变之后也会改变 this->oneImg = oneImg.copy(); this->flags.append(flag); } void FaceBaiDuLocal::append(const QString &flag, const QImage &img) { QMutexLocker lock(&mutex); int count = flags.count(); if (count < maxCount) { flags.append(flag); imgs.append(img); } } void FaceBaiDuLocal::append(const QString &flag, const QImage &img, const QImage &img2) { QMutexLocker lock(&mutex); int count = flags.count(); if (count < maxCount) { flags.append(flag); imgs.append(img); imgs2.append(img2); } } void FaceBaiDuLocal::getFaceFeatures(const QString &imgDir) { imgNames.clear(); features.clear(); //载入指定目录图像处理特征 QDir imagePath(imgDir); QStringList filter; filter << "*.jpg" << "*.bmp" << "*.png" << "*.jpeg" << "*.gif"; imgNames.append(imagePath.entryList(filter)); qDebug() << TIMEMS << "getFaceFeatures" << imgNames; //从目录下读取同名的txt文件(存储的特征) //如果存在则从文件读取特征,如果不存在则转码解析出特征 //转码完成后将得到的特征存储到同名txt文件 int count = imgNames.count(); for (int i = 0; i < count; i++) { QList<float> feature; int msec; QString imgName = imgNames.at(i); QStringList list = imgName.split("."); QString txtName = imgDir + "/" + list.at(0) + ".txt"; QFile file(txtName); if (file.exists()) { if (file.open(QFile::ReadOnly)) { QString data = file.readAll(); file.close(); qDebug() << TIMEMS << "readFaceFeature" << txtName; QStringList list = data.split(","); foreach (QString str, list) { if (!str.isEmpty()) { feature.append(str.toFloat()); } } } } else { QImage img(imgDir + "/" + imgName); bool ok = getFaceFeature(imgName, img, feature, msec); if (ok) { emit receiveFaceFeature(imgName, feature, msec); if (file.open(QFile::WriteOnly)) { QStringList list; foreach (float fea, feature) { list.append(QString::number(fea)); } qDebug() << TIMEMS << "writeFaceFeature" << txtName; file.write(list.join(",").toLatin1()); file.close(); } } } features.append(feature); msleep(1); } qDebug() << TIMEMS << "getFaceFeatures finsh"; emit receiveFaceFeatureFinsh(); } bool FaceBaiDuLocal::getFaceRect(const QString &flag, const QImage &img, QRect &rect, int &msec) { //qDebug() << TIMEMS << flag << "getFaceRect"; QTime time; if (countTime) { time.start(); } faces->clear(); QByteArray imageData = getImageData(img); int result = api->track_max_face(faces, imageData.constData(), 1); if (result == 1) { TrackFaceInfo info = faces->at(0); FaceInfo ibox = info.box; float width = ibox.mWidth; float x = ibox.mCenter_x; float y = ibox.mCenter_y; rect = QRect(x - width / 2, y - width / 2, width, width); if (countTime) { msec = time.elapsed() - delayms; } else { msec = delayms; } msec = msec < 0 ? 0 : msec; return true; } else { return false; } return false; } bool FaceBaiDuLocal::getFaceLive(const QString &flag, const QImage &img, float &result, int &msec) { //qDebug() << TIMEMS << flag << "getFaceLive"; QTime time; if (countTime) { time.start(); } result = 0; QByteArray imageData = getImageData(img); std::string value = api->rgb_liveness_check(imageData.constData(), 1); QString data = value.c_str(); data = data.replace(" ", ""); data = data.replace(""", ""); data = data.replace(" ", ""); int index = -1; QStringList list = data.split(" "); foreach (QString str, list) { index = str.indexOf("score:"); if (index >= 0) { result = str.mid(6, 4).toFloat(); break; } } if (index >= 0) { if (countTime) { msec = time.elapsed() - delayms; } else { msec = delayms; } msec = msec < 0 ? 0 : msec; return true; } else { return false; } return false; } bool FaceBaiDuLocal::getFaceFeature(const QString &flag, const QImage &img, QList<float> &feature, int &msec) { //qDebug() << TIMEMS << flag << "getFaceFeature" << img.width() << img.height() << img.size(); QTime time; if (countTime) { time.start(); } const float *fea = nullptr; QByteArray imageData = getImageData(img); int result = api->get_face_feature(imageData.constData(), 1, fea); if (result == 512) { feature.clear(); for (int i = 0; i < 512; i++) { feature.append(fea[i]); } if (countTime) { msec = time.elapsed() - delayms; } else { msec = delayms; } msec = msec < 0 ? 0 : msec; return true; } else { return false; } return false; } float FaceBaiDuLocal::getFaceCompare(const QString &flag, const QList<float> &feature1, const QList<float> &feature2) { //qDebug() << TIMEMS << flag << "getFaceCompareXXX"; std::vector<float> fea1, fea2; for (int i = 0; i < 512; i++) { fea1.push_back(feature1.at(i)); fea2.push_back(feature2.at(i)); } float result = api->compare_feature(fea1, fea2); //过滤非法的值 result = result > 100 ? 0 : result; return result; } bool FaceBaiDuLocal::getFaceCompare(const QString &flag, const QImage &img1, const QImage &img2, float &result, int &msec) { //qDebug() << TIMEMS << flag << "getFaceCompare"; result = 0; bool ok1, ok2; QList<float> feature1, feature2; int msec1, msec2; QString flag1, flag2; if (flag.contains("|")) { QStringList list = flag.split("|"); flag1 = list.at(0); flag2 = list.at(1); } else { flag1 = flag; flag2 = flag; } QTime time; if (countTime) { time.start(); } ok1 = getFaceFeature(flag1, img1, feature1, msec1); if (ok1) { emit receiveFaceFeature(flag1, feature1, msec1); } ok2 = getFaceFeature(flag2, img2, feature2, msec2); if (ok2) { emit receiveFaceFeature(flag2, feature2, msec2); } if (ok1 && ok2) { result = getFaceCompare(flag, feature1, feature2); if (countTime) { msec = time.elapsed() - delayms; } else { msec = delayms; } msec = msec < 0 ? 0 : msec; return true; } else { return false; } return false; } void FaceBaiDuLocal::getFaceOne(const QString &flag, const QImage &img, QString &targetName, float &result) { QList<float> feature; int msec; bool ok = getFaceFeature(flag, img, feature, msec); if (ok) { emit receiveFaceFeature(flag, feature, msec); getFaceOne(flag, feature, targetName, result); } } void FaceBaiDuLocal::getFaceOne(const QString &flag, const QList<float> &feature, QString &targetName, float &result) { //用当前图片的特征与特征数据库比对 result = 0; int count = imgNames.count(); for (int i = 0; i < count; i++) { QString imgName = imgNames.at(i); float currentResult = getFaceCompare(flag, feature, features.at(i)); //qDebug() << TIMEMS << "getFaceOne" << imgName << currentResult; if (currentResult > result) { result = currentResult; targetName = imgName; } } qDebug() << TIMEMS << "getFaceOne result" << targetName << result; } void FaceBaiDuLocal::appendFace(const QString &flag, const QImage &img, const QString &txtFile) { QList<float> feature; int msec; QImage image = img; bool ok = getFaceFeature(flag, image, feature, msec); msleep(100); qDebug() << TIMEMS << "getFaceFeature result" << ok << "appendFace" << txtFile; if (ok) { emit receiveFaceFeature(flag, feature, msec); //保存txt文件 QFile file(txtFile); if (file.open(QFile::WriteOnly)) { QStringList list; foreach (float fea, feature) { list.append(QString::number(fea)); } file.write(list.join(",").toLatin1()); file.close(); } //保存图片文件 QString imgName = txtFile; imgName = imgName.replace("txt", "jpg"); image.save(imgName, "jpg"); imgNames.append(QFileInfo(imgName).fileName()); features.append(feature); } } void FaceBaiDuLocal::deleteFace(const QString &flag) { //从图片名称中找到标识符 int index = imgNames.indexOf(flag); if (index >= 0) { imgNames.removeAt(index); features.removeAt(index); //删除图片文件 QString imgFileName = QString("%1/face/%2.jpg").arg(qApp->applicationDirPath()).arg(flag); QFile imgFile(imgFileName); imgFile.remove(); qDebug() << TIMEMS << "delete faceImage" << imgFileName; //删除特征文件 QString txtFileName = QString("%1/face/%2.txt").arg(qApp->applicationDirPath()).arg(flag); QFile txtFile(txtFileName); txtFile.remove(); qDebug() << TIMEMS << "delete faceTxt" << txtFileName; } }