深度學習之用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();};
}
#endif //MNN_CORE_MNN_HANDLER_H
// 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 如何在不同環境下,保證活體的準確率?
這都是在這個工作中需要重視的,而且這項工作并不會因為有了部署成功就能成功,而是需要不斷改善。如果有好的方法和建議,煩請留言告知,我們一起討論!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/80004.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/80004.shtml
英文地址,請注明出處:http://en.pswp.cn/web/80004.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

JVM學習專題(一)類加載器與雙親委派

目錄 1、JVM加載運行全過程梳理 2、JVM Hotspot底層 3、war包、jar包如何加載 4、類加載器 我們來查看一下getLauncher&#xff1a; 1.我們先查看getExtClassLoader() 2、再來看看getAppClassLoader(extcl) 5、雙親委派機制 1.職責明確&#xff0c;路徑隔離?&#xff…

部署安裝gitlab-ce-17.9.7-ce.0.el8.x86_64.rpm

目錄 ?編輯 實驗環境 所需軟件 實驗開始 安裝部署gitlab171.配置清華源倉庫&#xff08;版本高的系統無需做&#xff09;vim /etc/yum.repos.d/gitlab-ce.repo 2.提前下載包dnf localinstall gitlab-ce-17.9.7-ce.0.el8.x86_64.rpm --rocklinux 3.修改配…

使用LoRA微調Qwen2.5-VL-7B-Instruct完成電氣主接線圖識別

使用LoRA微調Qwen2.5-VL-7B-Instruct完成電氣主接線圖識別 動機 任務適配需求 Qwen2.5-VL在視覺理解方面表現優異&#xff0c;但電氣主接線圖識別需要特定領域的結構化輸出能力&#xff08;如設備參數提取、拓撲關系解析&#xff09;。微調可增強模型對專業符號&#xff08;如…

系統集成項目管理工程師學習筆記

第九章 項目管理概論 1、項目基本要素 項目基礎 項目是為創造獨特的產品、服務或成果而進行的臨時性工作。 項目具有臨時性、獨特性、漸進明細的特點。項目的“臨時性”是指項目只有明確的起點和終點。“臨時性”并一定意味著項目的持續時間短。 項目可宣告結束的情況&…

Secs/Gem第七講(基于secs4net項目的ChatGpt介紹)

好的&#xff0c;那我們現在進入&#xff1a; 第七講&#xff1a;掉電重連后&#xff0c;為什么設備不再上報事件&#xff1f;——持久化與自動恢復的系統設計 關鍵詞&#xff1a;掉電恢復、狀態重建、初始化流程、SecsMessage 緩存機制、自動重連、事件再注冊 本講目標 你將理…

室內定位:熱門研究方向與未解難題深度解析

I. 引言:對普適性室內定位的持續探索 A. 室內定位在現代應用中的重要性 室內定位系統(IPS)正迅速成為眾多應用領域的基石技術,其重要性源于現代社會人們約70%至90%的時間在室內度過的事實 1。這些應用橫跨多個行業,包括應急響應 1、智能建筑與智慧城市 6、醫療健康(如病…

Android學習總結之Glide自定義三級緩存(實戰篇)

一、為什么需要三級緩存 內存緩存&#xff08;Memory Cache&#xff09; 內存緩存旨在快速顯示剛瀏覽過的圖片&#xff0c;例如在滑動列表時來回切換的圖片。在 Glide 中&#xff0c;內存緩存使用 LruCache 算法&#xff08;最近最少使用&#xff09;&#xff0c;能自動清理長…

Linux的文件查找與壓縮

查找文件 find命令 # 命令&#xff1a;find 路徑范圍 選項1 選項1的值 \[選項2 選項2 的值…]# 作用&#xff1a;用于查找文檔&#xff08;其選項有55 個之多&#xff09;# 選項&#xff1a;# -name&#xff1a;按照文檔名稱進行搜索&#xff08;支持模糊搜索&#xff0c;\* &…

python處理異常,JSON

異常處理 #異常處理 # 在連接MySQL數據庫的過程中&#xff0c;如果不能有效地處理異常&#xff0c;則異常信息過于復雜&#xff0c;對用戶不友好&#xff0c;暴露過多的敏感信息 # 所以&#xff0c;在真實的生產環境中&#xff0c; 程序必須有效地處理和控制異常&#xff0c;按…

線程的兩種實現方式

線程的兩種實現方式——內核支持線程&#xff08;kernal Supported Thread, KST&#xff09;&#xff0c; 用戶級線程&#xff08;User Level Thread, ULT&#xff09; 1. 內核支持線程 顧名思義&#xff0c;內核支持線程即為在內核支持下的那些線程&#xff0c;它們的創建&am…

vue3基礎學習(上) [簡單標簽] (vscode)

目錄 1. Vue簡介 2. 創建Vue應用 2.1 下載JS文件 2.2 引用JS文件 2.3 調用Vue方法?編輯 2.4 運行一下試試: 2.5 代碼如下 3.模塊化開發模式 3.1 Live Server插件 3.2 運行 4. 常用的標簽 4.1 reactive 4.1.1 運行結果 4.1.2 代碼: 4.2 ref 4.2.1 運行結果 4.2.2…

自定義分區器-基礎

什么是分區 在 Spark 里&#xff0c;彈性分布式數據集&#xff08;RDD&#xff09;是核心的數據抽象&#xff0c;它是不可變的、可分區的、里面的元素并行計算的集合。 在 Spark 中&#xff0c;分區是指將數據集按照一定的規則劃分成多個較小的子集&#xff0c;每個子集可以獨立…

深入解析HTTP協議演進:從1.0到3.0的全面對比

HTTP協議作為互聯網的基礎協議&#xff0c;經歷了多個版本的迭代演進。本文將詳細解析HTTP 1.0、HTTP 1.1、HTTP/2和HTTP/3的核心特性與區別&#xff0c;幫助開發者深入理解網絡協議的發展脈絡。 一、HTTP 1.0&#xff1a;互聯網的奠基者 核心特點&#xff1a; 短連接模式&am…

基于windows環境Oracle主備切換之后OGG同步進程恢復

基于windows環境Oracle主備切換之后OGG同步進程恢復 場景&#xff1a;db1是主庫&#xff0c;db2是備庫&#xff0c;ogg從db2備庫抽取數據同步到目標數據庫 db1 - db2(ADG) – ogg – targetdb 場景&#xff1a;db2是主庫&#xff0c;db1是備庫&#xff0c;ogg從db1備庫抽取數…

微服務,服務粒度多少合適

項目服務化好處 復用性&#xff0c;消除代碼拷貝專注性&#xff0c;防止復雜性擴散解耦合&#xff0c;消除公共庫耦合高質量&#xff0c;SQL穩定性有保障易擴展&#xff0c;消除數據庫解耦合高效率&#xff0c;調用方研發效率提升 微服務拆分實現策略 統一服務層一個子業務一…

【工奧閥門科技有限公司】簽約智橙PLM

近日&#xff0c;工奧閥門科技有限公司正式簽約了智橙泵閥行業版PLM。 忠于質量&#xff0c;臻于服務&#xff0c;精于研發 工奧閥門科技有限公司&#xff08;以下簡稱工奧閥門&#xff09;坐落于浙江永嘉&#xff0c;是一家集設計、開發、生產、銷售、安裝、服務為一體的閥門…

2025-5-15Vue3快速上手

1、setup和選項式API之間的關系 (1)vue2中的data,methods可以與vue3的setup共存 &#xff08;2&#xff09;vue2中的data可以用this讀取setup中的數據&#xff0c;但是反過來不行&#xff0c;因為setup中的this是undefined &#xff08;3&#xff09;不建議vue2和vue3的語法混用…

基于智能推薦的就業平臺的設計與實現(招聘系統)(SpringBoot Thymeleaf)+文檔

&#x1f497;博主介紹&#x1f497;&#xff1a;?在職Java研發工程師、專注于程序設計、源碼分享、技術交流、專注于Java技術領域和畢業設計? 溫馨提示&#xff1a;文末有 CSDN 平臺官方提供的老師 Wechat / QQ 名片 :) Java精品實戰案例《700套》 2025最新畢業設計選題推薦…

什么是路由器環回接口?

路由器環回接口&#xff08;LoopbackInterface&#xff09;是網絡設備中的一種邏輯虛擬接口&#xff0c;不依賴物理硬件&#xff0c;但在網絡配置和管理中具有重要作用。以下是其核心要點&#xff1a; 一、基本特性 1.虛擬性與穩定性 環回接口是純軟件實現的邏輯接口&#x…

HOT100 (滑動窗口子串普通數組矩陣)

先填坑 滑動窗口 3. 無重復字符的最長子串 給定一個字符串 s ,請你找出其中不含有重復字符的最長子串的長度。 思路:用一個uset容器存放當前滑動窗口中的元素 #include <bits/stdc++.h> using namespace std; class Solution {public:int lengthOfLongestSubstring(st…