NCNN 使用方式与典型场景:从 PyTorch 到端上推理
NCNN 使用方式与典型场景:从 PyTorch 到端上推理
示例一:转换 PyTorch 模型
# 1. 从 PyTorch 导出 TorchScript
python -c '
import torch
m = torch.load("model.pt", map_location="cpu").eval()
torch.jit.trace(m, torch.randn(1, 3, 224, 224)).save("model.torchscript.pt")
'
# 2. pnnx 转换,输入形状必须写清楚
./pnnx model.torchscript.pt inputshape=[1,3,224,224]
# 输出 model.ncnn.param 与 model.ncnn.bin
# 3. 结构优化
./ncnnoptimize model.ncnn.param model.ncnn.bin model_opt.param model_opt.bin 0
如果手上是 ONNX 模型,换成 ./onnx2ncnn model.onnx model.param model.bin 即可。转换完务必打开 .param 看一眼输入输出 blob 的名字,常见是 in0 与 out0,但不同版本与不同转换路径可能不一样,推理代码里写错名字会直接抛异常。
示例二:C++ 推理骨架
#include "net.h"
#include <opencv2/opencv.hpp>
int main() {
ncnn::Net net;
net.opt.num_threads = 4;
net.opt.use_vulkan_compute = false; // 有 Vulkan 驱动再置 true
net.opt.use_fp16_packed = true;
net.opt.use_fp16_storage = true;
net.opt.lightmode = true;
if (net.load_param("model_opt.param") != 0) return -1;
if (net.load_model("model_opt.bin") != 0) return -1;
cv::Mat bgr = cv::imread("test.jpg");
ncnn::Mat in = ncnn::Mat::from_pixels_resize(
bgr.data, ncnn::Mat::PIXEL_BGR2RGB, bgr.cols, bgr.rows, 224, 224);
const float mean_vals[3] = {104.f, 117.f, 123.f};
const float norm_vals[3] = {0.017f, 0.017f, 0.017f};
in.substract_mean_normalize(mean_vals, norm_vals);
ncnn::Extractor ex = net.create_extractor();
ex.input("in0", in);
ncnn::Mat out;
ex.extract("out0", out);
// out 的形状由模型决定,分类模型通常是 (num_class, )
return 0;
}
编译时链上静态库即可:g++ main.cpp -I/path/to/ncnn/include -L/path/to/ncnn/lib -lncnn -fopenmp(Android 上改用 c++_static 并去掉 -fopenmp)。注意 from_pixels_resize 的像素格式参数必须与训练时的预处理一致,BGR 输入写成 RGB 会让精度莫名其妙掉几个点,而且完全不报错。
示例三:Python 绑定快速验证
import ncnn
import numpy as np
net = ncnn.Net()
net.opt.num_threads = 4
net.opt.use_vulkan_compute = False
net.load_param('model_opt.param')
net.load_model('model_opt.bin')
mat_in = ncnn.Mat(np.random.rand(3, 224, 224).astype(np.float32))
with net.create_extractor() as ex:
ex.input('in0', mat_in)
ret, mat_out = ex.extract('out0')
print(mat_out.numpy().shape)
print('输入名:', net.input_names())
print('输出名:', net.output_names())
Python 绑定适合做精度比对与端到端验证,不适合做最终交付。实践中的正确姿势是:桌面用 Python 或 C++ 验证数值,与 PyTorch 的输出逐层比对(误差应在浮点噪声量级,因为算子实现与浮点累加顺序不同),确认无误后再做量化与交叉编译。
适合与不适合的场景
- 适合:PyTorch 训练、ARM Linux 部署、需要 Vulkan GPU 加速,以及模型结构比较标准(分类、检测、分割、关键点)的项目;
- 适合:需要深度裁剪并把推理库静态链进单一可执行文件,或交叉编译到冷门 ARM 板卡的场景;
- 不适合:只想要开箱即用的 NPU / DSP 委托,NCNN 在这方面的生态不如 TFLite 与 NNAPI 丰富;
- 不适合:团队完全没有 C++ 能力,或模型含大量动态控制流,转换阶段会非常痛苦。

