鸣涧 发表于 2022-5-5 22:26:28

基于 NXP i.MX 的人脸识别初探

基于 NXP i.MX 的人脸识别初探

一、 人脸识别的facenet 模型

1.1、facenet
谷歌人脸检测算法,发表于 CVPR 2015,利用相同人脸在不同角度等姿态的照片下有高内聚性,不同人脸有低耦合性,提出使用 cnn + triplet mining 方法,在 LFW 数据集上准确度达到 99.63%。

通过 CNN 将人脸映射到欧式空间的特征向量上,实质上:不同图片人脸特征的距离较大;通过相同个体的人脸的距离,总是小于不同个体的人脸这一先验知识训练网络。测试时只需要计算人脸特征EMBEDDING,然后计算距离使用阈值即可判定两张人脸照片是否属于相同的个体。

图1:人脸识别过程
简单来讲,在使用阶段,facenet即是:
1、输入一张人脸图片
2、通过深度学习网络提取特征
3、L2标准化
4、得到128维特征向量。

其中 Inception-ResNetV1是facenet使用的主干网络,如下图所示:




图2:Inception-ResNetV1 网络结构
可以看到里面的结构分为几个重要的部分
1、stem
2、Inception-resnet-A
3、Inception-resnet-B
4、Inception-resnet-C
为了能够在手机或者是其他端平台实时运行人脸识别功能,需要对其中的 Backbone 进行替换成更更小同事精度也高的模型,于是又提出了 MobileFaceNet 模型。



1.2、MobileFaceNetMobileFaceNets,该模型使用少于一百万个参数,专门针对移动和嵌入式设备上的高精度实时人脸验证而量身定制。文章首先分析普通的mobilenet应用在人脸验证上有哪些缺陷,本文设计的MobileFaceNet可以很好克服这个缺陷,在同样的实验条件下,MobileFaceNet达到明显优越的精度,而且实际速度是MobileNetV2的两倍。单个4M尺寸大小的MobileFaceNet在MS-Celeb-1M数据集上用ArcFace训练后,可以在LFW达到99.55%精度,甚至可以和一些大型几百M的CNN网络相比较。最快的MobileFaceNet在手机上推断时间仅有18毫秒,对于人脸验证,相比之前的MobileCNN,它的效率大大提高。



二、i.MX 中人脸识别


2.1、数据库的初始化数据库中每一张图片对应一个人的人脸,图片名字就是这个人的名字。数据库初始化指的是人脸数据库的初始化。想要实现人脸识别,首先要知道自己需要识别哪些人脸。所以我们数据库的一个功能,将想要检测到的人脸放入数据库中,并进行编码。
数据库的初始化具体执行的过程就是:


1、遍历数据库中所有的图片。
2、检测每个图片中的人脸位置。

该步需要使用到face_detect_helpers.cpp 中的 init_face_detect() 函数功能:
void init_face_detect(Settings *setting)
{
String face_cascade_name = "haarcascade_frontalface_alt.xml";
if( !face_cascade.load(face_cascade_name) )
    printf("--(!)Error loading haarcascade_frontalface_alt.xml\n");
s = setting;
}

3、利用CascadeClassifier将人脸截取下载。

在face_recognition.cpp 中可以实现:
    Mat face = get_data_face(img);
其中 get_data_face() 中用来检测获取人脸 face ,所使用的的模型是 haarcascade_frontalface_alt.xml
<div>Mat get_data_face(Mat &img)
</div>{
float scale = (float)rescaled_width / img.cols;
if (scale > 1)
    scale = 1;
Mat small_img = preprocess(img, scale);
Rect faceRef = detect_face(small_img, scale);
Mat face;
if (!faceRef.empty())
{
    cvtColor(img(faceRef), face, COLOR_BGR2RGB);
}
return face;
}


5、利用facenet将人脸进行编码。

6、将所有人脸编码的结果放在一个array中。

利用 facenet 模型将 face 信息转为 feature_vec ,并存在 array 中
std::array<float, num_class> feature_vec = get_output_tensor(face, s);
第6步得到的列表就是已知的所有人脸的特征列表,在之后获得的实时图片中的人脸都需要与已知人脸进行比对,这样我们才能知道谁是谁。

其完整的实现代码如下:
face_recognition.cpp


void init_database(Settings *s)
{
if (s->verbose)
    LOG(INFO) << "Enrolling...." << "\r\n";
std::vector image_names;
const int dir_err = mkdir(s->data_dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
if (dir_err != -1 && errno != EEXIST)
{
    return;
}
glob(s->data_dir, image_names);

auto start_time = GET_TIME_IN_US();
for (size_t i = 0; i < image_names.size(); i++)
{
    if (s->verbose)
      LOG(INFO) << "Add face " << i + 1 << "\r\n";
    Mat img = imread(image_names, IMREAD_COLOR);
    Mat face = get_data_face(img);
    if (face.empty())
    {
      continue;
    }
    std::array<float, num_class> feature_vec = get_output_tensor(face, s);
    cvtColor(face, face, COLOR_RGB2BGR);
    std::string label = image_names;
    label.erase(0, s->data_dir.length());
    label.erase(label.find("."));
    face_database = {face, feature_vec};
}
auto stop_time = GET_TIME_IN_US();
if (s->profiling)
{
    LOG(INFO) << "init face database\n\r";
    LOG(INFO) << "total time: "
            << (GET_TIME_DIFF(start_time, stop_time)) / 1000
            << " ms \n\r";
    LOG(INFO) << "average time: "
            << (GET_TIME_DIFF(start_time, stop_time)) / 1000 / image_names.size()
            << " ms \n\r";
}
if (s->verbose)
    LOG(INFO) << "The all " << image_names.size() << "faces database is done!\r\n";
}



2.2、实时图像的处理
    在上面讲完数据库的初始化部分后,既可以对摄像头实时处理图像数据部分开始进行研究,了解 i.MX 中图像处理流程。

2.2.1、人脸的检测    首先是从摄像头抓取 camera 图像,图像尺寸大小为 640 x 480


void init_camera()
{
cap.open(s->camera);
if (!cap.isOpened())
{
    LOG(FATAL) << "Capture from camera #" << s->camera << " didn't work"
               << "\n\r";
    exit(-1);
}
cap.set(CAP_PROP_FRAME_WIDTH, 640);
cap.set(CAP_PROP_FRAME_HEIGHT, 480);
}


利用 opencv 中的 CascadeClassifier face_cascade ,人脸检测器来检测人脸,过程如下:

Rect detect_face(Mat &img, float scale)
{
std::vector faces;
face_cascade.detectMultiScale(img, faces, 1.3, 3,
                              CASCADE_FIND_BIGGEST_OBJECT, Size(65, 65));
Rect faceRef;
if (faces.empty())
{
    return faceRef;
}
for (int i = 0; i < faces.size(); i++)
{
    if (faces.area() > faceRef.area())
    {
      faceRef = faces;
    }
}
faceRef = Rect(Point(cvRound(faceRef.x / scale),
                     cvRound(faceRef.y / scale)),
               Point(cvRound((faceRef.x + faceRef.width - 1) / scale),
                     cvRound((faceRef.y + faceRef.height - 1) / scale)));
return faceRef;
}
将人脸 face 从输入的图像中截取下来,节省计算资源:

Mat get_data_face(Mat &img)
{
float scale = (float)rescaled_width / img.cols;
if (scale > 1)
    scale = 1;
Mat small_img = preprocess(img, scale);
Rect faceRef = detect_face(small_img, scale);
Mat face;
if (!faceRef.empty())
{
    cvtColor(img(faceRef), face, COLOR_BGR2RGB);
}
return face;
}


2.2.2、Tflite 中对人脸进行编码
i.MX 中对人脸进行编码,源码中并没有将 inference 过程给出,仅仅是针对不同的模型进行推理,代码如下所示:


std::array<float, num_class> get_output_tensor(Mat img, Settings *s)
{

int output = interpreter->outputs();
TfLiteTensor *output_tensor = interpreter->tensor(output);
TfLiteIntArray *output_dims = output_tensor->dims;
// assume output dims to be something like (1, 1, ... ,size)
auto output_size = output_dims->data;
std::array<float, num_class> array;
switch (interpreter->tensor(output)->type)
{
case kTfLiteFloat32:
{
    float *output_float = interpreter->typed_output_tensor(0);
    for (int i = 0; i < output_size; i++)
      array = output_float;
    break;
}
case kTfLiteUInt8:
{
    uint8_t *output_uint8 = interpreter->typed_output_tensor(0);
    for (int i = 0; i < output_size; i++)
      array = output_uint8;
    break;
}
default:
{
    LOG(FATAL) << "cannot handle output type "
               << interpreter->tensor(input)->type << " yet";
    exit(-1);
}
}
return array;
}


2.2.3、将实时图像中的人脸特征与数据库中的进行对比

    对得到的人脸特征编码进行计算,大于 threshold 则判断是同一人脸,具体计算过程如下:


std::pair<std::string, float> predict_label(std::array<float, num_class> &pred, float threshold)
{
float ret = 0, mod1 = 0, mod2 = 0;
float max = 0;
std::string label;
for (int i = 0; i < num_class; i++)
    mod1 += pred * pred;
for (auto &face : face_database)
{
    ret = 0, mod2 = 0;
    std::array<float, num_class> &vec = face.second.second;
    for (int i = 0; i < num_class; i++)
    {
      ret += pred * vec;
      mod2 += vec * vec;
    }
    float cosine_similarity = (ret / sqrt(mod1) / sqrt(mod2) + 1) / 2.0;
    if (max <= cosine_similarity)
    {
      max = cosine_similarity;
      label = face.first;
    }
}
if (max < threshold)
{
    label.clear();
}
return {label, max};
}

三、总结归纳

      通过本文的讲述,初步阐述了如何在NXP 的 i.MX 中使用 tflite 框架对人脸进行识别的过程,重点强调算法实现的过程原理。当然实际上的操作还有增加人脸数据库的内容,需要再官网的demo 中演示查看效果。


四、参考资料

1.《睿智的目标检测40——Keras搭建Retinaface人脸检测与关键点定位平台》:

      https://blog.csdn.net/weixin_44791964/article/details/106871010



页: [1]
查看完整版本: 基于 NXP i.MX 的人脸识别初探