Qt编写百度离线版人脸识别+比对+活体检测

在AI技术发展迅猛的今天,很多设备都希望加上人脸识别功能,好像不加上点人脸识别功能感觉不够高大上,都往人脸识别这边靠,手机刷脸解锁,刷脸支付,刷脸开门,刷脸金融,刷脸安防,是不是以后还可以刷脸匹配男女交友?

很多人认为人脸识别直接用opencv做,其实那只是极其基础的识别个人脸,然并卵,好比学C++写了个hello类似。拿到人脸区域图片只是万里长征的第一步,真正能够起作用的是人脸特征值的提取,然后用于搜索和查找人脸,比如两张图片比较相似度,从一堆人脸库中找到最相似的人脸,对当前人脸识别是否是活体等。

对于可以接入外网的设备,可以直接通过在线api的http请求方式获得结果,但是有很多应用场景是离线的,或者说不通外网,只能局域网使用,为了安全性考虑,这个时候就要求所有的人脸处理在本地完成,本篇文章采用的百度离线SDK作为解决方案。可以去官网申请,默认有6个免费的密钥使用三个月,需要与本地设备的指纹信息匹配,感兴趣的同学可以自行去官网下载SDK。

百度离线人脸识别SDK文件比较大,光模型文件就645MB,估计这也许是识别率比较高的一方面原因吧,不断训练得出的模型库,本篇文章只放出Qt封装部分源码。官网对应的使用说明还是非常详细的,只要是学过编程的人就可以看懂。

第一步:初始化SDK

第二步:执行动作,比如查找人脸、图片比对、特征值比对等

Qt编写百度离线版人脸识别+比对+活体检测_第1张图片
Qt编写百度离线版人脸识别+比对+活体检测_第2张图片
Qt编写百度离线版人脸识别+比对+活体检测_第3张图片
Qt编写百度离线版人脸识别+比对+活体检测_第4张图片
Qt编写百度离线版人脸识别+比对+活体检测_第5张图片

头文件完整代码:

#ifndef FACEBAIDULOCAL_H

#define FACEBAIDULOCAL_H

/**

* 百度离线版人脸识别+人脸比对等功能类 作者:feiyangqingyun(QQ:517216493) 2018-8-30

* 1:支持活体检测

* 2:可设置最大队列中的图片数量

* 3:多线程处理,通过type控制当前处理类型

* 4:支持单张图片检索相似度最高的图片

* 5:支持指定目录图片生成特征文件

* 6:支持两张图片比对方式

* 7:可设置是否快速查找

* 8:可设置是否统计用时

*/

#include

#include

#if (QT_VERSION > QT_VERSION_CHECK(5,0,0))

#include

#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 self;

    BaiduFaceApi *api;

    std::vector *faces;

    QMutex mutex;                  //锁对象

    bool stopped;                  //线程停止标志位

    int maxCount;                  //最大图片张数

    int type;                      //当前处理类型

    int percent;                    //最小人脸百分比

    int delayms;                    //减去毫秒数,用于造假

    bool findFast;                  //是否快速模式

    bool countTime;                //统计用时

    bool busy;                      //是否正忙

    QList flags;          //等待处理的图像队列的名称

    QList imgs;            //等待处理的图像队列

    QList imgs2;            //等待处理的比对图像队列

    QString sdkPath;                //SDK目录

    QString imgDir;                //图片目录

    QImage oneImg;                  //单张图片比对找出最大特征图像

    QList imgNames;        //图像队列

    QList > features;  //特征队列

signals:

    //人脸区域坐标返回

    void receiveFaceRect(const QString &flag, const QRect &rect, int msec);

    //获取人脸区域坐标失败

    void receiveFaceRectFail(const QString &flag);

    //人脸特征返回

    void receiveFaceFeature(const QString &flag, const QList &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 &feature, int &msec);

    //人脸比对,传入两张照片特征

    float getFaceCompare(const QString &flag, const QList &feature1, const QList &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 &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::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");

    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();

}

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 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 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 &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 &feature1, const QList &feature2)

{

    //qDebug() << TIMEMS << flag << "getFaceCompareXXX";

    std::vector 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 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 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 &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 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;

    }

}

你可能感兴趣的:(Qt编写百度离线版人脸识别+比对+活体检测)