在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;
}
}
