【更新】完成人脸检测和特征提取模块
This commit is contained in:
@@ -1,9 +1,9 @@
|
|||||||
cmake_minimum_required(VERSION 3.8)
|
cmake_minimum_required(VERSION 3.8)
|
||||||
project(Demo)
|
project(Demo)
|
||||||
|
set(CMAKE_BUILD_PARALLEL_LEVEL 8)
|
||||||
set(CMAKE_CXX_STANDARD 11)
|
set(CMAKE_CXX_STANDARD 11)
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
set(CMAKE_CXX_COMPILER "MSVC")
|
set(CMAKE_CXX_COMPILER "MSVC")
|
||||||
|
|
||||||
elseif(UNIX)
|
elseif(UNIX)
|
||||||
set(CMAKE_CXX_COMPILER "g++")
|
set(CMAKE_CXX_COMPILER "g++")
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
66
app/main.cpp
66
app/main.cpp
@@ -2,26 +2,72 @@
|
|||||||
#include <opencv2/opencv.hpp>
|
#include <opencv2/opencv.hpp>
|
||||||
#include "util/TimeCount.h"
|
#include "util/TimeCount.h"
|
||||||
#include "CenterFaceMnn.h"
|
#include "CenterFaceMnn.h"
|
||||||
|
#include "MobileFaceFeatureMnn.h"
|
||||||
|
|
||||||
int32_t main(int32_t argc, char** argv) {
|
void test_face_detect(std::string img_path) {
|
||||||
CenterFaceMnn::GetInstance();
|
cv::Mat img = cv::imread(img_path);
|
||||||
std::vector<FaceInfo> faces;
|
std::vector<FaceInfo> faces;
|
||||||
cv::Mat img = cv::imread("F:/33.jpg");
|
|
||||||
int32_t count = 0;
|
|
||||||
re_test:
|
|
||||||
count++;
|
|
||||||
{
|
{
|
||||||
USE_TIME t(USE_TIME_US, "face_detect: ");
|
USE_TIME t(USE_TIME_US, "face_detect: ");
|
||||||
CenterFaceMnn::GetInstance()->Detect(img, faces, 1);
|
CenterFaceMnn::GetInstance()->Detect(img, faces, 1);
|
||||||
}
|
}
|
||||||
if (count < 10) {
|
|
||||||
goto re_test;
|
|
||||||
}
|
|
||||||
|
|
||||||
for(auto& face : faces) {
|
for(auto& face : faces) {
|
||||||
cv::rectangle(img, cv::Rect(face.x1, face.y1, face.x2 - face.x1, face.y2 - face.y1), cv::Scalar(0, 0, 255), 2);
|
cv::rectangle(img, cv::Rect(face.x1, face.y1, FACE_BOX_WIDTH(face), FACE_BOX_HEIGHT(face)), cv::Scalar(0, 0, 255), 2);
|
||||||
}
|
}
|
||||||
cv::imshow("test", img);
|
cv::imshow("test", img);
|
||||||
cv::waitKey(0);
|
cv::waitKey(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_face_compare(std::string img1, std::string img2) {
|
||||||
|
cv::Mat img1_ = cv::imread(img1);
|
||||||
|
cv::Mat img2_ = cv::imread(img2);
|
||||||
|
std::vector<FaceInfo> faces1;
|
||||||
|
std::vector<FaceInfo> faces2;
|
||||||
|
CenterFaceMnn::GetInstance()->Detect(img1_, faces1, 1);
|
||||||
|
CenterFaceMnn::GetInstance()->Detect(img2_, faces2, 1);
|
||||||
|
|
||||||
|
if (faces1.size() == 0 || faces2.size() == 0) {
|
||||||
|
std::cout << faces1.size() << " == " << faces2.size() << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
float feature1[512];
|
||||||
|
float feature2[512];
|
||||||
|
float score = 0.f;
|
||||||
|
{
|
||||||
|
USE_TIME t(USE_TIME_US, "GetFaceFeature img1 time: ");
|
||||||
|
MobileFaceFeatureMnn::GetInstance()->GetFaceFeature(img1_, faces1[0].landmarks, feature1);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
USE_TIME t(USE_TIME_US, "GetFaceFeature img2 time: ");
|
||||||
|
MobileFaceFeatureMnn::GetInstance()->GetFaceFeature(img2_, faces2[0].landmarks, feature2);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
USE_TIME t(USE_TIME_US, "DeepCamFaceFeatureCompare time: ");
|
||||||
|
MobileFaceFeatureMnn::GetInstance()->MobileFaceFeatureCompare(feature1, feature2, score);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "score: " << score << std::endl;
|
||||||
|
|
||||||
|
for(auto& face : faces2) {
|
||||||
|
cv::rectangle(img2_, cv::Rect(face.x1, face.y1, FACE_BOX_WIDTH(face), FACE_BOX_HEIGHT(face)), cv::Scalar(0, 0, 255), 2);
|
||||||
|
}
|
||||||
|
for(auto& face : faces1) {
|
||||||
|
cv::rectangle(img1_, cv::Rect(face.x1, face.y1, FACE_BOX_WIDTH(face), FACE_BOX_HEIGHT(face)), cv::Scalar(0, 0, 255), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::imshow("img1", img1_);
|
||||||
|
cv::imshow("img2", img2_);
|
||||||
|
cv::waitKey(0);
|
||||||
|
}
|
||||||
|
int32_t main(int32_t argc, char** argv) {
|
||||||
|
CenterFaceMnn::GetInstance();
|
||||||
|
MobileFaceFeatureMnn::GetInstance();
|
||||||
|
// test_face_detect("E:/dd.jpg");
|
||||||
|
|
||||||
|
test_face_compare("F:/1.jpg", "E:\\6.jpg");
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
14
include/AppCfg.h
Normal file
14
include/AppCfg.h
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class AppCfg {
|
||||||
|
public:
|
||||||
|
static void SetWorkPath(const std::string& work_path);
|
||||||
|
static const std::string GetWorkPath();
|
||||||
|
static bool SetLicense(const std::string& license);
|
||||||
|
static void SetFaceMatchThreshold(float threshold);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static std::string m_work_path; // 静态成员变量声明
|
||||||
|
static bool m_auth;
|
||||||
|
};
|
||||||
23
src/AppCfg.cpp
Normal file
23
src/AppCfg.cpp
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#include "AppCfg.h"
|
||||||
|
|
||||||
|
bool AppCfg::m_auth = false;
|
||||||
|
std::string AppCfg::m_work_path="";
|
||||||
|
std::string AppCfg::m_license="";
|
||||||
|
float AppCfg::m_face_match_threshold = 0.78f;
|
||||||
|
void AppCfg::SetWorkPath(const std::string& work_path) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string AppCfg::GetWorkPath() {
|
||||||
|
return AppCfg::m_work_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AppCfg::SetLicense(const std::string& license) {
|
||||||
|
|
||||||
|
return AppCfg::m_auth;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppCfg::SetFaceMatchThreshold(float threshold) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
31
src/AppCfg.h
Normal file
31
src/AppCfg.h
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
|
||||||
|
#ifndef __APP_CFG_H__
|
||||||
|
#define __APP_CFG_H__
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class AppCfg {
|
||||||
|
private:
|
||||||
|
AppCfg() {}
|
||||||
|
AppCfg(const AppCfg&) = delete;
|
||||||
|
AppCfg& operator=(const AppCfg&) = delete;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static void SetWorkPath(const std::string& work_path);
|
||||||
|
static const std::string GetWorkPath();
|
||||||
|
|
||||||
|
/// 设置授权信息
|
||||||
|
static bool SetLicense(const std::string& license);
|
||||||
|
|
||||||
|
/// @brief 设置人脸比对阈值
|
||||||
|
static void SetFaceMatchThreshold(float threshold);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static std::string m_work_path;
|
||||||
|
static std::string m_license;
|
||||||
|
static float m_face_match_threshold;
|
||||||
|
|
||||||
|
static bool m_auth;
|
||||||
|
};
|
||||||
|
#endif
|
||||||
261
src/CenterFaceMnn.cpp2
Normal file
261
src/CenterFaceMnn.cpp2
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
#include "CenterFaceMnn.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static const unsigned char centerface_model[] = {
|
||||||
|
#include "centerface_small_mnn.dat"
|
||||||
|
};
|
||||||
|
|
||||||
|
CenterFaceMnn* CenterFaceMnn::m_hInstance;
|
||||||
|
|
||||||
|
|
||||||
|
CenterFaceMnn* CenterFaceMnn::GetInstance()
|
||||||
|
{
|
||||||
|
if (!m_hInstance)
|
||||||
|
{
|
||||||
|
m_hInstance = new CenterFaceMnn();
|
||||||
|
}
|
||||||
|
return m_hInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
CenterFaceMnn::CenterFaceMnn()
|
||||||
|
{
|
||||||
|
m_detector = std::shared_ptr<MNN::Interpreter>(MNN::Interpreter::createFromBuffer(centerface_model, sizeof(centerface_model)));
|
||||||
|
|
||||||
|
MNN::ScheduleConfig config;
|
||||||
|
MNN::BackendConfig backendConfig;
|
||||||
|
backendConfig.precision = MNN::BackendConfig::Precision_High;
|
||||||
|
backendConfig.power = MNN::BackendConfig::Power_High;
|
||||||
|
backendConfig.memory = MNN::BackendConfig::Memory_High;
|
||||||
|
config.backendConfig = &backendConfig;
|
||||||
|
config.type = MNN_FORWARD_CPU;
|
||||||
|
config.numThread = 4;
|
||||||
|
m_session = m_detector->createSession(config);
|
||||||
|
|
||||||
|
const float mean_vals[3] = { 127.5f, 127.5f, 127.5f };
|
||||||
|
const float norm_vals[3] = { 0.0078431373f, 0.0078431373f, 0.0078431373f };
|
||||||
|
::memcpy(m_img_config.mean, mean_vals, sizeof(mean_vals));
|
||||||
|
::memcpy(m_img_config.normal, norm_vals, sizeof(norm_vals));
|
||||||
|
m_img_config.sourceFormat = (MNN::CV::ImageFormat)2;
|
||||||
|
m_img_config.destFormat = (MNN::CV::ImageFormat)1;
|
||||||
|
m_img_config.filterType = (MNN::CV::Filter)(1);
|
||||||
|
m_img_config.wrap = (MNN::CV::Wrap)(1);
|
||||||
|
|
||||||
|
input_tensor = m_detector->getSessionInput(m_session, NULL);
|
||||||
|
hm_tensor = m_detector->getSessionOutput(m_session, "hm");
|
||||||
|
wh_tensor = m_detector->getSessionOutput(m_session, "wh");
|
||||||
|
reg_tensor = m_detector->getSessionOutput(m_session, "reg");
|
||||||
|
lm_tensor = m_detector->getSessionOutput(m_session, "lm");
|
||||||
|
}
|
||||||
|
|
||||||
|
CenterFaceMnn::~CenterFaceMnn()
|
||||||
|
{
|
||||||
|
m_detector->releaseSession(m_session);
|
||||||
|
}
|
||||||
|
|
||||||
|
int CenterFaceMnn::Detect(const cv::Mat& img, std::vector<FaceInfo>& faces, float scale)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_mt);
|
||||||
|
faces.clear();
|
||||||
|
if (img.cols * img.rows == 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scale <= 0 || scale > 1) {
|
||||||
|
scale = img.cols > img.rows ? 160.0f / img.cols : 160.0f / img.rows;
|
||||||
|
}
|
||||||
|
int resize_w = (int)(img.cols * scale) / 32 * 32;
|
||||||
|
int resize_h = (int)(img.rows * scale) / 32 * 32;
|
||||||
|
float scale_w = (float)img.cols / (float)resize_w;
|
||||||
|
float scale_h = (float)img.rows / (float)resize_h;
|
||||||
|
|
||||||
|
|
||||||
|
cv::Mat input;
|
||||||
|
cv::resize(img, input, cv::Size(resize_w, resize_h));
|
||||||
|
m_detector->resizeTensor(input_tensor, 1, 3, resize_h, resize_w);
|
||||||
|
m_detector->resizeSession(m_session);
|
||||||
|
//prepare data
|
||||||
|
std::shared_ptr<MNN::CV::ImageProcess> pretreat(MNN::CV::ImageProcess::create(m_img_config));
|
||||||
|
pretreat->convert(input.data, resize_w, resize_h, input.step[0], input_tensor);
|
||||||
|
m_detector->runSession(m_session);
|
||||||
|
|
||||||
|
MNN::Tensor tensor_hm(hm_tensor, MNN::Tensor::CAFFE);
|
||||||
|
MNN::Tensor tensor_wh(wh_tensor, MNN::Tensor::CAFFE);
|
||||||
|
MNN::Tensor tensor_reg(reg_tensor, MNN::Tensor::CAFFE);
|
||||||
|
MNN::Tensor tensor_lm(lm_tensor, MNN::Tensor::CAFFE);
|
||||||
|
|
||||||
|
hm_tensor->copyToHostTensor(&tensor_hm);
|
||||||
|
wh_tensor->copyToHostTensor(&tensor_wh);
|
||||||
|
reg_tensor->copyToHostTensor(&tensor_reg);
|
||||||
|
lm_tensor->copyToHostTensor(&tensor_lm);
|
||||||
|
|
||||||
|
std::vector<int> hm_shape = tensor_hm.shape();
|
||||||
|
|
||||||
|
float* heatmap = tensor_hm.host<float>();
|
||||||
|
float* wh = tensor_wh.host<float>();
|
||||||
|
float* reg = tensor_reg.host<float>();
|
||||||
|
float* lm = tensor_lm.host<float>();
|
||||||
|
|
||||||
|
Decode(heatmap, wh, reg, lm, hm_shape[2], hm_shape[3], faces, 0.4, 0.3);
|
||||||
|
for (int i = 0; i < faces.size(); i++)
|
||||||
|
{
|
||||||
|
FaceInfo& tmp = faces[i];
|
||||||
|
tmp.x1 *= scale_w;
|
||||||
|
tmp.x2 *= scale_w;
|
||||||
|
tmp.y1 *= scale_h;
|
||||||
|
tmp.y2 *= scale_h;
|
||||||
|
|
||||||
|
for (int j = 0; j < 5; j++)
|
||||||
|
{
|
||||||
|
tmp.landmarks[j * 2] *= scale_w;
|
||||||
|
tmp.landmarks[j * 2 + 1] *= scale_h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return faces.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CenterFaceMnn::Decode(float* heatmap, float* scale, float* offset, float* landmarks, int h, int w, std::vector<FaceInfo>& faces, float scoreThresh, float nmsThresh)
|
||||||
|
{
|
||||||
|
int fea_h = h;
|
||||||
|
int fea_w = w;
|
||||||
|
int d_w = w * 4;
|
||||||
|
int d_h = h * 4;
|
||||||
|
int spacial_size = fea_w*fea_h;
|
||||||
|
|
||||||
|
|
||||||
|
float *scale1 = (float*)(scale);
|
||||||
|
float *scale0 = scale1 + spacial_size;
|
||||||
|
|
||||||
|
float *offset1 = (float*)(offset);
|
||||||
|
float *offset0 = offset1 + spacial_size;
|
||||||
|
|
||||||
|
std::vector<int> ids = GetIds(heatmap, fea_h, fea_w, scoreThresh);
|
||||||
|
|
||||||
|
std::vector<FaceInfo> faces_tmp;
|
||||||
|
for (int i = 0; i < ids.size() / 2; i++) {
|
||||||
|
int id_h = ids[2 * i];
|
||||||
|
int id_w = ids[2 * i + 1];
|
||||||
|
int index = id_h*fea_w + id_w;
|
||||||
|
|
||||||
|
float s0 = std::exp(scale0[index]) * 4;
|
||||||
|
float s1 = std::exp(scale1[index]) * 4;
|
||||||
|
//cout << s0 << "," << s1 << endl;
|
||||||
|
float o0 = offset0[index];
|
||||||
|
float o1 = offset1[index];
|
||||||
|
|
||||||
|
//std::cout << s0 << " " << s1 << " " << o0 << " " << o1 << std::endl;
|
||||||
|
|
||||||
|
float x1 = std::max(0., (id_w + o1 + 0.5) * 4 - s1 / 2);
|
||||||
|
float y1 = std::max(0., (id_h + o0 + 0.5) * 4 - s0 / 2);
|
||||||
|
float x2 = std::max(0., (id_w + o1 + 0.5) * 4 + s1 / 2);
|
||||||
|
float y2 = std::max(0., (id_h + o0 + 0.5) * 4 + s0 / 2);
|
||||||
|
x1 = std::min(x1, (float)d_w);
|
||||||
|
y1 = std::min(y1, (float)d_h);
|
||||||
|
x2 = std::min(x2, (float)d_w);
|
||||||
|
y2 = std::min(y2, (float)d_h);
|
||||||
|
|
||||||
|
//std::cout << x1 << " " << y1 << " " << x2 << " " << y2 << std::endl;
|
||||||
|
|
||||||
|
FaceInfo facebox;
|
||||||
|
facebox.x1 = x1;
|
||||||
|
facebox.y1 = y1;
|
||||||
|
facebox.x2 = x2;
|
||||||
|
facebox.y2 = y2;
|
||||||
|
facebox.score = heatmap[index];
|
||||||
|
|
||||||
|
float box_w = x2 - x1;//s1?
|
||||||
|
float box_h = y2 - y1;// s0 ?
|
||||||
|
|
||||||
|
for (int j = 0; j < 5; j++) {
|
||||||
|
float *xmap = (float*)landmarks + (2 * j)*spacial_size;
|
||||||
|
float *ymap = (float*)landmarks + (2 * j + 1)*spacial_size;
|
||||||
|
facebox.landmarks[2 * j] = x1 + xmap[index] * s1;
|
||||||
|
facebox.landmarks[2 * j + 1] = y1 + ymap[index] * s0;
|
||||||
|
|
||||||
|
}
|
||||||
|
faces_tmp.push_back(facebox);
|
||||||
|
}
|
||||||
|
NMS(faces_tmp, faces, nmsThresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CenterFaceMnn::NMS(std::vector<FaceInfo>& input, std::vector<FaceInfo>& output, float nmsthreshold)
|
||||||
|
{
|
||||||
|
std::sort(input.begin(), input.end(),
|
||||||
|
[](const FaceInfo& a, const FaceInfo& b)
|
||||||
|
{
|
||||||
|
return a.score > b.score;
|
||||||
|
});
|
||||||
|
|
||||||
|
int box_num = input.size();
|
||||||
|
|
||||||
|
std::vector<int> merged(box_num, 0);
|
||||||
|
|
||||||
|
for (int i = 0; i < box_num; i++)
|
||||||
|
{
|
||||||
|
if (merged[i])
|
||||||
|
continue;
|
||||||
|
|
||||||
|
output.push_back(input[i]);
|
||||||
|
|
||||||
|
float h0 = input[i].y2 - input[i].y1 + 1;
|
||||||
|
float w0 = input[i].x2 - input[i].x1 + 1;
|
||||||
|
|
||||||
|
float area0 = h0 * w0;
|
||||||
|
|
||||||
|
|
||||||
|
for (int j = i + 1; j < box_num; j++)
|
||||||
|
{
|
||||||
|
if (merged[j])
|
||||||
|
continue;
|
||||||
|
|
||||||
|
float inner_x0 = input[i].x1 > input[j].x1 ? input[i].x1 : input[j].x1;//std::max(input[i].x1, input[j].x1);
|
||||||
|
float inner_y0 = input[i].y1 > input[j].y1 ? input[i].y1 : input[j].y1;
|
||||||
|
|
||||||
|
float inner_x1 = input[i].x2 < input[j].x2 ? input[i].x2 : input[j].x2; //bug fixed ,sorry
|
||||||
|
float inner_y1 = input[i].y2 < input[j].y2 ? input[i].y2 : input[j].y2;
|
||||||
|
|
||||||
|
float inner_h = inner_y1 - inner_y0 + 1;
|
||||||
|
float inner_w = inner_x1 - inner_x0 + 1;
|
||||||
|
|
||||||
|
|
||||||
|
if (inner_h <= 0 || inner_w <= 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
float inner_area = inner_h * inner_w;
|
||||||
|
|
||||||
|
float h1 = input[j].y2 - input[j].y1 + 1;
|
||||||
|
float w1 = input[j].x2 - input[j].x1 + 1;
|
||||||
|
|
||||||
|
float area1 = h1 * w1;
|
||||||
|
|
||||||
|
float score;
|
||||||
|
|
||||||
|
score = inner_area / (area0 + area1 - inner_area);
|
||||||
|
|
||||||
|
if (score > nmsthreshold)
|
||||||
|
merged[j] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<int> CenterFaceMnn::GetIds(float *heatmap, int h, int w, float thresh)
|
||||||
|
{
|
||||||
|
std::vector<int> ids;
|
||||||
|
for (int i = 0; i < h; i++) {
|
||||||
|
for (int j = 0; j < w; j++) {
|
||||||
|
if (heatmap[i*w + j] > thresh) {
|
||||||
|
ids.push_back(i);
|
||||||
|
ids.push_back(j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string CenterFaceMnn::GetModelVer()
|
||||||
|
{
|
||||||
|
return m_model_ver;
|
||||||
|
}
|
||||||
220
src/MobileFaceFeatureMnn.cpp
Normal file
220
src/MobileFaceFeatureMnn.cpp
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
#include <omp.h>
|
||||||
|
#include <MobileFaceFeatureMnn.h>
|
||||||
|
|
||||||
|
|
||||||
|
MobileFaceFeatureMnn* MobileFaceFeatureMnn::m_hInstance = nullptr;
|
||||||
|
|
||||||
|
|
||||||
|
#define FEATURE_IMG_SIZE 112
|
||||||
|
|
||||||
|
extern unsigned char mobile_face_model0[];
|
||||||
|
extern unsigned char mobile_face_model1[];
|
||||||
|
extern unsigned char mobile_face_model2[];
|
||||||
|
|
||||||
|
extern int mobile_face_model0_len;
|
||||||
|
extern int mobile_face_model1_len;
|
||||||
|
extern int mobile_face_model2_len;
|
||||||
|
|
||||||
|
|
||||||
|
MobileFaceFeatureMnn* MobileFaceFeatureMnn::GetInstance(){
|
||||||
|
if (m_hInstance == nullptr){
|
||||||
|
m_hInstance = new MobileFaceFeatureMnn;
|
||||||
|
}
|
||||||
|
return m_hInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
MobileFaceFeatureMnn::MobileFaceFeatureMnn(){
|
||||||
|
const unsigned char pw[] = "~12321@AAA";
|
||||||
|
std::vector<unsigned char> model;
|
||||||
|
model.insert(model.end(), mobile_face_model0, mobile_face_model0 + mobile_face_model0_len);
|
||||||
|
model.insert(model.end(), mobile_face_model1, mobile_face_model1 + mobile_face_model1_len);
|
||||||
|
model.insert(model.end(), mobile_face_model2, mobile_face_model2 + mobile_face_model2_len);
|
||||||
|
|
||||||
|
int len = mobile_face_model0_len + mobile_face_model1_len + mobile_face_model2_len;
|
||||||
|
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
model[i] = model[i] ^ pw[i % sizeof(pw)];
|
||||||
|
}
|
||||||
|
|
||||||
|
//m_detector = std::shared_ptr<MNN::Interpreter>(MNN::Interpreter::createFromFile(R"(I:\DeepCamPro\DeepCamFace\Release\MobileFaceNet_v6.6.4.mnn)"));
|
||||||
|
m_detector = std::shared_ptr<MNN::Interpreter>(MNN::Interpreter::createFromBuffer(model.data(), len));
|
||||||
|
|
||||||
|
MNN::ScheduleConfig config;
|
||||||
|
config.type = MNN_FORWARD_OPENCL;
|
||||||
|
config.numThread = 4;
|
||||||
|
MNN::BackendConfig backendConfig;
|
||||||
|
backendConfig.precision = MNN::BackendConfig::Precision_High;
|
||||||
|
backendConfig.power = MNN::BackendConfig::Power_High;
|
||||||
|
config.backendConfig = &backendConfig;
|
||||||
|
|
||||||
|
m_sess_mobileface = m_detector->createSession(config);
|
||||||
|
m_input_tensor = m_detector->getSessionInput(m_sess_mobileface, NULL);
|
||||||
|
m_feature_tensor = m_detector->getSessionOutput(m_sess_mobileface, "feature");
|
||||||
|
m_detector->resizeTensor(m_input_tensor, 1, 3, FEATURE_IMG_SIZE, FEATURE_IMG_SIZE);
|
||||||
|
m_detector->resizeSession(m_sess_mobileface);
|
||||||
|
|
||||||
|
const static float mean_vals[3] = { 127.5f, 127.5f, 127.5f };
|
||||||
|
const static float norm_vals[3] = { 0.0078431373, 0.0078431373, 0.0078431373 };
|
||||||
|
::memcpy(m_img_config.mean, mean_vals, sizeof(mean_vals));
|
||||||
|
::memcpy(m_img_config.normal, norm_vals, sizeof(norm_vals));
|
||||||
|
|
||||||
|
m_img_config.sourceFormat = (MNN::CV::ImageFormat)2;
|
||||||
|
m_img_config.destFormat = (MNN::CV::ImageFormat)1;
|
||||||
|
|
||||||
|
m_img_config.filterType = (MNN::CV::Filter)(1);
|
||||||
|
m_img_config.wrap = (MNN::CV::Wrap)(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
MobileFaceFeatureMnn::~MobileFaceFeatureMnn(){
|
||||||
|
m_detector->releaseModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
ret_t MobileFaceFeatureMnn::GetFaceFeature(const cv::Mat& img, const float* landmarks, float* feature){
|
||||||
|
std::lock_guard<std::mutex> lock(m_mt);
|
||||||
|
cv::Mat alignFace = FaceAlign(img, landmarks);
|
||||||
|
//cv::imshow("alignFace", alignFace);
|
||||||
|
//cv::waitKey(1);
|
||||||
|
if (alignFace.cols != FEATURE_IMG_SIZE || alignFace.rows != FEATURE_IMG_SIZE){
|
||||||
|
cv::resize(alignFace, alignFace, cv::Size(FEATURE_IMG_SIZE, FEATURE_IMG_SIZE));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<MNN::CV::ImageProcess> pretreat(MNN::CV::ImageProcess::create(m_img_config));
|
||||||
|
pretreat->convert(alignFace.data, FEATURE_IMG_SIZE, FEATURE_IMG_SIZE, alignFace.step[0], m_input_tensor);
|
||||||
|
|
||||||
|
m_detector->runSession(m_sess_mobileface);
|
||||||
|
|
||||||
|
std::shared_ptr<MNN::Tensor> tensor_feature = std::make_shared<MNN::Tensor>(m_feature_tensor, MNN::Tensor::CAFFE);
|
||||||
|
m_feature_tensor->copyToHostTensor(tensor_feature.get());
|
||||||
|
|
||||||
|
//std::vector<int> feature_shape = tensor_feature->shape();
|
||||||
|
|
||||||
|
//float* face_feature = tensor_feature->host<float>();
|
||||||
|
|
||||||
|
memcpy(feature, tensor_feature->host<float>(), 512 * sizeof(float));
|
||||||
|
|
||||||
|
//feature.insert(feature.end(), face_feature, face_feature + 512);
|
||||||
|
//for (int i = 0; i < 512; i++) {
|
||||||
|
// feature.push_back(face_feature[i]);
|
||||||
|
//}
|
||||||
|
return RET_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::Mat MobileFaceFeatureMnn::estimateTrans(std::vector<cv::Point2f>& srcLmks, std::vector<cv::Point2f>& dstLmks)
|
||||||
|
{
|
||||||
|
cv::Mat A(2 * srcLmks.size(), 4, CV_32FC1);
|
||||||
|
cv::Mat B(2 * srcLmks.size(), 1, CV_32FC1);
|
||||||
|
for (int i = 0; i < srcLmks.size(); i++)
|
||||||
|
{
|
||||||
|
A.at<float>(2 * i, 0) = srcLmks[i].x;
|
||||||
|
A.at<float>(2 * i, 1) = -srcLmks[i].y;
|
||||||
|
A.at<float>(2 * i, 2) = 1;
|
||||||
|
A.at<float>(2 * i, 3) = 0;
|
||||||
|
B.at<float>(2 * i, 0) = dstLmks[i].x;
|
||||||
|
|
||||||
|
A.at<float>(2 * i + 1, 0) = srcLmks[i].y;
|
||||||
|
A.at<float>(2 * i + 1, 1) = srcLmks[i].x;
|
||||||
|
A.at<float>(2 * i + 1, 2) = 0;
|
||||||
|
A.at<float>(2 * i + 1, 3) = 1;
|
||||||
|
B.at<float>(2 * i + 1, 0) = dstLmks[i].y;
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::Mat C, X;
|
||||||
|
C = (A.t() * A);
|
||||||
|
X = C.inv() * A.t() * B;
|
||||||
|
cv::Mat transformMatrix(2, 3, CV_32FC1);
|
||||||
|
transformMatrix.at<float>(0, 0) = X.at<float>(0, 0);
|
||||||
|
transformMatrix.at<float>(0, 1) = -X.at<float>(1, 0);
|
||||||
|
transformMatrix.at<float>(0, 2) = X.at<float>(2, 0);
|
||||||
|
transformMatrix.at<float>(1, 0) = X.at<float>(1, 0);
|
||||||
|
transformMatrix.at<float>(1, 1) = X.at<float>(0, 0);
|
||||||
|
transformMatrix.at<float>(1, 2) = X.at<float>(3, 0);
|
||||||
|
return transformMatrix;
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::Mat MobileFaceFeatureMnn::FaceAlign(const cv::Mat& inputImage, const float* landmarks)
|
||||||
|
{
|
||||||
|
std::vector<cv::Point2f> templateLmks;
|
||||||
|
std::vector<cv::Point> salientLmks;
|
||||||
|
for (int l = 0; l < 5; l++) {
|
||||||
|
salientLmks.push_back(cv::Point(landmarks[l * 2], landmarks[l * 2 + 1]));
|
||||||
|
}
|
||||||
|
static double const template_points[] = { 30.2946, 65.5318, 48.0252, 33.5493, 62.7299, 51.6963, 51.5014, 71.7366, 92.3655, 92.2041 };
|
||||||
|
static double shift_x = 8.0;
|
||||||
|
static size_t n = sizeof(template_points) / (2 * sizeof(template_points[0]));
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
templateLmks.push_back(cv::Point2f(template_points[i] + shift_x, template_points[i + n]));
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::Mat normalizedImage;
|
||||||
|
cv::Mat noFace(0, 0, CV_8UC1);
|
||||||
|
|
||||||
|
cv::Point2f leftEye;
|
||||||
|
cv::Point2f rightEye;
|
||||||
|
|
||||||
|
leftEye = salientLmks.at(0);
|
||||||
|
rightEye = salientLmks.at(1);
|
||||||
|
|
||||||
|
cv::Point2f nose;
|
||||||
|
cv::Point2f leftMouth;
|
||||||
|
cv::Point2f rightMouth;
|
||||||
|
|
||||||
|
nose = salientLmks.at(2);
|
||||||
|
leftMouth = salientLmks.at(3);
|
||||||
|
rightMouth = salientLmks.at(4);
|
||||||
|
|
||||||
|
std::vector<cv::Point2f> srcLmks;
|
||||||
|
srcLmks.push_back(leftEye);
|
||||||
|
srcLmks.push_back(rightEye);
|
||||||
|
srcLmks.push_back(nose);
|
||||||
|
srcLmks.push_back(leftMouth);
|
||||||
|
srcLmks.push_back(rightMouth);
|
||||||
|
|
||||||
|
cv::Mat matTransform = estimateTrans(srcLmks, templateLmks);
|
||||||
|
|
||||||
|
if (matTransform.empty())
|
||||||
|
{
|
||||||
|
normalizedImage = noFace;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cv::warpAffine(inputImage, normalizedImage, matTransform, cv::Size(FEATURE_IMG_SIZE, FEATURE_IMG_SIZE));
|
||||||
|
}
|
||||||
|
return normalizedImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret_t MobileFaceFeatureMnn::MobileFaceFeatureCompare(const float* feature1, const float* feature2, float& fSimilarity) {
|
||||||
|
fSimilarity = 0.f;
|
||||||
|
|
||||||
|
if (feature1 == nullptr || feature2 == nullptr) {
|
||||||
|
return RET_INVALID_FEATURE;
|
||||||
|
}
|
||||||
|
float tmp0 = 0, tmp1 = 0, tmp2 = 0;
|
||||||
|
|
||||||
|
#pragma omp parallel for reduction(+:tmp0,tmp1,tmp2)
|
||||||
|
for (int i = 0; i < 512; i++) {
|
||||||
|
tmp0 += feature1[i] * feature2[i];
|
||||||
|
tmp1 += feature1[i] * feature1[i];
|
||||||
|
tmp2 += feature2[i] * feature2[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
float score = (float(tmp0 / (sqrt(tmp1) * sqrt(tmp2))));
|
||||||
|
|
||||||
|
// 拉高分数
|
||||||
|
float max_score = log10f(1.01);
|
||||||
|
float min_score = log10f(0.02);
|
||||||
|
float ret, temp;
|
||||||
|
if (score <= 0) {
|
||||||
|
temp = fabs(score);
|
||||||
|
if (temp > 0.3) {
|
||||||
|
ret = 0.0001;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ret = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
temp = log10f(score + 0.01);
|
||||||
|
fSimilarity = fabsf(temp - min_score) / (max_score - min_score);
|
||||||
|
}
|
||||||
|
return RET_OK;
|
||||||
|
}
|
||||||
38
src/MobileFaceFeatureMnn.h
Normal file
38
src/MobileFaceFeatureMnn.h
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#ifndef _MOBILEFACEFEATURE_MNN_H
|
||||||
|
#define _MOBILEFACEFEATURE_MNN_H
|
||||||
|
#include <opencv2/opencv.hpp>
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <MNN/Interpreter.hpp>
|
||||||
|
#include <MNN/MNNDefine.h>
|
||||||
|
#include <MNN/ImageProcess.hpp>
|
||||||
|
#include <MNN/Tensor.hpp>
|
||||||
|
#include "TypeInfo.h"
|
||||||
|
|
||||||
|
class MobileFaceFeatureMnn{
|
||||||
|
public:
|
||||||
|
static MobileFaceFeatureMnn* GetInstance();
|
||||||
|
|
||||||
|
ret_t GetFaceFeature(const cv::Mat& img, const float* landmarks, float* feature);
|
||||||
|
|
||||||
|
ret_t MobileFaceFeatureCompare(const float* feature1, const float* feature2, float& fSimilarity);
|
||||||
|
private:
|
||||||
|
MobileFaceFeatureMnn();
|
||||||
|
~MobileFaceFeatureMnn();
|
||||||
|
|
||||||
|
cv::Mat estimateTrans(std::vector<cv::Point2f>& srcLmks, std::vector<cv::Point2f>& dstLmks);
|
||||||
|
cv::Mat FaceAlign(const cv::Mat& inputImage, const float* landmarks);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static MobileFaceFeatureMnn* m_hInstance;
|
||||||
|
std::mutex m_mt;
|
||||||
|
|
||||||
|
std::shared_ptr<MNN::Interpreter> m_detector;
|
||||||
|
MNN::CV::ImageProcess::Config m_img_config;
|
||||||
|
MNN::Session* m_sess_mobileface;
|
||||||
|
MNN::Tensor* m_input_tensor;
|
||||||
|
MNN::Tensor* m_feature_tensor;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
6
src/MobileFaceModel_0.cpp
Normal file
6
src/MobileFaceModel_0.cpp
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
//v6.6.4
|
||||||
|
unsigned char mobile_face_model0[] = {
|
||||||
|
#include "MobileFace/mnn_v6.64/mobile_face_mnn_v6.64_p1.dat"
|
||||||
|
};
|
||||||
|
|
||||||
|
int mobile_face_model0_len = sizeof(mobile_face_model0) / sizeof(mobile_face_model0[0]);
|
||||||
7
src/MobileFaceModel_1.cpp
Normal file
7
src/MobileFaceModel_1.cpp
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
//v6.6.4
|
||||||
|
unsigned char mobile_face_model1[] = {
|
||||||
|
#include "MobileFace/mnn_v6.64/mobile_face_mnn_v6.64_p2.dat"
|
||||||
|
};
|
||||||
|
|
||||||
|
int mobile_face_model1_len = sizeof(mobile_face_model1) / sizeof(mobile_face_model1[0]);
|
||||||
8
src/MobileFaceModel_2.cpp
Normal file
8
src/MobileFaceModel_2.cpp
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
//v6.6.4
|
||||||
|
unsigned char mobile_face_model2[] = {
|
||||||
|
#include "MobileFace/mnn_v6.64/mobile_face_mnn_v6.64_p3.dat"
|
||||||
|
};
|
||||||
|
|
||||||
|
int mobile_face_model2_len = sizeof(mobile_face_model2) / sizeof(mobile_face_model2[0]);
|
||||||
|
|
||||||
@@ -1 +1,4 @@
|
|||||||
|
#include <math.h>
|
||||||
#include "TypeInfo.h"
|
#include "TypeInfo.h"
|
||||||
|
// #include "MobileFaceFeatureMnn.h"
|
||||||
|
|
||||||
|
|||||||
@@ -12,21 +12,19 @@ typedef enum _ret_t {
|
|||||||
// #define ERR_FACE_RECT -10 //人脸框无效
|
// #define ERR_FACE_RECT -10 //人脸框无效
|
||||||
// #define ERR_INDEX_INVALID -20 //索引无效
|
// #define ERR_INDEX_INVALID -20 //索引无效
|
||||||
|
|
||||||
struct FaceInfo {
|
#define FACE_BOX_WIDTH(info) static_cast<int>(info.x2 - info.x1)
|
||||||
float x1; //人脸框 左上x坐标
|
#define FACE_BOX_HEIGHT(info) static_cast<int>(info.y2 - info.y1)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class FaceInfo {
|
||||||
|
public:
|
||||||
|
float x1; //人脸框 左上x坐标
|
||||||
float y1; //人脸框 左上y坐标
|
float y1; //人脸框 左上y坐标
|
||||||
float x2; //人脸框 右下x坐标
|
float x2; //人脸框 右下x坐标
|
||||||
float y2; //人脸框 右下y坐标
|
float y2; //人脸框 右下y坐标
|
||||||
float score; //人脸框 置信度
|
float score; //人脸框 置信度
|
||||||
float landmarks[10]; //人脸框 关键点坐标
|
float landmarks[10]; //人脸框 关键点坐标
|
||||||
|
|
||||||
int GetWidth() {
|
|
||||||
return static_cast<int>(x2 - x1);
|
|
||||||
}
|
|
||||||
|
|
||||||
int GetHeight() {
|
|
||||||
return static_cast<int>(y2 - y1);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
Reference in New Issue
Block a user