基于 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[label] = {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()[0];
- 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[output_dims->size - 1];
- 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
|