ubuntu下用Qt实现人脸识别(四)
一、流程
首先是程序启动的时候,从指定的路径下获取图片,然后从图片中提取出人脸特征值,将这些人脸特征值存储到缓冲区中。接着不断从摄像头获取一帧图片,传到人脸识别的算法接口,然后通过判别人脸阈值的大小,来确定是否识别成功。如果成功,在人脸框上显示该人脸的姓名。又因为,提取特征值和识别人脸的任务用时可能比较大,所以需要创建一条线程来执行这些任务。
二、源码
在 ubuntu下用Qt实现人脸识别之检测人脸并绘制人脸框(三)的基础下增加以下修改
主线程
主线程头文件代码如下(示例):
#ifndef HRQTPRO_H
#define HRQTPRO_H
#include <QWidget>
#include <QImage>
#include <QPainter>
#include <QObject>
#include <QTimer>
#include <QDebug>
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/core/core.hpp"
#include "opencv2/objdetect/objdetect.hpp"
#include "opencv2/highgui.hpp"
#include "ASF/inc/merror.h"
#include "ASF/inc/asvloffscreen.h"
#include "ASF/inc/arcsoft_face_sdk.h"
#include "ASF/inc/amcomdef.h"
#include "services/detect/workpthread.h"
//激活SDK的ID和KEY
#define APP_ID ""
#define SDK_KEY ""
using namespace cv;
namespace Ui {
class hrQtPro;
}
class hrQtPro : public QWidget
{
Q_OBJECT
public:
explicit hrQtPro(QWidget *parent = 0);
~hrQtPro();
protected:
void paintEvent(QPaintEvent* e);
private slots:
int slotReadFarme();
void slotRegisterFace();
void slotFaceCompareFinished(QString name, QString ID, float scores);
private:
Ui::hrQtPro *ui;
VideoCapture capture;
QTimer *cvTimer;
Mat cap;
QImage qImg;
ASF_MultiFaceInfo detectedFaces;
MHandle handle;
//识别线程
workPthread *thread1;
//人脸姓名
QString faceName;
QString faceID;
int ASFInit();
int OpenCamera();
QImage Mat2QImage(Mat &src);
};
#endif // HRQTPRO_H
主线程cpp代码如下(示例):
#include "hrqtpro.h"
#include "ui_hrqtpro.h"
hrQtPro::hrQtPro(QWidget *parent) :
QWidget(parent),
handle(NULL),
ui(new Ui::hrQtPro)
{
ui->setupUi(this);
thread1 = new workPthread();
connect(thread1, SIGNAL(faceCompareFinished(QString , QString ,float)),
this, SLOT(slotFaceCompareFinished(QString , QString ,float)));
thread1->start();
//注册人脸按钮
connect(ui->registerBtn, SIGNAL(clicked()), this, SLOT(slotRegisterFace()));
cvTimer = new QTimer(this);
connect(cvTimer, SIGNAL(timeout()), this, SLOT(slotReadFarme()));
ASFInit();
OpenCamera();
}
hrQtPro::~hrQtPro()
{
if(thread1) {
thread1->slotDestroy(true);
thread1->quit();
thread1->wait(300);
delete thread1;
thread1 = nullptr;
}
MRESULT res = ASFUninitEngine(handle);
if (res != MOK)
printf("ASFUninitEngine fail: %d\n", res);
else
printf("ASFUninitEngine sucess: %d\n", res);
if(cvTimer) {
cvTimer->stop();
delete cvTimer;
cvTimer = NULL;
}
delete ui;
}
int hrQtPro::ASFInit()
{
MRESULT res = MOK;
#if 0
//激活SDK,只需要激活一次即可
//这里的APP_ID和SDK_KEY是从官网中获取到的
res = ASFOnlineActivation(APP_ID, SDK_KEY);
if (MOK != res && MERR_ASF_ALREADY_ACTIVATED != res)
qDebug("ASFOnlineActivation fail: %d\n", res);
else
qDebug("ASFOnlineActivation sucess: %d\n", res);
#endif
//只是检测人脸,如果需要人脸识别还需要或上ASF_FACERECOGNITION,例如:ASF_FACE_DETECT | ASF_FACERECOGNITION
MInt32 mask = ASF_FACE_DETECT;
res = ASFInitEngine(ASF_DETECT_MODE_VIDEO, ASF_OP_0_ONLY, 16, 5, mask, &handle);
if (res != MOK)
qDebug("ASFInitEngine fail: %d\n", res);
else
qDebug("ASFInitEngine sucess: %d\n", res);
return res;
}
int hrQtPro::OpenCamera()
{
//打开摄像头
if(capture.open(0)) {
qDebug() << "open success";
} else {
qDebug() << "open error";
return -1;
}
//设置格式
capture.set(cv::CAP_PROP_FOURCC, cv::VideoWriter::fourcc('M','J','P','G'));
cvTimer->start(20);
return 0;
}
//Mat转换为QImage
QImage hrQtPro::Mat2QImage(Mat &src) {
if(src.type() == CV_8UC3) {
const uchar *pSrc = (const uchar*)src.data;
QImage qImage(pSrc,src.cols,src.rows,src.step,QImage::Format_RGB888);
return qImage.rgbSwapped();
}
return QImage();
}
int hrQtPro::slotReadFarme()
{
//读取摄像头数据
capture.read(cap);
if(!cap.data || cap.empty()) {
return -1;
}
//Mat转换为QImage
qImg = Mat2QImage(cap);
if(qImg.isNull())
return -1;
IplImage ipi(cap);
IplImage* img = cvCreateImage(cvSize(ipi.width - ipi.width % 4,
ipi.height), IPL_DEPTH_8U, ipi.nChannels);
ASVLOFFSCREEN offscreen = { 0 };
if (img) {
CutIplImage(&ipi, img, 0, 0);
offscreen.u32PixelArrayFormat = ASVL_PAF_RGB24_B8G8R8;
offscreen.i32Width = img->width;
offscreen.i32Height = img->height;
offscreen.pi32Pitch[0] = img->widthStep;
offscreen.ppu8Plane[0] = (MUInt8*)img->imageData;
//人脸检测
MRESULT res = ASFDetectFacesEx(handle, &offscreen, &detectedFaces);
if (MOK != res) {
qDebug("ASFDetectFacesEx failed: %d\n", res);
} else {
if(detectedFaces.faceNum > 0 ) {
compareInfo t;
t.faceImg = NULL;
t.faceImg = cvCreateImage(cvSize(ipi.width - ipi.width % 4,
ipi.height), IPL_DEPTH_8U, ipi.nChannels);
CutIplImage(&ipi, t.faceImg, 0, 0);
t.compareDetectedFaces.faceRect.left = detectedFaces.faceRect[0].left;
t.compareDetectedFaces.faceRect.top = detectedFaces.faceRect[0].top;
t.compareDetectedFaces.faceRect.right = detectedFaces.faceRect[0].right;
t.compareDetectedFaces.faceRect.bottom = detectedFaces.faceRect[0].bottom;
t.compareDetectedFaces.faceOrient = detectedFaces.faceOrient[0];
t.faceID[0] = detectedFaces.faceID[0];
t.faceNum = detectedFaces.faceNum;
memcpy(&t.compareOffscreen, &offscreen, sizeof(offscreen));
//将检测到的人脸数据添加到一个队列中,等待识别线程的处理
thread1->addFrame(t);
//设置faceID
thread1->setFaceID(*detectedFaces.faceID);
cvReleaseImage(&t.faceImg);
}
}
cvReleaseImage(&img);
}
}
//绘制人脸框
void hrQtPro::paintEvent(QPaintEvent* e)
{
if(!qImg.isNull()) {
QPainter painter(&qImg);
if(detectedFaces.faceNum > 0) {
// 设置人脸框颜色
painter.setPen(QColor(255, 0, 0));
//显示识别结果
painter.drawText(QPoint(detectedFaces.faceRect[0].left, detectedFaces.faceRect[0].top - 20),
faceName);
//绘制人脸的框
painter.drawRect(detectedFaces.faceRect[0].left,
detectedFaces.faceRect[0].top,
detectedFaces.faceRect[0].right - detectedFaces.faceRect[0].left,
detectedFaces.faceRect[0].bottom - detectedFaces.faceRect[0].top);
}
ui->previewLabel->setPixmap(QPixmap::fromImage(qImg));
}
}
void hrQtPro::slotFaceCompareFinished(QString name, QString ID, float scores)
{
//识别成功后,得到一个姓名和ID
if(scores >= 0.8) {
faceName = name;
faceID = ID;
} else {
//识别失败,清除信息
faceName = "";
faceID = "";
}
}
//手动点击按钮进行拍照注册
void hrQtPro::slotRegisterFace()
{
QString name = QString::number(thread1->getFaceNum());
QString ID = QString::number(thread1->getFaceNum());
QString imgStr = AUTOMATIC_REGISTERI_MAGE_PATH + name + ".jpg";
if(qImg.isNull()) {
qDebug("[%s:%d] img is NULL!!!", __func__, __LINE__);
return;
}
//保存这张图片,以便下次程序启动的时候自动获取人脸特征值
if(!qImg.save(imgStr)) {
qDebug("[%s:%d] save %s failed!!!", __func__, __LINE__, imgStr.toUtf8().data());
return;
} else {
qDebug("[%s:%d] save %s success!!!", __func__, __LINE__, imgStr.toUtf8().data());
}
ASF_FaceFeature feature = { 0 };
if(exFeaturesOfFile(imgStr, &feature) == 0){
persionInfo registerInfo;
memset(®isterInfo, 0, sizeof(persionInfo));
strcpy(registerInfo.name, name.toUtf8().data());
strcpy(registerInfo.ID, ID.toUtf8().data());
memcpy(registerInfo.feature, feature.feature, feature.featureSize);
registerInfo.featureLen = feature.featureSize;
//更新缓存区的人脸数据
thread1->addFace(®isterInfo);
qDebug("[%s:%d] register %s success!!!", __func__, __LINE__, name.toUtf8().data());
} else {
qDebug("[%s:%d] register %s failed!!!", __func__, __LINE__, name.toUtf8().data());
}
}
识别线程
头文件代码如下:
#ifndef WORKPTHREAD_H
#define WORKPTHREAD_H
#include <QThread>
#include <QDebug>
#include <QQueue>
#include <QObject>
#include <QDir>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/objdetect/objdetect.hpp>
#include "opencv2/highgui.hpp"
#include "ASF/inc/merror.h"
#include "ASF/inc/asvloffscreen.h"
#include "ASF/inc/arcsoft_face_sdk.h"
#include "ASF/inc/amcomdef.h"
#define NSCALE 16
#define FACE_NUM 5
#define FEATURE_LEN 2048
#define ID_LEN 64
#define NAME_LEN 64
//注册的图片路径
#define AUTOMATIC_REGISTERI_MAGE_PATH "image/automatic/"
#define SafeFree(p) { if ((p)) free(p); (p) = NULL; }
#define SafeArrayDelete(p) { if ((p)) delete [] (p); (p) = NULL; }
#define SafeDelete(p) { if ((p)) delete (p); (p) = NULL; }
typedef struct compare{
IplImage* faceImg;
ASF_SingleFaceInfo compareDetectedFaces;
ASVLOFFSCREEN compareOffscreen;
MInt32 faceID[5];
MInt32 faceNum;
}compareInfo;
Q_DECLARE_METATYPE(compareInfo)
struct persionInfo {
char ID[ID_LEN];
char name[NAME_LEN];
char feature[FEATURE_LEN];
unsigned int featureLen;
};
Q_DECLARE_METATYPE(persionInfo)
void CutIplImage(IplImage* src, IplImage* dst, int x, int y);
int exFeaturesOfFile(QString filePth, ASF_FaceFeature *OutFeature);
class workPthread : public QThread
{
Q_OBJECT
public:
explicit workPthread(QObject *parent = nullptr);
void run();
void addFace(persionInfo *p);
void addFrame(compareInfo frame);
int getFaceNum();
void slotDestroy(bool flag);
void setFaceID(MInt32 id);
signals:
void faceCompareFinished(QString name, QString ID, float scores);
private:
//待识别的人脸信息队列
QQueue<compareInfo> compareInfoQeue;
//线程结束的标志
bool isDestroy;
QVector<persionInfo> faces;
compareInfo recvCompareInfo;
ASF_FaceFeature copyfeature1;
//上一个人脸ID
MInt32 lastID;
int ASFInit();
int compareResult(compareInfo *info, ASF_FaceFeature *databaseFeature, float *scores);
int exFeaturesOfImage(compareInfo* p, ASF_FaceFeature *feature);
void getFeatures();
};
#endif // WORKPTHREAD_H
cpp代码如下:
#include "workpthread.h"
MHandle handle = NULL;
#if 1
//图像四字节对齐
void CutIplImage(IplImage* src, IplImage* dst, int x, int y)
{
CvSize size = cvSize(dst->width, dst->height);//区域大小
cvSetImageROI(src, cvRect(x, y, size.width, size.height));//设置源图像ROI
cvCopy(src, dst); //复制图像
cvResetImageROI(src);//源图像用完后,清空ROI
}
//提取人脸特征值
int exFeaturesOfFile(QString filePth, ASF_FaceFeature *OutFeature)
{
if(filePth.isEmpty() || filePth.isNull()) {
qDebug("filePth is NULL");
return -1;
}
MRESULT res = MOK;
IplImage* pImg = cvLoadImage(filePth.toUtf8().data());
IplImage* img = NULL;
ASF_FaceFeature feature = { 0 };
if(pImg) {
img = cvCreateImage(cvSize(pImg->width - pImg->width % 4,
pImg->height), IPL_DEPTH_8U, pImg->nChannels);
} else {
qDebug("pImg is null");
return -1;
}
if(img) {
CutIplImage(pImg, img, 0, 0);
} else {
qDebug("img is null");
cvReleaseImage(&pImg);
return -1;
}
if(img) {
ASVLOFFSCREEN offscreen = { 0 };
offscreen.u32PixelArrayFormat = ASVL_PAF_RGB24_B8G8R8;
offscreen.i32Width = img->width;
offscreen.i32Height = img->height;
offscreen.pi32Pitch[0] = img->widthStep;
offscreen.ppu8Plane[0] = (MUInt8*)img->imageData;
ASF_MultiFaceInfo detectedFaces;
res = ASFDetectFacesEx(handle, &offscreen, &detectedFaces);
if (MOK != res) {
qDebug("[%s]ASFDetectFacesEx failed: %d\n", __func__, res);
} else {
ASF_SingleFaceInfo SingleDetectedFaces = { 0 };
SingleDetectedFaces.faceRect.left = detectedFaces.faceRect[0].left;
SingleDetectedFaces.faceRect.top = detectedFaces.faceRect[0].top;
SingleDetectedFaces.faceRect.right = detectedFaces.faceRect[0].right;
SingleDetectedFaces.faceRect.bottom = detectedFaces.faceRect[0].bottom;
SingleDetectedFaces.faceOrient = detectedFaces.faceOrient[0];
res = ASFFaceFeatureExtractEx(handle, &offscreen, &SingleDetectedFaces, &feature);
if (res != MOK) {
qDebug("%s ASFFaceFeatureExtractEx fail: %d\n", filePth.toUtf8().data(), res);
} else{
OutFeature->featureSize = feature.featureSize;
OutFeature->feature = (MByte *)malloc(feature.featureSize);
memset(OutFeature->feature, 0, feature.featureSize);
memcpy(OutFeature->feature, feature.feature, feature.featureSize);
qDebug("%s ASFFaceFeatureExtractEx:%#x success: %d\n", filePth.toUtf8().data(), feature.feature,res);
}
}
cvReleaseImage(&img);
} else {
qDebug("cutImg is null %s", filePth.toUtf8().data());
}
cvReleaseImage(&pImg);
return res;
}
#endif
workPthread::workPthread(QObject *parent): QThread(parent)
{
ASFInit();
getFeatures();
isDestroy = false;
}
void workPthread::run()
{
int ret = -1;
int i = 0;
int lastSize = 0;
int delCnt = 10;
bool isFind = false;
MFloat confidenceLevel = 0.0;
MRESULT res;
MInt32 curID = 0;
ASF_FaceFeature featureImage = { 0 };
lastSize = faces.size();
lastID = -1;
while(1){
if(isDestroy) {
break;
}
//去重,如果识别到一直是同一个人,就不重复提取人脸特征去识别了
if((lastID != curID || confidenceLevel < 0.8) && !compareInfoQeue.isEmpty()) {
//10 * 100 * 1000us 内一直是同一个人,不重复识别
if(delCnt-- <= 0) {
delCnt = 10;
curID = lastID;
}
recvCompareInfo = compareInfoQeue.back();
memset(&featureImage, 0, sizeof(ASF_FaceFeature));
//提取特征值
ret = exFeaturesOfImage(&recvCompareInfo, &featureImage);
lastSize = faces.size();
copyfeature1.feature = (MByte *)malloc(FEATURE_LEN + 1);
if(!isFind) {
for(i=0; i<faces.size(); ++i) {
copyfeature1.featureSize = faces[i].featureLen;
memset(copyfeature1.feature, 0, FEATURE_LEN + 1);
memcpy(copyfeature1.feature, faces[i].feature, FEATURE_LEN);
//特征值比对
res = ASFFaceFeatureCompare(handle, &featureImage, ©feature1, &confidenceLevel);
//阈值大于0.8,则识别成功
if(confidenceLevel >= 0.8) {
if(copyfeature1.feature) {
SafeFree(copyfeature1.feature);
}
break;
}
}
}
isFind = false;
SafeFree(copyfeature1.feature);
copyfeature1.featureSize = 0;
compareInfoQeue.dequeue();
if(confidenceLevel >= 0.8 && faces.size() > 0) {
qDebug() << "confidenceLevel=" << confidenceLevel;
//识别成功,向主线程发送该人脸的信息
emit faceCompareFinished(faces[i].name,
faces[i].ID,
confidenceLevel);
} else {
//识别失败,清除主线程上一个人脸的信息
emit faceCompareFinished("", "", 0.0);
}
}
usleep(100*000);
}
res = ASFUninitEngine(handle);
if (res != MOK)
qDebug("ASFUninitEngine fail: %d\n", res);
else
qDebug("ASFUninitEngine sucess: %d\n", res);
compareInfoQeue.clear();
}
void workPthread::slotDestroy(bool flag)
{
isDestroy = flag;
}
void workPthread::addFrame(compareInfo frame)
{
compareInfoQeue.enqueue(frame);
}
int workPthread::ASFInit()
{
MRESULT res = MOK;
handle = NULL;
MInt32 mask = ASF_FACE_DETECT | ASF_FACERECOGNITION;
res = ASFInitEngine(ASF_DETECT_MODE_VIDEO, ASF_OP_0_ONLY, NSCALE * 2, FACE_NUM, mask, &handle);
if (res != MOK)
qDebug("ASFInitEngine fail: %d\n", res);
else
qDebug("ASFInitEngine sucess: %d\n", res);
return res;
}
int workPthread::exFeaturesOfImage(compareInfo* p, ASF_FaceFeature *feature)
{
MRESULT res = MOK;
if(p->faceNum <= 0) {
qDebug() << "faceNum == 0";
return -1;
}
res = ASFFaceFeatureExtractEx(handle, &p->compareOffscreen, &p->compareDetectedFaces, feature);
if (res != MOK) {
qDebug("%s ASFFaceFeatureExtractEx fail: %d\n", __func__, res);
emit faceCompareFinished("", "", 0.0);
}
return res;
}
//添加人脸到缓冲区中
void workPthread::addFace(persionInfo *p) {
persionInfo t;
memset(&t, 0, sizeof(persionInfo));
memcpy(t.feature, p->feature, p->featureLen);
strcpy(t.ID, p->ID);
strcpy(t.name, p->name);
t.featureLen = p->featureLen;
faces.push_back(t);
}
//获取人脸数量
int workPthread::getFaceNum() {
return faces.size();
}
//设置人脸ID,起到去重的作用
void workPthread::setFaceID(MInt32 id) {
lastID = id;
}
//程序启动的时候,加载指定路径下的人脸图片特征
void workPthread::getFeatures() {
QDir dir;
//如果该路径不存在,则创建
if (!dir.exists(AUTOMATIC_REGISTERI_MAGE_PATH)) {
dir.mkpath(AUTOMATIC_REGISTERI_MAGE_PATH);
} else {
qDebug() << AUTOMATIC_REGISTERI_MAGE_PATH << "is exists";
}
dir.setPath(AUTOMATIC_REGISTERI_MAGE_PATH);
QStringList filename;
QStringList results;
quint32 ID = 0;
QString path, name;
persionInfo registerInfo;
ASF_FaceFeature feature = { 0 };
filename << "*.png" << "*.jpg"; //只选择png和jpg的图片
results = dir.entryList(filename, QDir::Files | QDir::Readable, QDir::Name);
for (int i=0; i<results.size(); ++i) {
path = AUTOMATIC_REGISTERI_MAGE_PATH + results.at(i);
//使用图片的名字来作为人脸的名字
name = results.at(i).split(".").at(0);
if(exFeaturesOfFile(path, &feature) == 0){
ID++;
memset(®isterInfo, 0, sizeof(persionInfo));
strcpy(registerInfo.name, name.toUtf8().data());
sprintf(registerInfo.ID, "%d", ID);
memcpy(registerInfo.feature, feature.feature, feature.featureSize);
registerInfo.featureLen = feature.featureSize;
//更新缓存区的人脸数据
faces.push_back(registerInfo);
qDebug("obtain facial features success:%s", registerInfo.name);
} else {
qDebug("Num %d failed to obtain facial features. Name=%s", i, name.toUtf8().data());
}
}
}
最后结果
OK,完成。但是,在指定路径读取图片,并且提取人脸特征值的方法还需要改进,应该将注册好的特征值和人脸信息存储在一个数据库中,比如sqlite,下次程序启动只需要从数据库中进行读取即可;同时,还需要添加一些增删改查等功能。