NCNN部署YoloX:模型转换、优化、int8量化、C++部署

1、NCNN简介

腾讯推出的 NCNN(Ncnn Convolutional Neural Network)是一个轻量级的高性能深度学习框架,专门为移动设备和嵌入式设备优化,旨在提供高效的推理性能和低延迟。NCNN 具有高度优化的计算图、快速有效的算法实现、灵活的编程接口等特点,适用于各种常见的深度学习模型,如卷积神经网络(CNN)、循环神经网络(RNN)和变分自编码器(VAE)等。
NCNN 的主要特点有以下几点:
高性能:NCNN 针对移动设备进行了专门的优化,采用了一些高效的算法实现,如 Winograd、量化、bf16 和异构计算等,以提高推理性能和降低延迟。
轻量级:NCNN 框架本身具有较小的体积,可以在资源有限的移动设备和嵌入式设备上快速运行。
灵活性:NCNN 提供了灵活的编程接口,开发者可以根据需求自由选择模型结构和参数配置,进行定制化开发。
易用性:NCNN 具有简单的使用流程,开发者可以快速地将自己的模型导入框架中进行推理。
开源:NCNN 是开源的,具有活跃的社区支持,可以方便地获取到最新的技术发展和优化方案。

2、项目结构

3、模型转换与量化

3.1、ONNX模型转NCNN

对上图中的baseline_sim.onnx进行转换

D:\Documents\cxx_packages\ncnn-20230816-windows-vs2022-shared\x64\bin\onnx2ncnn.exe .\assets\model\baseline_sim.onnx .\assets\ncnn_model\baseline_sim.param .\assets\ncnn_model\baseline_sim.bin

在指定的路径下生成了ncnn的模型文件

3.2、NCNN模型优化

D:\Documents\cxx_packages\ncnn-20230816-windows-vs2022-shared\x64\bin\ncnnoptimize.exe .\assets\ncnn_model\baseline_sim.param .\assets\ncnn_model\baseline_sim.bin .\assets\ncnn_model\baseline_sim_opt.param .\assets\ncnn_model\baseline_sim_opt.bin 0

生成优化后的模型文件

3.3、生成校准数据集

D:\Documents\cxx_packages\ncnn-20230816-windows-vs2022-shared\x64\bin\ncnn2table ./assets/ncnn_model/baseline_sim_opt.param ./assets/ncnn_model/baseline_sim_opt.bin .\assets\images.txt ./assets/yolox.table mean=[104,117,123] norm=[0.017,0.017,0.017] shape=[640,480,3] pixel=BGR thread=8 method=kl

生成的校准数据集文件

3.4、int8量化

 D:\Documents\cxx_packages\ncnn-20230816-windows-vs2022-shared\x64\bin\ncnn2int8.exe ./assets/ncnn_model/baseline_sim_opt.param ./assets/ncnn_model/baseline_sim_opt.bin ./assets/ncnn_model/baseline_sim_opt_int8.param ./assets/ncnn_model/baseline_sim_opt_int8.bin ./assets/yolox.table

量化后的模型文件

4、部署

4.1、主函数

#include <opencv2/opencv.hpp>
#include <ncnn/net.h>

#include <iostream>
#include <fstream>
#include <stdio.h>
#include <algorithm>
#include <vector>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include "opencv2/imgproc/imgproc.hpp"


using namespace std;

//这个函数是官方提供的用于打印输出的tensor
void pretty_print(const ncnn::Mat &m) {
    for (int q = 0; q < m.c; q++) {
        const float *ptr = m.channel(q);
        for (int y = 0; y < m.h; y++) {
            for (int x = 0; x < m.w; x++) {
                printf("%f ", ptr[x]);
            }
            ptr += m.w;
            printf("\n");
        }
        printf("------------------------\n");
    }
}


void visualize(const char* title, const ncnn::Mat& m)
{
    std::vector<cv::Mat> normed_feats(m.c);

    for (int i=0; i<m.c; i++)
    {
        cv::Mat tmp(m.h, m.w, CV_32FC1, (void*)(const float*)m.channel(i));

        cv::normalize(tmp, normed_feats[i], 0, 255, cv::NORM_MINMAX, CV_8U);

        cv::cvtColor(normed_feats[i], normed_feats[i], cv::COLOR_GRAY2BGR);

        // check NaN
        for (int y=0; y<m.h; y++)
        {
            const float* tp = tmp.ptr<float>(y);
            uchar* sp = normed_feats[i].ptr<uchar>(y);
            for (int x=0; x<m.w; x++)
            {
                float v = tp[x];
                if (v != v)
                {
                    sp[0] = 0;
                    sp[1] = 0;
                    sp[2] = 255;
                }

                sp += 3;
            }
        }
    }

    int tw = m.w < 10 ? 32 : m.w < 20 ? 16 : m.w < 40 ? 8 : m.w < 80 ? 4 : m.w < 160 ? 2 : 1;
    int th = (m.c - 1) / tw + 1;

    cv::Mat show_map(m.h * th, m.w * tw, CV_8UC3);
    show_map = cv::Scalar(127);

    // tile
    for (int i=0; i<m.c; i++)
    {
        int ty = i / tw;
        int tx = i % tw;

        normed_feats[i].copyTo(show_map(cv::Rect(tx * m.w, ty * m.h, m.w, m.h)));
    }

    cv::resize(show_map, show_map, cv::Size(0,0), 2, 2, cv::INTER_NEAREST);
    cv::imshow(title, show_map);
}

float sigmoid(float x) {
    return (1 / (1 + exp(-x)));
}

float desigmoid(float x) {
    return -log(1 / x - 1);
}


//main函数模板
int main() {
    float deconf = desigmoid(0.5);
    const float mean_vals[3] = { 103.94f, 116.78f, 123.68f };
    const float norm_vals[3] = { 0.017f, 0.017f, 0.017f };
    // 加载转换并且量化后的yolox网络
    ncnn::Net net;
    net.load_param("D:\\WorkSpace\\PySolution\\ncnntest\\assets\\ncnn_model\\baseline_sim_opt_int8.param");
    net.load_model("D:\\WorkSpace\\PySolution\\ncnntest\\assets\\ncnn_model\\baseline_sim_opt_int8.bin");
    // ncnn前向计算器

    // 设置输入输出
    ncnn::Mat input;
    ncnn::Mat output[3];//取决于模型的输出有几个

    // 遍历文件夹
    std::string src_path = "D:\\WorkSpace\\PySolution\\ncnntest\\assets\\images\\";
    std::vector<cv::String> file_vec;
    cv::glob(src_path + "*.jpg", file_vec, false);//遍历满足条件的文件并添加到列表中
    std::random_shuffle(file_vec.begin(), file_vec.end());//对文件进行洗牌乱序
    for (std::string file_name: file_vec) {
        ncnn::Extractor extractor = net.create_extractor();

        // 读图
        cv::Mat img = cv::imread(file_name, cv::IMREAD_COLOR);

        // resize
        cv::Mat img2, src;
        int input_width = 640;//转onnx时指定的输入大小
        int input_height = 480;
        if (!img.isContinuous()) {
            return -1;
        }
        float scale_w = 640.0 / img.cols;
        float scale_h = 480.0 / img.rows;
        float resize_scale = min(scale_h, scale_w);
        // resize
        cv::resize(img, src, cv::Size(), resize_scale, resize_scale);
        img2 = cv::Mat::zeros(cv::Size(640, 480), CV_8UC3);
        src.copyTo(img2(cv::Rect(0, 0, src.cols, src.rows)));



        // 把opencv的mat转换成ncnn的mat

        input = ncnn::Mat::from_pixels(img2.data, ncnn::Mat::PIXEL_BGR, img2.cols, img2.rows);
        // 差一步归一化
        input.substract_mean_normalize(mean_vals, norm_vals);




        int64 start = cv::getTickCount();
        extractor.input("input_node", input);
        extractor.extract("l", output[0]);
        extractor.extract("m", output[1]);
        extractor.extract("s", output[2]);

        visualize("l",output[0]);



        //结果解析
        vector<cv::Rect> o_rect;
        vector<float> o_rect_cof;
        vector<int> class_ids;
        float scales[] = {8.0f, 16.0f, 32.0f};
        for (int i = 0; i < 3; i++) {
            float scale = scales[i];
            int feature_w = input_width / scale;
            int feature_h = input_height / scale;
            float *outdata = (float *) output[i].data;
            for (int row = 0; row < feature_h; ++row) {
                for (int col = 0; col < feature_w; ++col) {
                    float isObj = outdata[feature_h * feature_w * 4 + row * feature_w + col];
                    if (isObj < deconf) {
                        continue;
                    }
                    isObj = sigmoid(isObj);
                    float conf[5];
                    for (int cls_id = 0; cls_id < 5; cls_id++) {
                        conf[cls_id] = outdata[feature_h * feature_w * (5 + cls_id) + row * feature_w + col];
                        conf[cls_id] = sigmoid(conf[cls_id]);

                    }

                    int idx = distance(conf, max_element(conf, conf + sizeof(conf) / sizeof(conf[0])));
                    float max = conf[idx];
                    float cof = max * isObj;

                    //注意此处输出为中心点坐标,需要转化为角点坐标
                    float x = outdata[feature_h * feature_w * 0 + row * feature_w + col];
                    x += col;
                    x *= scale;
                    x /= resize_scale;
                    float y = outdata[feature_h * feature_w * 1 + row * feature_w + col];
                    y += row;
                    y *= scale;
                    y /= resize_scale;
                    float w = outdata[feature_h * feature_w * 2 + row * feature_w + col];
                    w = exp(w);
                    w *= scale;
                    w /= resize_scale;
                    float h = outdata[feature_h * feature_w * 3 + row * feature_w + col];
                    h = exp(h);
                    h *= scale;
                    h /= resize_scale;
                    float r_x = (x - w / 2);
                    float r_y = (y - h / 2);
                    cv::Rect rect = cv::Rect(round(r_x), round(r_y), round(w), round(h));
                    o_rect.push_back(rect);
                    o_rect_cof.push_back(cof);
                    class_ids.push_back(idx);
                }
            }
        }

        vector<int> final_id;
        cv::dnn::NMSBoxes(o_rect, o_rect_cof, 0.1, 0.5, final_id);
        float fps = cv::getTickFrequency() / (cv::getTickCount() - start);
        float time = (cv::getTickCount() - start) / cv::getTickFrequency();


        std::ostringstream ss1;
        ss1 << "FPS : " << fps << " detection time: " << time * 1000 << " ms";
        cv::putText(img, ss1.str(), cv::Point(20, 50), cv::FONT_HERSHEY_DUPLEX, 0.5, cv::Scalar(0, 0, 255), 2);
        for (int i = 0; i < final_id.size(); ++i) {
            cv::Scalar color = cv::Scalar(100, 255, 50);
            if (class_ids[final_id[i]] == 0) {
                color = cv::Scalar(0, 0, 250);
            }
            int xmin = o_rect[final_id[i]].x;
            int ymin = o_rect[final_id[i]].y;
            int width = o_rect[final_id[i]].width;
            int height = o_rect[final_id[i]].height;
            cv::Rect rect(xmin, ymin, width, height);//左上坐标(x,y)和矩形的长(x)宽(y)
            cv::rectangle(img, rect, color, 1, cv::LINE_8, 0);
            cv::rectangle(img, cv::Rect(xmin, ymin - 20, 120, 20), color, -1);
            std::ostringstream ss;
            ss << class_ids[final_id[i]] << ":" << "(" << o_rect_cof[final_id[i]] << ")";
            cv::putText(img, ss.str(), cv::Point(xmin, ymin - 5), cv::FONT_HERSHEY_DUPLEX, 0.5,
                        cv::Scalar(255, 255, 255), 1, 8);
        }
        cv::imshow("result", img);
        cv::waitKey(0);
    }
    cout << "done" << endl;
    return 0;
}

4.2、CMakeLists.txt

cmake_minimum_required(VERSION 3.8)

project(ncnn_test)

set(OpenCV_DIR "D:/Documents/cxx_packages/opencv/x64/vc16/lib")
find_package(OpenCV REQUIRED)

include_directories("D:/Documents/cxx_packages/ncnn-20230816-windows-vs2022-shared/x64/include"
"C:/ProgramData/VulkanSDK/1.3.261.1/Include")
link_directories("D:/Documents/cxx_packages/ncnn-20230816-windows-vs2022-shared/x64/lib")
add_executable(main "${PROJECT_SOURCE_DIR}/src/cpp/source/main.cpp"
)
target_link_libraries(main ncnn.lib
                            ${OpenCV_LIBS})

5、测试效果

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
NCNN int8量化是指在NCNN框架中将CNN模型从float32转换INT8的过程。这种量化方法可以减小模型的大小、提升运行速度,而对精度的降低相对较小。在实际中,有多种方法可以实现NCNN int8量化,其中包括基于腾讯的NCNN框架、Tensorflow和Nvidia的TensorRT。其中,本回答将介绍一种基于NCNN框架的实践方法。 在这个方法中,首先需要使用Caffe-Int8-Convert-Tools工具将Caffe模型转换int8格式,然后在NCNN框架上运行。具体过程可以参考中提供的链接。接下来,我将以自顶向下的方式叙述NCNN int8量化的kernel实现原理及过程。 首先,需要了解NCNN框架中的INT8版本和优化INT8版本。INT8版本是NCNN的开源版本,而优化后的INT8版本是经过优化处理后的版本。在这两个版本中,都包含了INT8的kernel实现。 在INT8的kernel实现中,主要包括对权重和特征图的量化和反量化操作。量化操作将浮点数转换INT8格式,而反量化操作则将INT8格式转换回浮点数格式。这样可以实现模型参数的压缩和运算速度的提升。 除了量化和反量化操作,还需要对卷积操作进行优化。通过对卷积操作进行INT8计算,可以降低计算复杂度,提高计算速度。优化的卷积操作还可以使用SIMD指令集来进行向量化计算,进一步提升计算效率。 总结来说,NCNN int8量化是将CNN模型从float32转换INT8的过程,可以通过Caffe-Int8-Convert-Tools工具进行转换,并在NCNN框架中运行。量化和反量化操作以及优化INT8卷积操作是实现这一过程的关键。你可以参考中的内容了解更多细节。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [CNN模型 INT8 量化实现方式(一)](https://blog.csdn.net/zhangjunhit/article/details/84562334)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [NCNN INT8实现](https://blog.csdn.net/weixin_41521681/article/details/115156819)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值