概述
本文将详细介绍如何在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下模型推理成功