ncnn之resnet图像分类网络模型部署

2024-09-01 10:44

本文主要是介绍ncnn之resnet图像分类网络模型部署,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

    • 一、ONNX模型导出
    • 二、模型转换ONNX2NCNN
      • 2.1 net.param文件
      • 2.2 net.bin文件
    • 三、ncnn模型推理
    • 四、编译与运行

一、ONNX模型导出

将 PyTorch 模型导出为 ONNX 格式,代码如下:

import torch
import torchvision.models as models
import torch.onnx as onnx# 加载预训练的ResNet-18模型
# 这里使用了ResNet-18的预训练权重,但不再使用 `pretrained=True` 参数
# # resnet = models.resnet18(pretrained=True)
# 而是采用了新的 'weights' 参数来指定预训练的权重集
# 'ResNet18_Weights.IMAGENET1K_V1' 对应于在ImageNet数据集上训练的权重
resnet = models.resnet18(weights='ResNet18_Weights.IMAGENET1K_V1')# 将模型设置为评估模式, 模型中的Dropout层和BatchNorm层将被禁用或固定
resnet.eval()# 创建一个形状为 (1, 3, 224, 224) 的随机输入张量,模拟单张图片输入模型
dummy_input = torch.randn(1, 3, 224, 224)# 使用torch.onnx.export函数导出模型为ONNX格式
# 第一个参数是要导出的模型(resnet)
# 第二个参数是示例输入张量(dummy_input),用于定义模型的输入大小和形状
# 第三个参数是导出的ONNX模型文件的保存路径
onnx_file_path = "../model_param/resnet18.onnx"
onnx.export(resnet, dummy_input, onnx_file_path)print("ResNet-18模型已成功导出为ONNX格式:", onnx_file_path)

导出 ONNX 的过程相对简单。提供一个示例输入,然后通过执行一次模型的前向传播,将模型的计算图结构保存下来。执行这段代码后,resnet18.onnx 文件就会被保存到 model_param/ 目录下。大致步骤如下:

  • 加载预训练的ResNet-18模型

  • **模型设置为评估模式:**resnet.eval() 将模型切换到评估模式确保模型在推理阶段(即模型预测阶段)不会对内部状态进行更新,比如 BatchNorm 层和 Dropout 层。

  • 创建示例输入张量:dummy_input = torch.randn(1, 3, 224, 224)生成一个随机输入张量,代表一张3通道的224x224像素的图像

  • **导出为ONNX格式:**使用 torch.onnx.export 函数将 PyTorch 模型转换并保存为 ONNX 格式。

使用pretrained的警告:

UserWarning: The parameter 'pretrained' is deprecated since 0.13 and may be removed in the future, please use 'weights' instead.warnings.warn(
UserWarning: Arguments other than a weight enum or `None` for 'weights' are deprecated since 0.13 and may be removed in the future. The current behavior is equivalent to passing `weights=ResNet18_Weights.IMAGENET1K_V1`. You can also use `weights=ResNet18_Weights.DEFAULT` to get the most up-to-date weights.warnings.warn(msg)

在0.13版本后,开始使用weights参数,要解决关于 Torchvision 中pretrained参数弃用的警告,可以将代码中的 pretrained=True参数替换为 weights 参数:weights=ResNet18_Weights.IMAGENET1K_V1或者weights=ResNet18_Weights.DEFAULT

二、模型转换ONNX2NCNN

NCNN 提供了一个模型转换工具,可以将 ONNX 格式的模型转换为 NCNN 支持的格式。所有模型转换的源代码都在 ncnn/tools 目录下,在编译后,这些工具的可执行文件会生成在 build/tools/ 目录下。

使用 onnx2ncnn 工具来转换模型,具体命令如下:

bin/onnx2ncnn model_param/resnet18.onnx model_param/resnet18.param model_param/resnet18.bin

这条命令将 resnet18.onnx 模型转换为 resnet18.paramresnet18.bin 两个文件:

  • resnet18.param:记录的是计算图的结构,即模型的参数信息
  • resnet18.bin:存放的是模型的所有具体参数

通过这一步,模型就从 ONNX 格式成功转换为了 NCNN 可以使用的格式,接下来可以在 NCNN 上进行推理任务。

image-20240823210359677

2.1 net.param文件

resnet18.param中部分参数如下:

7767517
58 66
Input            input.1                  0 1 input.1
Convolution      /conv1/Conv              1 1 input.1 /conv1/Conv_output_0 0=64 1=7 11=7 2=1 12=1 3=2 13=2 4=3 14=3 15=3 16=3 5=1 6=9408
ReLU             /relu/Relu               1 1 /conv1/Conv_output_0 /relu/Relu_output_0
Pooling          /maxpool/MaxPool         1 1 /relu/Relu_output_0 /maxpool/MaxPool_output_0 0=0 1=3 11=3 2=2 12=2 3=1 13=1 14=1 15=1 5=1
Split            splitncnn_0              1 2 /maxpool/MaxPool_output_0 /maxpool/MaxPool_output_0_splitncnn_0 /maxpool/MaxPool_output_0_splitncnn_1
Convolution      /layer1/layer1.0/conv1/Conv 1 1 /maxpool/MaxPool_output_0_splitncnn_1 /layer1/layer1.0/conv1/Conv_output_0 0=64 1=3 11=3 2=1 12=1 3=1 13=1 4=1 14=1 15=1 16=1 5=1 6=36864
ReLU             /layer1/layer1.0/relu/Relu 1 1 /layer1/layer1.0/conv1/Conv_output_0 /layer1/layer1.0/relu/Relu_output_0
.......
Pooling          /avgpool/GlobalAveragePool 1 1 /layer4/layer4.1/relu_1/Relu_output_0 /avgpool/GlobalAveragePool_output_0 0=1 4=1
Flatten          /Flatten                 1 1 /avgpool/GlobalAveragePool_output_0 /Flatten_output_0
InnerProduct     /fc/Gemm                 1 1 /Flatten_output_0 191 0=1000 1=1 2=512000
param:
[magic]
[layer count] [blob count]
[layer type] [layer name] [input count] [output count] [input blobs] [output blobs] [layer specific params]

首先第一行是一个magic数字,用于标识文件的格式。对于ncnn格式,7767517用于确认文件的正确性和格式。 magic数字是特定的数值或字符序列,用于文件的开头,帮助程序识别文件类型。

第二行是两个数组58 66分别表示Layer(算子)和Blob的个数,即模型中层(Layer)和数据块(Blob)的数量。

  • 58: 表示模型中的总层数。层是模型的基本组成单位,例如卷积层、池化层等。

  • 66: 表示模型中数据块的总数。数据块通常包括模型参数(如权重)和中间计算结果。

    image-20240823212116408

使用netron打开resnet18.param可以看到resnet18的结构,如上图所示,其中像Convolution,ReLU,Pooling,Split,BinaryOp都是一个算子也就是layer。**blob在网络中主要用于存储中间数据、输入数据和输出数据。**以Split算子为例,它有1个输入2个输出,则一共有3个blob。而Convolution和Relu等算子它输入输出都是1个blob。

每个算子(layer)可能会涉及多个blob,因为算子可以有多个输入和输出,而每个blob都代表一个数据存储单元。在查看模型文件时,通常blob的数量会比layer的数量多,因为网络中会有许多中间计算结果需要存储。

Layer和Blob的区别

  • Layer(层):
    • 定义: 模型中的每一层代表一个操作或计算单元。例如,卷积层、池化层、激活层等。
    • 作用: 每一层执行特定的计算任务,将输入数据转换为输出数据。层定义了模型的结构和计算流程。
  • Blob(数据块):
    • 定义: Blob是模型中存储数据的单位。包括模型的权重、偏置以及中间计算结果等。
    • 作用: Blob用于存储和传递数据。模型的每一层可能有输入Blob和输出Blob,数据在这些Blob之间流动。

接下来分析剩下的每一行的格式时,可以看到这些行都遵循类似的结构,它们描述了模型中的各个层或操作及其参数。通常格式如下:

[layer type] [layer name] [input count] [output count] [input blobs] [output blobs] [layer specific params]
  • 层类型 (Layer Type): 层的操作类型,例如 Convolution、Softmax 等。
  • 层名称 (Layer Name): 该层的唯一名称。
  • 输入数量 (Input Count): 该层需要的输入 blob 数量。
  • 输出数量 (Output Count): 该层产生的输出 blob 数量。
  • 输入 blob (Input Blobs): 该层输入 blob 的名字列表,名称之间用空格分隔。
  • 输出 blob (Output Blobs): 该层输出 blob 的名字列表,名称之间用空格分隔。
  • 层特定参数 (Layer Specific Params): 该层的参数信息,以 key=value 的形式列出,参数之间用空格分隔。

层参数 (Layer Param)格式如下:

0=1 1=2.5 -23303=2,2.0,3.0
key=value 的形式

在每一层的行中,键索引应该是唯一的。如果使用了默认值,则可以省略键值对,可以在 operation-param-weight-table中查找已有的参数键索引的含义,如下图所示。

image-20240823214928129
Convolution conv1 1 1 input.1 conv1_weight 0=64 1=7 2=1 3=2 4=3 5=64 6=3 7=448 8=1 9=1 10=0
  • Convolution: 操作类型,表示这是一个卷积层。

  • conv1: 该层的名称。

  • 1: 输入个数。

  • 1: 输出个数。

  • input.1 conv1_weight: 输入的张量名称,input.1是输入数据,conv1_weight是卷积核权重。

  • 0=64: 输出通道数64。

  • 1=7: 卷积核大小7x7。

以上这些对ncnn的使用者来说,主要需要关注的是整个模型的输入输出。对于当前这个网络来说,整个网络的输入blob名字是input.1,输出blob是191,这些信息在写推理代码的时候会用到。

2.2 net.bin文件

  +---------+---------+---------+---------+---------+---------+| weight1 | weight2 | weight3 | weight4 | ....... | weightN |+---------+---------+---------+---------+---------+---------+^         ^         ^         ^0x0      0x80      0x140     0x1C0

模型二进制文件是所有==权重数据(Weight Data)==的串联,每个权重缓冲区按 32 位对齐。

权重缓冲区 (Weight Buffer)格式如下所示:

[flag] (optional) [raw data] [padding] (optional)
  • flag :无符号整数,小端序,表示权重存储类型,0 => float32,0x01306B47 => float16,否则 => 量化 int8,如果层实现强制明确存储类型,则可以省略。
  • Raw Data原始数据:原始权重数据,小端数据,float32 数据或 float16 数据或量化表和索引,具体取决于存储类型标志
  • padding:32 位对齐的填充空间,如果已经对齐,则可以省略

net.param文件包含网络的层信息和参数,而 net.bin文件则包含权重数据。

参考文档:https://ncnn.readthedocs.io/en/latest/developer-guide/param-and-model-file-structure.html#layer-param

三、ncnn模型推理

#include "net.h"
#include <algorithm>
#if defined(USE_NCNN_SIMPLEOCV)
#include "simpleocv.h"
#else
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#endif
#include <stdio.h>
#include <vector>// 模型检测函数,输入图像是BGR格式,输出是类别得分的向量
static int detect_resnet18(const cv::Mat& bgr, std::vector<float>& cls_scores)
{ncnn::Net resnet18;  // 创建ncnn::Net实例以加载模型// 启用Vulkan计算以使用GPU加速resnet18.opt.use_vulkan_compute = true;// 加载模型的参数和权重if (resnet18.load_param("/home/xiaochao/Infer/NCNN/use_ncnn/resnet18/model_param/resnet18.param"))exit(-1);if (resnet18.load_model("/home/xiaochao/Infer/NCNN/use_ncnn/resnet18/model_param/resnet18.bin"))exit(-1);// opencv读取图片是BGR格式,需要转换为RGB格式,并调整大小为224x224ncnn::Mat in = ncnn::Mat::from_pixels_resize(bgr.data, ncnn::Mat::PIXEL_BGR2RGB, bgr.cols, bgr.rows, 224, 224);// 使用均值和标准差值对图像进行归一化处理// 图像归一标准化,以R通道为例(x/225-0.485)/0.229,化简后可以得到下面的式子// 需要注意的是substract_mean_normalize里的标准差其实是标准差的倒数,这样在算的时候就可以将除法转换为乘法计算// 所以norm_vals里用的是1除const float mean_vals[3] = {0.485f*255.f, 0.456f*255.f, 0.406f*255.f};const float norm_vals[3] = {1/0.229f/255.f, 1/0.224f/255.f, 1/0.225f/255.f};in.substract_mean_normalize(mean_vals, norm_vals);// 创建一个网络提取器,用于输入数据和提取结果ncnn::Extractor ex = resnet18.create_extractor();// 将归一化后的图像数据输入到网络的"input.1" blob中ex.input("input.1", in);ncnn::Mat out;// 提取出推理结果,推理结果存放在191这个blob里 该结果包含类别得分ex.extract("191", out);// 调整输出向量的大小以存储类别得分cls_scores.resize(out.w);for (int j = 0; j < out.w; j++){cls_scores[j] = out[j];  // 将每个得分存储到向量中}return 0;
}// 打印得分最高的前k个类别及其对应的索引
static int print_topk(const std::vector<float>& cls_scores, int topk)
{int size = cls_scores.size();std::vector<std::pair<float, int> > vec(size);// 将得分与其对应的索引存储到一个pair向量中for (int i = 0; i < size; i++){	// [score,index]vec[i] = std::make_pair(cls_scores[i], i);}// 对向量进行部分排序,以获取前k个最高得分,按降序排列std::partial_sort(vec.begin(), vec.begin() + topk, vec.end(),std::greater<std::pair<float, int> >());// 打印前k个得分及其对应的类别索引for (int i = 0; i < topk; i++){float score = vec[i].first;int index = vec[i].second;fprintf(stderr, "%d = %f\n", index, score);}return 0;
}int main(int argc, char** argv)
{// 检查是否提供了正确数量的参数if (argc != 2){fprintf(stderr, "Usage: %s [imagepath]\n", argv[0]);return -1;}const char* imagepath = argv[1];// 使用OpenCV读取输入图像cv::Mat m = cv::imread(imagepath, 1);if (m.empty()){fprintf(stderr, "cv::imread %s failed\n", imagepath);return -1;}std::vector<float> cls_scores;// 检测图像中的对象,并获取类别得分detect_resnet18(m, cls_scores);// 打印得分最高的前三个类别print_topk(cls_scores, 3);return 0;
}

resnet18网络推理的主要流程步骤如下:

  1. 图像加载以及图像预处理

    使用OpenCV加载推理图像,将BGR(cv::Mat)格式的图像转换为RGB(ncnn::Mat)格式,并调整为模型所需的输入尺寸的大小(224x224像素)。然后对图像进行归一化处理,和模型在训练时候的预处理操作一样,使用指定的均值和标准差对图像的像素值进行标准化。

  2. 加载ResNet-18模型

    使用ncnn库加载ResNet-18模型的参数文件(.param)和权重文件(.bin)。启用Vulkan计算以利用GPU加速推理过程。

  3. 进行推理

    将预处理后的图像数据输入到ResNet-18模型中,并运行前向传播以获取模型的输出结果。输出结果是一个包含每个类别得分的向量。

  4. 处理并输出推理结果

再来看看归一化处理操作, imagenet图片三通道的均值和标准差分别是mean=[0.485, 0.456, 0.406],std=[0.229, 0.224, 0.225]。以R通道为例,原始图片的像素值是从0到255,所以像素值归一化即像x/255,减去均值再除以标准差就是==(x/255-0.485)/0.299==,把255乘下去也就是==(x-0.485×255)/255×0.299==。如果把归一化和标准化一起处理的话,等价均值就是0.485×255,等价标准差就是255×0.299。但由于substract_mean_normalize里的标准差实际是标准差的倒数,这样可以把除法转换为乘法来计算加快效率,所以这里norm_vals用的是标准差的倒数。

const float mean_vals[3] = {0.485f*255.f, 0.456f*255.f, 0.406f*255.f};
const float norm_vals[3] = {1/0.229f/255.f, 1/0.224f/255.f, 1/0.225f/255.f};
in.substract_mean_normalize(mean_vals, norm_vals);

类别下载:https://storage.googleapis.com/download.tensorflow.org/data/imagenet_class_index.json

四、编译与运行

image-20240824095521810

整个工程如上所示,使用CMakeLists.txt来构建整个工程,bin存放可执行程序,比如onnx2ncnn,程序生成的可执行文件。build存放CMakeLists编译后的文件。image存放图片,model_param存储网络结构和参数相关的文件,python存放导出onnx模型的脚步,src存放推理的源文件。

CMakeLists代码如下:

# 指定CMake的最低版本要求为2.8.12
cmake_minimum_required(VERSION 2.8.12)# 定义项目名称为 "NCNN_RESNET18"
project(NCNN_RESNET18)# 设置构建类型为Debug模式,便于调试
set(CMAKE_BUILD_TYPE Debug)# 指定NCNN库的安装路径,并添加到CMake的查找路径中
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} "/home/xiaochao/Infer/NCNN/ncnn/build/install")# 查找并加载NCNN库,要求该库必须存在
find_package(ncnn REQUIRED)# 查找并加载OpenCV库,要求该库必须存在
find_package(OpenCV REQUIRED)# 设置生成的可执行文件的输出目录为项目的bin文件夹
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin)# 添加可执行文件 "resnet18",其源文件为 "src/resnet18.cpp"
add_executable(resnet18 src/resnet18.cpp)# 链接必要的库文件,即ncnn和OpenCV库
target_link_libraries(resnet18 ncnn ${OpenCV_LIBS})

运行结果如下:

image-20240824100755238

可以看出输出了三个类别263,264,151,可以对imageneg类别中查找分别对应的标签。

这篇关于ncnn之resnet图像分类网络模型部署的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1126709

相关文章

闲置电脑也能活出第二春?鲁大师AiNAS让你动动手指就能轻松部署

对于大多数人而言,在这个“数据爆炸”的时代或多或少都遇到过存储告急的情况,这使得“存储焦虑”不再是个别现象,而将会是随着软件的不断臃肿而越来越普遍的情况。从不少手机厂商都开始将存储上限提升至1TB可以见得,我们似乎正处在互联网信息飞速增长的阶段,对于存储的需求也将会不断扩大。对于苹果用户而言,这一问题愈发严峻,毕竟512GB和1TB版本的iPhone可不是人人都消费得起的,因此成熟的外置存储方案开

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

基于人工智能的图像分类系统

目录 引言项目背景环境准备 硬件要求软件安装与配置系统设计 系统架构关键技术代码示例 数据预处理模型训练模型预测应用场景结论 1. 引言 图像分类是计算机视觉中的一个重要任务,目标是自动识别图像中的对象类别。通过卷积神经网络(CNN)等深度学习技术,我们可以构建高效的图像分类系统,广泛应用于自动驾驶、医疗影像诊断、监控分析等领域。本文将介绍如何构建一个基于人工智能的图像分类系统,包括环境

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

Andrej Karpathy最新采访:认知核心模型10亿参数就够了,AI会打破教育不公的僵局

夕小瑶科技说 原创  作者 | 海野 AI圈子的红人,AI大神Andrej Karpathy,曾是OpenAI联合创始人之一,特斯拉AI总监。上一次的动态是官宣创办一家名为 Eureka Labs 的人工智能+教育公司 ,宣布将长期致力于AI原生教育。 近日,Andrej Karpathy接受了No Priors(投资博客)的采访,与硅谷知名投资人 Sara Guo 和 Elad G

阿里开源语音识别SenseVoiceWindows环境部署

SenseVoice介绍 SenseVoice 专注于高精度多语言语音识别、情感辨识和音频事件检测多语言识别: 采用超过 40 万小时数据训练,支持超过 50 种语言,识别效果上优于 Whisper 模型。富文本识别:具备优秀的情感识别,能够在测试数据上达到和超过目前最佳情感识别模型的效果。支持声音事件检测能力,支持音乐、掌声、笑声、哭声、咳嗽、喷嚏等多种常见人机交互事件进行检测。高效推

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

Retrieval-based-Voice-Conversion-WebUI模型构建指南

一、模型介绍 Retrieval-based-Voice-Conversion-WebUI(简称 RVC)模型是一个基于 VITS(Variational Inference with adversarial learning for end-to-end Text-to-Speech)的简单易用的语音转换框架。 具有以下特点 简单易用:RVC 模型通过简单易用的网页界面,使得用户无需深入了

透彻!驯服大型语言模型(LLMs)的五种方法,及具体方法选择思路

引言 随着时间的发展,大型语言模型不再停留在演示阶段而是逐步面向生产系统的应用,随着人们期望的不断增加,目标也发生了巨大的变化。在短短的几个月的时间里,人们对大模型的认识已经从对其zero-shot能力感到惊讶,转变为考虑改进模型质量、提高模型可用性。 「大语言模型(LLMs)其实就是利用高容量的模型架构(例如Transformer)对海量的、多种多样的数据分布进行建模得到,它包含了大量的先验

图神经网络模型介绍(1)

我们将图神经网络分为基于谱域的模型和基于空域的模型,并按照发展顺序详解每个类别中的重要模型。 1.1基于谱域的图神经网络         谱域上的图卷积在图学习迈向深度学习的发展历程中起到了关键的作用。本节主要介绍三个具有代表性的谱域图神经网络:谱图卷积网络、切比雪夫网络和图卷积网络。 (1)谱图卷积网络 卷积定理:函数卷积的傅里叶变换是函数傅里叶变换的乘积,即F{f*g}