4. 使用NCNN部署量化后的MobileNetV2
任务目标
将上一案例中量化后的MobileNetV2模型(INT8或FP16)转换为NCNN格式,在PC端使用NCNN推理引擎加载模型,对单张测试图片进行分类推理,并验证输出结果与量化前模型的预测类别一致。
环境依赖
- NCNN(编译安装或直接下载预编译包):
ncnn库及ncnn2mem工具 - ONNX模型:
mobilenetv2_quant.onnx(量化后模型,可由PyTorch导出或由前序案例生成) - OpenCV(用于图片读取与预处理)
- 测试图片:任选一张ImageNet类别图片(如
cat.jpg) - 类别标签文件:
synset.txt(ImageNet 1000类标签)
实现步骤
整体流程分为两步:模型转换(ONNX→NCNN)与C++推理。转换使用NCNN自带的 onnx2ncnn 工具完成,推理代码使用NCNN C++ API实现。
1. ONNX模型转换为NCNN格式
使用NCNN提供的转换工具,将ONNX模型转为NCNN的 .param 和 .bin 文件:
onnx2ncnn mobilenetv2_quant.onnx mobilenetv2.param mobilenetv2.bin
若模型包含量化信息(如QDQ节点),转换后需检查 .param 文件中是否包含 Quantize/Dequantize 层。若需进一步压缩为FP16存储,可执行:
ncnn2mem mobilenetv2.param mobilenetv2.bin mobilenetv2.id.h mobilenetv2.mem.h
本案例使用普通 .param/.bin 即可完成推理闭环。
2. C++推理代码
// mobilenetv2_ncnn_infer.cpp
#include <opencv2/opencv.hpp>
#include <ncnn/net.h>
#include <fstream>
#include <iostream>
#include <vector>
#include <string>
int main(int argc, char** argv) {
if (argc < 3) {
std::cerr << "Usage: " << argv[0] << " <image_path> <label_file>" << std::endl;
return -1;
}
// 加载标签
std::vector<std::string> labels;
std::ifstream label_file(argv[2]);
std::string line;
while (std::getline(label_file, line)) {
labels.push_back(line);
}
// 加载NCNN模型
ncnn::Net net;
int ret = net.load_param("mobilenetv2.param");
if (ret != 0) {
std::cerr << "Failed to load param" << std::endl;
return -1;
}
ret = net.load_model("mobilenetv2.bin");
if (ret != 0) {
std::cerr << "Failed to load model" << std::endl;
return -1;
}
// 读取图片并预处理
cv::Mat img = cv::imread(argv[1]);
if (img.empty()) {
std::cerr << "Failed to read image" << std::endl;
return -1;
}
cv::resize(img, img, cv::Size(224, 224));
img.convertTo(img, CV_32FC3, 1.0 / 255.0);
// 转换为NCNN Mat(RGB顺序,减均值除方差)
ncnn::Mat in = ncnn::Mat::from_pixels_resize(img.data, ncnn::Mat::PIXEL_BGR2RGB, 224, 224, 224, 224);
const float mean_vals[3] = {0.485f, 0.456f, 0.406f};
const float norm_vals[3] = {1/0.229f, 1/0.224f, 1/0.225f};
in.substract_mean_normalize(mean_vals, norm_vals);
// 推理
ncnn::Extractor ex = net.create_extractor();
ex.input("input", in);
ncnn::Mat out;
ex.extract("output", out);
// 获取top-1结果
float* ptr = (float*)out.data;
int max_idx = 0;
float max_score = ptr[0];
for (int i = 1; i < out.w; i++) {
if (ptr[i] > max_score) {
max_score = ptr[i];
max_idx = i;
}
}
std::cout << "Predicted class: " << labels[max_idx] << " (index " << max_idx << ")" << std::endl;
std::cout << "Confidence: " << max_score << std::endl;
return 0;
}
3. 编译与运行
g++ -O2 -std=c++11 mobilenetv2_ncnn_infer.cpp -o infer \
-I/path/to/ncnn/include -I/path/to/opencv/include \
-L/path/to/ncnn/lib -lncnn -L/path/to/opencv/lib -lopencv_core -lopencv_imgproc -lopencv_imgcodecs
./infer cat.jpg synset.txt
验证方法
运行程序后,观察输出类别是否为图片的真实内容(如 tabby cat)。同时,使用同一张图片在量化前模型(原始ONNX)上推理,对比两者的预测类别是否一致。若类别一致,说明NCNN部署正确;若不一致,检查模型转换时是否丢失了量化信息或预处理参数是否匹配。
通过条件:程序正常输出top-1类别与置信度,且类别与量化前模型预测结果一致。
常见问题
- 输入输出节点名不匹配:需在转换前确认ONNX模型的输入输出名称,并在代码中对应修改
ex.input和ex.extract的字符串。 - 量化精度损失导致类别变化:若INT8量化后精度下降明显,可改用FP16量化模型,或检查量化校准数据集是否覆盖了测试图片类别。
- 编译链接错误:确认NCNN和OpenCV的库路径正确,且NCNN编译时启用了
NCNN_VULKAN或NCNN_OPENMP等选项时,需额外链接对应依赖库。