MNN 示例工程实战指南:C++ 推理、Python 训练到移动端部署的完整 Demo 路线
【免费下载链接】MNNMNN: A blazing-fast, lightweight inference engine battle-tested by Alibaba, powering high-performance on-device LLMs and Edge AI.项目地址: https://gitcode.com/GitHub_Trending/mn/MNN
MNN 仓库内置了一组覆盖“模型转换 → 推理 → 移动端集成”全流程的官方示例工程,分别位于 demo/exec、pymnn/examples 与 project/android、project/ios 目录下。本文以官方文档中的示例工程为主线,逐一讲解姿态检测、图像分割、图像识别、Python Session/表达式推理、模型微调以及 Android/iOS 工程的编译运行方法,并结合各 Demo 源码剖析其中的前处理、Session 配置与后处理实现细节,帮助读者快速上手 MNN 的典型应用开发。
C++ 推理 Demo
编译生成 Demo 可执行文件
按照 从源码编译 的说明,使用仓库顶层的 CMakeLists.txt 打开MNN_BUILD_DEMO选项即可编译出全部 Demo 程序。Linux/macOS 下操作如下(来源:demo/exec/README.md):
cd path/to/MNN mkdir build && cd build cmake -DMNN_BUILD_DEMO=ON .. make -j8Windows 下则使用 NMake 生成器:
cd path/to/MNN mkdir build cd build cmake -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release -DMNN_BUILD_DEMO=ON .. nmake从 demo/exec/CMakeLists.txt 的源码可以看到,Demo 目录注册了十余个可执行目标,包括multiPose.out、segment.out、pictureRecognition.out、pictureRecognition_module.out、multithread_imgrecog.out、transformerDemo.out、nluDemo.out等,每个目标统一链接${MNN_DEPS}依赖库。本文聚焦其中三个经典示例。
三个 Demo 的推理代码都遵循同一个 Interpreter-Session 骨架:
Interpreter::createFromFile(modelPath)加载.mnn模型;- 配置
ScheduleConfig(后端类型、线程数等)后createSession; resizeTensor+resizeSession设定输入形状;- 通过
ImageProcess完成缩放、通道转换与归一化,把图像数据直接写入输入 Tensor; runSession执行推理,再按输出 Tensor 的名字或顺序取结果。
其中第 4 步正是文档反复强调模型转换时要加--keepInputFormat=0的原因:使用 Interpreter-Session 接口时,把输入由 NHWC 转换为 NC4HW4 布局后,ImageProcess的convert可以直接把像素数据按目标布局写进设备内存,省去一次显式布局转换。该参数的完整语义可参考 模型转换工具文档(其“说明 2”专门指出:如果使用 Interpreter-Session C++ 接口开发,可以考虑转换时使用--keepInputFormat=0)。
姿态检测(MultiPose)
代码位置:demo/exec/multiPose.cpp
步骤:
- 下载 posenet 项目中的原始 TensorFlow 模型
model-mobilenet_v1_075.pb(frozen model); - 使用 模型转换工具 转换为 MNN 模型,转换时加上参数
--keepInputFormat=0【把输入由 NHWC 转换为 NC4HW4 布局】,例如:
./MNNConvert -f TF --modelFile model-mobilenet_v1_075.pb --MNNModel model.mnn --bizCode biz --keepInputFormat=0- 执行姿态检测:
./multiPose.out model.mnn input.png pose.png效果示例:输入图与检测结果(关键点被红色圆点标出)。
从源码结构看,该 Demo 的关键实现细节包括:
- 网络配置:
ScheduleConfig指定MNN_FORWARD_CPU且numThread = 4;若模型输入是动态 batch,则调用resizeTensor(input, {1, 3, targetHeight, targetWidth})将其固定为 513×513 附近、宽高均为 16 的倍数加 1 的形状(MODEL_IMAGE_SIZE 513、OUTPUT_STRIDE 16)。 - 前处理:
ImageProcess配置mean = {127.5, 127.5, 127.5}、normal = {2/255, 2/255, 2/255},即把像素从[0, 255]归一化到[-1, 1];sourceFormat = CV::RGBA、destFormat = CV::RGB,配合一个 postScale 矩阵完成原图到模型输入尺寸的缩放。 - 后处理:posenet 模型的四个输出节点分别为
heatmap、offset_2(OFFSET_NODE_NAME)、displacement_fwd_2、displacement_bwd_2。decodeMultiPose先在热力图上做半径为LOCAL_MAXIMUM_RADIUS的局部极大值过滤(阈值SCORE_THRESHOLD 0.5),再按NMS_RADIUS 20做非极大值抑制,通过正向/反向位移场沿PoseChain骨架边遍历,把单个人体的 17 个关键点串起来;单个人体综合得分低于MIN_POSE_SCORE 0.25时丢弃,最多输出MAX_POSE_DETECTIONS 10个人体。 - 结果绘制:
drawPose对得分超阈值的每个关键点画半径为 3 的圆,最终用stb_image_write输出 PNG。
这些常数都以宏形式定义在文件头部,若要针对自己的 pose 模型调整检测灵敏度,直接修改对应宏即可。
图像实例分割(Segment)
代码位置:demo/exec/segment.cpp
步骤:
- 下载 deeplabv3 分割模型
deeplabv3_257_mv_gpu.tflite(TensorFlow 官方提供的 DeepLabv3 MobileNet 分割模型); - 使用 模型转换工具 转换为 MNN 模型,转换时加上参数
--keepInputFormat=0【把输入由 NHWC 转换为 NC4HW4 布局】:
./MNNConvert -f TFLITE --modelFile deeplabv3_257_mv_gpu.tflite --MNNModel model.mnn --bizCode biz --keepInputFormat=0- 执行分割:
./segment.out model.mnn input.png result.png效果示例:result.png是一张二值掩膜图,人体区域为白色(255),其余为黑色(0)。
从源码看,这个 Demo 展示了 Interpreter 接口与 Express(表达式)接口混用的典型写法:
- 推理部分使用默认的
ScheduleConfig(后端自动选择)创建 Session,并同样通过ImageProcess做前处理(mean = 127.5、normal = 1/127.5,sourceFormat = RGBA、destFormat = RGB、filterType = BILINEAR); - 后处理则绕开了手写循环:先
Variable::create(Expr::create(outputTensor))把设备上的输出 Tensor 包装成 VARP,再依次_Convert(output, NHWC)→_Reshape(output, {-1, channel})→_TopKV2(output, 1)取每个像素概率最高的一类,随后用_Select(_Equal(index, 15), 255, 0)判断“是否为 person 类(类别索引 15)”,最后_Cast<uint8_t>得到掩膜并写出 PNG。
源码中还附了一行注释给出的更快写法:_Equal(index, 15) * 255可直接替代_Select,对性能敏感时可以参考。仓库内置的 demo/model 目录里也提供了 MobileNet 与 SqueezeNet 对应的测试图片(如 testcat.jpg),没有素材时可直接拿来跑通流程。
图像识别(PictureRecognition)
代码位置:demo/exec/pictureRecognition.cpp
准备 mobilenet 模型并转换为 MNN 格式(Caffe 权重转换命令可参考 Android Demo README 中 MobileNet_v2 的示例)。程序参数含义:
- 第一个参数:MNN 模型地址;
- 第二个参数:图像地址;
- 追加的参数:更多待识别图像地址(源码中
shape[0] = argc - 2,即按传入图像数自动设置 batch)。
示例:
./pictureRecognition.out moiblenet.mnn Test.jpg输出:
Can't Find type=4 backend, use 0 instead For Image: TestMe.jpg 386, 0.419250 101, 0.345093 385, 0.214722 347, 0.012001 346, 0.002010 348, 0.001876 294, 0.001247 349, 0.000761 354, 0.000443 345, 0.000441第一行表示识别出可能性最大的类别编号,在相应的synset_words.txt中查找对应的类别,如:demo/model/MobileNet/synset_words.txt。
源码层面这个 Demo 有三个值得注意的写法:
net->setCacheFile(".cachefile")与net->setSessionMode(Interpreter::Session_Backend_Auto):开启执行配置缓存与后端自动调优,并setSessionHint(Interpreter::MAX_TUNING_NUMBER, 5)限制调优尝试次数,首次运行“Can't Find type=4 backend, use 0 instead”这类提示表示请求的某后端不可用、自动回退到 CPU(后端枚举定义见 include/MNN/MNNForwardType.h);- 通过
getSessionInfo打印MEMORY(显存/内存占用 MB)、FLOPS(浮点运算量)与BACKENDS(实际使用的后端类型)三类 Session 信息,方便快速评估模型规模与调度结果; - 前处理使用
mean = {103.94, 116.78, 123.68}、normal = 0.017、destFormat = BGR的 Caffe 风格归一化;多张图像逐张ImageProcess::convert写入同一个 batch 输入 Tensor 的不同 offset,最后统一copyFromHostTensor并一次runSession完成批量推理。
Python Demo
以下示例默认已通过pip install MNN或从源码安装 Python 包,资源文件(mobilenet_v1.mnn与 ImageNet 验证图ILSVRC2012_val_00049999.JPEG)以压缩包形式在官方文档中提供,解压后即可按如下方式运行。
Session 图片分类
代码位置:pymnn/examples/MNNEngineDemo/,包含三个文件:
- mobilenet_demo.py:使用 Session 进行图片分类示例;
- mobilenet_demo_2.py:使用 Runtime 创建 Session 进行图片分类示例;
- gpu_session_demo.py:使用 Session 的 GPU(OpenCL)后端进行图片分类示例。
示例输出:
$ unzip mobilenet_demo.zip $ python mobilenet_demo.py mobilenet_demo/mobilenet_v1.mnn mobilenet_demo/ILSVRC2012_val_00049999.JPEG Load Cache file error. expect 983 output belong to class: 983 $ python mobilenet_demo_2.py mobilenet_demo/mobilenet_v1.mnn mobilenet_demo/ILSVRC2012_val_00049999.JPEG Load Cache file error. MNN use low precision <capsule object NULL at 0x7fdd0185a270> (True,) MNN use low precision memory_info: 22.382057MB flops_info: 568.792175M backend_info: 13 expect 983 output belong to class: 983 $ python gpu_session_demo.py mobilenet_demo/mobilenet_v1.mnn mobilenet_demo/ILSVRC2012_val_00049999.JPEG Testing gpu model calling method Load Cache file error. MNN use high precision Can't Find type=3 backend, use 0 instead Can't Find type=3 backend, use 0 instead Run on backendtype: 13 expect 983 output belong to class: 983三个脚本的差异恰好对应三种 Session 创建方式:
- mobilenet_demo.py:最简路径——
MNN.Interpreter(model)后直接createSession(),传入{'precision': 'low'}配置; - mobilenet_demo_2.py:先
MNN.Interpreter.createRuntime(config)显式创建 Runtime,再createSession(config, runtimeinfo),并演示用getSessionInfo(session, 0/1/2)分别读取内存占用(MB)、FLOPS 与后端类型三类信息,便于在 Python 侧做调度诊断; - gpu_session_demo.py:
config['backend'] = "OPENCL"、config['precision'] = "high"指定 GPU 后端;net.setSessionMode(9)对应Session_Backend_Auto(后台调优),setSessionHint(0, 20)设置调优次数上限,运行结束后updateCacheFile落盘缓存。输出中的 “Can't Find type=3 backend, use 0 instead” 表示当前环境缺少 OpenCL 后端而回退 CPU,属于正常的降级提示而非错误。
三个脚本的前处理逻辑一致:cv2 读图(BGR)→ 翻转为 RGB →cv2.resize到 224×224 → 减均值{103.94, 116.78, 123.68}乘0.017→transpose成 NCHW 的float32numpy 数组,再用MNN.Tensor(..., MNN.Tensor_DimensionType_Caffe)包装后copyFrom到输入 Tensor;输出则通过copyToHostTensor拷回 host 侧(避免 NC4HW4 布局带来的读取歧义),np.argmax取最大类别(该验证图的标准类别是 983)。
表达式图片分类
代码位置:pymnn/examples/MNNExpr/,其中 mobilenet_demo.py 使用 MNN-Express(表达式)接口:
$ python mobilenet_demo.py mobilenet_demo/mobilenet_v1.mnn mobilenet_demo/ILSVRC2012_val_00049999.JPEG expect 983 output belong to class: 983与 Session 写法相比,表达式路径更简洁:MNN.nn.load_module_from_file(model, ["input"], ["MobilenetV1/Predictions/Reshape_1"])按输入/输出 Tensor 名加载模型,MNN.expr.placeholder([1,224,224,3], MNN.expr.NHWC)创建 NHWC 输入,MNN.expr.convert完成 NC4HW4 转换后net.forward([input_var])一步得到输出。同目录下还有 gpu_express_demo.py、gpu_io_interface.py 与 mnn_numpy_cv_demo.py,可分别参考 GPU 表达式调用、GPU 输入输出接口与 numpy/CV 互操作写法。
模型训练
代码位置:pymnn/examples/MNNTrain/。官方文档记载的测试代码包含:
mnist:使用 mnist 数据训练模型并测试准确率;mobilenet_finetune:使用 MobileNetV2 在自己的数据集上 finetune 一个图像分类器;module_save:演示模型权值的存储和加载;quantization_aware_training:训练量化(Quantization Aware Training)示例。
当前仓库中该目录实际包含 mnist(train_mnist.py、dataset.py)、mobilenet_finetune(mobilenet_transfer.py、finetune_dataset.py)、module_save(test_save.py、grad_test.py)与 simple(grad_loss.py、solve_equation.py等梯度/自动微分小例)四类子目录,文档提到的quantization_aware_training在现有目录中未直接对应,运行训练类示例时以仓库内实际脚本为准。
mnist 示例无需下载资源,用法如下:
$ pip install mnist $ python train_mnist.py train loss: 2.3346531 train loss: 0.28027835 train loss: 0.26191226 train loss: 0.09180952 train loss: 0.14287554 train loss: 0.14296289 train loss: 0.060721636 train loss: 0.037558462 train loss: 0.11289845 train loss: 0.04905951 Epoch cost: 47.505 s. Save to 0.mnist.mnn test acc: 96.25 %训练完成后直接保存为0.mnist.mnn,说明 MNN 的训练产物本身就是可部署的 MNN 模型文件。
mobilenet_finetune 示例需要自行解压model.zip、train_dataset.zip、test_dataset.zip三组资源,然后运行:
$ unzip model.zip train_dataset.zip test_dataset.zip $ python mobilenet_transfer.py --model_file mobilenet_v2_tfpb_train_public.mnn --train_image_folder train_images --train_txt train.txt --test_image_folder test_images --test_txt test.txt --num_classes 1000需要说明的是,文档在该示例下标注了 “当前版本不支持 FixModule,无法执行该示例”(运行时报AttributeError: module 'MNN.nn' has no attribute 'FixModule'),因此在缺少MNN.nn.FixModule接口的版本中应把它当作 API 参考而非可跑通的脚本。
module_save 演示了模型权值的存储和加载:
$ python test_save.py 0.0004 10Android Demo
代码位置:project/android/demo,详细步骤见 project/android/demo/README.md。
- 环境准备:安装
Android Studio与NDK; - 模型下载与转换:先编译
MNNConvert(已编译可跳过):
cd MNN mkdir build && cd build cmake -DMNN_BUILD_CONVERTER=ON .. make -j8然后下载模型:可以直接执行sh ../tools/script/get_model.sh(脚本位于 tools/script,会自动完成模型下载与转换),也可以按 README 手动转换,例如 MobileNet_v2(Caffe 格式):
./MNNConvert -f CAFFE --modelFile mobilenet_v2.caffemodel --prototxt mobilenet_v2_deploy.prototxt --MNNModel mobilenet_v2.caffe.mnn mv mobilenet_v2.caffe.mnn ../resource/model/MobileNet/v2/README 中还给出了 SqueezeNet_v1.1 与 DeepLab_v3(TFLite)两组同样格式的转换命令;
- 编译运行:使用 Android Studio 打开
demo目录,在local.properties中指定sdk.dir与ndk.dir,即可编译执行。
效果示例:
iOS Demo
模型下载与转换
首先编译(如果已编译可以跳过)MNNConvert:
cd MNN mkdir build && cd build cmake -DMNN_BUILD_CONVERTER=ON .. make -j8然后下载并转换模型:切到编译了 MNNConvert 的目录(如上为 build 目录),执行:
sh ../tools/script/get_model.sh该脚本会下载 MobileNet、SqueezeNet 等示例模型并调用MNNConvert转成.mnn文件。
工程编译
代码位置:project/ios。使用 Xcode 打开project/ios/MNN.xcodeproj,target选择demo,即可编译运行。
效果示例:
社区贡献示例
官方文档同时欢迎开发者提交自己的 MNN 示例项目(可通过仓库 issue 渠道提交,审核通过后展示在文档中)。社区围绕 MNN 沉淀的示例方向包括:MobileNet/YOLOv8 等分类检测模型部署、Stable Diffusion 文生图、ChatGLM 等 LLM 推理、嵌入式设备部署、车道线检测、人脸检测/识别/跟踪、视频抠图、SuperGlue 关键点匹配、OCR、语音合成与虚拟角色面捕系统等。这些项目的具体用法需要参考各自代码仓库。
小结:示例工程的参考价值
| 示例 | 入口 | 学到的核心能力 |
|---|---|---|
| multiPose | demo/exec/multiPose.cpp | NC4HW4 输入布局、ImageProcess 前处理、多输出 Tensor 的后处理解码 |
| segment | demo/exec/segment.cpp | Session 推理 + Express 表达式后处理(Convert/Reshape/TopKV2/Select) |
| pictureRecognition | demo/exec/pictureRecognition.cpp | 缓存文件、后端自动调优、SessionInfo 诊断、batch 推理 |
| Python Session 三例 | pymnn/examples/MNNEngineDemo/ | 三种 Session 创建方式、OpenCL 后端配置、Tensor host 拷贝 |
| Python Express | pymnn/examples/MNNExpr/mobilenet_demo.py | Module 加载、placeholder/convert 的 NHWC-NC4HW4 转换 |
| MNNTrain | pymnn/examples/MNNTrain/ | 训练-保存-部署一体化、微调与梯度接口 |
| Android / iOS | project/android/demo、project/ios | MNNConvert 编译、模型脚本化下载转换、原生工程集成 |
以上示例覆盖了从桌面命令行到移动端的典型链路:先按 docs/tools/convert.md 用MNNConvert准备模型(推理类 C++ 接口建议加--keepInputFormat=0),再对照相应 Demo 的“加载 → resize → 前处理 → 推理 → 后处理”骨架替换为自己的模型输入输出即可快速搭建应用原型。
【免费下载链接】MNNMNN: A blazing-fast, lightweight inference engine battle-tested by Alibaba, powering high-performance on-device LLMs and Edge AI.项目地址: https://gitcode.com/GitHub_Trending/mn/MNN
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考