简介:这是一份基于PyTorch的MobileNet-YOLO目标检测实现,整合了MobileNetV1/V2/V3与YOLOv3/YOLOv4系列模型,面向深度学习中目标检测方向的开发者与研究人员。资源包含完整的训练、测试与推理代码,支持在VOC2007/VOC2012数据集上进行训练和评估,模型加载ImageNet预训练权重,适合用来对比不同轻量主干网络与YOLO检测头的组合效果。压缩包共29个文件,以16个Python源码文件为主,涵盖模型结构、训练脚本、评估工具与数据加载等模块;另有5个Shell脚本用于数据集下载、LMDB创建与一键训练,2个YAML文件提供数据与模型配置,4张示例图片便于快速预览检测效果。资源包仅240KB,属于纯代码工程包。目前已有1943人学习下载,对于想在移动端或嵌入式平台部署目标检测的开发者,这套代码提供了可复现的基准实现。 做目标检测的应该都体会过这种纠结:想上移动端或嵌入式设备,YOLO系列虽然检测效果好,但DarkNet骨干网太吃算力;想用MobileNet做轻量化主干,又要重新实现检测头、调anchor、改loss,工程量大还容易出bug。Mobilenet-YOLO-Pytorch这个项目的价值,就是把mobilenet系列(v1、v2、v3)和yolo系列(yolov3、yolov4)整合进同一套Pytorch代码里,骨干网和检测框架可以自由组合,省掉大量重复造轮子的时间。这篇文章我会从项目结构拆解、环境配置、训练调参、部署落地到常见坑点,完整过一遍我的实操记录,适合正在做轻量化目标检测的学生、算法工程师和嵌入式开发同学参考。
1. 项目整体设计与选型思路
1.1 为什么要把MobileNet和YOLO绑在一起
先聊清楚一个本质问题:MobileNet和YOLO根本不是同一层级的产物。MobileNet是一族轻量级卷积骨干网络,负责把输入图片逐步压缩成高语义特征图;YOLO是一套完整的目标检测框架,包含骨干网络、多尺度特征融合、检测头和损失函数。传统YOLO的骨干是DarkNet,参数量和计算量都偏大,单个batch在GPU上跑没问题,但放到手机、树莓派或者RK3588这类边缘设备上,实时性就很难看。
Mobilenet-YOLO-Pytorch的思路就是“换心不换壳”——把YOLO的DarkNet骨干换成MobileNet系列,保留YOLO的检测头和损失设计。这样做的好处很直接:模型体积能压到十几MB甚至几MB,推理速度在边缘设备上可以跑到几十甚至上百FPS,同时还能保留YOLO系列的多尺度检测能力,对小目标和中目标的召回率不至于崩掉。这个方向其实就是工业界常说的轻量化检测方案,类似的工作还有YOLOv5的n/s/m版本、NanoDet等,但这个项目把mobilenet系列和yolo系列全部放在一起直接切换,方便做横向对比实验。
1.2 MobileNet系列的三个版本差异
MobileNet v1的核心是深度可分离卷积(Depthwise Separable Convolution),把一个标准卷积分解成逐通道卷积和逐点卷积两步,计算量能直接降一个量级。这个结构当时很惊艳,但训练完容易有通道信息丢失的问题,因为深度卷积部分每个通道只被一个卷积核处理,通道间的信息融合全靠最后的点卷积。
MobileNet v2引入了Inverted Residual(倒残差结构)和Linear Bottleneck。所谓“倒残差”是先压缩通道再扩张回来,和ResNet的残差块正好反过来——普通残差是先降维再升维,MobileNet v2是先升维再降维。原因是深度卷积在高维空间效果更好,先用1x1卷积把通道撑开,经过深度卷积后再压缩回低维,并且低维层不用激活函数,避免ReLU把信息砸掉。这个设计让v2在精度和速度上全面优于v1。
MobileNet v3是组合拳:用NAS搜出来的结构加上h-swish激活函数和SE注意力模块。h-swish是swish的硬版本,计算更快但保留了非线性表达能力,SE模块用极小的成本提升了通道注意力。v3还区分了Large和Small两个版本,Small版更极端地压缩通道数,适合算力非常紧张的场景。实际用下来,v3-Small做检测骨干会有些吃力,特征图表达能力弱,建议至少用v3-Large起步。
1.3 YOLO v3和v4在检测框架上的演进
YOLO v3最大的贡献是多尺度预测,在3个不同尺寸的特征图上分别做检测,分别负责大、中、小目标。每个网格预测3个anchor,配合logistic回归做目标性得分,相较于v2的单一尺度特征图,小目标召回率提升非常明显。损失函数里也用了Binary Cross Entropy代替softmax,因为一个目标理论上可以同时属于多个类别(比如“人”和“行人”)。
YOLO v4则是一堆有效技巧的集大成者:Mish激活函数、CSPDarkNet结构、PANet路径聚合、Mosaic数据增强、CIoU损失、DIoU NMS等等。这套组合让v4在COCO上的AP比v3高了一大截,但计算量也上去了。在Mobilenet-YOLO-Pytorch项目里,yolov4的检测头设计通常结合了PANet的上下路径融合,用MobileNet替代CSPDarkNet后,计算瓶颈明显下降,适合对帧率要求苛刻的场景。
2. 环境搭建与依赖准备
2.1 PyTorch环境配置要点
这个项目基于Pytorch框架,第一步就是把环境搞定。先说CUDA版本匹配的问题——很多人装完Pytorch发现GPU不可用,十有八九是CUDA Toolkit和Pytorch的CUDA版本没对齐。最简单的做法是去Pytorch官网选对应的安装命令,比如当前常用的CUDA 11.8和12.1版本,安装命令会直接帮你装好配套的cuDNN和CUDA运行时,不需要单独装完整版CUDA Toolkit。
Windows下我推荐用Anaconda创建独立环境,避免污染系统Python:
conda create -n yolo_mobilenet python=3.8 -y conda activate yolo_mobilenet pip install torch==2.0.1 torchvision==0.15.2 --index-url https://download.pytorch.org/whl/cu118 conda install pyyaml matplotlib tqdm tensorboard装完跑一下:
import torch print(torch.__version__) print(torch.cuda.is_available()) # 输出True就说明GPU可用顺便说一句,笔记本开热点下载pytorch安装包很慢的问题,优先换国内镜像源,清华源和阿里的pypi镜像都比官方源快几十倍,如果是conda包就用清华的anaconda镜像。不要硬等,我之前等过半小时的官方源,最后发现镜像源半分钟就下完了。
2.2 项目克隆与数据组织
项目克隆下来之后,核心去看三个东西:模型定义文件、配置文件、训练入口脚本。数据格式上,这个项目遵循类VOC或类COCO的组织方式,图片放在JPEGImages目录,标注放在Annotations目录,然后生成train.txt、val.txt文件列表。如果真的没有标准标注,也可以先用LabelImg工具打一批VOC格式的数据,再转成项目需要的格式。
git clone https://github.com/xxx/Mobilenet-YOLO-Pytorch.git cd Mobilenet-YOLO-Pytorch pip install -r requirements.txt数据目录建好之后,记得去配置文件里修改类别数量和数据路径。类别数量这个参数直接影响检测头的输出通道数,少改一个数字训练时直接报维度不匹配的错,所以启动训练前先确认几遍。
3. 模型结构与核心实现解析
3.1 配置文件里怎么切换backbone和detector
这个项目最省心的地方在于模型配置通过文件控制,不需要改代码。比如你可以在yaml或py配置里指定:
backbone = 'mobilenetv2' # mobilenetv1 / mobilenetv2 / mobilenetv3 yolo_version = 'yolov3' # yolov3 / yolov4选择不同的组合,项目会分别加载对应的特征提取网络和检测头,并自动拼接。这个设计的本质是解耦——骨干网络输出三个不同尺度的特征图(比如下采样8x、16x、32x),检测头拿这些特征图去做多尺度预测。切换骨干时,只要保证输出特征图的通道数衔接得上就行。
MobileNet v3输出特征图时会根据配置返回不同层的特征,代码里常常会有类似return [feat1, feat2, feat3]的结构,分别对应浅层细节、中层语义、深层高语义。如果接到yolov4的PANet结构,还需要额外加自底向上的特征融合路径,这个项目如果实现了的话,会在neck部分组合这些特征。
3.2 Anchor的设计与计算
Anchor是YOLO系列绕不开的环节。YOLO v3默认在COCO数据集上用k-means聚类出了9组anchor,按面积大小分成三组,分别对应大、中、小目标的预测层。但如果你换了自己的数据集,必须重新聚类anchor,否则训练初期的损失会异常高,收敛很慢。
推荐在项目里跑一下k-means聚类脚本,比如:
# 伪代码示意,实际脚本可能不同 from utils import kmeans_anchors kmeans_anchors('data/train.txt', n_anchors=9, image_size=416)聚类完得到的新anchor写回配置文件。如果不想改anchor,至少用ImageNet预训练权重做迁移学习,让网络自己适应新的anchor分布,但训练轮数会长很多。
3.3 Backbone预训练权重怎么处理
用MobileNet做骨干时,强烈建议加载ImageNet预训练权重。这个项目一般会支持传递预训练权重路径,训练启动时骨干网络加载预训练参数,检测头随机初始化。这样做的好处是骨干网络已经学会了通用特征提取,微调阶段只需要专注学习检测任务的特有信息,收敛快还稳定。
实际操作时要注意:不要直接torch.load整个模型文件然后model.load_state_dict,因为骨干的key和检测头的key混在一起会报错。正确做法是单独加载骨干权重文件,或者用项目封装好的加载逻辑。踩过坑的朋友应该知道,最典型的报错是size mismatch for backbone.features.14.conv2.weight之类,说明预训练权重的通道数和当前模型不匹配,多半是backbone版本选错了。
4. 训练实操与调参心得
4.1 训练启动命令与参数解释
让我用一条实际命令来说明训练的基本用法:
python train.py --data data/custom.yaml --cfg cfg/yolov3_mobilenetv2.cfg --weights weights/mobilenetv2.pth --batch-size 16 --img-size 640 --epochs 100 --device 0每个参数背后都是有讲究的。--img-size决定了训练时的输入分辨率,640比416在检测小目标时有优势,但显存占用成倍上涨。如果你的GPU(比如单张2080Ti或3060)只有11-12GB显存,batch-size设16配640分辨率可能直接OOM,稳妥的方案是batch-size降到8,或者图片尺寸降到512。--epochs不要迷信固定值,先用小学习率跑20个epoch观察损失曲线,再决定要不要继续。
学习率策略上,推荐使用warmup加余弦退火的组合。前几个epoch用较低的学习率让模型稳定起步,避免开局震荡,然后再用余弦退火把学习率平滑降下来,最后阶段收敛更稳:
# 伪代码示意 from torch.optim.lr_scheduler import CosineAnnealingLR, LinearLR warmup = LinearLR(optimizer, start_factor=0.01, total_iters=5) cosine = CosineAnnealingLR(optimizer, T_max=95) scheduler = SequentialLR(optimizer, [warmup, cosine], milestones=[5])4.2 训练结果里的参数量和FLOPs怎么看
很多同学训练刚启动时看终端打印的模型参数量,训练完又去统计模型文件大小,发现对不上号,就会疑惑怎么回事。这里简单解释:训练启动时统计的是模型全量参数,包括骨干、neck、检测头,还包括BN层的gamma和beta。但保存权重时有两种情况——有的代码只保存了state_dict里requires_grad=True的参数,有的把BN的running_mean和running_var也排除在外,导致文件大小比启动统计值小很多,这是正常现象,不是模型被裁剪了。
想精确看待部署到实际设备需要多少参数,直接用torchsummary或者自己遍历model.parameters()统计即可。FLOPs可以用thop库来测:
from thop import profile input_tensor = torch.randn(1, 3, 416, 416) flops, params = profile(model, inputs=(input_tensor,)) print(flops / 1e9, 'GFLOPs', params / 1e6, 'M')实测下来的数据,mobilenetv2做骨干的yolov3检测头,输入416x416,FLOPs大约在3-5G左右,参数量8-12M,相比DarkNet53骨干的yolov3少了将近一个数量级。
4.3 损失曲线怎么判断是否训练正常
训练过程中主要盯三个loss:box loss(边界框回归)、obj loss(目标性损失)、cls loss(分类损失)。正常情况是三者都平稳下降,尤其obj loss和box loss在前期下降明显。如果训练了30个epoch后cls loss还在高位震荡,多半是类别不平衡或者标注噪声大,可以检查数据里有没漏标、错标的框。
还有一个高频问题:训练时验证集mAP很高但实际图片预测一堆漏检。这往往是因为只用了训练集做数据增强,模型对真实场景的泛化不足。建议训练时加上Mosaic和MixUp,如果项目支持的话,默认开启就行。Mosaic把4张图拼成一张训练,能显著提升小目标的检测能力,代价是训练速度稍微变慢。
5. 模型部署导出与工程落地
5.1 怎么导出模型给Qt或C++调用
训练完的模型是Pytorch的.pt格式,Qt/C++环境直接调用不方便,通常转成TorchScript或ONNX。TorchScript的好处是不需要额外安装推理框架,直接用LibTorch加载;ONNX的好处是通用性强,能转成TensorRT、OpenVINO、NCNN等格式。
导出TorchScript的代码示例:
import torch from models import create_model model = create_model(cfg, num_classes=20) model.load_state_dict(torch.load('best.pt', map_location='cpu')['model']) model.eval() example_input = torch.randn(1, 3, 416, 416) traced_model = torch.jit.trace(model, example_input) traced_model.save('best.torchscript.pt')Qt里用LibTorch调用,核心就是把输入图像预处理成网络需要的格式,再跑一次forward,最后解析输出的[batch, num_anchors, 5+num_classes]张量。注意Pytorch的图像通道顺序是CHW,Qt的QImage是HWC,转换时不要搞反,这个坑我踩过至少两次。
5.2 边缘设备部署(RK3588 / RV1126B)
如果你要跑在瑞芯微的RK3588或RV1126B这类平台上,流程一般是这样:先把Pytorch模型导出为ONNX,再用RKNN-Toolkit转成RKNN格式,最后在板子上调用RKNN的C/Python接口推理。转RKNN的过程中最容易出的问题是算子不支持,遇到不支持的算子需要回源码里替换掉,比如Mish激活函数在部分版本里不支持,就要换成LeakyReLU重新训练,或者手动编写自定义算子。
另外,边缘设备上如果跑CPU多进程推理,测试时发现每帧耗时比单进程还慢1.4秒,这个现象和模型本身无关,大概率是线程调度和数据拷贝的问题。RK3588的NPU调用方式比较特殊,不要盲目开多线程去跑同一个NPU推理任务,多个进程同时申请NPU资源反而会互相阻塞,建议保持单进程推理,用队列异步处理采集帧。
5.3 推理结果重叠框和漏检如何调优
推理时经常碰到两个目标靠太近,输出一堆几乎重叠的框。原因是NMS阈值没调好,或者anchor配置不合理。先检查NMS的IoU阈值,默认0.45左右,如果重叠框还是很多,降到0.3试试。同时检查conf_thres(置信度阈值),太低会产生大量低质量预测框,太高又容易漏检,一般设0.25-0.5之间调试。
如果某个类别的框总是漏掉,手动去验证集里挑几张图看那个类别目标的尺寸分布,是不是偏小或偏大。比如遥感场景目标非常小,3个特征层里浅层特征图负责的小目标检测能力不够,就需要训练时输入更高分辨率,或者增加浅层anchor数量。这是目标检测调优里最常见的路径:先看数据分布,再动anchor,最后调阈值。
6. 常见问题与避坑实录
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 训练时报size mismatch | 预训练权重通道数和当前模型不匹配 | 检查backbone选型,换匹配的权重文件 |
| mAP很高但实际推理漏检 | 训练数据增强不够 | 开启Mosaic,加入多尺度训练 |
| 推理重叠框严重 | NMS阈值过高或anchor大小不合适 | 调低NMS IoU阈值,重新聚类anchor |
| CPU多进程推理反而更慢 | 数据拷贝开销和进程调度问题 | 改单进程或引入队列做流水线 |
| 训练后参数量和启动时不一致 | 保存权重时部分参数状态不同 | 统一点用model.parameters()计算 |
| 部署到板子算力不足 | 模型FLOPs还是太高 | 换mobilenetv3-small,输入分辨率降到320 |
再补充一个不太好排查的问题:训练时用的是GPU,但推理环境是CPU,模型输出结果差很多。这个一般是BN层在推理模式下的running_mean和running_var没有正确写入,或者导出模型前没有调model.eval()。Pytorch默认训练模式下BN用batch统计量,eval模式才用running统计量,所以导出前必须切eval模式,否则导出的模型在CPU上结果会飘。
另外做迁移学习的时候,如果用了冻结骨干的方式训练,记得训练完解冻所有层再微调几轮。冻结骨干时detection head单独训练,骨干参数不变;解冻后全部参数一起更新,能在现有基础上再涨1-2个点的mAP。有一种情况是解冻后反而掉点,那就保持冻结骨干的权重,只训头部,在数据量少的时候很常见。
我个人做轻量化检测项目时发现,MobileNet v2作为骨干综合体验最好,训练稳定、部署生态好、转RKNN或NCNN都很顺利;v3精度略高一点,但某些算子在嵌入式平台不支持,需要额外适配;v1已经不推荐在正经项目里使用了,精度比较旧,省的那点计算量在当下硬件上意义不大。如果你的数据集中小目标占比高,建议优先考虑mobilenetv2加yolov3的组合,在检测头和骨干之间平衡最好,后续需要更高速率再换mobilenetv3。这个组合方案我实测在自定义数据集上能跑到接近DarkNet版本yolov3的90%精度,帧率却快3到4倍,对项目落地来说这个交换是值得的。
本文还有配套的精品资源,点击获取