涉及到三个模型 静默活体检测模型<2M,人脸检测模型<2M ,人脸识别<5M(模型大小)
至于NCNN不必多说,全C++实现,不依赖第三方库实现,在第三方移动端CPU运行最快。
首先,这是三者结合的推理,这意味着从训练到转ncnn模型全部完成且作用效果的精度达到了不错的要求。
训练在此就省略了,复现的都是都是GitHub上的原项目,然后使用其最终模型进行C++上的推理。
一个demo实现对单个人的识别。(因为未涉及C++的太多知识,比如通过序列化对象转储到文件并从文件中读取,或者是通过连接数据库进行数据的转储,所以测试的推理的整个流程针对单人。)
步骤:先进行静默活体检测->人脸检测->人脸识别。后面的步骤都是在前面的步骤上实现,识别完后进行静默活体检测开始循环反复。
main函数
#include
#include
#include
#include "platform.h"
#include
#include
#include
#include "iostream"
#include
#include "net.h"
#if NCNN_VULKAN
#include "gpu.h"
#endif // NCNN_VULKAN
#define IMAGE_SIZE 80
#define CV_RGB(r,g,b) cvScalar((b),(g),(r),0)
/*声明*/
bool JudgeReal(cv::Mat bgr, ncnn::Extractor ex, ncnn::Net & SilentFaceSpoofing);
cv::Mat GetMyMat(cv::Mat img, const std::string& s);//获取人脸特征
cv::Mat GetMat(const std::string & path);//获取人脸框
cv::Mat GetMat(cv::Mat img);//重载
float* getFeatByMobileFaceNetNCNN(cv::Mat img);//获取特征向量
float CalcSimilarity_1(float* fc1,//比较出相识度
float* fc2,
long dim);
std::string Convert(float Num)
{
std:: ostringstream oss;
oss << Num;
std:: string str(oss.str());
return str;
}
int main(){
/*
先加载一个人的图片及其特征向量
*/
cv::Mat sample = GetMat("./pic/yzh1.jpg");
float *sam= getFeatByMobileFaceNetNCNN( sample);
/*
加载活体检测模型
*/
static ncnn::Net SilentFaceSpoofing;
SilentFaceSpoofing.load_param("./model/2.7_80x80_MiniFASNetV2_sim.param");
SilentFaceSpoofing.load_model("./model/2.7_80x80_MiniFASNetV2_sim.bin");
static ncnn::Extractor ex = SilentFaceSpoofing.create_extractor();
//打开摄像头
cv::VideoCapture capture(0);//创建VideoCapture对象
if (!capture.isOpened())//判断是否打开摄像头,打开isOpened返回ture
return 1;
bool stop(false);//定义一个用来停止循环的变量
cv::Mat frame;//用来存放读取的视频序列,承载每一帧的图像 ,Mat类是用于保存图像以及其他矩阵数据的数据结构
cv::namedWindow("Camera");//创建一个窗口,显示每一帧的窗口
while (!stop)
{
if (!capture.read(frame))//如果没有读取到就中断
{
break;
}
bool result;
result = JudgeReal(frame, ex, SilentFaceSpoofing);//判断真假脸
if (result) {//活体
cv::Mat frame2= GetMat(frame);//获取人脸框
float * fr= getFeatByMobileFaceNetNCNN(frame2);
float res= CalcSimilarity_1(fr, sam, 112);
std::cout << "相识度:" << res << std::endl;
delete[]fr;
if (res > 0.3) { //认为是同一个人
std::string name = "yzh-sim:" + Convert(res);
cv::Mat imgs = GetMyMat(frame, name);
putText(imgs, "realface", cv::Point(0, 100), cv::FONT_HERSHEY_SIMPLEX, 2, cv::Scalar(123, 0, 255), 4, 4);
cv::imshow("Camera", imgs);//正常显示,把获取的视频填充到窗口中
}
else
{
cv::Mat imgs = GetMyMat(frame, "unknow");
putText(imgs, "unkonwrealface", cv::Point(0, 100), cv::FONT_HERSHEY_SIMPLEX, 2, cv::Scalar(123, 0, 255), 4, 4);
cv::imshow("Camera", imgs);//正常显示,把获取的视频填充到窗口中
}
}
else {
putText(frame, "FalseFace", cv::Point(0, 100), cv::FONT_HERSHEY_SIMPLEX, 2, cv::Scalar(123, 0, 255), 4, 4);
cv::imshow("Camera", frame);//正常显示,把获取的视频填充到窗口中
std::cout << result << std::endl;
}
char c = cvWaitKey(33);
if (c == 32)break; //使用空格键来停止ASCII 为32 关闭摄像头
}
capture.release();//释放
return 0;
}
获取人的特征向量以及比较人的相识度(mobilefacenet网络实现识别)
#include
#include "net.h"
#include
using namespace std;
static int flag = 0;
//Mobilefacenet 初始化
float simd_dot_1(const float* x, const float* y, const long& len) {
float inner_prod = 0.0f;
__m128 X, Y; // 128-bit values
__m128 acc = _mm_setzero_ps(); // set to (0, 0, 0, 0)
float temp[4];
long i;
for (i = 0; i + 4 < len; i += 4) {
X = _mm_loadu_ps(x + i); // load chunk of 4 floats
Y = _mm_loadu_ps(y + i);
acc = _mm_add_ps(acc, _mm_mul_ps(X, Y));
}
_mm_storeu_ps(&temp[0], acc); // store acc into an array
inner_prod = temp[0] + temp[1] + temp[2] + temp[3];
// add the remaining values
for (; i < len; ++i) {
inner_prod += x[i] * y[i];
}
return inner_prod;
}
float CalcSimilarity_1( float* fc1,
float* fc2,
long dim) {
return simd_dot_1(fc1, fc2, dim)
/ (sqrt(simd_dot_1(fc1, fc1, dim))
* sqrt(simd_dot_1(fc2, fc2, dim)));
}
float* getFeatByMobileFaceNetNCNN( cv::Mat img)
{
static ncnn::Net Recognet;
if (!flag) {
Recognet.load_param("./model/mobilefacenet.param");
Recognet.load_model("./model/mobilefacenet.bin");
}
ncnn::Extractor ex= Recognet.create_extractor();
ex.set_light_mode(true);
ex.set_num_threads(4);
//cout << "getFeatByMobileFaceNetNCNN" << endl;
float* feat = new float[512];
ncnn::Mat in = ncnn::Mat::from_pixels_resize(img.data, ncnn::Mat::PIXEL_BGR, img.cols, img.rows, 112, 112);
ex.input("data", in);
ncnn::Mat out;
ex.extract("fc1", out);
//std::vector cls_scores;
for (int j = 0; j < out.w; j++)
{
feat[j] = out[j];
}
return feat;
}
静默活体识别引用函数(实现预处理)
#include
#include
#include
#include
#include
#include "iostream"
#include "platform.h"
#include "net.h"
#if NCNN_VULKAN
#include "gpu.h"
#endif // NCNN_VULKAN
#define IMAGE_SIZE 80
bool JudgeReal(cv::Mat bgr, ncnn::Extractor ex, ncnn::Net& SilentFaceSpoofing) {
std::vector cls_scores;
ncnn::Mat in = ncnn::Mat::from_pixels_resize(bgr.data, ncnn::Mat::PIXEL_BGR, bgr.cols, bgr.rows, IMAGE_SIZE, IMAGE_SIZE);
fprintf(stderr, "input shape: %d %d %d %d\n", in.dims, in.h, in.w, in.c);
ex.input("input", in);//input 是 .param文件中输入节点名称
ncnn::Mat out;
ex.extract("output", out);
{
ncnn::Layer* softmax = ncnn::create_layer("Softmax");
ncnn::ParamDict pb;
softmax->load_param(pb);
softmax->forward_inplace(out, SilentFaceSpoofing.opt);
delete softmax;
}
out = out.reshape(out.h * out.w * out.c);
fprintf(stderr, "output shape: %d %d %d %d\n", out.dims, out.h, out.w, out.c);
cls_scores.resize(out.w);
for (int j = 0; j < out.w; j++)
{
cls_scores[j] = out[j];
printf("cls_scores[%d]=%f\n", j, cls_scores[j]);
}
std::cout << std::endl;
auto itMax = max_element(cls_scores.begin(), cls_scores.end());
std::cout << "the max:" << *itMax << " the location:" << distance(cls_scores.begin(), itMax) << std::endl;
if (distance(cls_scores.begin(), itMax) == 1) {
std::cout << "Image " << distance(cls_scores.begin(), itMax) << " is Real Face. Score: " << *itMax << std::endl;
return true;
}
else {
std::cout << "Image " << distance(cls_scores.begin(), itMax) << " is Fake Face. Score: " << *itMax << std::endl;
return false;
}
}
获取图像中的人脸框及其特征点 (retinaface网络实现检测,这里的boxsize是图片中检测的人脸个数,由于本次demo只针对单人,所以返回的总是最后一个人的检测框)
#include
#include
#include
#include
#include
#include
#include
#include "FaceDetector.h"
using namespace std;
static bbox mybox;
//cv::Mat GetMat()
/*
图像预处理
获取框选后的人脸图像
*/
cv::Mat GetMat(const std::string & path)
{
string imgPath = path;
static string param = "./model/face.param";
static string bin = "./model/face.bin";
static const int max_side = 320;
static Detector detector(param, bin, false);
// retinaface
// Detector detector(param, bin, true);
Timer timer;
cv::Mat img = cv::imread(imgPath.c_str(), CV_LOAD_IMAGE_COLOR);
// scale
float long_side = max(img.cols, img.rows);
float scale = max_side/long_side;
cv::Mat img_scale;
cv::Size size = cv::Size(img.cols*scale, img.rows*scale);
cv::resize(img, img_scale, cv::Size(img.cols*scale, img.rows*scale));
if (img.empty())
{
fprintf(stderr, "cv::imread %s failed\n", imgPath.c_str());
}
std::vector boxes;
timer.tic();
detector.Detect(img_scale, boxes);
timer.toc("----total timer:");
if (!boxes.size()) {
cv::Mat temp;
cv::resize(img, temp, cv::Size(112, 112));
return temp;
}
cv::Mat temps;
// draw image 裁剪出图片中人脸框
for (int j = 0; j < boxes.size(); ++j) {
cv::Rect rect(boxes[j].x1 / scale, boxes[j].y1 / scale, boxes[j].x2 / scale - boxes[j].x1 / scale, boxes[j].y2 / scale - boxes[j].y1 / scale);
cv::rectangle(img, rect, cv::Scalar(0, 0, 255), 1, 8, 0);
// char test[80];
cv::Mat m = img(rect);
cv::resize(m, temps, cv::Size(112, 112));
mybox = boxes[j];
}
return temps;
}
cv::Mat GetMat( cv::Mat img)
{
static string param = "./model/face.param";
static string bin = "./model/face.bin";
static const int max_side = 320;
static Detector detector(param, bin, false);
// retinaface
// Detector detector(param, bin, true);
Timer timer;
// scale
float long_side = max(img.cols, img.rows);
float scale = max_side / long_side;
cv::Mat img_scale;
cv::Size size = cv::Size(img.cols * scale, img.rows * scale);
cv::resize(img, img_scale, cv::Size(img.cols * scale, img.rows * scale));
if (img.empty())
{
std::cout << "empty img" << std::endl;
}
std::vector boxes;
timer.tic();
detector.Detect(img_scale, boxes);
timer.toc("----total timer:");
if (!boxes.size()) {
cv::Mat temp;
cv::resize(img, temp, cv::Size(112, 112));
return temp;
}
cv::Mat temps;
// draw image 裁剪出图片中人脸框
for (int j = 0; j < boxes.size(); ++j) {
cv::Rect rect(boxes[j].x1 / scale, boxes[j].y1 / scale, boxes[j].x2 / scale - boxes[j].x1 / scale, boxes[j].y2 / scale - boxes[j].y1 / scale);
cv::rectangle(img, rect, cv::Scalar(0, 0, 255), 1, 8, 0);
// char test[80];
cv::Mat m = img(rect);
cv::resize(m, temps, cv::Size(112, 112));
mybox = boxes[j];
}
return temps;
}
/*
获取图像框
*/
cv::Mat GetMyMat( cv::Mat img, const std::string &s) {
float long_side = max(img.cols, img.rows);
float scale = 320 / long_side;
cv::Rect rect(mybox.x1 / scale, mybox.y1 / scale, mybox.x2 / scale - mybox.x1 / scale, mybox.y2 / scale - mybox.y1 / scale);
cv::rectangle(img, rect, cv::Scalar(0, 0, 255), 3, 8, 0);
cv::putText(img, s, cv::Size((mybox.x1 / scale), mybox.y1 / scale), cv::FONT_HERSHEY_COMPLEX, 0.5, cv::Scalar(0, 255, 255));
cv::circle(img, cv::Point(mybox.point[0]._x / scale, mybox.point[0]._y / scale), 1, cv::Scalar(0, 0, 225), 4);
cv::circle(img, cv::Point(mybox.point[1]._x / scale, mybox.point[1]._y / scale), 1, cv::Scalar(0, 255, 225), 4);
cv::circle(img, cv::Point(mybox.point[2]._x / scale, mybox.point[2]._y / scale), 1, cv::Scalar(255, 0, 225), 4);
cv::circle(img, cv::Point(mybox.point[3]._x / scale, mybox.point[3]._y / scale), 1, cv::Scalar(0, 255, 0), 4);
cv::circle(img, cv::Point(mybox.point[4]._x / scale, mybox.point[4]._y / scale), 1, cv::Scalar(255, 0, 0), 4);
return img;
}
#include
//#include "omp.h"
#include "FaceDetector.h"
using namespace std;
Detector::Detector():
_nms(0.4),
_threshold(0.6),
_mean_val{104.f, 117.f, 123.f},
_retinaface(false),
Net(new ncnn::Net())
{
}
inline void Detector::Release(){
if (Net != nullptr)
{
delete Net;
Net = nullptr;
}
}
Detector::Detector(const std::string &model_param, const std::string &model_bin, bool retinaface):
_nms(0.4),
_threshold(0.6),
_mean_val{104.f, 117.f, 123.f},
_retinaface(retinaface),
Net(new ncnn::Net())
{
Init(model_param, model_bin);
}
void Detector::Init(const std::string &model_param, const std::string &model_bin)
{
int ret = Net->load_param(model_param.c_str());
ret = Net->load_model(model_bin.c_str());
}
void Detector::Detect(cv::Mat& bgr, std::vector& boxes)
{
Timer timer;
timer.tic();
ncnn::Mat in = ncnn::Mat::from_pixels_resize(bgr.data, ncnn::Mat::PIXEL_BGR, bgr.cols, bgr.rows, bgr.cols, bgr.rows);
in.substract_mean_normalize(_mean_val, 0);
timer.toc("precoss:");
timer.tic();
ncnn::Extractor ex = Net->create_extractor();
ex.set_light_mode(true);
ex.set_num_threads(4);
ex.input(0, in);
ncnn::Mat out, out1, out2;
// loc
ex.extract("output0", out);
// class
ex.extract("530", out1);
//landmark
ex.extract("529", out2);
timer.toc("det:");
std::vector anchor;
timer.tic();
if (_retinaface)
create_anchor_retinaface(anchor, bgr.cols, bgr.rows);
else
create_anchor(anchor, bgr.cols, bgr.rows);
timer.toc("anchor:");
std::vector total_box;
float *ptr = out.channel(0);
float *ptr1 = out1.channel(0);
float *landms = out2.channel(0);
// #pragma omp parallel for num_threads(2)
for (int i = 0; i < anchor.size(); ++i)
{
if (*(ptr1+1) > _threshold)
{
box tmp = anchor[i];
box tmp1;
bbox result;
// loc and conf
tmp1.cx = tmp.cx + *ptr * 0.1 * tmp.sx;
tmp1.cy = tmp.cy + *(ptr+1) * 0.1 * tmp.sy;
tmp1.sx = tmp.sx * exp(*(ptr+2) * 0.2);
tmp1.sy = tmp.sy * exp(*(ptr+3) * 0.2);
result.x1 = (tmp1.cx - tmp1.sx/2) * in.w;
if (result.x1<0)
result.x1 = 0;
result.y1 = (tmp1.cy - tmp1.sy/2) * in.h;
if (result.y1<0)
result.y1 = 0;
result.x2 = (tmp1.cx + tmp1.sx/2) * in.w;
if (result.x2>in.w)
result.x2 = in.w;
result.y2 = (tmp1.cy + tmp1.sy/2)* in.h;
if (result.y2>in.h)
result.y2 = in.h;
result.s = *(ptr1 + 1);
// landmark
for (int j = 0; j < 5; ++j)
{
result.point[j]._x =( tmp.cx + *(landms + (j<<1)) * 0.1 * tmp.sx ) * in.w;
result.point[j]._y =( tmp.cy + *(landms + (j<<1) + 1) * 0.1 * tmp.sy ) * in.h;
}
total_box.push_back(result);
}
ptr += 4;
ptr1 += 2;
landms += 10;
}
std::sort(total_box.begin(), total_box.end(), cmp);
nms(total_box, _nms);
printf("%d\n", (int)total_box.size());
for (int j = 0; j < total_box.size(); ++j)
{
boxes.push_back(total_box[j]);
}
}
inline bool Detector::cmp(bbox a, bbox b) {
if (a.s > b.s)
return true;
return false;
}
inline void Detector::SetDefaultParams(){
_nms = 0.4;
_threshold = 0.6;
_mean_val[0] = 104;
_mean_val[1] = 117;
_mean_val[2] = 123;
Net = nullptr;
}
Detector::~Detector(){
Release();
}
void Detector::create_anchor(std::vector &anchor, int w, int h)
{
// anchor.reserve(num_boxes);
anchor.clear();
std::vector > feature_map(4), min_sizes(4);
float steps[] = {8, 16, 32, 64};
for (int i = 0; i < feature_map.size(); ++i) {
feature_map[i].push_back(ceil(h/steps[i]));
feature_map[i].push_back(ceil(w/steps[i]));
}
std::vector minsize1 = {10, 16, 24};
min_sizes[0] = minsize1;
std::vector minsize2 = {32, 48};
min_sizes[1] = minsize2;
std::vector minsize3 = {64, 96};
min_sizes[2] = minsize3;
std::vector minsize4 = {128, 192, 256};
min_sizes[3] = minsize4;
for (int k = 0; k < feature_map.size(); ++k)
{
std::vector min_size = min_sizes[k];
for (int i = 0; i < feature_map[k][0]; ++i)
{
for (int j = 0; j < feature_map[k][1]; ++j)
{
for (int l = 0; l < min_size.size(); ++l)
{
float s_kx = min_size[l]*1.0/w;
float s_ky = min_size[l]*1.0/h;
float cx = (j + 0.5) * steps[k]/w;
float cy = (i + 0.5) * steps[k]/h;
box axil = {cx, cy, s_kx, s_ky};
anchor.push_back(axil);
}
}
}
}
}
void Detector::create_anchor_retinaface(std::vector &anchor, int w, int h)
{
// anchor.reserve(num_boxes);
anchor.clear();
std::vector > feature_map(3), min_sizes(3);
float steps[] = {8, 16, 32};
for (int i = 0; i < feature_map.size(); ++i) {
feature_map[i].push_back(ceil(h/steps[i]));
feature_map[i].push_back(ceil(w/steps[i]));
}
std::vector minsize1 = {10, 20};
min_sizes[0] = minsize1;
std::vector minsize2 = {32, 64};
min_sizes[1] = minsize2;
std::vector minsize3 = {128, 256};
min_sizes[2] = minsize3;
for (int k = 0; k < feature_map.size(); ++k)
{
std::vector min_size = min_sizes[k];
for (int i = 0; i < feature_map[k][0]; ++i)
{
for (int j = 0; j < feature_map[k][1]; ++j)
{
for (int l = 0; l < min_size.size(); ++l)
{
float s_kx = min_size[l]*1.0/w;
float s_ky = min_size[l]*1.0/h;
float cx = (j + 0.5) * steps[k]/w;
float cy = (i + 0.5) * steps[k]/h;
box axil = {cx, cy, s_kx, s_ky};
anchor.push_back(axil);
}
}
}
}
}
void Detector::nms(std::vector &input_boxes, float NMS_THRESH)
{
std::vectorvArea(input_boxes.size());
for (int i = 0; i < int(input_boxes.size()); ++i)
{
vArea[i] = (input_boxes.at(i).x2 - input_boxes.at(i).x1 + 1)
* (input_boxes.at(i).y2 - input_boxes.at(i).y1 + 1);
}
for (int i = 0; i < int(input_boxes.size()); ++i)
{
for (int j = i + 1; j < int(input_boxes.size());)
{
float xx1 = max(input_boxes[i].x1, input_boxes[j].x1);
float yy1 = max(input_boxes[i].y1, input_boxes[j].y1);
float xx2 = min(input_boxes[i].x2, input_boxes[j].x2);
float yy2 = min(input_boxes[i].y2, input_boxes[j].y2);
float w = max(float(0), xx2 - xx1 + 1);
float h = max(float(0), yy2 - yy1 + 1);
float inter = w * h;
float ovr = inter / (vArea[i] + vArea[j] - inter);
if (ovr >= NMS_THRESH)
{
input_boxes.erase(input_boxes.begin() + j);
vArea.erase(vArea.begin() + j);
}
else
{
j++;
}
}
}
}
真脸
静默活体检测还存在一定误差。感谢各位大佬的开源,让作为菜鸟的我完成了缝合。
38、静默活体检测测试及ncnn、mnn部署_sxj731533730的博客-CSDN博客_ncnn和mnn
deepinsight/insightface: State-of-the-art 2D and 3D Face Analysis Project (github.com)
GitHub - biubug6/Pytorch_Retinaface: Retinaface get 80.99% in widerface hard val using mobilenet0.25.