1

前言:

本软件是基于虹软 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

image.png
image.png
》填写相关信息,根据自己想要的平台的版本进行选择和创建应用。

--->这里选择linux64:sdk4.1,点击获取试用码。(sdk4.1属于增值版,有3个月的免费试用期,4之前版本的可以免费使用)
image.png
》得到注册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主要包含三个部分

  1. 人脸注册
  2. 图像识别
  3. 视频识别

以下代码并不完整,只是为了便于说明,完整代码项目会放在文章末尾链接。
软件界面如下:
image.png
Qt控件布局:
image.png

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的识别模式,用以判断两张照片是否为同一个人。识别过程如下图。其中,活体检测功能可以根据需求进行添加或者移除。
image.png
图片识别流程
image.png
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),然后进行人脸检测->特征提取->人脸库对比->得到结果

image.png
image.png

数据库中有两张刘德华的照片,但是识别对比第一个就结束,说明设置的0.9的阈值起到效果,同时,识别的也确实时刘德华这个人。年龄结果也准确。在检测章子怡的照片,其他信息也很准确,区别在与活体检测结果,这和算法和图片的数据有关。在进行视频检测中,可以发现,活体检测是有效果的。

3. 视频识别
初始化引擎时,选用的是ASF_DETECT_VIDOE模式。在进行视频识别的时候,为了减轻主线程的压力, 我们可以创建两个线程,一个摄像头线程,一个视频检测线程,摄像头获取视频帧,然后通过信号和槽的方式将视频帧发送给视频检测线程,视频线程对视频帧进行处理、检测、识别,将识别结果发送给主线程进行绘制,实现动态检测。需要注意的的是:视频检测并不是要每一帧都要检测,可以通过faceID这个字段进行监控,如果faceID发生变化,我们才进行检测,否则,保持之前检测信息即可。同时,为了优化检测速度,我们可以在识别用的两个子线程,用以特征提取和活体检测。

视频检测流程如下:

image.png
创建相机线程 和 视频帧检测线程:

 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;
    }

视频识别效果:

image.png

从结果上来看,一个是真人在动,一个是图片进入摄像头,活体检测能够很好进行活体区分,也有着很好的实时性。
其他

  1. IR检测问题

基于虹SDK4.1的识别大致功能基本完成,虹软的SDK对与特征提取,活体检测,人脸识别都有着很好的支持,同时相比于之前的SDK,在识别速度的,精确度上也有着很好的效果,防伪识别(活体)也比之前更加准确。

虹软的活体识别可以配合IR进行识别,以达到更高精度的检测,可用各种安防,甚至支付场景。由于条件有限,并未实现IR识别功能,但大致流程与现有实现流程一致。只是需要二外的摄像头,提取红外图片,进行检测,再与RGB活体检测结合对比。流程如下图:

image.png

  1. 数据库表的格式。
    数据库的表默认命名为 :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)

了解更多人脸识别产品相关内容请到虹软视觉开发平台


丸子小姐
48 声望18 粉丝