概述
本文將詳細介紹如何在Linux環境下部署MTCNN模型進行人臉檢測,并使用NCNN框架進行推理。
1. CMake的安裝與配置
下載CMake源碼
前往CMake官網下載,找到適合您系統的最新版本tar.gz文件鏈接,或者直接通過wget下載:CMake官方下載頁面https://cmake.org/download/
cd ~
wget https://github.com/Kitware/CMake/releases/download/v3.x.x/cmake-3.x.x.tar.gz
請將3.x.x
替換為您想要安裝的具體版本號。
解壓并進入解壓后的目錄
tar -xzvf cmake-3.x.x.tar.gz
cd cmake-3.x.x
編譯與安裝
-
配置編譯選項:使用bootstrap腳本進行配置:
./bootstrap
-
編譯:使用所有可用的核心進行并行編譯:
make -j$(nproc)
-
安裝:將CMake安裝到系統中:
sudo make install
-
刷新共享庫緩存(如果需要):
sudo ldconfig
安裝完成后,再次運行以下命令驗證安裝是否成功:
cmake --version
配置環境變量(可選)
如果您選擇自定義安裝路徑(例如/usr/local/bin
以外的路徑),可能需要手動配置環境變量以確保系統能夠找到新安裝的CMake。
編輯~/.bashrc
或~/.zshrc
文件(取決于您使用的shell),添加以下行:
export PATH=/path/to/cmake/bin:$PATH
其中/path/to/cmake/bin
是您指定的CMake安裝路徑下的bin
目錄。
保存文件后,運行以下命令使更改生效:
source ~/.bashrc # 對于Bash用戶
# 或者
source ~/.zshrc # 對于Zsh用戶
2. Protobuf的安裝
更新系統軟件包
首先,更新您的系統軟件包列表,確保所有現有的包都是最新的:
sudo apt-get update
sudo apt-get upgrade
安裝依賴項
安裝構建Protobuf所需的各種工具和庫:
sudo apt-get install autoconf automake libtool curl make g++ unzip
下載Protobuf源碼
您可以從GitHub上克隆官方Protobuf倉庫,或者直接下載特定版本的壓縮包。這里我們使用Git進行操作:
cd ~
git clone https://github.com/protocolbuffers/protobuf.git
cd protobuf
git submodule update --init --recursive
編譯Protobuf
創建并進入構建目錄
為了保持源代碼目錄的整潔,建議在一個新的目錄中進行編譯:
cd ~/protobuf
mkdir -p build && cd build
使用CMake配置項目
CMake是一個跨平臺的構建系統生成器,支持多種IDE和構建工具。
-
配置CMake:
cmake .. -DCMAKE_BUILD_TYPE=Release
-
編譯Protobuf:
make -j$(nproc)
-
安裝Protobuf:
sudo make install sudo ldconfig # 刷新共享庫緩存
驗證安裝
檢查版本信息
驗證Protobuf是否正確安裝,并檢查其版本號:
protoc --version
您應該看到類似如下的輸出:
libprotoc 3.x.x
其中3.x.x
是具體的版本號。
配置環境變量(可選)
如果希望在任何位置都能直接運行protoc
命令,而無需指定完整路徑,可以將Protobuf的bin目錄添加到系統的PATH
環境變量中。編輯~/.bashrc
或~/.zshrc
文件,根據您的shell類型,添加以下行:
export PATH=$PATH:/usr/local/bin
保存文件后,運行以下命令使更改生效:
source ~/.bashrc # 對于Bash用戶
# 或者
source ~/.zshrc # 對于Zsh用戶
3. OpenCV庫的安裝與配置
更新系統軟件包
首先,更新您的系統軟件包列表:
sudo apt-get update
sudo apt-get upgrade
安裝依賴項
安裝構建OpenCV所需的各種工具和庫:
sudo apt-get install build-essential cmake git pkg-config libgtk-3-dev
sudo apt-get install libavcodec-dev libavformat-dev libswscale-dev
sudo apt-get install python3-dev python3-numpy libtbb2 libtbb-dev libjpeg-dev libpng-dev libtiff-dev
sudo apt-get install libdc1394-22-dev libopenblas-dev liblapack-dev gfortran
sudo apt-get install libprotobuf-dev protobuf-compiler
下載OpenCV源碼
您可以通過Git克隆OpenCV的GitHub倉庫來獲取最新的穩定版本:
cd ~
git clone https://github.com/opencv/opencv.git
cd opencv
git checkout 4.x # 替換為所需的版本號# 克隆contrib倉庫(可選)
cd ~
git clone https://github.com/opencv/opencv_contrib.git
cd opencv_contrib
git checkout 4.x # 確保與主倉庫版本一致
編譯OpenCV
創建并進入構建目錄
cd ~/opencv
mkdir -p build && cd build
使用CMake配置項目
運行CMake以配置構建選項。這里我們指定一些常用的選項,例如啟用Python支持、設置安裝路徑等。如果您不需要這些功能或使用了不同的路徑,請相應地調整命令。
cmake -D CMAKE_BUILD_TYPE=Release \-D CMAKE_INSTALL_PREFIX=/usr/local \-D OPENCV_EXTRA_MODULES_PATH=~/opencv_contrib/modules \-D BUILD_opencv_python3=ON \-D PYTHON3_EXECUTABLE=$(which python3) \-D PYTHON3_INCLUDE_DIR=$(python3 -c "from distutils.sysconfig import get_python_inc; print(get_python_inc())") \-D PYTHON3_PACKAGES_PATH=$(python3 -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())") ..
編譯
make -j$(nproc)
這可能需要一些時間,具體取決于您的硬件性能。
安裝
完成編譯后,使用以下命令安裝OpenCV到系統中:
sudo make install
sudo ldconfig
驗證安裝
查找頭文件和庫文件
安裝完成后,OpenCV的頭文件通常位于/usr/local/include/opencv4/
,而庫文件則位于/usr/local/lib/
。
您可以檢查這些位置是否包含必要的文件:
ls /usr/local/include/opencv4/
ls /usr/local/lib/
4. ncnn庫在Linux環境下的編譯
更新系統軟件包
首先,更新您的系統軟件包列表,確保所有現有的包都是最新的:
sudo apt-get update
sudo apt-get upgrade
安裝依賴項
安裝構建NCNN所需的各種工具和庫:
sudo apt-get install build-essential cmake git libprotobuf-dev protobuf-compiler
sudo apt-get install libvulkan-dev vulkan-utils # 如果需要Vulkan支持
libprotobuf-dev
和 protobuf-compiler
是用于處理模型文件(如 .param
和 .bin
文件)的必要依賴項。
下載NCNN源碼
您可以通過Git克隆NCNN的GitHub倉庫來獲取最新的穩定版本:
cd ~
git clone https://github.com/Tencent/ncnn.git
cd ncnn
如果您想要特定的版本,可以切換到對應的分支或標簽:
git checkout <branch_or_tag_name>
例如,切換到最新穩定版:
git checkout master
編譯NCNN
創建并進入構建目錄
為了保持源代碼目錄的整潔,建議在一個新的目錄中進行編譯:
mkdir -p build && cd build
使用CMake配置項目
運行CMake以配置構建選項。這里我們指定一些常用的選項,例如啟用Vulkan支持、設置安裝路徑等。如果您不需要這些功能或使用了不同的路徑,請相應地調整命令。
cmake .. \-DCMAKE_BUILD_TYPE=Release \-DNCNN_VULKAN=ON \ # 如果你希望使用Vulkan加速,請啟用此選項-DNCNN_PROTOBUF_USE_SYSTEM=ON \-DProtobuf_DIR=/usr/lib/x86_64-linux-gnu/pkgconfig \ # 手動指定 Protobuf 的路徑-DProtobuf_INCLUDE_DIR=/usr/include \-DProtobuf_LIBRARY=/usr/lib/x86_64-linux-gnu/libprotobuf.so \-DProtobuf_PROTOC_EXECUTABLE=/usr/bin/protoc
編譯
使用所有可用的核心進行并行編譯:
make -j$(nproc)
這可能需要一些時間,具體取決于您的硬件性能。
安裝
完成編譯后,使用以下命令安裝NCNN到系統中:
sudo make install
sudo ldconfig # 刷新共享庫緩存
默認情況下,頭文件會被安裝到 /usr/local/include/ncnn
,庫文件會被安裝到 /usr/local/lib
。
驗證安裝
查找頭文件和庫文件
安裝完成后,NCNN的頭文件通常位于 /usr/local/include/ncnn
,而庫文件則位于 /usr/local/lib
。
您可以檢查這些位置是否包含必要的文件:
ls /usr/local/include/ncnn/
ls /usr/local/lib/
5. MTCNN源碼
ncnn框架實現的mtcnn主要包含兩個核心代碼文件mtcnn.h,mtcnn.cpp
mtcnn.h代碼如下:
#pragma once#ifndef __MTCNN_NCNN_H__
#define __MTCNN_NCNN_H__
#include <ncnn/net.h>
#include <string>
#include <vector>
#include <time.h>
#include <algorithm>
#include <map>
#include <iostream>using namespace std;
struct Bbox
{float score;int x1;int y1;int x2;int y2;bool exist;float area;float ppoint[10];float regreCoord[4];
};class MTCNN {public:MTCNN(const string& model_path);MTCNN(const std::vector<std::string> param_files, const std::vector<std::string> bin_files);~MTCNN();void configure_ncnn(ncnn::Net& net, int num_threads);void SetMinFace(int minSize);void detect(ncnn::Mat& img_, std::vector<Bbox>& finalBbox);
private:void generateBbox(ncnn::Mat score, ncnn::Mat location, vector<Bbox>& boundingBox_, float scale);void nms(vector<Bbox>& boundingBox_, const float overlap_threshold, string modelname = "Union");void refine(vector<Bbox>& vecBbox, const int& height, const int& width, bool square);void PNet();void RNet();void ONet();ncnn::Net Pnet, Rnet, Onet;ncnn::Mat img;const float nms_threshold[3] = { 0.5f, 0.7f, 0.7f };const float mean_vals[3] = { 127.5, 127.5, 127.5 };const float norm_vals[3] = { 0.0078125, 0.0078125, 0.0078125 };const int MIN_DET_SIZE = 12;std::vector<Bbox> firstBbox_, secondBbox_, thirdBbox_;int img_w, img_h;private://部分可調參數const float threshold[3] = { 0.8f, 0.8f, 0.6f };int minsize = 20;const float pre_facetor = 0.709f;};#endif //__MTCNN_NCNN_H__
mtcnn.cpp代碼如下:
#include "mtcnn.h"bool cmpScore(Bbox lsh, Bbox rsh) {if (lsh.score < rsh.score)return true;elsereturn false;
}bool cmpArea(Bbox lsh, Bbox rsh) {if (lsh.area < rsh.area)return false;elsereturn true;
}MTCNN::MTCNN(const std::string& model_path) {std::vector<std::string> param_files = {model_path + "/det1.param",model_path + "/det2.param",model_path + "/det3.param"};std::vector<std::string> bin_files = {model_path + "/det1.bin",model_path + "/det2.bin",model_path + "/det3.bin"};// 配置多線程int num_threads = 4; // 設置線程數configure_ncnn(Pnet, num_threads);configure_ncnn(Rnet, num_threads);configure_ncnn(Onet, num_threads);// 加載模型Pnet.load_param(param_files[0].data());Pnet.load_model(bin_files[0].data());Rnet.load_param(param_files[1].data());Rnet.load_model(bin_files[1].data());Onet.load_param(param_files[2].data());Onet.load_model(bin_files[2].data());
}MTCNN::~MTCNN(){Pnet.clear();Rnet.clear();Onet.clear();
}void MTCNN::configure_ncnn(ncnn::Net& net, int num_threads) {ncnn::Option opt;opt.num_threads = num_threads; // 設置線程數opt.use_vulkan_compute = false; // 如果不使用 Vulkan,設置為 falsenet.opt = opt;
}void MTCNN::SetMinFace(int minSize){minsize = minSize;
}void MTCNN::generateBbox(ncnn::Mat score, ncnn::Mat location, std::vector<Bbox>& boundingBox_, float scale){const int stride = 2;const int cellsize = 12;//score pfloat *p = score.channel(1);//score.data + score.cstep;//float *plocal = location.data;Bbox bbox;float inv_scale = 1.0f/scale;for(int row=0;row<score.h;row++){for(int col=0;col<score.w;col++){if(*p>threshold[0]){bbox.score = *p;bbox.x1 = round((stride*col+1)*inv_scale);bbox.y1 = round((stride*row+1)*inv_scale);bbox.x2 = round((stride*col+1+cellsize)*inv_scale);bbox.y2 = round((stride*row+1+cellsize)*inv_scale);bbox.area = (bbox.x2 - bbox.x1) * (bbox.y2 - bbox.y1);const int index = row * score.w + col;for(int channel=0;channel<4;channel++){bbox.regreCoord[channel]=location.channel(channel)[index];}boundingBox_.push_back(bbox);}p++;//plocal++;}}
}void MTCNN::nms(std::vector<Bbox> &boundingBox_, const float overlap_threshold, string modelname){if(boundingBox_.empty()){return;}sort(boundingBox_.begin(), boundingBox_.end(), cmpScore);float IOU = 0;float maxX = 0;float maxY = 0;float minX = 0;float minY = 0;std::vector<int> vPick;int nPick = 0;std::multimap<float, int> vScores;const int num_boxes = boundingBox_.size();vPick.resize(num_boxes);for (int i = 0; i < num_boxes; ++i){vScores.insert(std::pair<float, int>(boundingBox_[i].score, i));}while(vScores.size() > 0){int last = vScores.rbegin()->second;vPick[nPick] = last;nPick += 1;for (std::multimap<float, int>::iterator it = vScores.begin(); it != vScores.end();){int it_idx = it->second;maxX = (std::max)(boundingBox_.at(it_idx).x1, boundingBox_.at(last).x1);maxY = (std::max)(boundingBox_.at(it_idx).y1, boundingBox_.at(last).y1);minX = (std::min)(boundingBox_.at(it_idx).x2, boundingBox_.at(last).x2);minY = (std::min)(boundingBox_.at(it_idx).y2, boundingBox_.at(last).y2);//maxX1 and maxY1 reuse maxX = ((minX-maxX+1)>0)? (minX-maxX+1) : 0;maxY = ((minY-maxY+1)>0)? (minY-maxY+1) : 0;//IOU reuse for the area of two bboxIOU = maxX * maxY;if(!modelname.compare("Union"))IOU = IOU/(boundingBox_.at(it_idx).area + boundingBox_.at(last).area - IOU);else if(!modelname.compare("Min")){IOU = IOU/((boundingBox_.at(it_idx).area < boundingBox_.at(last).area)? boundingBox_.at(it_idx).area : boundingBox_.at(last).area);}if(IOU > overlap_threshold){it = vScores.erase(it);}else{it++;}}}vPick.resize(nPick);std::vector<Bbox> tmp_;tmp_.resize(nPick);for(int i = 0; i < nPick; i++){tmp_[i] = boundingBox_[vPick[i]];}boundingBox_ = tmp_;
}void MTCNN::refine(vector<Bbox> &vecBbox, const int &height, const int &width, bool square){if(vecBbox.empty()){cout<<"Bbox is empty!!"<<endl;return;}float bbw=0, bbh=0, maxSide=0;float h = 0, w = 0;float x1=0, y1=0, x2=0, y2=0;for(vector<Bbox>::iterator it=vecBbox.begin(); it!=vecBbox.end();it++){bbw = (*it).x2 - (*it).x1 + 1;bbh = (*it).y2 - (*it).y1 + 1;x1 = (*it).x1 + (*it).regreCoord[0]*bbw;y1 = (*it).y1 + (*it).regreCoord[1]*bbh;x2 = (*it).x2 + (*it).regreCoord[2]*bbw;y2 = (*it).y2 + (*it).regreCoord[3]*bbh;if(square){w = x2 - x1 + 1;h = y2 - y1 + 1;maxSide = (h>w)?h:w;x1 = x1 + w*0.5 - maxSide*0.5;y1 = y1 + h*0.5 - maxSide*0.5;(*it).x2 = round(x1 + maxSide - 1);(*it).y2 = round(y1 + maxSide - 1);(*it).x1 = round(x1);(*it).y1 = round(y1);}//boundary checkif((*it).x1<0)(*it).x1=0;if((*it).y1<0)(*it).y1=0;if((*it).x2>width)(*it).x2 = width - 1;if((*it).y2>height)(*it).y2 = height - 1;it->area = (it->x2 - it->x1)*(it->y2 - it->y1);}
}void MTCNN::PNet(){firstBbox_.clear();float minl = img_w < img_h? img_w: img_h;float m = (float)MIN_DET_SIZE/minsize;minl *= m;float factor = pre_facetor;vector<float> scales_;while(minl>MIN_DET_SIZE){scales_.push_back(m);minl *= factor;m = m*factor;}for (size_t i = 0; i < scales_.size(); i++) {int hs = (int)ceil(img_h*scales_[i]);int ws = (int)ceil(img_w*scales_[i]);ncnn::Mat in;resize_bilinear(img, in, ws, hs);ncnn::Extractor ex = Pnet.create_extractor();//ex.set_num_threads(2);ex.set_light_mode(true);ex.input("data", in);ncnn::Mat score_, location_;ex.extract("prob1", score_);ex.extract("conv4-2", location_);std::vector<Bbox> boundingBox_;generateBbox(score_, location_, boundingBox_, scales_[i]);nms(boundingBox_, nms_threshold[0]);firstBbox_.insert(firstBbox_.end(), boundingBox_.begin(), boundingBox_.end());boundingBox_.clear();}
}
void MTCNN::RNet(){secondBbox_.clear();int count = 0;for(vector<Bbox>::iterator it=firstBbox_.begin(); it!=firstBbox_.end();it++){ncnn::Mat tempIm;copy_cut_border(img, tempIm, (*it).y1, img_h-(*it).y2, (*it).x1, img_w-(*it).x2);ncnn::Mat in;resize_bilinear(tempIm, in, 24, 24);ncnn::Extractor ex = Rnet.create_extractor();//ex.set_num_threads(2);ex.set_light_mode(true);ex.input("data", in);ncnn::Mat score, bbox;ex.extract("prob1", score);ex.extract("conv5-2", bbox);if ((float)score[1] > threshold[1]) {for (int channel = 0; channel<4; channel++) {it->regreCoord[channel] = (float)bbox[channel];//*(bbox.data+channel*bbox.cstep);}it->area = (it->x2 - it->x1)*(it->y2 - it->y1);it->score = score.channel(1)[0];//*(score.data+score.cstep);secondBbox_.push_back(*it);}}
}
void MTCNN::ONet(){thirdBbox_.clear();for(vector<Bbox>::iterator it=secondBbox_.begin(); it!=secondBbox_.end();it++){ncnn::Mat tempIm;copy_cut_border(img, tempIm, (*it).y1, img_h-(*it).y2, (*it).x1, img_w-(*it).x2);ncnn::Mat in;resize_bilinear(tempIm, in, 48, 48);ncnn::Extractor ex = Onet.create_extractor();//ex.set_num_threads(2);ex.set_light_mode(true);ex.input("data", in);ncnn::Mat score, bbox, keyPoint;ex.extract("prob1", score);ex.extract("conv6-2", bbox);ex.extract("conv6-3", keyPoint);if ((float)score[1] > threshold[2]) {for (int channel = 0; channel < 4; channel++) {it->regreCoord[channel] = (float)bbox[channel];}it->area = (it->x2 - it->x1) * (it->y2 - it->y1);it->score = score.channel(1)[0];for (int num = 0; num<5; num++) {(it->ppoint)[num] = it->x1 + (it->x2 - it->x1) * keyPoint[num];(it->ppoint)[num + 5] = it->y1 + (it->y2 - it->y1) * keyPoint[num + 5];}thirdBbox_.push_back(*it);}}
}void MTCNN::detect(ncnn::Mat& img_, std::vector<Bbox>& finalBbox_){img = img_;img_w = img.w;img_h = img.h;img.substract_mean_normalize(mean_vals, norm_vals);PNet();//the first stage's nmsif(firstBbox_.size() < 1) return;nms(firstBbox_, nms_threshold[0]);refine(firstBbox_, img_h, img_w, true);//second stageRNet();if(secondBbox_.size() < 1) return;nms(secondBbox_, nms_threshold[1]);refine(secondBbox_, img_h, img_w, true);//third stage ONet();if(thirdBbox_.size() < 1) return;refine(thirdBbox_, img_h, img_w, true);nms(thirdBbox_, nms_threshold[2], "Min");finalBbox_ = thirdBbox_;
}
6. Linux下進行推理
在Linux下配置完Opencv和ncnn的環境后編寫簡單的main.cpp進行模型的推理,代碼如下:
#include "mtcnn.h"
#include <opencv2/opencv.hpp>
#include <chrono>using namespace cv;int main()
{std::string model_path = "./models"; //根據模型權重所在位置修改路徑MTCNN mm(model_path);mm.SetMinFace(20);cv::VideoCapture video("./video/video.mp4"); //根據測試視頻所在位置修改路徑if (!video.isOpened()) {std::cerr << "failed to load video" << std::endl;return -1;}std::vector<Bbox> finalBbox;cv::Mat frame;// 記錄開始時間auto start = std::chrono::high_resolution_clock::now();do {finalBbox.clear();video >> frame;if (!frame.data) {std::cerr << "Capture video failed" << std::endl;break;}ncnn::Mat ncnn_img = ncnn::Mat::from_pixels(frame.data, ncnn::Mat::PIXEL_BGR2RGB, frame.cols, frame.rows);mm.detect(ncnn_img, finalBbox);for (vector<Bbox>::iterator it = finalBbox.begin(); it != finalBbox.end(); it++) {if ((*it).exist) {cv::rectangle(frame, cv::Point((*it).x1, (*it).y1), cv::Point((*it).x2, (*it).y2), cv::Scalar(0, 0, 255), 2, 8, 0);}}} while (1);// 釋放資源video.release();// 記錄結束時間auto end = std::chrono::high_resolution_clock::now();// 計算持續時間std::chrono::duration<double> duration = end - start;// 輸出結果(秒)std::cout << "Time taken: " << duration.count() << " seconds" << std::endl;return 0;
}
測試項目的目錄結構如下:
mtcnn/
├── Makefile
├── video
│ └── video.mp4
├── include/
│ └── mtcnn.h
├── src/
│ ├── mtcnn.cpp
│ └── main.cpp
└── models├── det1.bin├── det1.param├── det2.bin├── det2.param├── det3.bin└── det3.param
ncnn架構的mtcnn模型權重下載鏈接如下:
ncnn架構的mtcnn模型權重下載https://download.csdn.net/download/m0_57010556/90433089Makefile的內容如下:
# 編譯器
CXX = g++# 編譯選項
CXXFLAGS = -Wall -I./include -O2 -fopenmp `pkg-config --cflags opencv4`# 目標可執行文件名
TARGET = face_detection# 源文件目錄
SRCDIR = src# 頭文件目錄
INCDIR = include# 鏈接庫路徑
OPENCV_LIBS = `pkg-config --libs opencv4`
OPENCV_CFLAGS = `pkg-config --cflags opencv4`
NCNN_CFLAGS = -I/home/ncnn/build/install/include
NCNN_LIBS = -L/home/ncnn/build/install/lib -lncnn# 找到所有源文件
SOURCES := $(wildcard $(SRCDIR)/*.cpp)# 生成目標文件列表
OBJECTS := $(patsubst $(SRCDIR)/%.cpp, %.o, $(SOURCES))# 默認目標
all: $(TARGET)# 鏈接目標文件生成可執行文件
$(TARGET): $(OBJECTS)$(CXX) $(CXXFLAGS) $^ -o $@ $(OPENCV_LIBS) $(NCNN_LIBS) -lpthread -ldl -lgomp# 規則:從源文件生成目標文件
%.o: $(SRCDIR)/%.cpp$(CXX) $(CXXFLAGS) $(OPENCV_CFLAGS) $(NCNN_CFLAGS) -c $< -o $@# 清理生成的文件
clean:rm -f $(OBJECTS) $(TARGET).PHONY: all clean
編譯和運行
在項目目錄下運行以下命令來編譯和運行你的程序:
編譯
make
這將編譯 src/mtcnn.cpp
和 src/main.cpp
并生成可執行文件 face_detection
。
運行
./face_detection
你應該會看到輸出:
Capture video failed
Time taken: 22.336 seconds
此時說明在linux下模型推理成功