前言:
本软件是基于虹软 SDK4.1 c++ for linux 做的一个人脸识别demo,至于选择虹软的理由,因为其简单易用,平台支持全面,最重要的是 免费:), 目前官网提供的免费sdk 支持linux64,window64,window32,以及ios的3.0 及以前版本(SDK4.1属于增值项,只有3个月的试用期),并提供了相关的开发文档,开发文档对SDK中每个函数的说明及使用都有相应的示例,对于实际开发很有帮助。见虹软官网开发者中心 (arcsoft.com.cn)
本项目是针对sdk4.1 在Linux下使用Qt开发的一个demo,试过3.0版本的sdk以及4.1版本的sdk,给我的感受是:sdk4.1相比以前的版本,识别速度更快,更稳定,实时性能更好,同时,也兼容了口罩识别。
虹软SDK的获取
获取方法:
进入虹软官网开发者中心 (arcsoft.com.cn)
》注册账号->选择 AI开放平台->人脸识别SDK
》填写相关信息,根据自己想要的平台的版本进行选择和创建应用。
--->这里选择linux64:sdk4.1,点击获取试用码。(sdk4.1属于增值版,有3个月的免费试用期,4之前版本的可以免费使用)
》得到注册APP_ID和SDK_KEY和(activeKey)激活码,在使用SDK开发时需要用到。
SDK激活方式:
/*#### 初次使用需要进行激活,激活信息会保存下来。只有mac地址发生变化,才需要激活####*/
MRESULT res = ASFOnlineActivation((char*)APPID, (char*)SDKKEY,(char*)ACTIVEKEY);
// 可以通过 ASFGetActiveFileInfo 函数获取激活信息
ASF_ActiveFileInfo activeFileInfo = {0};
res = ASFGetActiveFileInfo(&activeFileInfo); // 获取激活信息
SDK的各种API在官方提供的文档中都有详细的介绍,同时也包含了简单易懂的代码示例。文档可在下载的SDK demo包中找到。
软件介绍:
该demo主要包含三个部分
- 人脸注册
- 图像识别
- 视频识别
以下代码并不完整,只是为了便于说明,完整代码项目会放在文章末尾链接。
软件界面如下:
Qt控件布局:
SDK功能实现
注意:图像识别和视频识别的引擎是有区别的,进行图像模式识别时,只能一张一张检测,而在采用视频模式的情况下,带有人脸追踪功能,能够连续多张检测,同时维持着faceID字段,这可以在视频模式中进行不同的优化,如检测条件判断,只要faceID发生变化,则重新检测人脸信息。所以在进行单张图片对比检测时,选择图像模式,会有更高的精度,在对视频流进行检测时,应选择视频模式,提高视频实时检测的流畅性。
1. 人脸注册
人脸注册是对照片进行 人脸检测,提取对应的人脸特征,保存到sqlite数据库(Qt自带) 和 QMap中,QMap存储了人名和对应的人脸特征。
引擎初始化:使用SDK之前,必须对引擎进行初始化,所有的操作都和对应的引擎有关。mask设置对引擎所能开放的功能。
/*#### 引擎初始化 ####*/
//设置引擎的功能
MInt32 mask = ASF_FACE_DETECT | ASF_FACERECOGNITION | ASF_AGE | ASF_GENDER | ASF_LIVENESS | ASF_IR_LIVENESS | ASF_MASKDETECT;
// 图片模式的初始化,对于视频模式,将ASF_DETECT_MODE_IMAGE换成ASF_DETECT_MODE_VIDEO即可
res = ASFInitEngine(ASF_DETECT_MODE_IMAGE, ASF_OP_0_ONLY, FACENUM, mask, &m_imageEngine);
/*识别模式*/ /*可检测的人脸角度*/ /*人脸数*/
注册:加载指定文件夹,对文件夹所有的照片进行特征提取,进行注册登记。界面按钮(Register Face)
// 人脸注册部分 包括人脸检测+特征提取,提取后的结果会保存在QMap以及数据库中,同时会将检测到的人脸放在列表控件中显示。
dir = QDir(dirName); // 获取指定目录的数据
QStringList imageList;
imageList<< "*.YUYV"<<"*.NV21"<<"*.jpg"<<"*.bmp"<<"*.png"; // 对文件类型进行过滤,只保留指定类型的数据
dir.setNameFilters(imageList);
MInt32 imageCount = dir.count(); // 获取扫描得到文件数量,
for(int i=0;i<imageCount;++i)
{ // 循环遍历每个文件,提取相关的特征
QImage Icon; // 用来保存提取的人脸部分
// 特征提取
if(extractFeature(dir.absoluteFilePath(dir[i]),dir[i],&Icon) > -1) // 将进入特征提取函数
{ // 提取特征,并将得到的人脸框部分作为icon添加到列表控件中
QListWidgetItem *imageItem = new QListWidgetItem();
QString name = dir[i];
name.truncate(name.indexOf('.')); // 获取图片名子,作为标识
imageItem->setIcon(QIcon(QPixmap::fromImage(Icon))); // 列表控件中相关设置
imageItem->setText(name);
ui->registerListWidget->addItem(imageItem); // 在列表控件中显示人脸
}
}
特征提取: 首先将图片进行处理(注意:sdk只能处理宽为4的倍数,高为2的倍数的像素的图像),再通过ASFDetectFacesEx 进行人脸检测,然后通过ASFFaceFeatureExtractEx对检测到的人脸进行特征提取,最后将提取的特征与对应的名字存储到数据库和QMap中。ASFProcessEx是用来检测是否带口罩,进行口罩识别使用的。对应代码在arcfacedemo.cpp的extractFeature()内。
// 规范化图片格式:sdk只能处理宽是4的倍数,高是2的倍数的图片
img = img.scaled(img.width()-img.width()%4,img.height()-img.height()%2,Qt::IgnoreAspectRatio);
ASFDetectFacesEx(args...) # 这个函数用于检测图片中人脸。并保存在输出行参数中
ASFFaceFeatureExtractEx (args...) # 该函数用于对指定的人脸进行特征提取,得到人脸特征数据
ASFProcessEx(args...) # 该函数用于图像对数据预处理,可以对活体,年龄,性别,口罩等信息进行检测,得到相关和数据
int format = ASVL_PAF_RGB24_B8G8R8; // 图片格式
/* 照片处理 */
ASVLOFFSCREEN offscreen = {0,0,0,{0},{0}}; // 用以存放图片数据信息,传入人脸检测方法中
// weith,height 都是 int类型,表示图片的宽高,img是QImage类型,img.bits() 取得图片数据
ColorSpaceConvert(width, height, format, img.bits(), offscreen); // 根据format 获取图片信息,保存在offscreen中
/* 脸部数据 */
ASF_MultiFaceInfo detectedFaces = {0,0,0,0}; // 存放 检测到的多张人脸信息
ASF_SingleFaceInfo SingleDetectedFaces = {{},0}; // 存放 detecedFaces中的单个人脸信息
ASF_FaceFeature feature = {0,0};
/* 特征提取 ,图像识别采用 图像模式的引擎 */
// 人脸检测,得到多张人脸信息,存放在detectedFaces
MRESULT res = ASFDetectFacesEx(m_imageEngine, &offscreen, &detectedFaces);
//...
// 进行口罩检测,特征提取时需要用到。兼容了口罩识别
MRESULT res = ASFProcessEx(m_imageEngine,&offscreen,&detectedFaces,processMask);
//...
res = ASFGetMask(m_imageEngine,&maskInfo); // 获取口罩信息,包含所有检测的人脸是否带口罩
//...
// 单人脸特征提取,选择人脸识别模式
res = ASFFaceFeatureExtractEx(m_imageEngine, &offscreen, &SingleDetectedFaces,ASF_RECOGNITION, maskInfo.maskArray[0],&feature); // 获取脸部特征
//...
ASF_FaceFeature ff = {0,0}; // 保存提取的特征
ff.feature = (MByte *)malloc(feature.featureSize);
ff.featureSize = feature.featureSize;
memset(ff.feature,0,ff.featureSize);
memcpy(ff.feature,feature.feature,ff.featureSize);
//...
featureDB.insert(person,ff); // 提取的特征保存到特征map中,用于人脸检测 1:N 的
// process faceture 特征字节数组
QByteArray farray = QByteArray::fromRawData((char *)ff.feature,ff.featureSize); // uchar* 数据 转 binary 数据
// img data deal
QByteArray imgData; // 图片数据
QBuffer inBuffer( &imgData );
QPixmap pix = QPixmap::fromImage(*Icon); // 讲人脸框区域的数据进行保存
inBuffer.open( QIODevice::WriteOnly );
pix.save( &inBuffer,"PNG"); // 必须指定存储数据的格式
fDatabase->insertItem(person,ff.featureSize,farray,imgData); // 数据库中永久插入数据,再次启动可以从数据库中进行加载
//...
2. 图片识别
初始化引擎时,选用的是ASF_DETECT_MODE_IMAGE模式。通过检测照片,提取特征,然后在人脸库中进行特征对比。判断图片中的人是谁,如果对两张照片单独提取特征,再进行对比,则是实现的是1:1的识别模式,用以判断两张照片是否为同一个人。识别过程如下图。其中,活体检测功能可以根据需求进行添加或者移除。
图片识别流程
RGB单目摄像头的活体识别流程
为了提高软件的运行速度,我们可以将活体检测和特征提取分别放入两个线程中进行,以降低主线程的等待时间,防止程序出现卡顿。,Qt线程的使用可在网上查找,有很多相关资料。线程之间可以通过信号槽来传递参数。
FaceDetect *fd = new FaceDetect; // 人脸检测,这里采用继承QObject 和 QRunnable 的方式实现多线程
LivenessRecognize *lr = new LivenessRecognize; // 活体识别
// 发送基本参数数据,便于特征提取和绘制
emit sendImageData(localImage,ui->previewLabel->size(),featureDB,m_imageEngine); // 发送数据
QThreadPool::globalInstance()->start(fd); // open threads,放入线程进行人脸识别
QThreadPool::globalInstance()->start(lr); // 放入线程进行活体检测 ,当两个线程都完成识别,再将结果一起绘制到 图像显示区域
同时,为了提高检测速度,在人脸库对比时,只要得分大于0.9,我们便可以认为匹配成功,提前结束对比,这个阈值可以根据需求自行调整。特征提取过程前面提到的一致,下面代码只是大致描述活体检测过程,具体代码见文章末尾项目链接,在facedetect.cpp和livenessrecognize的run()函数内。
// 特征提取部分与上一部分的内容一致,
// 活体识别需要 另外初始化一个引擎,否则会报错
MInt32 mask = ASF_FACE_DETECT | ASF_FACERECOGNITION | ASF_AGE | ASF_GENDER | ASF_LIVENESS | ASF_IR_LIVENESS | ASF_MASKDETECT; //set functions we need
res = ASFInitEngine(ASF_DETECT_MODE_IMAGE, ASF_OP_0_ONLY, FACENUM, mask, &mHandle);
//...图像处理+人脸检测
//活体检测
mask = ASF_AGE | ASF_GENDER | ASF_LIVENESS | ASF_MASKDETECT; // open age , gander & liveness detect
res = ASFProcessEx(mHandle,&offscreen,&detectedFaces,mask); // 这里mask也必须重新赋值
//...
ASF_AgeInfo ageInfo = {0,0}; // 获取年龄信息
ASFGetAge(mHandle,&ageInfo);
ASF_GenderInfo genderInfo = {0,0}; // 性别
ASFGetGender(mHandle,&genderInfo);
ASF_LivenessInfo livenessInfo = {0,0}; // 活体信息
ASFGetLivenessScore(mHandle,&livenessInfo);
//...
检测结果示例:通过选择本地照片(Select Image),然后进行人脸检测->特征提取->人脸库对比->得到结果
数据库中有两张刘德华的照片,但是识别对比第一个就结束,说明设置的0.9的阈值起到效果,同时,识别的也确实时刘德华这个人。年龄结果也准确。在检测章子怡的照片,其他信息也很准确,区别在与活体检测结果,这和算法和图片的数据有关。在进行视频检测中,可以发现,活体检测是有效果的。
3. 视频识别
初始化引擎时,选用的是ASF_DETECT_VIDOE模式。在进行视频识别的时候,为了减轻主线程的压力, 我们可以创建两个线程,一个摄像头线程,一个视频检测线程,摄像头获取视频帧,然后通过信号和槽的方式将视频帧发送给视频检测线程,视频线程对视频帧进行处理、检测、识别,将识别结果发送给主线程进行绘制,实现动态检测。需要注意的的是:视频检测并不是要每一帧都要检测,可以通过faceID这个字段进行监控,如果faceID发生变化,我们才进行检测,否则,保持之前检测信息即可。同时,为了优化检测速度,我们可以在识别用的两个子线程,用以特征提取和活体检测。
视频检测流程如下:
创建相机线程 和 视频帧检测线程:
workCam = new threadCam(); // 相机线程对象
workCameraThread = new QThread(this);
workCam->moveToThread(workCameraThread);
// video ft thread
vFt = new videoFt(); // 人脸识别线程
videoFtWorkThread = new QThread(this); // 这些对象运行完后,通过信号槽会自动删除
vFt->moveToThread(videoFtWorkThread);
//...线程的数据是利用信号槽机制,从主线程将数据传入子线程中的。
//...
视频检测的中,人脸检测与视频帧的处理与处理图片的方法一样,不同处在于,只有在faceID发生变化时,才进行重新检测,这里检测部分放入线程执行,保证画面的流畅性。注意:这里采用了共享指针方式,将要检测的数据进行深拷贝,防止在检测是指针指向的数据区内的数据发生变化,影响检测结果。
//...图片处理
//...人脸检测
// 判断faceID,识别次数frCount,以及同一张脸连续出现的帧数frames
if((idChanged || (frCount<=3 && predict=="UnFound" && frames>15)) && detectedFaces.faceNum>0)
{ // 共享指针,深拷贝数据,引用全部结束时,自动释放内存
QSharedPointer<OffScreen> offscreenPtr( new OffScreen(offscreen));
QSharedPointer<MutilFace> detectedFacesPtr( new MutilFace(detectedFaces));
// 进入识别线程
QFuture<void> frThread = QtConcurrent::run(this,&videoFt::threadFr, offscreenPtr, detectedFacesPtr);
idChanged = false;
++frCount;
frames = 0;
}
为了将提高软件流畅度,可以在识别线程中再创建两个子线程,分别用以人脸识别和活体信息检测。再通过两个线程返回结果进行同步,如果检测到了人脸特征,且目标是活体,则再人脸库中进行比对,否则提示unfound:fake的提示。
ASF_FaceFeature newFeature={0,0}; // 提取的特征,用于比较
// 活体,性别,年龄检测线程
QFuture<MInt32> lrture = QtConcurrent::run(this,&videoFt::livenessDetectEx,
offscreen,detectedFaces);
// 人脸识别 线程
QFuture<MRESULT> ffture = QtConcurrent::run(this,&videoFt::featureExtractEx,
offscreen,detectedFaces,&newFeature);
MRESULT res = ffture.result();
MInt32 isLiveness = lrture.result(); // 等待两个线程的结果
if(res==MOK && isLiveness>0) {
// 特征提取成功,且是活体时,才进行人脸库对{
MFloat confidenceLevel=0.0; // 人脸库对比。
QMap<QString,ASF_FaceFeature>::iterator it = featureDB.begin();
for(;it != featureDB.end();++it){
res = ASFFaceFeatureCompare(mImageEngine, &it.value(), &newFeature, &confidenceLevel);
//... 识别成功结果处理
}
else{ // 识别失败处理
predict="UnFound";
detectData = ":fake";
score = 0.0;
}
视频识别效果:
从结果上来看,一个是真人在动,一个是图片进入摄像头,活体检测能够很好进行活体区分,也有着很好的实时性。
其他
- IR检测问题
基于虹SDK4.1的识别大致功能基本完成,虹软的SDK对与特征提取,活体检测,人脸识别都有着很好的支持,同时相比于之前的SDK,在识别速度的,精确度上也有着很好的效果,防伪识别(活体)也比之前更加准确。
虹软的活体识别可以配合IR进行识别,以达到更高精度的检测,可用各种安防,甚至支付场景。由于条件有限,并未实现IR识别功能,但大致流程与现有实现流程一致。只是需要二外的摄像头,提取红外图片,进行检测,再与RGB活体检测结合对比。流程如下图:
- 数据库表的格式。
数据库的表默认命名为 :face_feature。包含了四个字段。依次是:姓名,人脸特征大小,人脸特征和对应的脸部数据。
// 使用Qt内置的QSqlite数据库,创建表
bool FacesDataBase::createTable() // 创建默认的 face_feature 数据库表,如果不存在,就创建新表
{
QString createSql = "CREATE TABLE IF NOT EXISTS face_feature (name text,featureSize int,feature BLOB,imagedata BLOB)";
return m_Query.exec(createSql);
}
创建数据库可以保存注册的人脸信息,也可以在再次启动的时候读取数据库的数据,并在列表控件中显示。
// 加载数据库中的信息,并在列表控件中显示
QString tsql = "select * from face_feature"; // 查询数据库所有数据,并返回保存到数据中
if(m_Query.exec(tsql)){
while(m_Query.next()){
QString name = m_Query.value(0).toString();
ASF_FaceFeature ff={0,0};
ff.featureSize = m_Query.value(1).toInt();
ff.feature = (MByte *)malloc(ff.featureSize);
memset(ff.feature,0,ff.featureSize);
// get feature data
QByteArray data = m_Query.value(2).toByteArray();
memcpy(ff.feature,reinterpret_cast<unsigned char*>(data.data()),ff.featureSize);
featureDB.insert(name,ff);
// deal image data
QByteArray outByteArray = m_Query.value(3).toByteArray();
QPixmap pix = QPixmap();
pix.loadFromData(outByteArray);
pixMap.insert(name,pix); // QMap对象,临时保存数据,然后再主线程中读取,加载到列表控件中
}
}
//........
// 加载数据,在列表控件中显示图片
QMap<QString,QPixmap>::iterator it = pixMap.begin();
while(it!=pixMap.end())
{
// set to listWidget
QListWidgetItem *imageItem = new QListWidgetItem();
imageItem->setIcon(QIcon(it.value()));
imageItem->setText(it.key());
ui->registerListWidget->addItem(imageItem);
++it;
}
pixMap.clear(); // 清楚加载后的临时数据
总结
虹软人脸识别的SDK在识别效率,识别准确性都有很不错的表现,对于我们学习,工作,生活都有着不错的应用。同时免费的SDK每年能支持100太设备的使用,在学习,和开发中都是不错的选择。如果想要更安全,高效的功能,可以考虑虹软SDK4.1,有口罩识别功能,其防伪识别也更加有效。
最后,希望这篇文章对你有所帮助!
完整项目链接
项目链接zhouxuanlang/arcsoft_linux_demo: arcsoft sdk4.1 c++for linux with qt5.15 (github.com)
了解更多人脸识别产品相关内容请到虹软视觉开发平台哦
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。