Qt編寫百度離線版人臉識別+比對+活體檢測
阿新 • • 發佈:2018-12-10
在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("\t", ""); data = data.replace("\"", ""); data = data.replace(" ", ""); int index = -1; QStringList list = data.split("\n"); 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; } }