实操教程|NCNN+Int8+YOLOv4量化模型和实时推理

部署运行你感兴趣的模型镜像

作者 | pengtougu 编辑 | 极市平台

原文链接 : https://zhuanlan.zhihu.com/p/372278785

点击下方卡片,关注“自动驾驶之心”公众号

ADAS巨卷干货,即可获取

点击进入→自动驾驶之心【模型部署】技术交流群

后台回复【模型部署工程】获取基于TensorRT的分类、检测任务的部署源码!

导读

 

本文作者使用NCNN量化YOLOV4模型以及进行推理的全过程,附有相关代码。 

一、前言

2021年5月7日,腾讯优图实验室正式推出了ncnn新版本,这一版本的贡献毫无疑问,又是对arm系列的端侧推理一大推动,先剖出nihui大佬博客上关于新版ncnn的优化点:继续保持优秀的接口稳定性和兼容性

  • API接口完全不变

  • 量化校准table完全不变

  • int8模型量化流程完全不变(重点是这个!!!之前对tensorflow框架一直不感冒,很大一部分源于tensorflow每更新一次版本,就杀死一片上一版本的接口,可能上了2.0以后这种情况好了很多,不过依旧训练是torch用的更多)

ncnn int8量化工具(ncnn2table)新特性

  • 支持 kl aciq easyquant 三种量化策略

  • 支持多输入的模型量化

  • 支持RGB/RGBA/BGR/BGRA/GRAY输入的模型量化

  • 大幅改善多线程效率

  • 离线进行(反量化-激活-量化)->(requantize)融合,实现端到端量化推理

更多详情大家可以去看下nihui大佬的博客:https://zhuanlan.zhihu.com/p/370689914

二、新版ncnn的int8量化初探

趁着这股热风,赶紧试下新版ncnn量化版int8(更重要的原因是月底要中期答辩了,毕设还没搞完,赶紧跑跑大佬的库,顺带嫖一波)

2.1 安装编译ncnn

话不多说,在跑库前先安装编译好需要的环境,安装和编译过程可以看我的另一条博客:https://zhuanlan.zhihu.com/p/368653551

2.2 yolov4-tiny量化int8

  • 在量化前,先不要着急,我们先看看ncnn的wiki,看下量化前需要做什么工作:

https//github.com/Tencent/ncnn/wiki/quantized-int8-inference

wiki中:为了支持int8模型在移动设备上的部署,我们提供了通用的训练后量化工具,可以将float32模型转换为int8模型。

也就是说,在进行量化前,我们需要yolov4-tiny.bin和yolov4-tiny.param这两个权重文件,因为想快速测试int8版本的性能,这里就不把yolov4-tiny.weights转yolov4-tiny.bin和yolov4-tiny.param的步骤写出来了,大家上model.zoo去嫖下这两个opt文件

地址:https://github.com/nihui/ncnn-assets/tree/master/models

  • 接着,按照步骤使用编译好的ncnn对两个模型进行优化:

./ncnnoptimize yolov4-tiny.param yolov4-tiny.bin yolov4-tiny-opt.param yolov4-tiny.bin 0

如果是直接上model.zoo下的两个opt文件,可以跳过这一步。

  • 下载校准表图像

先下载官方给出的1000张ImageNet图像,很多同学没有梯子,下载慢,可以用下这个链接:

https://download.youkuaiyun.com/download/weixin_45829462/18704213

这里给大家设置的是免费下载,如果后续被官方修改了下载积分,那就么得办法啦(好人的微笑.jpg)

f268bed3eff26ff2c61b6300d4ecbd77.jpeg
ImageNet图像下载
  • 制作校准表文件

linux下,切换到和images同个文件夹的根目录下,直接

find images/ -type f > imagelist.txt

windows下,打开Git Bash(没有的同学自行百度安装,这个工具是真的好用),切换到切换到和images同个文件夹的根目录下,也是直接上面的命令行:

080bb76731c7265cb78656fd21ba8a6f.png
生成图片名列表文件命令

生成所需的list.txt列表,格式如下:

e806527c8641852597e9e85cd12a84e5.jpeg
图片名列表文件预览

接着继续输入命令:

./ncnn2table yolov4-tiny-opt.param yolov4-tiny-opt.bin imagelist.txt yolov4-tiny.table mean=[104,117,123] norm=[0.017,0.017,0.017] shape=[224,224,3] pixel=BGR thread=8 method=kl

其中,上述所包含变量含义如下:

mean平均值和norm范数是你传递给Mat::substract_mean_normalize()的值,shape形状是模型的斑点形状

pixel是模型的像素格式,图像像素将在Extractor::input()之前转换为这种类型 thread线程是可用于并行推理的CPU线程数(这个要根据自己电脑或者板子的性能自己定义) 量化方法是训练后量化算法,目前支持kl和aciq

  • 量化模型

./ncnn2int8 yolov4-tiny-opt.param yolov4-tiny-opt.bin yolov4-tiny-int8.param yolov4-tiny-int8.bin yolov4-tiny.table

直接一步走,所有量化的工具在ncnn\build-vs2019\tools\quantize文件夹下

6960319bab5618da0179e2051e1ce210.png
量化工具所在目录

找不到的读者请看下自己编译过程是不是有误,正常编译下是会有这些量化文件的运行成功后会生成两个int8的文件,分别是:

040c3684ab961528ed051bef023aa118.png
生成的量化模型

对比一下原来的两个opt模型,小了整整一倍!

三、新版ncnn的int8量化再探

量化出了int8模型仅仅是成功了一半,有模型但是内部参数全都错乱的情况也不是没见过。。。

a3b69269d4a33e5a6069125e31a22e12.png
  • 调用int8模型进行推理

打开vs2019,建立新的工程,配置的步骤我在上一篇博客已经详细说过了,再狗头翻出来祭给大家:

https://zhuanlan.zhihu.com/p/368653551

大家直接去ncnn\example文件夹下copy一下yolov4.cpp的代码(一个字!嫖!)

但是我在这里却遇到了点问题,因为一直搞不懂大佬主函数写的传参是什么,在昨晚复习完教资后搞到了好晚。。

int main(int argc, char** argv)
{
    cv::Mat frame;
    std::vector<Object> objects;
    cv::VideoCapture cap;
    ncnn::Net yolov4;
    const char* devicepath;
    int target_size = 0;
    int is_streaming = 0;


    if (argc < 2)
    {
        fprintf(stderr, "Usage: %s [v4l input device or image]\n", argv[0]);
        return -1;
    }


    devicepath = argv[1];


#ifdef NCNN_PROFILING
    double t_load_start = ncnn::get_current_time();
#endif
    int ret = init_yolov4(&yolov4, &target_size); //We load model and param first!
    if (ret != 0)
    {
        fprintf(stderr, "Failed to load model or param, error %d", ret);
        return -1;
    }


#ifdef NCNN_PROFILING
    double t_load_end = ncnn::get_current_time();
    fprintf(stdout, "NCNN Init time %.02lfms\n", t_load_end - t_load_start);


#endif
    if (strstr(devicepath, "/dev/video") == NULL)
    {
        frame = cv::imread(argv[1], 1);
        if (frame.empty())
        {
            fprintf(stderr, "Failed to read image %s.\n", argv[1]);
            return -1;
        }
    }
    else
    {
        cap.open(devicepath);


        if (!cap.isOpened())
        {
            fprintf(stderr, "Failed to open %s", devicepath);
            return -1;
        }
        cap >> frame;
        if (frame.empty())
        {
            fprintf(stderr, "Failed to read from device %s.\n", devicepath);
            return -1;
        }
        is_streaming = 1;
    }
    while (1)
    {
        if (is_streaming)
        {
#ifdef NCNN_PROFILING
            double t_capture_start = ncnn::get_current_time();
#endif
            cap >> frame;


#ifdef NCNN_PROFILING
            double t_capture_end = ncnn::get_current_time();
            fprintf(stdout, "NCNN OpenCV capture time %.02lfms\n", t_capture_end - t_capture_start);
#endif
            if (frame.empty())
            {
                fprintf(stderr, "OpenCV Failed to Capture from device %s\n", devicepath);
                return -1;
            }
        }


#ifdef NCNN_PROFILING
        double t_detect_start = ncnn::get_current_time();
#endif
        detect_yolov4(frame, objects, target_size, &yolov4); //Create an extractor and run detection


#ifdef NCNN_PROFILING
        double t_detect_end = ncnn::get_current_time();
        fprintf(stdout, "NCNN detection time %.02lfms\n", t_detect_end - t_detect_start);
#endif
#ifdef NCNN_PROFILING
        double t_draw_start = ncnn::get_current_time();
#endif
        draw_objects(frame, objects, is_streaming); //Draw detection results on opencv image


#ifdef NCNN_PROFILING
        double t_draw_end = ncnn::get_current_time();
        fprintf(stdout, "NCNN OpenCV draw result time %.02lfms\n", t_draw_end - t_draw_start);
#endif
        if (!is_streaming)
        {   //If it is a still image, exit!
            return 0;
        }
    }
    return 0;
}

果然大佬就是大佬,写的代码高深莫测,我只是一个小白,好难

729bc15ab21c9154940cece44b7991ea.png

靠,第二天直接不看了,重新写了一个main函数,调用大佬写的那几个function:

int main(int argc, char** argv)
{
    cv::Mat frame;
    std::vector<Object> objects;
    cv::VideoCapture cap;
    ncnn::Net yolov4;
    const char* devicepath;
    int target_size = 160;
    int is_streaming = 0;
    /*
    const char* imagepath = "E:/ncnn/yolov5/person.jpg";


    cv::Mat m = cv::imread(imagepath, 1);
    if (m.empty())
    {
        fprintf(stderr, "cv::imread %s failed\n", imagepath);
        return -1;
    }


    double start = GetTickCount();
    std::vector<Object> objects;
    detect_yolov5(m, objects);
    double end = GetTickCount();
    fprintf(stderr, "cost time:  %.5f\n ms", (end - start)/1000);


    draw_objects(m, objects);


    */
    int ret = init_yolov4(&yolov4, &target_size); //We load model and param first!
    if (ret != 0)
    {
        fprintf(stderr, "Failed to load model or param, error %d", ret);
        return -1;
    }


    cv::VideoCapture capture;
    capture.open(0);  //修改这个参数可以选择打开想要用的摄像头


    //cv::Mat frame;
    while (true)
    {
        capture >> frame;
        cv::Mat m = frame;
        double start = GetTickCount();
        std::vector<Object> objects;
        detect_yolov4(frame, objects, 160, &yolov4);
        double end = GetTickCount();
        fprintf(stderr, "cost time:  %.5f ms \n", (end - start));
        // imshow("外接摄像头", m);  //remember, imshow() needs a window name for its first parameter
        draw_objects(m, objects, 8);


        if (cv::waitKey(30) >= 0)
            break;
    }


    return 0;
}

还有几点注意,大家在进行推理的时候

把fp16禁掉,不用了 

换成int8推理 

把线程改成你之前制作int8模型的那个线程 

模型也替换掉

具体如下:

db844888c50de7fbc93f0faea26596ee.jpeg
代码需要修改的几点

走到这里,就可以愉快的推理了

a8c7490b01957524c7d649ab17c957dd.jpeg
推理效果展示

四、总结

说一下我的电脑配置,神舟笔记本K650D-i5,处理器InterCorei5-4210M,都是相对过时的老机器了,毕竟买了6年,性能也在下降。

跑库过程全程用cpu,为什么不用gpu?(问的好,2g显存老古董跑起来怕电脑炸了)

对比之前的fp16模型,明显在input_size相同的情况下快了40%-70%,且精度几乎没有什么损耗

总结来说,新版ncnn的int8量化推理确实是硬货,后续会尝试更多模型的int8推理,做对比实验给各位网友看

所有的文件和修改后的代码放在这个仓库里,欢迎大家白嫖:https://github.com/pengtougu/ncnn-yolov4-int8

感兴趣的朋友可以git clone下载跑跑,即下即用(前提要安装好ncnn)

往期回顾

手把手教学!TensorRT部署实战:YOLOv5的ONNX模型部署

b79867e1e64f7493add10d449e8ea48a.png

自动驾驶之心】全栈技术交流群

自动驾驶之心是首个自动驾驶开发者社区,聚焦目标检测、语义分割、全景分割、实例分割、关键点检测、车道线、目标跟踪、3D目标检测、BEV感知、多传感器融合、SLAM、光流估计、深度估计、轨迹预测、高精地图、规划控制、模型部署落地、自动驾驶仿真测试、硬件配置、AI求职交流等方向;

53efa4d367345ab981d4ceb2f8987408.jpeg

添加汽车人助理微信邀请入群

备注:学校/公司+方向+昵称

自动驾驶之心【知识星球】

想要了解更多自动驾驶感知(分类、检测、分割、关键点、车道线、3D目标检测、多传感器融合、目标跟踪、光流估计、轨迹预测)、自动驾驶定位建图(SLAM、高精地图)、自动驾驶规划控制、领域技术方案、AI模型部署落地实战、行业动态、岗位发布,欢迎扫描下方二维码,加入自动驾驶之心知识星球(三天内无条件退款),日常分享论文+代码,这里汇聚行业和学术界大佬,前沿技术方向尽在掌握中,期待交流!

9b0b918bd166776348259cada3016a5f.jpeg

您可能感兴趣的与本文相关的镜像

Yolo-v5

Yolo-v5

Yolo

YOLO(You Only Look Once)是一种流行的物体检测和图像分割模型,由华盛顿大学的Joseph Redmon 和Ali Farhadi 开发。 YOLO 于2015 年推出,因其高速和高精度而广受欢迎

<think>我们被要求提供一个使用darknet结合ncnn框架,在C++中实现YOLOv3模型GPU推理的示例。注意,ncnn本身是一个独立的前向推理框架,并不直接依赖darknet,但我们可以将darknet训练的YOLOv3模型转换为ncnn格式,然后使用ncnn进行推理。 步骤概述: 1. 将darknet格式的YOLOv3模型(.cfg.weights)转换为ONNX格式。 2. 使用ncnn提供的工具将ONNX模型转换为ncnn格式(.param.bin)。 3. 在C++代码中加载ncnn模型,并使用ncnn的GPU推理功能进行推理。 由于ncnn直接支持YOLOv3,我们可以参考ncnn官方提供的yolov3例子:https://github.com/Tencent/ncnn/tree/master/examples 但是,用户要求使用darknet结合ncnn,这里我们理解为使用darknet训练的模型,然后通过ncnn进行推理。 详细步骤: 1. 模型转换 a. 使用darknet2onnx工具将darknet模型转换为ONNX模型。我们可以使用以下开源工具:https://github.com/linghu8812/darknet2onnx 或者使用pytorch的转换方法(需要先加载darknet模型再导出为ONNX)。 b. 使用ncnn提供的onnx2ncnn工具将ONNX模型转换为ncnn模型2. C++代码实现 a. 初始化ncnn网络,并设置使用Vulkan(GPU)推理。 b. 加载转换后的模型(.param.bin文件)。 c. 对输入图像进行预处理(调整大小、归一化等)。 d. 使用ncnn的Extractor进行前向推理。 e. 解析输出层,获取检测框类别概率(YOLOv3输出为多个尺度的特征图,需要后处理)。 3. 后处理 a. 对多个尺度的输出进行解码,生成候选框。 b. 应用非极大值抑制(NMS)去除重叠框。 下面是一个简化的C++示例代码,展示如何使用ncnn进行YOLOv3的GPU推理: 注意:这个示例假设你已经成功将YOLOv3模型转换为ncnn格式,并且模型有三个输出(对应于三个尺度的特征图)。 代码示例:</think>下面是一个使用darknet结合ncnn框架,在C++中实现YOLOv3模型GPU推理的完整示例: ```cpp #include <ncnn/gpu.h> #include <ncnn/net.h> #include <opencv2/opencv.hpp> // YOLOv3模型参数 const float conf_threshold = 0.5; const float nms_threshold = 0.4; const int input_size = 416; // 加载模型并进行推理 void yolov3_inference(cv::Mat& image) { // 初始化ncnn网络并使用Vulkan GPU加速 ncnn::Net net; net.opt.use_vulkan_compute = true; // 启用Vulkan GPU加速 net.opt.use_fp16_packed = true; // 使用FP16优化[^4] // 加载转换后的ncnn模型 net.load_param("yolov3.param"); net.load_model("yolov3.bin"); // 图像预处理 cv::Mat resized; cv::resize(image, resized, cv::Size(input_size, input_size)); ncnn::Mat in = ncnn::Mat::from_pixels(resized.data, ncnn::Mat::PIXEL_BGR, input_size, input_size); // 归一化处理 (与Darknet训练一致) const float mean_vals[3] = {0, 0, 0}; const float norm_vals[3] = {1/255.f, 1/255.f, 1/255.f}; in.substract_mean_normalize(mean_vals, norm_vals); // GPU推理 ncnn::Extractor ex = net.create_extractor(); ex.input("data", in); // 获取三个尺度的输出层 (YOLOv3特征金字塔) ncnn::Mat out; std::vector<ncnn::Mat> outputs; ex.extract("yolo_82", outputs[0]); // 大目标检测层 ex.extract("yolo_94", outputs[1]); // 中目标检测层 ex.extract("yolo_106", outputs[2]); // 小目标检测层 // 后处理 - 解析检测结果 std::vector<cv::Rect> boxes; std::vector<float> confidences; std::vector<int> class_ids; for (int i = 0; i < 3; i++) { ncnn::Mat& output = outputs[i]; for (int j = 0; j < output.h; j++) { const float* values = output.row(j); // 获取类别概率 cv::Mat scores = cv::Mat(1, output.w - 5, CV_32FC1, values + 5); cv::Point class_id; double max_conf; cv::minMaxLoc(scores, 0, &max_conf, 0, &class_id); if (max_conf > conf_threshold) { // 解析边界框 (Darknet格式: center_x, center_y, width, height) float center_x = values[0] * image.cols; float center_y = values[1] * image.rows; float width = values[2] * image.cols; float height = values[3] * image.rows; // 转换为OpenCV格式 (x, y, w, h) cv::Rect box; box.x = center_x - width / 2; box.y = center_y - height / 2; box.width = width; box.height = height; boxes.push_back(box); confidences.push_back(max_conf); class_ids.push_back(class_id.x); } } } // 应用非极大值抑制(NMS) std::vector<int> indices; cv::dnn::NMSBoxes(boxes, confidences, conf_threshold, nms_threshold, indices); // 绘制检测结果 for (size_t i = 0; i < indices.size(); i++) { int idx = indices[i]; cv::rectangle(image, boxes[idx], cv::Scalar(0, 255, 0), 2); cv::putText(image, cv::format("%.2f", confidences[idx]), cv::Point(boxes[idx].x, boxes[idx].y - 5), cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 255, 0), 1); } } int main() { // 初始化Vulkan GPU环境 ncnn::create_gpu_instance(); cv::Mat image = cv::imread("test.jpg"); if (image.empty()) return -1; yolov3_inference(image); cv::imshow("YOLOv3 Detection", image); cv::waitKey(0); // 清理GPU资源 ncnn::destroy_gpu_instance(); return 0; } ``` ### 关键步骤说明: 1. **模型转换流程**: $$ \text{Darknet(.cfg+.weights)} \xrightarrow{\text{darknet2onnx}} \text{ONNX} \xrightarrow{\text{onnx2ncnn}} \text{ncnn(.param+.bin)} $$ - 使用`darknet2onnx`工具转换模型 - 使用ncnn提供的`onnx2ncnn`完成最终转换 2. **GPU加速配置**: ```cpp net.opt.use_vulkan_compute = true; // 启用Vulkan GPU net.opt.use_fp16_packed = true; // FP16加速[^4] ``` 3. **YOLOv3特有处理**: - 同时处理三个尺度输出层(82,94,106)[^2] - 使用Darknet风格的归一化参数: ```cpp const float norm_vals[3] = {1/255.f, 1/255.f, 1/255.f}; ``` 4. **性能优化建议**: - 使用ncnn的`paramoptimizer`优化模型结构 - 启用内存池减少分配开销: ```cpp net.opt.use_packing_layout = true; // 内存优化[^4] ``` - 对于移动端部署,考虑INT8量化(需配合校准数据集) ### 编译说明 使用以下CMake配置(需安装Vulkan SDK): ```cmake cmake_minimum_required(VERSION 3.10) project(YOLOv3_ncnn) find_package(OpenCV REQUIRED) find_package(ncnn REQUIRED) add_executable(yolov3_demo main.cpp) target_link_libraries(yolov3_demo ncnn ${OpenCV_LIBS}) ``` > **注意**:实际部署时,需要根据目标平台编译ncnn的Vulkan支持版本,Android平台需在`AndroidManifest.xml`中添加`<uses-feature android:name="android.hardware.vulkan.level" android:required="true"/>`[^1]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值