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

實現文件完整代碼: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;
    }
}
相關文章
相關標籤/搜索