在Linux环境下利用MTCNN进行人脸检测(基于ncnn架构)

概述

本文将详细介绍如何在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

编译与安装

  1. 配置编译选项:使用bootstrap脚本进行配置:

    ./bootstrap
  2. 编译:使用所有可用的核心进行并行编译:

    make -j$(nproc)
  3. 安装:将CMake安装到系统中:

    sudo make install
  4. 刷新共享库缓存(如果需要):

    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和构建工具。

  1. 配置CMake:

    cmake .. -DCMAKE_BUILD_TYPE=Release
  2. 编译Protobuf:

    make -j$(nproc)
  3. 安装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-devprotobuf-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;
    	else
    		return false;
    }
    
    bool cmpArea(Bbox lsh, Bbox rsh) {
    	if (lsh.area < rsh.area)
    		return false;
    	else
    		return 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,设置为 false
        net.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 p
        float *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 bbox
                IOU = 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 check
            if((*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 nms
        if(firstBbox_.size() < 1) return;
        nms(firstBbox_, nms_threshold[0]);
        refine(firstBbox_, img_h, img_w, true);
    
        //second stage
        RNet();
        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.cppsrc/main.cpp 并生成可执行文件 face_detection

    运行

    ./face_detection

    你应该会看到输出:

    Capture video failed
    Time taken: 22.336 seconds

    此时说明在linux下模型推理成功

    本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/980119.html

    如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

    相关文章

    Ruby 数组(Array)

    一、参数 符号赋值数组对象 时&#xff0c;是指向同一个数组对象 eg: irb(main):019:0> a [] //定义一个数组 > [] irb(main):020:0> b a //将变量a赋值给b&#xff0c;a和b指向同个内存 > [] irb(main):021:0> a.object_id //a的对象id > 25204596 …

    【无人机】无人机飞行日志下载及分析,飞行日志分析软件的使用

    目录 一、飞行日志下载 1.1 通过地面站下载 1.1.1 QGroundControl&#xff08;QGC&#xff09;地面站 1.1.2 Mission Planner 地面站 1.2 通过内存卡读卡器下载 1.3 通过数传模块下载&#xff08;数传日志&#xff09; 二、飞行日志分析 2.1 使用 Flight Review 分析 …

    Coredns延迟NodeLocalDNS解决之道

    #作者&#xff1a;邓伟 文章目录 问题列表问题分析&#xff1a;问题分析解决方案详情方案验证部署步骤验证结论回滚方案回滚验证注意事项NodeLocalDNS介绍 问题列表 近来发现K8s频繁出现5s超时问题&#xff0c;业务反馈收到一定影响&#xff0c;问题包括&#xff1a; coredn…

    由堆栈异常引发的一系列问题启发

    文章目录 背景分析现象排查定位数据比对解决方案反思背景 背景:今天下午初始化经营体数据到预发环境,需要将生产环境的经营体表的数据同步到预发环境,同步结束后。再调用批量禁用接口,目的是将原来一级经营体【中国移动】下的所有经营体禁用掉,然后导入Excel文件将新的省…

    [2/11]C#性能优化-不要使用空析构函数-每个细节都有示例代码

    前言 在C#开发中&#xff0c;性能优化是提升系统响应速度和资源利用率的关键环节。 当然&#xff0c;同样是所有程序的关键环节。 通过遵循下述建议&#xff0c;可以有效地减少不必要的对象创建&#xff0c;从而减轻GC的负担&#xff0c;提高应用程序的整体性能。记住&#xf…

    Llama-factory详细基础使用说明

    最近很多小伙伴在使用llama-factory时遇到了不少问题&#xff0c;特别是基础不太好的童鞋&#xff0c;对各种参数或者webui界面的内容不理解&#xff0c;总是一会一个问题&#xff0c;网上大部分的教程&#xff0c;都只是教到能跑起来就行&#xff0c;对于细的调整没有做介绍&a…

    excel单、双字节字符转换函数(中英文输入法符号转换)

    在Excel中通常使用函数WIDECHAR和ASC来实现单、双字节字符之间的转换。其中 WIDECHAR函数将所有的字符转换为双字节&#xff0c;ASC函数将所有的字符转换为单字节 首先来解释一下单双字节的含义。单字节一般对应英文输入法的输入&#xff0c;如英文字母&#xff0c;英文输入法…

    VS Code 如何搭建CC++开发环境

    VS Code 如何搭建C/C开发环境 文章目录 VS Code 如何搭建C/C开发环境1. VS Code是什么2. VS Code的下载和安装2.1 下载和安装2.2 环境的介绍 3. VS Code配置C/C开发环境3.1 下载和配置MinGW-w64编译器套件3.2 安装C/C插件3.3 重启VS Code 4. 在VS Code上编写C语言代码并编译成功…

    间隙波导阵列天线 学习笔记2 使用加载脊U型辐射层的一种Ka波段高效率圆极化卫星天线的发展

    摘要&#xff1a; 本文提出了一种低剖面&#xff0c;高增益背腔槽阵列天线&#xff0c;基于RGW技术&#xff0c;工作在Ka频段。天线阵列包含两层。上层是一个U型槽阵列&#xff0c;在金属脊上腔体的位置上方&#xff0c;同时下层是RGW公共分布网络&#xff0c;给每个背腔槽等辐…

    【软考-架构】1.1、计算机硬件-CPU校验码

    GitHub地址&#xff1a;https://github.com/tyronczt/system_architect ✨资料&文章更新✨ 计算机硬件组成 中央处理单元 运算器 算术逻辑单元ALU&#xff08;实现对数据的算术和逻辑运算&#xff09;;累加寄存器AC&#xff08;运算结果或源操作数的存放区&#xff09;;数…

    【Spring】Spring AOP原理

    目录 前言 代理模式 静态代理 优缺点 动态代理 JDK动态代理 工作原理 JDK动态原理实现关键步骤 CGLib动态代理 CGLIB动态代理实现关键步骤 总结 前言 在上一篇中&#xff0c;我们讲解了什么是AOP&#xff0c;以及Spring AOP是如何使用的&#xff0c;那么本篇我们就…

    SpringBoot五:JSR303校验

    精心整理了最新的面试资料和简历模板&#xff0c;有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 松散绑定 意思是比如在yaml中写的是last-name&#xff0c;这个和lastName意思是一样的&#xff0c;-后的字母默认是大写的 JSR303校验 就是可以在字段增加…

    uniapp 系统学习,从入门到实战(六)—— 样式与布局

    全篇大概 4700 字(含代码)&#xff0c;建议阅读时间 30min &#x1f4da; 目录 Flex 布局在 UniApp 中的应用响应式设计与适配多端使用 SCSS 提升样式开发效率实战案例演示总结 1. Flex 布局在 UniApp 中的应用 1.1 基础布局实现 通过 display: flex 快速构建弹性容器&#…

    Redis---缓存穿透,雪崩,击穿

    文章目录 缓存穿透什么是缓存穿透&#xff1f;缓存穿透情况的处理流程是怎样的&#xff1f;缓存穿透的解决办法缓存无效 key布隆过滤器 缓存雪崩什么是缓存雪崩&#xff1f;缓存雪崩的解决办法 缓存击穿什么是缓存击穿&#xff1f;缓存击穿的解决办法 区别对比 在如今的开发中&…

    从UNIX到Linux:操作系统进化史与开源革命

    从UNIX到Linux&#xff1a;操作系统进化史与开源革命 一、操作系统&#xff1a;数字世界的基石 1.1 什么是操作系统&#xff1f; 操作系统&#xff08;OS&#xff09;是计算机系统的核心管理者&#xff0c;承担着三大核心使命&#xff1a; 硬件指挥官&#xff1a;直接管理C…

    【Python 语法】算法合集

    查找二分查找代码大 O 表示法 广度优先搜索代码 狄克斯特拉算法 递归递归调用栈 分而治之&#xff08;divide and conquer&#xff0c;D&C&#xff09;贪心教室调度问题背包问题集合覆盖问题 动态规划背包问题旅游行程最优化 遇到问题时&#xff0c; 如果不确定该如何 高效…

    IDEAPyCharm安装ProxyAI(CodeGPT)插件连接DeepSeek-R1教程

    背景&#xff1a;最近DeepSeek比较火嘛&#xff0c;然后在githup上也看到了GitHub Copilot&#xff0c;就想着现在AI的准确率已经可以提高工作效率了。所以从网上找了一些编程插件&#xff0c;发现Proxy支持的模型比较多&#xff0c;通用性和适配性比较好。所以本文记录一下pro…

    基于javaweb的SSM+Maven幼儿园管理系统设计和实现(源码+文档+部署讲解)

    技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论…

    Java-Lambda表达式详解

    引言&#xff1a;为什么需要 Lambda 表达式&#xff1f; 在 Java 8 之前&#xff0c;处理需要传递代码块的场景&#xff08;如事件监听、线程任务&#xff09;通常依赖匿名内部类。这种方式代码冗余&#xff0c;可读性差。例如&#xff1a; // 传统匿名内部类实现 Runnable n…

    springboot之集成Elasticsearch

    目录 二、Elasticsearch 是什么&#xff1f;三、Elasticsearch 安装四、Springboot 集成 Elasticsearch 的方式五、创建项目集成 Elasticsearch 2.创建 Spring Initializr 项目 es &#xff08;3&#xff09;.新建实体类 User&#xff08;4&#xff09;.新建 dao 接口类 UserR…