1 本期内容介绍
上一期介绍了基于EAIDK的人脸算法应用,本期从应用角度,解读一下该案例源码。
本期案例源码解读,主要从源码目录结构、配置文件、模型目录、源码流程、重点源码文件等进行解读。
本期源码解读目标:
1. 让EAIDK-310开发者对EAIDK-310环境更加熟练
2. 熟悉EAIDK-310人脸识别案例源码
3. 方便开发者使用vision.sdk的调用流程,为二次开发做指引参考
2 目录结构介绍
从ftp://ftp.eaidk.net/EAIDK310\_Source/eaidk310\_face\_package/获取源码包后,直接解压后得到eaidk310_face_package
文件夹,文件夹内容如下图所示:
将eaidk310_face_demo.zip
解压后,源码包整体目录结构如下:
doc目录:指文档目录,包含Vision.Face SDK手册,Vision.Face SDK人脸算法Q&A手册。其中Vision.Face SDK手册,详细介绍了人脸算法的各个API接口,本案例人脸应用调用的接口全部在该文档中有介绍。
face-sdk目录:vision.sdk人脸算法库(libface.so
)
eaidk310\_face\_demo.zip:eaidk310人脸推广案例的源代码包,解压后的目录截图如下:
build-eaidk\_visual\_embedded-Desktop-Debug:编译目录和运行目录,里面有demo运行时必需的文件和目录,比如配置文件demo.conf,models模型目录。
eaidk\_visual\_embeded:源码目录,案例实现的源代码都在该目录下,下文也将重点介绍该目录下源码文件。
libs:本案例依赖的vision.sdk人脸库,内容与上文提到的face-sdk目录内容一致。
3 配置文件&模型文件
demo.conf,该文件必须在案例程序的运行的当前路径,已配置好默认的参数,理解即可。
VideoWidth=1280 -->采集视频图像的宽
VideoHeight=720 -->采集视频图像的高
Scale=1 -->采集视频图像的缩放比例
MinFaceSize=40 -->人脸算法的最低像素要求
Clarity=200 -->人脸算法的最低清晰度要求
FaceAngle=0.4 -->人脸算法的最低角度要求
ThresHold=0.7 -->人脸识别的最低阈值要求
UseApi=0 -->人脸算法的调用策略
Dbsize=10000 -->人脸库的最大存储人脸数
RegisterMethod=0 -->人脸注册策略
FacePicturePATH=./models/faces/ -->人脸存储本地路径
模型目录:
mfn.tmfile,mobilefacenet人脸识别模型文件
det1,det2,det3.tmfile,人脸检测模型文件
face_attr.tmfile,人脸属性模型文件
4 流程介绍及源码解读
4.1 实现流程
整体流程结构如下:
4.2 流程与源码解读
下面我们对此案例中的重要&核心的代码内容进行解释,为读者提供参考,方便读者了解vision.sdk算法应用。
4.2.1 mainwindow.cpp
程序从main
函数入口,在main
中创建显示窗口,具体显示内容在mainwindow.cpp
中实现。mainwindow.cpp
源程序中主要实现2个功能:
1. UI布局
2. 图像采集
mainwindow.cpp
源程序包含构造函数、updateImage
函数、open_camera
函数等重要函数。
4.2.1.1 构造函数
所有对象创建时,都需要初始化才可以使用,而构造函数就是用于给对象进行初始化,在堆内存中开辟出一个空间来存放建立的对象并赋初始值。
/* connects */
connect(&theTimer, &QTimer::timeout, this, &MainWindow::updateImage);
connect(ui->face_attr_2, SIGNAL(clicked(bool)), this, SLOT(convert_to_face_attr()));
connect(ui->face_rec_2, SIGNAL(clicked(bool)), this, SLOT(convert_to_face_rec()));
connect(ui->rb_face_track, SIGNAL(clicked(bool)), this, SLOT(convert_to_face_track()));
updateImage
,是theTimer
对应的槽函数,每隔33ms调用一次,该函数是mainwindow.cpp
文件中最重要的函数,它包含图像的显示、人脸算法处理结果的显示。
其他connect
均为显示界面的各个按钮及其对应槽函数:
/* regist related */
connect(ui->regist, SIGNAL(clicked(bool)), this, SLOT(user_regist()));
connect(ui->cancel, SIGNAL(clicked(bool)), this, SLOT(user_cancel_regist()));
connect(ui->ok, SIGNAL(clicked(bool)), this, SLOT(user_save_regist()));
connect(ui->ok_2, SIGNAL(clicked(bool)), this, SLOT(get_user_name()));
connect(ui->cancel_2, SIGNAL(clicked(bool)), this, SLOT(back_to_face_rec()));
4.2.1.2 updateImage函数
updateimage
函数是UI显示的核心函数,每隔33ms调用1次,填充视频窗口的区域。
void MainWindow::updateImage()
{
ui->label_diku->clear();
ui->label_diku_2->clear();
cam->videoCapL>>cam->srcImageL;
cam->videoCapL>>cam->srcImageL;
该行功能是获取摄像头数据并存入srcImageL
变量。
updateimage
函数下调用了face_rec_enabled
、face_attr_enabled
、face_track_enabled
等函数
face\_rec\_enabled,表示打开人脸识别功能,该{}内表示人脸识别的代码处理部分。
人脸识别处理部分:
if(face_rec_enabled)
{
algThd->setUseApiParams(0);
algThd->face_rec_label = true;
cv::resize(cam->srcImageL, ResImg, ResImgSiz, CV_INTER_LINEAR);
algThd->sendFrame(ResImg, cam->srcImageL);
Mface face_result = algThd->getFace();
获取人脸的结果后,line
函数对图像中的人脸画框,label_diku_2
对人脸图像做show(显示)处理。
if(face_result.drawflag && strcmp(face_result.name, "")!=0 && strcmp(face_result.name, "unknown")!=0){
if(access(facepath, F_OK) < 0)
{
printf("%s:%d %s not exist.\n", __func__, __LINE__, facepath);
return;
}
int x = face_result.pos[0].x;
int y = face_result.pos[0].y;
int w = face_result.pos[0].width;
int h = face_result.pos[0].height;
line(cam->srcImageL, cvPoint(x, y), cvPoint(x, y + 20), cvScalar(0, 255, 0, 0), 2);
line(cam->srcImageL, cvPoint(x, y), cvPoint(x + 20, y), cvScalar(0, 255, 0, 0), 2);
line(cam->srcImageL, cvPoint(x + w - 20, y), cvPoint(x + w, y), cvScalar(0, 255, 0, 0), 2);
line(cam->srcImageL, cvPoint(x + w, y), cvPoint(x + w, y + 20), cvScalar(0, 255, 0, 0), 2);
line(cam->srcImageL, cvPoint(x, y + h), cvPoint(x + 20, y + h), cvScalar(0, 255, 0, 0), 2);
line(cam->srcImageL, cvPoint(x, y + h), cvPoint(x, y + h - 20), cvScalar(0, 255, 0, 0), 2);
line(cam->srcImageL, cvPoint(x + w, y + h), cvPoint(x + w, y + h - 20), cvScalar(0, 255, 0, 0), 2);
line(cam->srcImageL, cvPoint(x + w, y + h), cvPoint(x + w - 20, y + h), cvScalar(0, 255, 0, 0), 2);
Mat diku = cv::imread(facepath);
cvtColor(diku, diku, CV_BGR2RGB);
QImage imagel = QImage((uchar*)(diku.data), diku.cols, diku.rows, QImage::Format_RGB888);
ui->label_diku->setPixmap(QPixmap::fromImage(imagel));
ui->label_diku->resize(imagel.size());
ui->label_diku->show();
cv::Mat realtime_face;
cam->srcImageL(Rect(face_result.pos[0].x, face_result.pos[0].y, face_result.pos[0].width, face_result.pos[0].height)).copyTo(realtime_face);
cvtColor(realtime_face, realtime_face, CV_BGR2RGB);
cv::resize(realtime_face, realtime_face, Size(120, 120), CV_INTER_LINEAR);
QImage imagel2 = QImage((uchar*)(realtime_face.data), realtime_face.cols, realtime_face.rows, realtime_face.cols*realtime_face.channels(), QImage::Format_RGB888);
ui->label_diku_2->setPixmap(QPixmap::fromImage(imagel2));
ui->label_diku_2->resize(imagel2.size());
ui->label_diku_2->show();
face_attr\enabled,表示打开人脸属性功能,该{}内是人脸属性演示的代码部分。
人脸属性处理部分:
else if(face_attr_enabled)
{
algThd->setUseApiParams(1);
cv::resize(cam->srcImageL, ResImg, ResImgSiz, CV_INTER_LINEAR);
algThd->sendFrame(ResImg, cam->srcImageL);
Mface face_result = algThd->getFace();
face\_track\_enabled,表示打开人脸跟踪功能,该{}内是人脸跟踪的处理部分。
人脸跟踪处理部分:
else if(face_track_enabled)
{
algThd->setUseApiParams(6);
cv::resize(cam->srcImageL, ResImg, ResImgSiz, CV_INTER_LINEAR);
algThd->Tracker(ResImg,cam->srcImageL);
cv::resize(cam->srcImageL, ResImg, ResImgSiz, CV_INTER_LINEAR);
algThd->sendFrame(ResImg, cam->srcImageL);
Mface face_result = algThd->getFace();
上面人脸识别、人脸属性、人脸跟踪的处理部分都用到了setUseApiParams()
、sendFrame()
、getFace()
函数:
setUseApiParams(),设置算法处理策略,0表示做人脸识别功能,1表示做人脸属性功能,6表示做人脸跟踪功能;
sendFrame(),将采集到的图像发送到算法线程进行处理;
getFace(),获取返回的人脸数据。当mainwindow采集图象时,调用sendFrame()函数,把采集到的数据传送给循环处理的线程,并通过getFace()函数获取结果并显示。
4.2.1.3 open_camera函数
open_camera
是用来采集USB摄像头视频的函数,获取的图像用来输入给人脸算法函数。其中参数0表示USB摄像头的设备节点(/dev/video0),当该节点不存在或者非usb摄像头设备时,采集图像会失败。
if(cam->videoCapL.open(0))
{
cam->srcImageL = Mat::zeros(cam->videoCapL.get(CV_CAP_PROP_FRAME_HEIGHT), cam->videoCapL.get(CV_CAP_PROP_FRAME_WIDTH), CV_8UC3);
theTimer.start(33);
}
4.2.2 AlgThread.cpp
在mainwindow
类的构造函数中,line131创建人脸算法处理的对象algThd
,并调用start()
函数开启人脸处理线程,其内容的实现在AlgThread.cpp
中。人脸算法的流程是输入图像、深度学习类型人脸算法处理、返回结果、窗口显示。
ConfigParam param;
LoadConfig(param,false);
algThd = new AlgThread(param);
algThd->start();
ResImgSiz = cv::Size(NORMAL_FRAME_W, NORMAL_FRAME_H);
AlgThread.cpp
源程序包含线程函数run
,来实现人脸跟踪/人脸检测/特征值提取/人脸特征值比对/人脸注册等核心功能。
4.2.2.1 线程函数run
线程函数run()
是一个while(1)
循环函数,param.useapi
为0时表示人脸识别,为1时表示人脸属性,为6时表示人脸跟踪。
void AlgThread::run()
{
static unsigned long long tv_start, tv_end,t0,t1;
float feature0[FEATURE_SIZE];
float feature1[FEATURE_SIZE];
float score;
for (int i=0;i<FEATURE_SIZE;++i) {
feature0[i]=0.1;
feature1[i]=1.0;
}
while(1) {
int res;
int ret;
Mat mat;
Mat grayframe;
if(thread_status_exit == 1)
return;
if(6 == param.useapi)
{
continue;
}
std::unique_lock<std::mutex> lck(m_mtx_reg);
getframe(mat);
getSrcframe(grayframe);
if (mat.empty()) continue;
int face_recognize_return_value = FaceRecognize(mat,grayframe,0);
其他重要的函数如下,用黄色背景标注:FaceRecognize
,人脸识别函数GetFeature
,获取特征值函数,提取检测到的人脸的特征值Register
,人脸注册,特征值存入数据库函数CompareFaceDB
,人脸比对函数
int face_recognize_return_value = FaceRecognize(mat,grayframe,0);
if(0x0b == param.useapi)//liveness do not need compare feature
{
continue;
}
printf("%s %d face_recognize_return_value:%d\n", __FUNCTION__, __LINE__, face_recognize_return_value);
if(face_recognize_return_value != SUCCESS){
printf("%s %d face_recognize fail...\n", __FUNCTION__, __LINE__);
t0=tv_start;
if (param.useapi==2) {
//gettimeofday(&tv_end, NULL);
//tv_end = get_ms();
//m_fps = (float)1000.0 /(tv_end.tv_sec * 1000 + tv_end.tv_usec / 1000 - tv_start.tv_sec * 1000 - tv_start.tv_usec / 1000);
}
continue;
}
else
{
printf("%s %d face_recognize success...\n", __FUNCTION__, __LINE__);
}
if(1 == param.useapi)
{
continue;
}
ret=faceapp::GetFeature(mFace,feature1,&res);
if (ret!=SUCCESS) {
printf("%s %d get feature fail....\n", __FUNCTION__, __LINE__);
t0=tv_start;
if(ret==ERROR_BAD_QUALITY)
m_face.name="unknown";
continue;
}
if(regist_label){
Register(mat, feature1, (char*)reg_name.c_str());
regist_label = false;
reg_name = "";
}
if(face_rec_label){
ret = CompareFaceDB(feature1);
vision.sdk的使用,流程是初始化->人脸检测->人脸特征值提取->人脸注册->人脸比对函数使用,如上函数调用顺序基本也是vision.sdk的调用顺序:
5 总结
该案例解读文档主要主要从源码目录结构、源码简要流程、重点源码函数几个方面进行了解读和介绍,并按照功能实现流程顺序,介绍了基于EAIDK的人脸识别案例源码中的重要函数及其实现的功能,并贴出实际使用代码,方便开发者理解,为开发者进行二次开发提供参考。
如有疑问或想要了解更多关于EAIDK开发平台方面的内容,欢迎加入EAIDK开发者大本营,QQ群:625546458。