深度学习之用CelebA_Spoof数据集搭建一个活体检测-训练好的模型用MNN来推理
一、模型转换准备
首先确保已完成PyTorch到ONNX的转换:深度学习之用CelebA_Spoof数据集搭建活体检测系统:模型验证与测试。这里有将PyTorch到ONNX格式的模型转换。
二、ONNX转MNN
使用MNN转换工具进行格式转换:具体的编译过程可以参考MNN的官方代码。MNN是一个轻量级的深度神经网络引擎,支持深度学习的推理与训练。适用于服务器/个人电脑/手机/嵌入式各类设备。
./MNNConvert -f ONNX --modelFile live_spoof.onnx --MNNModel live_spoof.mnn
三、C++推理工程搭建
工程结构
mnn_inference/
├── CMakeLists.txt
├── include/
│ ├── InferenceInit.h
│ └── LiveSpoofDetector.h
├── src/
│ ├── InferenceInit.cpp
│ ├── LiveSpoofDetector.cpp
│ └── CMakeLists.txt
└── third_party/MNN/
根目录下的 CMakeLists.txt
cmake_minimum_required(VERSION 3.12)
project(MNNInference)# 设置C++标准
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)# 查找OpenCV
find_package(OpenCV REQUIRED)# 包含第三方库MNN
set(MNN_DIR ${CMAKE_SOURCE_DIR}/third_party/MNN)
include_directories(${MNN_DIR}/include)# 添加子目录
add_subdirectory(src)# 主可执行文件
add_executable(mnn_inference_main src/main.cpp
)# 链接库
target_link_libraries(mnn_inference_mainPRIVATEinference_lib${MNN_DIR}/lib/libMNN.so${OpenCV_LIBS}
)# 安装规则
install(TARGETS mnn_inference_mainRUNTIME DESTINATION bin
)
src目录下的 CMakeLists.txt
# 添加库
add_library(inference_lib STATICInferenceInit.cppLiveSpoofDetector.cpp
)# 包含目录
target_include_directories(inference_libPUBLIC${CMAKE_SOURCE_DIR}/include${MNN_DIR}/include${OpenCV_INCLUDE_DIRS}
)# 编译选项
target_compile_options(inference_libPRIVATE-Wall-O3
)
核心实现代码
将MNN读取导入模型和一些mnn_session进行预处理的公共部分抽取出来,以后可以更换不同的模型,只需要给出特定的预处理。
// InferenceInit.h
#ifndef MNN_CORE_MNN_HANDLER_H
#define MNN_CORE_MNN_HANDLER_H
#include "MNN/Interpreter.hpp"
#include "MNN/MNNDefine.h"
#include "MNN/Tensor.hpp"
#include "MNN/ImageProcess.hpp"#include <iostream>
#include "opencv2/opencv.hpp"
#endif
#include "mylog.h"
#define LITEMNN_DEBUG
namespace mnncore
{class BasicMNNHandler{protected:std::shared_ptr<MNN::Interpreter> mnn_interpreter;MNN::Session *mnn_session = nullptr;MNN::Tensor *input_tensor = nullptr; // assume single input.MNN::ScheduleConfig schedule_config;std::shared_ptr<MNN::CV::ImageProcess> pretreat; // init at subclassconst char *log_id = nullptr;const char *mnn_path = nullptr;const char *mnn_model_data = nullptr;//int mnn_model_size = 0;protected:const int num_threads; // initialize at runtime.int input_batch;int input_channel;int input_height;int input_width;int dimension_type;int num_outputs = 1;protected:explicit BasicMNNHandler(const std::string &_mnn_path, int _num_threads = 1);int initialize_handler();std::string turnHeadDataToString(std::string headData);virtual ~BasicMNNHandler();// un-copyableprotected:BasicMNNHandler(const BasicMNNHandler &) = delete; //BasicMNNHandler(BasicMNNHandler &&) = delete; //BasicMNNHandler &operator=(const BasicMNNHandler &) = delete; //BasicMNNHandler &operator=(BasicMNNHandler &&) = delete; //protected:virtual void transform(const cv::Mat &mat) = 0; // ? needed ?private:void print_debug_string();};
}
// InferenceInit.cpp
#include "mnn/core/InferenceInit.h"
namespace mnncore
{
BasicMNNHandler::BasicMNNHandler(const std::string &_mnn_path, int _num_threads) :log_id(_mnn_path.data()), mnn_path(_mnn_path.data()),num_threads(_num_threads)
{//initialize_handler();
}int BasicMNNHandler::initialize_handler()
{std::cout<<"load Model from file: " << mnn_path << "\n";mnn_interpreter = std::shared_ptr<MNN::Interpreter>(MNN::Interpreter::createFromFile(mnn_path));myLog(ERROR_, "mnn_interpreter createFromFile done!");if (nullptr == mnn_interpreter) {std::cout << "load centerface failed." << std::endl;return -1;}// 2. init schedule_configschedule_config.numThread = (int) num_threads;MNN::BackendConfig backend_config;backend_config.precision = MNN::BackendConfig::Precision_Low; // default Precision_Highbackend_config.memory = MNN::BackendConfig::Memory_Low;backend_config.power = MNN::BackendConfig::Power_Low;schedule_config.backendConfig = &backend_config;// 3. create sessionmyLog(ERROR_, "createSession...");mnn_session = mnn_interpreter->createSession(schedule_config);// 4. init input tensormyLog(ERROR_, "getSessionInput...");input_tensor = mnn_interpreter->getSessionInput(mnn_session, nullptr);// 5. init input dimsinput_batch = input_tensor->batch();input_channel = input_tensor->channel();input_height = input_tensor->height();input_width = input_tensor->width();dimension_type = input_tensor->getDimensionType();myLog(ERROR_, "input_batch: %d, input_channel: %d, input_height: %d, input_width: %d, dimension_type: %d", input_batch, input_channel, input_height, input_width, dimension_type);// 6. resize tensor & session needed ???if (dimension_type == MNN::Tensor::CAFFE){// NCHWmnn_interpreter->resizeTensor(input_tensor, {input_batch, input_channel, input_height, input_width});mnn_interpreter->resizeSession(mnn_session);} // NHWCelse if (dimension_type == MNN::Tensor::TENSORFLOW){mnn_interpreter->resizeTensor(input_tensor, {input_batch, input_height, input_width, input_channel});mnn_interpreter->resizeSession(mnn_session);} // NC4HW4else if (dimension_type == MNN::Tensor::CAFFE_C4){
#ifdef LITEMNN_DEBUGstd::cout << "Dimension Type is CAFFE_C4, skip resizeTensor & resizeSession!\n";
#endif}// output countnum_outputs = (int)mnn_interpreter->getSessionOutputAll(mnn_session).size();
#ifdef LITEMNN_DEBUGthis->print_debug_string();
#endifreturn 0;
}BasicMNNHandler::~BasicMNNHandler()
{mnn_interpreter->releaseModel();if (mnn_session)mnn_interpreter->releaseSession(mnn_session);
}
void BasicMNNHandler::print_debug_string()
{std::cout << "LITEMNN_DEBUG LogId: " << log_id << "\n";std::cout << "=============== Input-Dims ==============\n";if (input_tensor) input_tensor->printShape();if (dimension_type == MNN::Tensor::CAFFE)std::cout << "Dimension Type: (CAFFE/PyTorch/ONNX)NCHW" << "\n";else if (dimension_type == MNN::Tensor::TENSORFLOW)std::cout << "Dimension Type: (TENSORFLOW)NHWC" << "\n";else if (dimension_type == MNN::Tensor::CAFFE_C4)std::cout << "Dimension Type: (CAFFE_C4)NC4HW4" << "\n";std::cout << "=============== Output-Dims ==============\n";auto tmp_output_map = mnn_interpreter->getSessionOutputAll(mnn_session);std::cout << "getSessionOutputAll done!\n";for (auto it = tmp_output_map.cbegin(); it != tmp_output_map.cend(); ++it){std::cout << "Output: " << it->first << ": ";it->second->printShape();}std::cout << "========================================\n";
}
} // namespace mnncore
主要的推理处理代码:
头文件声明
//LiveSpoofDetector.h
#include "mnn/core/InferenceInit.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include<iterator>
#include <algorithm>#define RESIZE_LIVE_SPOOF_SIZE 112
using namespace mnncore;
namespace mnncv {class SqueezeNetLiveSpoof : public BasicMNNHandler{public:explicit SqueezeNetLiveSpoof(const std::string &model_path, int numThread = 1);~SqueezeNetLiveSpoof() override = default;// 保留原有函数int Init(const char* model_path);float detect_handler(const unsigned char* pData, int width, int height, int nchannel, int mod);cv::Mat m_image;private:void initialize_pretreat();void transform(const cv::Mat &mat) override;std::vector<cv::Point2f> coord5points;const float meanVals_[3] = { 103.94f, 116.78f, 123.68f};const float normVals_[3] = {0.017f, 0.017f, 0.017f};};
}
函数定义:
// LiveSpoofDetector.cpp
#include "include/mnn/cv/RGB/LiveSpoofDetector.h"
#include <opencv2/opencv.hpp>using namespace mnncv;SqueezeNetLiveSpoof::SqueezeNetLiveSpoof(const std::string &model_path, int numThread) : BasicMNNHandler(model_path, numThread) {initialize_pretreat();
}int SqueezeNetLiveSpoof::Init(const char* model_path) {std::string model_path_str = model_path;int FileLoadFlag = initialize_handler(model_path_str, 0);if (FileLoadFlag >= 0 ){return 0;}return FileLoadFlag;
}
template<typename T> std::vector<float> softmax(const T *logits, unsigned int _size, unsigned int &max_id)
{//types::__assert_type<T>();if (_size == 0 || logits == nullptr) return{};float max_prob = 0.f, total_exp = 0.f;std::vector<float> softmax_probs(_size);for (unsigned int i = 0; i < _size; ++i){softmax_probs[i] = std::exp((float)logits[i]);total_exp += softmax_probs[i];}for (unsigned int i = 0; i < _size; ++i){softmax_probs[i] = softmax_probs[i] / total_exp;if (softmax_probs[i] > max_prob){max_id = i;max_prob = softmax_probs[i];}}return softmax_probs;
}
float SqueezeNetLiveSpoof::detect_handler(const unsigned char* pData, int width, int height, int nchannel, int mod)
{if (!pData || width <= 0 || height <= 0) return 0.0f;try {// 1. 将输入数据转换为OpenCV Matcv::Mat input_mat(height, width, nchannel == 3 ? CV_8UC3 : CV_8UC1, (void*)pData);if (nchannel == 1) {cv::cvtColor(input_mat, input_mat, cv::COLOR_GRAY2BGR);}// 2. 预处理图像this->transform(input_mat);// 3. 运行推理mnn_interpreter->runSession(mnn_session);// 4. 获取输出auto output_tensor = mnn_interpreter->getSessionOutput(mnn_session, nullptr);MNN::Tensor host_tensor(output_tensor, output_tensor->getDimensionType());output_tensor->copyToHostTensor(&host_tensor);auto embedding_dims = host_tensor.shape(); // (1,128)const unsigned int hidden_dim = embedding_dims.at(1);const float* embedding_values = host_tensor.host<float>();unsigned int pred_live = 0;auto softmax_probs = softmax<float>(embedding_values, hidden_dim, pred_live);//std::cout << "softmax_probs: " << softmax_probs[0]<<" "<<softmax_probs[1] << std::endl;float live_score = softmax_probs[0]; // 取真脸概率作为活体分数std::cout << "live_score: " << live_score<< " spoof_score:"<< softmax_probs[1]<< std::endl;return live_score;} catch (const std::exception& e) {std::cerr << "detect_handler exception: " << e.what() << std::endl;return 0.0f;}
}
void SqueezeNetLiveSpoof::initialize_pretreat() {// 初始化预处理参数MNN::CV::Matrix trans;trans.setScale(1.0f, 1.0f);MNN::CV::ImageProcess::Config img_config;img_config.filterType = MNN::CV::BICUBIC;::memcpy(img_config.mean, meanVals_, sizeof(meanVals_));::memcpy(img_config.normal, normVals_, sizeof(normVals_));img_config.sourceFormat = MNN::CV::BGR;img_config.destFormat = MNN::CV::RGB;pretreat = std::shared_ptr<MNN::CV::ImageProcess>(MNN::CV::ImageProcess::create(img_config));pretreat->setMatrix(trans);
}
void SqueezeNetLiveSpoof::transform(const cv::Mat &mat){cv::Mat mat_rs;cv::resize(mat, mat_rs, cv::Size(input_width, input_height));pretreat->convert(mat_rs.data, input_width, input_height, mat_rs.step[0], input_tensor);
}
四、结果展示
在返回的分类结果中,我们用0.8作为阈值对活体分数进行过滤,得到的结果如下:
五、留下来的问题
一个从数据整理到最后的MNN推理的2D活体检测的工作简单的完结了,这个系列的内容主要目的是讲诉一个模型如何从设计到部署的全过程,过程中的有些描述和个人理解并不一定正确,如果有其他理解或者错处指出,请严重指出。
深度学习之用CelebA_Spoof数据集搭建一个活体检测-数据处理
深度学习之用CelebA_Spoof数据集搭建一个活体检测-模型搭建和训练
深度学习之用CelebA_Spoof数据集搭建一个活体检测-模型验证与测试
深度学习之用CelebA_Spoof数据集搭建一个活体检测-一些模型训练中的改动带来的改善
那么这个系列完结,留下什么问题:
1 2D活体检测有没有更好的方法?
2 训练的过程如何更好更快的调参以及收敛,以及如何寻找更好的特征?
3 在实际使用过程中,怎样提高功能的体验感,至于那些判断错误的,该如何进行处理?
4 如何在不同环境下,保证活体的准确率?
这都是在这个工作中需要重视的,而且这项工作并不会因为有了部署成功就能成功,而是需要不断改善。如果有好的方法和建议,烦请留言告知,我们一起讨论!
相关文章:

深度学习之用CelebA_Spoof数据集搭建一个活体检测-训练好的模型用MNN来推理
一、模型转换准备 首先确保已完成PyTorch到ONNX的转换:深度学习之用CelebA_Spoof数据集搭建活体检测系统:模型验证与测试。这里有将PyTorch到ONNX格式的模型转换。 二、ONNX转MNN 使用MNN转换工具进行格式转换:具体的编译过程可以参考MNN的…...
【Java】泛型在 Java 中是怎样实现的?
先说结论 , Java 的泛型是伪泛型 , 在运行期间不存在泛型的概念 , 泛型在 Java 中是 编译检查 运行强转 实现的 泛型是指 允许在定义类 , 接口和方法时使用的类型参数 , 使得代码可以在不指定具体类型的情况下操作不同的数据类型 , 从而实现类型安全的代码复用 的语言机制 . …...

开源安全大模型Foundation-Sec-8B实操
一、兴奋时刻 此时此刻,晚上22点55分,从今天早上6点左右开始折腾,花费了接近10刀的环境使用费,1天的休息时间,总算是把Foundation-Sec-8B模型跑起来了,中间有两次胜利就在眼前,但却总在远程端口转发环节出问题,让人难受。直到晚上远程Jupyter访问成功那一刻,眉开眼笑,…...

【JavaWeb】MySQL
1 引言 1.1 为什么学? 在学习SpringBootWeb基础知识(IOC、DI等)时,在web开发中,为了应用程序职责单一,方便维护,一般将web应用程序分为三层,即:Controller、Service、Dao 。 之前的案例中&am…...

微信小游戏流量主广告自动化浏览功能案例5
功能需求: 支持APP单行文本框输入1个小程序链接,在“文件传输助手”界面发送小程序链接并进入。 主要有“文章列表首页”和“文章内容”页面。每个页面支持点击弹窗广告、槽位广告、视频广告入口、视频广告内第三方广告。 弹窗广告、槽位广告、视频广…...
【C++ Primer 学习札记】函数传参问题
参考博文: https://blog.csdn.net/weixin_40026739/article/details/121582395 什么是形参(parameter),什么是实参(argument) 1. 形参 在函数定义中出现的参数可以看做是一个占位符,它没有数据…...

软件的技术架构、应用架构、业务架构、数据架构、部署架构
一、各架构定义 1. 技术架构(Technical Architecture) 定义:技术架构关注的是支撑系统运行的底层技术基础设施和软件平台,包括硬件、操作系统、中间件、编程语言、框架、数据库管理系统等技术组件的选择和组合方式。它描述了系统…...

CSS 文字样式全解析:从基础排版到视觉层次设计
CSS 文字样式目录 一、字体家族(font-family) 二、字体大小(font-size) 三、字体粗细(font-weight) 四、字体样式(font-style) 五、文本转换(text-transform…...

【高德开放平台-注册安全分析报告】
前言 由于网站注册入口容易被黑客攻击,存在如下安全问题: 暴力破解密码,造成用户信息泄露短信盗刷的安全问题,影响业务及导致用户投诉带来经济损失,尤其是后付费客户,风险巨大,造成亏损无底洞…...
[特殊字符] React Fiber架构与Vue设计哲学撕逼实录
1. React这逼为什么搞Fiber? 他妈的DOM树太深:16版本前递归遍历组件树就像便秘,卡得页面直接阳痿调度器不给力:老子要打断渲染过程搞优先级调度,旧架构跟智障一样只会死循环增量渲染需求:Fiber链表结构让老…...

RabbitMQ的简介
三个概念 生产者:生产消息的服务消息代理:消息中间件,如RabbitMQ消费者:获取使用消息的服务 消息队列到达消费者的两种形式 队列(queue):点对点消息通信(point-to-point) 消息进入队…...
混合学习:Bagging与Boosting的深度解析与实践指南
引言 在机器学习的世界里,模型的性能优化一直是研究的核心问题。无论是分类任务还是回归任务,我们都希望模型能够在新的数据上表现出色,即具有良好的泛化能力。然而,实际应用中常常遇到模型过拟合(高方差)…...

使用Gemini, LangChain, Gradio打造一个书籍推荐系统 (第一部分)
第一部分:数据处理 import kagglehub# Download latest version path kagglehub.dataset_download("dylanjcastillo/7k-books-with-metadata")print("Path to dataset files:", path)自动下载该数据集的 最新版本 并返回本地保存的路径 impo…...

大语言模型 16 - Manus 超强智能体 Prompt分析 原理分析 包含工具列表分析
写在前面 Manus 是由中国初创公司 Monica.im 于 2025 年 3 月推出的全球首款通用型 AI 智能体(AI Agent),旨在实现“知行合一”,即不仅具备强大的语言理解和推理能力,还能自主执行复杂任务,直接交付完整成…...
物联网赋能7×24H无人值守共享自习室系统设计与实践!
随着"全民学习"浪潮的兴起,共享自习室市场也欣欣向荣,今天就带大家了解下在物联网的加持下,无人共享自习室系统的设计与实际方法。 一、物联网系统整体架构 1.1 系统分层设计 层级技术组成核心功能用户端微信小程序/H5预约选座、…...

以太联Intellinet带您深度解析PoE交换机的上行链路端口(Uplink Ports)
在当今网络技术日新月异的时代,以太网供电(PoE)交换机已然成为现代网络连接解决方案中不可或缺的“利器”。它不仅能够出色地完成数据传输任务,还能为所连接的设备提供电力支持,彻底摆脱了单独电源适配器的束缚,让网络部署更加简洁…...
浏览器播放 WebRTC 视频流
源码(vue) <template><video ref"videoElement" class"video" autoplay muted playsinline></video> </template><script setup lang"ts">import { onBeforeUnmount, onMounted, ref } fr…...
从零开始:使用 PyTorch 构建深度学习网络
从零开始:使用 PyTorch 构建深度学习网络 目录 PyTorch 简介环境配置PyTorch 基础构建神经网络训练模型评估与测试案例实战:手写数字识别进阶技巧常见问题解答 PyTorch 简介 PyTorch 是一个开源的深度学习框架,由 Facebook(现…...

分类算法 Kmeans、KNN、Meanshift 实战
任务 1、采用 Kmeans 算法实现 2D 数据自动聚类,预测 V180,V260 数据类别; 2、计算预测准确率,完成结果矫正 3、采用 KNN、Meanshift 算法,重复步骤 1-2 代码工具:jupyter notebook 视频资料 无监督学习ÿ…...
【razor】回环结构导致的控制信令错位:例如发送端收到 SR的问题
一、razor的echo程序 根据对 yuanrongxi/razor 仓库的代码和 echo 测试程序相关实现的分析,下面详细解读 echo 程序中 RTCP sender report(SR)、receiver report(RR)回显的问题及项目的解决方式。 1. 问题背景 在 RTP/RTCP 体系下,SR(Sender Report)由发送端周期性发…...

网络安全之身份验证绕过漏洞
漏洞简介 CrushFTP 是一款由 CrushFTP LLC 开发的强大文件传输服务器软件,支持FTP、SFTP、HTTP、WebDAV等多种协议,为企业和个人用户提供安全文件传输服务。近期,一个被编号为CVE-2025-2825的严重安全漏洞被发现,该漏洞影响版本1…...

MySQL 主从复制搭建全流程:基于 Docker 与 Harbor 仓库
一、引言 在数据库管理中,MySQL 主从复制是一种非常重要的技术,它可以实现数据的备份、读写分离,减轻主数据库的压力。本文将详细介绍如何使用 Docker 和 Harbor 仓库来搭建 MySQL 主从复制环境,适合刚接触数据库和 Docker 的新手…...
vscode打开vue + element项目
好嘞,我帮你详细整理一个用 VS Code 来可视化开发 Vue Element UI 的完整步骤,让你能舒服地写代码、预览界面、调试和管理项目。 用 VS Code 可视化开发 Vue Element UI 全流程指南 一、准备工作 安装 VS Code 官网下载安装:https://code…...

Django框架的前端部分使用Ajax请求一
Ajax请求 目录 1.ajax请求使用 2.增加任务列表功能(只有查看和新增) 3.代码展示集合 这篇文章, 要开始讲关于ajax请求的内容了。这个和以前文章中写道的Vue框架里面的axios请求, 很相似。后端代码, 会有一些细节点, 跟前几节文章写的有些区别。 一、ajax请求使用 我们先…...

cmd如何从C盘默认路径切换到D盘某指定目录
以从C盘cmd打开后的默认目录切换到目录"D:\Program Files\MySQL\MySQL Server 8.0\bin\mysqld"为例 打开cmd 首先点击开始键,搜索cms,右键以管理员身份运行打开管理员端的命令行提示符 1、首先要先切换到D盘 直接输入D:然后回车就可以&…...
693SJBH基于.NET的题库管理系统
计算机与信息学院 本科毕业论文(设计)开题报告 论文中文题目 基于asp.net的题库管理系统设计与实现 论文英文题目 Asp.net based database management system design and Implementation 学生姓名 专业班级 XXXXXX专业08 班 ⒈选题的背景和意…...
[Vue]跨组件传值
父子组件传值 详情可以看文章 跨组件传值 Vue 的核⼼是单向数据流。所以在父子组件间传值的时候,数据通常是通过属性从⽗组件向⼦组件,⽽⼦组件通过事件将数据传递回⽗组件。多层嵌套场景⼀般使⽤链式传递的⽅式实现provideinject的⽅式适⽤于需要跨层级…...

每日Prompt:实物与手绘涂鸦创意广告
提示词 一则简约且富有创意的广告,设置在纯白背景上。 一个真实的 [真实物体] 与手绘黑色墨水涂鸦相结合,线条松散而俏皮。涂鸦描绘了:[涂鸦概念及交互:以巧妙、富有想象力的方式与物体互动]。在顶部或中部加入粗体黑色 [广告文案…...

学习笔记:黑马程序员JavaWeb开发教程(2025.4.8)
12.11 登录校验-Filter-详解(过滤器链) 过滤器链及其执行顺序,一个Filter一个过滤器链,类名排名越靠前(按照ABC这样的顺序),就先执行谁 12.12 登录校验-Filter-登录校验过滤器 获取请求参数&…...
vue3 在线播放语音 mp3
播放、暂停、停止 <template><div><button click"togglePlay">{{ isPlaying ? "暂停" : "播放" }}</button><button click"stopAudio">停止</button><p>播放进度:{{ Math.rou…...