☰
YOLO-FastestV2实战:从训练到Android NCNN部署的轻量级目标检测全流程
2026/10/4 23:18:23 网站建设 项目流程

去年有个项目需要在树莓派上跑实时目标检测,当时试了一圈YOLO系列,从YOLOv5s到YOLOv8n,帧率始终不理想,模型和算力之间的矛盾折腾了我整整一周。后来接触到YOLO-FastestV2,这个模型让事情变得简单了——不到1.5MB的模型文件,在树莓派4B上推理耗时能做到单帧30毫秒量级。如果你也在做移动端、嵌入式设备或者边缘计算场景的目标检测,这篇实战手册能帮你省掉我当初踩过的那些坑。

YOLO-FastestV2是目前极少数专门以“极致轻量”为目标设计的检测模型,核心代码基于YOLOv5早期版本改造,骨干网络换成ShuffleNetV2,检测头做了大幅裁剪。它解决的核心矛盾是:边缘设备算力有限,但又要跑得动实时检测。这篇文章会从环境搭建、数据集准备、模型训练、pt转ONNX转NCNN,一直到Android端NCNN推理全流程写一遍,内容包括每个环节容易出的问题以及解决办法。适合正在做移动端部署、嵌入式开发,或者有边缘AI需求的读者参考。

1. 先搞清楚YOLO-FastestV2到底“轻”在哪

1.1 参数量、计算量和实测速度

官方仓库给出的数据是,YOLO-FastestV2的参数量约25万(250K),浮点计算量约0.23GFLOPs,FP32精度的模型权重文件约1.2MB。这个体量是什么概念?YOLOv5s参数量约700万,是它的28倍;YOLOv8n参数量约300万,是它的12倍。通常轻量检测模型会把参数压到几百万,直接压到几十万量级的,YOLO-FastestV2算是走得比较极端的。

我在树莓派4B(CPU为Cortex-A72,四核1.5GHz)上实测过,NCNN框架、单线程、352×352输入,FP32推理耗时约120~150ms;开四线程后能到40~60ms。在Android中端手机上用GPU(Vulkan)加速,FP16推理大约能跑15~25ms。这些数据意味着,在树莓派这类设备上它能做到20帧以上,在手机上做到40帧以上,满足大部分实时检测需求。

1.2 和YOLOv5s/YOLOv8n的选型对比

很多人在轻量模型选型时直接选YOLOv5s或YOLOv8n,这两个确实在通用场景下精度更好,但移动端部署不只是看精度,还要看内存占用、推理耗时、发热等多个维度。

模型参数量计算量(GFLOPs)权重大小(FP32)树莓派4B推理耗时适用场景
YOLOv5s~7.0M16.5~14MB500ms以上服务器或Jetson类设备
YOLOv8n~3.2M8.7~12MB200ms以上中端手机、边缘盒子
YOLO-FastestV2~0.25M0.23~1.2MB40~60ms(4线程)树莓派、低端手机、MCU类设备

实际选型时有个经验:如果目标设备的CPU主频低于1.8GHz,或者内存在1GB以下,直接放弃YOLOv5系列,YOLO-FastestV2是更合适的选择。如果设备是骁龙8系旗舰手机、Jetson Orin这类有GPU加速能力的平台,YOLOv8n也不差。关键还是先评估硬件底牌再定模型。

1.3 第一代YOLO-Fastest和V2的差别

YOLO-Fastest第一版基于Darknet框架实现,使用ResNet-DW做骨干,模型大小约1.3MB。V2版本换成PyTorch框架,骨干换为ShuffleNetV2,最大的变化在于:

  • 训练生态更好,可以直接用YOLOv5的很多工具链,不用碰Darknet那些反人类的配置。
  • 检测头做了重新设计,从原来的三尺度输出精简为双尺度输出(两个检测层),计算量进一步下降。
  • 支持与YOLOv5一脉相承的Mosaic数据增强、自动锚框计算等训练策略。

所以现在新项目建议直接上V2,除非你有必须用Darknet C++推理的历史包袱。

2. 训练阶段:环境依赖、数据集与关键参数

2.1 环境依赖细节,Python和PyTorch版本真有讲究

YOLO-FastestV2官方代码基于YOLOv5早期分支,对依赖版本有一定的兼容范围。我的实测结论是:

  • Python 3.7~3.8最稳定,3.9以上部分依赖安装会报错。
  • PyTorch建议用1.7~1.9版本,不要直接上PyTorch 2.x,推理能跑但训练时经常出现算子和旧代码不匹配的问题。
  • torchvision版本跟着PyTorch配套装。

安装命令建议这样写:

conda create -n yolofastest python=3.8 source activate yolofastest pip install torch==1.9.0 torchvision==0.10.0 git clone https://github.com/dog-qiuqiu/Yolo-FastestV2 cd Yolo-FastestV2 pip install -r requirements.txt

我在PyTorch 1.11上试过一次,训练时Loss能正常下降,但转到onnx时就会遇到算子兼容问题,排查成本很高,建议不要在这个环节浪费时间。

2.2 数据集格式:VOC和YOLO txt两种,按需选择

YOLO-FastestV2同时支持VOC格式(XML标注)和YOLO格式(txt标注),这一点继承了YOLOv5的做法。项目目录结构如下:

datasets/ voc/ images/ train/ # 训练图片 val/ # 验证图片 annotations/ # VOC xml文件 yolo/ images/ train/ val/ labels/ train/ # 每个txt文件对应一张图片的标注 val/

我习惯用YOLO格式,因为后期标注工具输出基本都是txt,省去一次转换。txt标注格式为:

类别id x_center y_center width height

注意坐标是归一化到0~1之间的相对值。比如一张640×480的图上,一个目标边界框左上角(100, 120)、宽200、高160,那txt这一行就是:

0 0.3125 0.4167 0.3125 0.3333

计算过程:x_center = (100 + 200/2) / 640 = 200/640 ≈ 0.3125;y_center = (120 + 160/2) / 480 = 200/480 ≈ 0.4167。这个换算出错是新手标注时最常见的错误之一。

2.3 配置文件修改:类别数、anchor和输入尺寸

训练前要修改模型配置文件models/yolofastestv2.yaml,里面最关键的是检测头输出层的anchor设置。YOLO-FastestV2采用双尺度输出,每层有3个anchor,一共6个组。如果你训练自己的数据集,不要手动去算anchor,直接删除配置文件里的anchor字段,YOLOv5的训练逻辑会自动用K-Means从你的数据集中重新聚类。

但输入尺寸建议提前定好,默认是352×352。如果目标物体普遍比较小,可以调到416甚至512,精度会涨一些,但推理耗时也会增加,移动端部署时先跑一下自己的场景再定。

data/voc.yaml里要改的是nc(类别数)和names(类别名称列表),这两个必须一致,否则训练必报错。

2.4 训练命令和参数选择逻辑

这是我最常用的一组训练命令:

python train.py --data data/voc.yaml --cfg models/yolofastestv2.yaml --weights '' --batch-size 32 --img 352 --epochs 200 --device 0

一些参数选择的实际经验:

  • --batch-size:取决于显存大小。YOLO-FastestV2很轻,普通6GB显存显卡可以开64以上,但batch太大会降低收敛稳定性,32比较稳妥。
  • --img:训练尺寸和后面部署时的输入尺寸最好保持一致,这样可以避免精度损失。比如你部署时打算用352输入,训练时就用352。
  • --epochs:小模型收敛比大模型快,一般150~200轮足够,不是越多越好,300轮以上反而容易过拟合。
  • 训练过程中如果发现训练集Loss还在下降但验证集指标上不去,就说明过拟合了,停止训练,减小模型容量或增加数据增强。

3. 模型转换链路:pt→ONNX→NCNN,中间堆了多少坑

3.1 为什么移动端不能用PyTorch推理

PyTorch官方虽然提供了TorchScript和PyTorch Mobile方案,但在移动端的工程实践里,大家还是更倾向NCNN。原因很直接:

  • PyTorch Mobile的模型加载和预热时间比NCNN长,内存占用也更大。
  • NCNN针对ARM、x86、Vulkan等后端做了大量算子级优化,推理效率更高。
  • NCNN是纯C++实现,对Android JNI调用更友好,接入成本低。

YOLO-FastestV2官方仓库提供了NCNN模型和转换脚本,这也是我选择它的一个重要原因。

3.2 ONNX导出的关键坑:opset版本、动态尺寸和算子兼容

ONNX是中间格式,但导出时有很多细节容易被忽略。直接跑官方提供的导出脚本:

python models/export_yolov5_onnx.py --weights weights/yolofastestv2.pt --img 352 --batch 1

如果没有脚本,也可以手动用torch.onnx导出,核心代码逻辑类似:

import torch from models.experimental import attempt_load model = attempt_load('weights/yolofastestv2.pt', map_location='cpu') model.eval() dummy_input = torch.randn(1, 3, 352, 352) torch.onnx.export( model, dummy_input, 'yolofastestv2.onnx', opset_version=11, input_names=['images'], output_names=['output'] )

这里有个值得注意的点:onnx模型默认保持动态输入,也就是可以接受任意batch和任意分辨率的输入,但NCNN转换时对动态尺寸支持不好,最好把输入尺寸写死。还需要设置--dynamic=False或者在导出时加torch.onnx.export的dynamic_axes参数保持静态。否则转换到NCNN后推理尺寸对不上,直接崩。

还有一个常见问题是导出后onnx模型里的P6层输出形状是(1, 255, 88, 88)这种四维结构,有些后处理代码需要的是(1, 88*88*3, 85),转换时需要注意reshape。

导出后用onnx-simplifier过一遍,能处理掉很多冗余算子:

pip install onnx-simplifier python -m onnxsim yolofastestv2.onnx yolofastestv2_sim.onnx

3.3 onnx2ncnn转换报错排查

拿到简化后的onnx,就可以转NCNN了。官方工具链:

git clone https://github.com/Tencent/ncnn cd ncnn mkdir build && cd build cmake -DCMAKE_BUILD_TYPE=Release .. make -j4 # 编译完成后工具在 build/tools/onnx/onnx2ncnn ./onnx2ncnn yolofastestv2_sim.onnx yolofastestv2.param yolofastestv2.bin

这里有几个高频报错:

  • “Unsupported ONNX op: ReduceMax”类错误:通常是opset版本过高或算子未实现,先用简化器试着消除,不行就降opset到10或11。
  • “MatMul with dynamic shape is not supported yet”:这是onnx里有动态形状的MatMul,说明导出时没有固定输入维度,回上一步重新固定。
  • 转换完成但是没有报错,但.param文件里少了某些层(比如DetectionOutput层),这是正常的。YOLO-FastestV2的检测头是普通卷积+reshape,NCNN不会自动生成decode逻辑,后处理需要自己写。

转换之后一定要做一个完整的前后对比:用PyTorch跑一张图和用NCNN跑同一张图,对比原始输出张量的数值。误差在1e-4以内才算正常,超过这个范围说明转换过程有精度损失,要检查是否有未支持的算子被降级了。这个验证步骤被很多人跳过,结果到Android端才发现识别结果不对,排查起来特别痛苦。

3.4 官方NCNN Demo的目录结构

YOLO-FastestV2作者提供了一个Android NCNN Demo,目录在models/ncnn/Android/下面,项目是基于NCNN自带examples改的。里面关键的三个文件:

  • yolofastestv2_ncnn.cpp:JNI层实现,负责图像预处理、推理和后处理。
  • yolofastestv2.h和yolofastestv2.cpp:封装了检测结果结构体(classId、score、bbox)。
  • MainActivity.java:Android上层调用,展示结果。

我建议先把Demo在Android Studio里跑通,再根据自己需求改造。跑通Demo能验证你的模型文件和整个工具链没问题,之后换成自己的模型会很快。

4. Android端NCNN推理的核心细节

4.1 图像预处理:这一步做错,后面全白搭

NCNN推理对输入图像有严格要求,YOLO-FastestV2训练时的预处理逻辑是:图缩放到352×352,RGB三通道,每个像素除以255归一化,数据布局为NCHW。在Android端,相机或相册返回的图像通常是NV21格式或Bitmap格式,需要先转换成RGB再缩放。

这里容易踩的坑是通道顺序。训练时用的是RGB,但Android相机和很多第三方库默认输出是BGR。如果不做转换,用BGR数据直接喂给模型,检测结果就是混乱的。我调试时发现检测框位置和类别完全对不上,排查了半天发现就是RGB/BGR问题。

NCNN的图像输入还有一个更隐蔽的细节:如果你的网络参数里用了pixel_type字段指定了图像类型,NCNN会自动做转换;但YOLO-FastestV2的转换脚本输出的是普通卷积层,不会自动处理,需要自己在JNI层做好缩放和归一化。

ncnn::Mat in = ncnn::Mat::from_pixels_resize(rgb_data, ncnn::Mat::PIXEL_RGB, width, height, 352, 352); const float norm_vals[3] = {1.f/255.f, 1.f/255.f, 1.f/255.f}; in.substract_mean_normalize(0, norm_vals);

4.2 模型加载和推理会话配置

NCNN模型加载方式有两种:从文件加载和从内存加载。Android工程里通常把.param和.bin文件放到assets目录,运行时用ncnn::Net加载:

ncnn::Net net; net.opt.use_vulkan_compute = true; // 有GPU就用GPU net.opt.use_fp16_packed = true; // 半精度混合推理 net.load_param("yolofastestv2_ncnn.param"); net.load_model("yolofastestv2_ncnn.bin");

有几个参数值得特别说明:

  • use_vulkan_compute:开启后使用GPU推理,但低端手机的GPU驱动不一定稳定,实测部分GPU会导致首次推理特别慢或者掉帧。我的建议是默认开GPU,如果在低端机上不稳定就关掉,CPU多线程也能跑。
  • use_fp16_packed:FP16推理,能提高速度但精度会有一点损失,一般检测场景影响不大。
  • use_fp16_storage:和上面配合使用,降低内存带宽。

还有一个容易被忽略的是net.opt.num_threads,不设置的话默认用4线程,但有些四核大核+小核架构的手机,设置成大核数量能获得更好的性能。

推理核心代码:

ncnn::Extractor ex = net.create_extractor(); ex.input("images", in); ncnn::Mat out; ex.extract("output", out);

4.3 后处理:anchor解码和NMS,手写还是调库?

YOLO-FastestV2的输出需要在后处理阶段解码才能得到最终的检测框和类别。它有两个检测头,分别处理不同尺度的特征图。后处理逻辑包括:

  • 将原始输出拆分成边界框回归(x,y,w,h)、置信度和类别概率。
  • 解码得到最终的坐标值,将归一化的坐标还原到原图尺度。
  • 用NMS去掉重复框。

NCNN没有内置YOLO检测头,所以这部分需要自己实现。Demo里的后处理代码大约100行,核心逻辑可以复用。但有一个细节:YOLO-FastestV2输出头的通道排列顺序和YOLOv5不完全一样,直接套用YOLOv5的解码代码会出错。

以我的经验,后处理最需要关注的是anchor的顺序。YOLO-FastestV2的anchor定义在模型的yaml配置里,转换到NCNN后不会自动包含anchor信息,需要手动在代码里写。anchor顺序如果搞反,检测框的位置就会错乱——比如把大目标的anchor用在了小目标检测层上。

NMS的实现有两种方式:一种是自己写循环遍历,另一种是用std::sort+IoU计算的方式。数据量小,几百个候选框,自己写完全没问题,不要引第三方库增加包体积。

4.4 性能实测:CPU/GPU、单线程/多线程的差别有多大

以下是我在Pixel 4(骁龙855)上跑YOLO-FastestV2时的实测数据,输入尺寸352×352,仅供参考:

配置单帧耗时备注
CPU 1线程88ms能跑但不流畅
CPU 4线程32ms日常推荐
CPU 4线程 + FP1627ms速度和精度折中
GPU Vulkan18ms帧率稳定,发热明显
GPU Vulkan + FP1614ms最优性能组合

从数据能看出,对YOLO-FastestV2这种小模型,CPU四线程已经能满足30帧需求,GPU提升效果有但有限。这主要是因为模型本身计算量太小,调度和内存拷贝的开销占比变高了。在树莓派这类纯CPU设备上,四线程是性能上限,超线程反而会导致CPU负载过高发热降频。

5. 进一步压榨性能的四条优化路径

5.1 INT8量化:耗时、内存和精度的三重权衡

NCNN支持INT8量化,YOLO-FastestV2转INT8后模型从1.2MB降到约300KB,推理速度在CPU上能再提升30%~50%,但精度会有明显下降,尤其对小目标。如果模型训练时的mAP本身就在80%以下,量化后掉到70%以下的风险很高。

NCNN的INT8量化需要准备一个校准集,用真实场景的图片来统计每层激活值的范围:

./ncnn2table yolofastestv2.param yolofastestv2.bin calibration_set.txt list.txt mean=0 norm=0.0039215686274509803 shape=352x352x3 pixel=rgb thread=4 method=kl ./ncnn2int8 yolofastestv2.param yolofastestv2.bin yolofastestv2_int8.param yolofastestv2_int8.bin table

校准集建议涵盖所有类别的典型样本,数量在100~500张之间。校准集选不好,量化后的精度损失会非常剧烈。

5.2 输入分辨率剪枝:速度翻倍的一条捷径

YOLO-FastestV2默认输入是352×352,但很多场景下不一定需要这么高的分辨率。当检测目标体在画面中占比比较大时(比如摄像头架在产线上方拍工件),把输入降到288×288或320×320,计算量会大幅下降,推理速度提升接近40%。

但注意,改推理分辨率后,模型输出的感受野和anchor匹配关系会变,最好的方式是训练时就直接用目标分辨率。如果已经用352训练完了,硬要降到288推理,检测精度会下降约3~5个百分点,但多数场景还是能用的。

5.3 后处理代码级别的优化

后处理虽然占比不高,但在帧率要求严格的场景下也是一块收益。具体做法:

  • 提前过滤低置信度框:得分低于0.25的候选框直接丢掉,避免进入NMS阶段,能减少80%以上的IoU计算。
  • 用循环展开或SIMD指令优化解码循环。
  • NMS排序用std::nth_element而不是完整std::sort,只需要知道最高分的那几个框就够了。

5.4 线程配置和内存复用

这块是容易被忽略的。推理框架默认会在每次推理时分配新的内存,如果你的程序是连续检测视频帧,就会有大量重复分配和释放,导致GC开销过高。建议在初始化阶段就配置NCNN的gpu_heap_allocator和cpu_allocator,让网络复用分配好的内存块。

线程数的设置原则是:优先占用高性能核心,不要盲目开满所有线程。在大小核架构的ARM芯片上,把小核线程数控制到最低能显著降低功耗和发热。

最后再聊几句

这一整套流程走下来,我的体会是:YOLO-FastestV2最大的优势并不是某一个数值特别亮眼,而是整个工具链对边缘部署非常友好,从训练到NCNN都有现成脚本,省掉了大量移植和调试时间。如果你在部署中遇到精度突然对不上的问题,优先排查RGB/BGR顺序和anchor顺序,这两个坑占了我遇到问题的七成以上。后续如果你想把模型进一步部署到像K210这种更低算力的MCU上,INT8量化并配合FBNet这类更极端的骨干,是可以试试的方向。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询