YOLOv4 NCNN 量化模型和实时推理

news2025/2/27 5:45:36

又搬来了大佬的啊  膜拜大佬

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)融合,实现端到端量化推理

二、新版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.csdn.net/download/weixin_45829462/18704213

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

ImageNet图像下载

  • 制作校准表文件

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

find images/ -type f > imagelist.txt

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

 生成图片名列表文件命令

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

图片名列表文件预览

接着继续输入命令:

./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文件夹下

 

量化工具所在目录

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

生成的量化模型

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

三、新版ncnn的int8量化再探

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

  • 调用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;}

因为没看明白~~所以重新写了一个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模型的那个线程 

模型也替换掉

 具体如下: 

 代码需要修改的几点

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

 推理效果展示

四、总结

说一下电脑配置,神处理器InterCorei5-4210M,都是相对过时的老机器了,买了6年,性能也在下降。

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

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

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

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

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

 whaosoft aiot http://143ai.com

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

代码随想录——字符串篇

1、反转字符串 344.反转字符串 力扣题目链接 编写一个函数&#xff0c;其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。 不要给另外的数组分配额外的空间&#xff0c;你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。 你可以假设数…

必知必会的Java多线程可算是被讲透彻了,让我们一起深入浅出多线程!

Java 提供了多线程编程的内置支持&#xff0c;让我们可以轻松开发多线程应用。 Java 中我们最为熟悉的线程就是 main 线程——主线程。 一个进程可以并发多个线程&#xff0c;每条线程并行执行不同的任务。线程是进程的基本单位&#xff0c;是一个单一顺序的控制流&#xff0c;…

计算机网络——HTTPS协议

目录 1、HTTPS是什么&#xff1f; 2、“加密”是什么&#xff1f; 3、HTTPS的加密机制 3.1、对称加密 3.2、非对称加密 3.3、“中间人问题” 1、HTTPS是什么&#xff1f; HTTPS是一个应用层协议&#xff0c;是在HTTP协议的基础上引入了一个加密层&#xff0c;进一步来保证…

23-职位分类展示平台响应式网页模板{HTML JS CSS)

扫码或搜索添加文末公众号「搞前端的半夏」&#xff1a;&#x1f357; 回复 ”网站模板“&#xff0c;免费送网站模板&#xff01; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 私信&#x1f4c4; 你还在未HTML网页设计作业头大吗&#xff1f; 你还在为自学前端&#xf…

【Vant Weapp】van-uploader 文件上传

目录 图片上传 图片回填 修改样式 循环多个上传图片 自定义上传的控件 图片上传 未上传样式&#xff1a; 上传限制6张后 &#xff1a; <van-uploader max-count"6" file-list"{{ fileList }}" bind:after-read"afterRead" bind:delete&…

【易忽视】方程两边同时平方会改变方程的解吗?【简洁证明】

问题提出 在做一道题的时候有这样一步&#xff1a;两边平方&#xff0c;最后求解&#xff0c;题很简单&#xff0c;但是这个方法是否能这么用引起了我的好奇。 思考 首先不妨假设一个通用的方程&#xff1a; f(x)g(x)f(x)g(x)f(x)g(x) 然后我们对方程两边平方&#xff1a; …

深度学习基础与线性回归实例

1、机器学习基础-线性回归 介绍&#xff1a;这是一个教育对收入影响的数据&#xff0c;从图像的走势来看&#xff0c;它是具有一个线性关系&#xff0c;即受教育年限越长收入越高&#xff0c;这样我们可以通直线来抽象出它们的关系。 接下来&#xff0c;我们将会介绍一些方法&…

Java中线程的状态

Java中线程的状态操作系统中线程的状态Java中线程的状态线程状态枚举类操作系统中线程的状态 从操作系统层面来看&#xff0c;线程通常有以下五种状态&#xff0c;前三种是线程的基本状态。 【运行态】&#xff1a;进程正处在处理机上运行&#xff0c;在单处理机环境下&#…

【学习笔记39】获取DOM标签对象

获取DOM标签对象一、认识DOM二、获取非常规DOM(html head body)1、HTML2、head3、body三、获取常规DOM&#xff08;一&#xff09;按照类名、标签名和ID名获取标签1、类名(伪数组)2、标签名(伪数组)3、ID名(唯一性)&#xff08;二&#xff09;按照选择器获取标签1、querySelect…

《人月神话》(The Mythical Man-Month)1 看清问题的本质:如果我们想解决问题,就必须试图先去理解它...

第一章 焦油坑&#xff08;The Tar Pit&#xff09;史前史中&#xff0c;没有比巨兽在焦油坑中垂死挣扎的场面更令人震撼的了。上帝见证着恐龙、猛犸象、剑齿虎在焦油中挣扎。它们挣扎得越是猛烈&#xff0c;焦油纠缠得越紧&#xff0c;没有任何猛兽足够强壮或具有足够的技巧&a…

IDEA注释配置程序员信息(带完整截图步骤,超级详细)

1.配置类注释的程序员信息 效果图 配置截图 模板 &#xff08;可根据需要进行位置调整及个数删除&#xff09; /***BelongsProject: ${PROJECT_NAME}*BelongsPackage: ${PACKAGE_NAME}*ClassName ${NAME}*Author: XUXIAN*CreateTime: ${YEAR}-${MONTH}-${DAY} ${HOUR}:${MINU…

这可能是最权威、最全面的Go语言编码风格规范了!

每种编程语言除了固定的语法之外&#xff0c;都会有属于自己的地道的(idiomatic)写法。其实&#xff0c;自然语言也不例外&#xff0c;你想&#xff0c;你用心想想是不是这样。语言的设计者们希望开发人员都能编写统一风格的地道的代码&#xff0c;这样不仅代码可读性好&#x…

细分图中的可到达节点 : 常规最短路运用题

题目描述 Tag : 「最短路」、「单源最短路」、「Dijkstra」、「SPFA」 给你一个无向图&#xff08;原始图&#xff09;&#xff0c;图中有 n 个节点&#xff0c;编号从 0 到 n - 1 。你决定将图中的每条边 细分 为一条节点链&#xff0c;每条边之间的新节点数各不相同。 图用…

[前端框架]-VUE(上篇)

Vue (读音 /vjuː/&#xff0c;类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是&#xff0c;Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层&#xff0c;不仅易于上手&#xff0c;还便于与第三方库或既有项目整合。另一方面&#xff0…

链表经典算法题目

1.回文链表 编写一个函数&#xff0c;检查输入的链表是否是回文的。 示例 1&#xff1a; 输入&#xff1a; 1->2 输出&#xff1a; false 示例 2&#xff1a; 输入&#xff1a; 1->2->2->1 输出&#xff1a; true 笔试的写法 重点是快速code,不考虑空间复杂度…

蒙特卡洛法(Monte Carlo)电动汽车负荷预测matlab程序设计

电动汽车充电负荷的时间分布预测 规模化电动汽车充电负荷在未来某一天随时间特性的分布规律是研究电动汽车发展对配 电网影响以及充电站选址定容问题的前提与基础。电动汽车充电负荷的分布情况与车主的行 为特征有关&#xff0c;不同类型的电动汽车车主出行规律以及充电习惯不…

<Linux系统复习>信号

一、本章重点 1、什么是信号&#xff1f; 2、查看信号列表 3、信号捕捉 4、信号产生的5种方式 5、介绍CoreDump 6、信号处理的方式 7、如何理解信号产生到处理的过程 8、sigpending、sigprocmask、sigaction函数的使用 9、信号处理的时机 10、SIGCHLD信号 11、可重入函数 01 什…

Codeforces Round 836 (Div. 2) A - C

A:SSeeeeiinngg DDoouubbllee 题意&#xff1a;给定一个字符串&#xff0c;每个字符串的字符可以出现两次&#xff0c;要求通过重新排列构造一个回文串。 思路&#xff1a;直接暴力可以&#xff0c;每个字符头部一个尾部一个。 #include<cstdio> #include <iostream…

不使用实体类的情况下接收SQL查询结果、@Autowired注入为null解决

目录 一、场景 二、环境 三、使用 1、数据库表以及数据准备 2、项目导入必要依赖 3、添加连接数据库配置文件 4、编写测试方法 5、执行结果 四、将SQL单独提取出来 2.1 定义查询接口方法 2.2 测试 2.3 测试结果 五、问题记录&#xff1a; Autowired注入失败/null的…

b、B、KB、Kib、MB、MiB、GB、GiB、TB、TiB的区别

1024这个数字&#xff0c;想必计算机行业从业人员应该不会陌生&#xff0c;甚至10月24日还被当做程序员日&#xff0c;如果你问一个程序员1GB等于多少MB,他大概率会不假思索回答:1024。 没错&#xff0c;对于稍微对计算机或者网络有了解的人&#xff0c;一般都认为1024是数据容…