YOLOv5实战指南:从ONNX模型到Android端高效部署

YOLOv5实战指南:从ONNX模型到Android端高效部署

1. YOLOv5模型转换全流程解析

第一次把YOLOv5模型部署到Android端时,我对着各种格式转换工具发懵——PT、ONNX、NCNN这些名词看得人眼花缭乱。后来踩过几次坑才发现,其实只要掌握关键步骤,整个过程就像组装乐高积木一样有章可循。

先说说为什么要用ONNX这个中间格式。想象你有个Python写的模型,现在要让它跑在Java开发的Android应用里,就像让说中文的人和说英文的人直接对话,双方都听不懂。ONNX就像个万能翻译官,先把PyTorch的.pt模型转换成通用语言,再让NCNN这个"本地翻译"转成Android能理解的格式。这种标准化流程比直接硬啃跨平台兼容性要高效得多。

实际操作时,模型转换最容易卡在三个地方:

  1. PT转ONNX时忘记加--train参数,导致后处理节点残留
  2. 没做ONNX简化就直接转换,遇到莫名其妙的节点报错
  3. param文件末尾参数没改成-1,结果检测框多到看不清图片

我建议在转换完成后立即用Netron检查模型结构。这个可视化工具能直观显示网络层,就像X光机一样帮你确认转换是否成功。最近还发现个偷懒技巧:用convertmodel.com在线转换,特别适合不想折腾环境配置的时候。不过要注意敏感模型别随便上传,毕竟数据安全最重要。

2. ONNX模型优化实战技巧

很多人以为PT转ONNX就是执行个命令行的事,其实里面的门道不少。最近帮同事处理一个工业质检模型时,发现同样的转换命令,在YOLOv5 6.2版本上就比老版本少了很多麻烦——主要因为官方去掉了Focus层这个"历史包袱"。

模型简化是另一个容易翻车的环节。有次我偷懒没做简化,结果ONNX转NCNN时报了一堆Unsupported slice steps错误。后来发现用这个命令能解决90%的问题:

python -m onnxsim input.onnx output-sim.onnx --dynamic-input-shape

--dynamic-input-shape特别重要,它让模型能适应不同尺寸的输入图像。就像给衣服加上松紧带,不管手机摄像头拍出什么比例的图片都能处理。

还有个隐藏技巧:转换前先在Python里验证ONNX模型:

import onnxruntime as ort sess = ort.InferenceSession("model.onnx") outputs = sess.run(None, {"images": dummy_input})

这步能提前发现runtime错误,比等到移动端才调试省时得多。记得检查输出维度是否和原模型一致,我遇到过转完ONNX后输出shape莫名其妙少一维的情况。

3. Android环境配置避坑指南

第一次装Android Studio的经历堪称噩梦——SDK版本冲突、Gradle下载卡住、模拟器启动失败...后来总结出最稳的安装流程:

  1. JDK版本要选8或11,新版可能不兼容
  2. 安装时勾选Android SDK Command-line Tools,后面编译NCNN要用
  3. 创建项目时选**Native C++**模板,省去JNI配置的麻烦

测试发现,用真机调试比模拟器快不止一个量级。建议在手机的开发者选项里开启USB调试保持唤醒状态,不然训练到一半锁屏就前功尽弃了。

遇到最奇葩的问题是:同样的APK,在华为手机上正常,到小米就崩溃。最后发现是OpenMP库冲突,解决方法是在build.gradle里加上:

packagingOptions { pickFirst '**/libomp.so' }

这个坑足足浪费我两天时间,现在看到小米手机都条件反射地加这行配置。

4. 模型适配与性能调优

把官方demo跑通只是第一步,要让自定义模型高效运行还得下功夫。有次部署一个安全帽检测模型,发现帧率只有3FPS,根本没法用。后来通过这四招提升到18FPS:

  1. 量化模型:把FP32转成INT8,体积缩小4倍
./ncnnoptimize yolov5.param yolov5.bin yolov5-opt.param yolov5-opt.bin 65536
  1. 调整线程数:根据CPU核心数设置ncnn::set_cpu_num_threads()
  2. 启用Vulkan:在支持GPU的设备上能提速30%
  3. 预处理优化:把RGB转换放到GLSL着色器里

anchors设置是另一个关键点。有次直接用了COCO数据集的默认值,结果小目标检测完全失效。后来学会用这个脚本提取训练时的anchor:

from utils.autoanchor import check_anchors check_anchors(dataset, model=model, thr=4.0)

建议在训练日志里就保存好这些参数,免得部署时又要翻旧账。

5. 部署后的实战调试

你以为模型转好、APK装上就万事大吉?真正的挑战才刚刚开始。在荣耀X10上测试时,发现检测框总是偏移10个像素,查了半天才发现是分辨率适配问题——模型训练用640x640,但手机摄像头输出是720p。

解决方法是在YOLOv5ncnn.cpp里修改预处理:

ncnn::Mat in = ncnn::Mat::from_pixels_resize( rgb.data, ncnn::Mat::PIXEL_RGB, src_w, src_h, target_w, target_h );

还要注意内存泄漏问题。有次APP跑着跑着就卡死,发现是每次检测都new对象没释放。现在都会在JNI函数开头加:

ncnn::create_gpu_instance();

结尾加:

ncnn::destroy_gpu_instance();

最近还发现个取巧的办法:用TFLite代替NCNN部署。虽然性能稍差,但Android官方支持更好。适合对帧率要求不高(>15FPS)的场景,能省去很多底层适配工作。