在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
實現文件完整代碼:api
#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; } }