以下内容将从 ncnn 简介、模型生成(从 PyTorch/ONNX 转换到 ncnn)、应用案例(C++ 推理示例) 三个部分进行介绍,并附带示例代码和详细说明,帮助你理解并上手使用 ncnn。
一、ncnn 简介
ncnn 是由腾讯优图团队开源的高性能神经网络推理框架,专为移动端和嵌入式设备上高效运行推理而设计,具有以下特点:
- 跨平台:支持多种操作系统(Android、iOS、Linux、Windows、Mac 等),并可轻松进行移植。
- 高效率:针对 ARM 设备做了大量优化,能够在移动端以较小的二进制体积、高速运行深度学习模型。
- 轻量级:使用 C++ 实现,不依赖第三方框架,在移动端部署时占用空间小。
- 易集成:提供了 C++ API 和 Android NDK 相关的支持,便于快速接入已有项目。
ncnn 框架主要解决了在移动端对训练好的深度学习模型进行推理的问题。它不包含训练部分(通常在服务器/PC 端完成训练后再转换模型到 ncnn),它的主要流程如下:
- 在 PC 端使用 PyTorch、TensorFlow 等框架对模型进行训练和导出(一般导出为 ONNX 或者 caffe 模式)。
- 使用 ncnn 提供的 模型转换工具(
onnx2ncnn
、caffe2ncnn
等),将 ONNX 或 Caffe 模型转换为 ncnn 专有的.param
(网络结构)和.bin
(权重)文件。 - 在移动端或嵌入式设备上,使用 ncnn 的 C++ 或 Java 接口加载
.param
和.bin
文件,通过 ncnn 的推理接口进行前向推理,从而得到推理结果。
二、ncnn 模型生成案例
这里以 PyTorch 训练一个简单分类模型 并 转换为 ONNX,再转换到 ncnn 为例,给出一个完整的流程示例。
2.1 在 PyTorch 中训练(或使用已有预训练模型)
下面的示例代码展示了如何用 PyTorch 定义一个简单的分类网络,并保存为 ONNX。你可以使用任意预训练模型,这里以一个示例网络为演示:
import torch
import torch.nn as nn
import torch.nn.functional as F
class SimpleNet(nn.Module):
def __init__(self, num_classes=10):
super(SimpleNet, self).__init__()
self.conv1 = nn.Conv2d(3, 16, kernel_size=3, padding=1)
self.conv2 = nn.Conv2d(16, 32, kernel_size=3, padding=1)
self.fc1 = nn.Linear(32 * 8 * 8, 128)
self.fc2 = nn.Linear(128, num_classes)
def forward(self, x):
x = F.relu(self.conv1(x)) # [N, 16, 32, 32]
x = F.max_pool2d(x, 2) # [N, 16, 16, 16]
x = F.relu(self.conv2(x)) # [N, 32, 16, 16]
x = F.max_pool2d(x, 2) # [N, 32, 8, 8]
x = x.view(x.size(0), -1) # flatten => [N, 32*8*8]
x = F.relu(self.fc1(x)) # [N, 128]
x = self.fc2(x) # [N, num_classes]
return x
if __name__ == "__main__":
# 假设已经训练完成的模型,这里只演示前向导出
model = SimpleNet(num_classes=10)
# 这里你通常会加载训练好的权重
# model.load_state_dict(torch.load("simple_net.pth"))
model.eval()
# 随机输入,模拟1张 RGB 32x32 的图像
dummy_input = torch.randn(1, 3, 32, 32)
# 导出 ONNX
torch.onnx.export(
model,
dummy_input,
"simple_net.onnx",
input_names=["input"],
output_names=["output"],
opset_version=11
)
print("ONNX model exported: simple_net.onnx")
- 说明:
- 在实际场景中,你需要使用真实数据进行训练,得到训练好的模型权重,再用
model.load_state_dict(torch.load(...))
来加载。 - 这里为了演示如何导出 ONNX,使用了随机输入
dummy_input
。 - 生成的文件
simple_net.onnx
就是后续 ncnn 转换所需要的输入文件。
- 在实际场景中,你需要使用真实数据进行训练,得到训练好的模型权重,再用
2.2 使用 ncnn 的转换工具将 ONNX 转换为 ncnn
- 需要从 ncnn 的仓库中获取
onnx2ncnn
工具,可以:- 直接下载已编译好的 Release 版(Windows / Linux / macOS 均有预编译可执行文件)。
- 或者自己编译 ncnn(cmake)并在
build/tools/onnx/
目录下获得可执行文件onnx2ncnn
。
- 假设你拿到
onnx2ncnn
可执行文件,并将其放到和simple_net.onnx
同一目录下,执行以下命令(终端 / 命令行):./onnx2ncnn simple_net.onnx simple_net.param simple_net.bin
- 执行完成后,你会得到两个文件:
simple_net.param
:网络结构描述simple_net.bin
:网络权重数据
这两个文件就是 ncnn 推理所需的最终模型文件。
三、ncnn 推理应用案例
下面展示 C++ 使用 ncnn 进行推理的示例(在 Linux 或者 Android NDK 下均可类似操作)。这里以输入一张图片并进行分类推理为例。流程如下:
- 加载 ncnn 模型:读取
.param
+.bin
。 - 准备输入:根据模型需要,预处理图片到对应的维度(
3 x 32 x 32
),并归一化等。 - 推理得到输出,并解析结果(比如输出是10维度的分类结果,就取最大值对应的类别)。
3.1 示例工程结构
假设工程目录如下:
ncnn-example/
|-- simple_net.param
|-- simple_net.bin
|-- main.cpp
|-- CMakeLists.txt (示例)
`-- ...
3.2 main.cpp (示例)
下面给出一个简化的示例代码(适用于 ncnn 2022+ 版本),使用 OpenCV 进行图像读取,使用 ncnn 进行推理:
#include <stdio.h>
#include <vector>
#include <algorithm>
#include <opencv2/opencv.hpp>
#include "net.h" // ncnn 主头文件
// 假设分类输出10个类别
#define NUM_CLASSES 10
int main(int argc, char** argv)
{
if (argc < 2)
{
fprintf(stderr, "Usage: %s [imagepath]\n", argv[0]);
return -1;
}
const char* imagepath = argv[1];
// 1. 读取图像
cv::Mat bgr = cv::imread(imagepath, 1);
if (bgr.empty())
{
fprintf(stderr, "cv::imread %s failed\n", imagepath);
return -1;
}
// 2. 初始化 ncnn::Net
ncnn::Net net;
// 可选的设置,比如不使用 Vulkan
net.opt.use_vulkan_compute = false;
// 3. 加载 param 和 bin
net.load_param("simple_net.param");
net.load_model("simple_net.bin");
// 4. 创建输入 (注意网络期望的输入尺寸及归一化)
// 这里 SimpleNet 期望输入 3x32x32,但实际可以先对 bgr 做 resize,然后再进行 ncnn Mat 构造。
cv::Mat resized;
cv::resize(bgr, resized, cv::Size(32, 32));
// ncnn 的 Mat 通常是 NCHW,但在传入时可以用 from_pixels() 等接口转换
ncnn::Mat in = ncnn::Mat::from_pixels(resized.data, ncnn::Mat::PIXEL_BGR, 32, 32);
// 如果有归一化需求,这里可以对 in 进行 normalize
const float mean_vals[3] = { 127.5f, 127.5f, 127.5f };
const float norm_vals[3] = { 1/127.5f, 1/127.5f, 1/127.5f };
in.substract_mean_normalize(mean_vals, norm_vals);
// 5. 创建 ncnn 预测 Extractor
ncnn::Extractor ex = net.create_extractor();
ex.set_num_threads(4); // 根据硬件情况设定线程数
ex.input("input", in); // 对应 PyTorch 导出时的 input name
// 6. 输出
ncnn::Mat out;
ex.extract("output", out); // 对应 PyTorch 导出时的 output name
// out 的 shape 一般是 1 x num_classes
// 这里假设是 [1, 10]
std::vector<float> scores(NUM_CLASSES);
for (int i = 0; i < NUM_CLASSES; i++)
{
scores[i] = out[i];
}
// 7. 找到分数最大值
int predicted_class = std::max_element(scores.begin(), scores.end()) - scores.begin();
float max_score = scores[predicted_class];
printf("Predicted class: %d, score = %f\n", predicted_class, max_score);
return 0;
}
- 要点说明:
- ncnn::Net 是 ncnn 中用于加载模型和进行推理的主要对象。
load_param()
和load_model()
分别加载.param
和.bin
文件,需保证路径正确。- 预处理阶段要注意和训练时一致的图像尺寸、归一化处理(均值、方差等)。
- 在 PyTorch 导出时指定的
input
、output
名称在推理时要对应一致。
3.3 简易 CMakeLists.txt 示例
在 Linux 平台下(假设 ncnn 已经安装在系统或有编译好的 static / shared library),一个简单的 CMakeLists.txt 可能是:
cmake_minimum_required(VERSION 3.10)
project(ncnn_example)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_BUILD_TYPE Release)
# 指定 ncnn 头文件和库文件的路径(根据实际情况修改)
# 假设 /usr/local/include/ncnn /usr/local/lib
include_directories(/usr/local/include/ncnn)
link_directories(/usr/local/lib)
# 找到 OpenCV
find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
# 生成可执行文件
add_executable(ncnn_example main.cpp)
# 链接 ncnn 和 OpenCV
target_link_libraries(ncnn_example ncnn ${OpenCV_LIBS})
然后在 ncnn-example/
目录下执行:
mkdir build
cd build
cmake ..
make
./ncnn_example ../test.jpg # 预测
四、其他应用方向
- 目标检测:ncnn 也广泛用于 Mobile端常见的目标检测模型(YOLO 系列、SSD、RetinaNet 等)部署,流程与上面类似,先转 ONNX,再使用
onnx2ncnn
。 - 人脸识别/人脸检测:如 scrfd、MobileFaceNet 等模型,也有相应的 ncnn 例程。
- 语音处理:ncnn 也可以用于一些语音推理模型(但要注意运算层支持,如 Transformer 相关算子支持)。
- 量化模型:ncnn 支持低比特量化,例如 int8 量化,可以进一步压缩模型、加速推理。
五、小结
- ncnn 的主要优势是在移动端和嵌入式端轻量且高效,容易移植,同时支持多线程、Vulkan 加速等优化手段。
- 使用流程一般为PC 端训练 -> 导出 ONNX ->
onnx2ncnn
转换 -> ncnn 推理。 - 推理过程在 C++ 中可以简要分为:
- 创建
ncnn::Net
load_param
+load_model
- 预处理输入数据为
ncnn::Mat
extractor.input()
/extractor.extract()
- 获得输出,进行结果解析
- 创建
希望通过上述模型生成与应用案例,你能清晰了解 ncnn 的模型转换和推理流程。结合你实际项目的需求,通常只需在 PC 端完成训练和转换,然后在移动/嵌入式端利用 ncnn 的 API 进行加载和推理即可快速部署。祝你在 ncnn 的使用中一切顺利!