搞了快一年的垃圾分拣项目,从最开始靠人工规则匹配,到后来上YOLOv5 4.0做视觉识别,我把从环境配置到模型上线跑通的全流程走了一遍。这篇就围绕YOLOv5 4.0版本,把环境配置、数据集制作、模型训练,以及最终接进垃圾分拣业务流的完整链路拉通来讲。很多人一上来就奔着"跑通官方demo"去,装完环境跑一次detect.py就以为完事了。实际做业务项目完全不是这么回事,尤其是垃圾分拣这种场景——目标种类多、外形差异大、现场光照复杂、还要考虑传输带上的实时速度,任何一个环节没处理好,模型精度再高也白搭。这篇文章我尽量把每一步为什么这么做、坑在哪、怎么判断结果对不对都讲清楚,希望能给准备上车目标检测的朋友省点时间。1. 项目背景与整体思路拆解1.1 为什么选YOLOv5 4.0版本做垃圾分拣先说我为什么锁定YOLOv5 4.0这个版本,而不是追最新版也不是回头用3.x。垃圾分拣业务的核心诉求是"实时识别+持续迭代",YOLOv5 4.0在2021年中发布时,已经具备非常成熟的CSPDarknet骨干网络和PANet颈部结构,检测速度和精度平衡做得非常好。拿实际数据说话:在普通消费级显卡上,YOLOv5s模型跑640×640输入,推理速度能做到5ms到10ms一帧,mAP在COCO类任务上能到37左右。对垃圾分拣这种场景,我们需要识别的是塑料瓶、易拉罐、纸箱、电池、果皮、玻璃瓶这些常见物品,类别数量不算多,但外形变化很大,YOLOv5 4.0的多尺度预测机制正好能覆盖大小目标混杂的情况。还有一点很关键:4.0版本的代码结构清晰、文档齐全,网上案例也多,出了问题容易排查。最新版虽然功能更多,但依赖变动大、踩坑成本高;更老的版本又没有Mish激活函数和自适应锚框这些改进。4.0正好是个"稳"字当头的版本,适合做工程落地。1.2 垃圾分拣业务流的核心链路与难点真正的垃圾分拣业务流不是"一张图丢进去、框出来"这么简单,完整的链路应该是这样:生产现场通过工位相机或传输带上的固定摄像头持续采集画面,画面经过目标检测模型推理,得到每个目标的类别和位置坐标,然后控制系统根据这些信息控制机械臂或分拣挡板,把对应垃圾推向不同的料仓。整个链路对检测模型的要求有三个:速度快、分类准、定位稳。速度快好理解,但"准"和"稳"是两回事。分类准要求模型能区分看起来很像的东西,比如透明矿泉水瓶和透明饮料瓶;定位稳要求同一件物体在连续几帧画面里,框的位置和大小不能跳来跳去,否则机械臂抓取的时候会扑空。我踩过的最大一个坑,是前期只追求测试集上的mAP,忽略了现场光照变化。厂房的灯光有频闪,传送带上的垃圾还会转动,同一个瓶子在不同角度下特征完全不同。所以整个项目从数据采集阶段就要围绕业务流设计,不能拍几张干净照片就完事。2. 环境配置全流程与版本搭配详解2.1 基础环境:Python、CUDA、PyTorch的版本搭配逻辑YOLOv5 4.0官方在README里给出的环境要求是Python 3.8+、PyTorch 1.7+,但我实测下来版本搭配有一些讲究。这是我最常用的组合,稳定不出幺蛾子:组件版本说明操作系统Ubuntu 20.04 / Windows 10均可,项目本身跨平台Python3.8.10太新版本可能兼容性问题,3.8最稳CUDA11.3向后兼容10.2,但11.x编译的算子更全cuDNN8.2.1和CUDA 11.3配套PyTorch1.10.0实测和4.0版本代码兼容性最好torchvision0.11.0必须和PyTorch版本严格对应为什么不推荐PyTorch 1.12以上?因为YOLOv5 4.0里有些针对老版本PyTorch写的算子,新版本里行为有变化,比如某些损失函数在不同版本下数值精度不一致,训练过程表现正常,但推理结果会差零点几个百分点,排查起来非常费劲。安装过程我用conda管理环境,照着执行就行:# 创建独立环境,避免污染系统Python conda create -n yolov5 python=3.8.10 conda activate yolov5 # 安装PyTorch,注意指定cuda版本 pip install torch==1.10.0+cu113 torchvision==0.11.0+cu113 -f https://download.pytorch.org/whl/torch_stable.html # 安装YOLOv5 4.0依赖 pip install -r requirements.txt提示:如果你机器上没有NVIDIA显卡,也可以用CPU版本做小规模测试,但训练速度会慢到怀疑人生。真实业务项目建议至少有8GB显存的显卡起步。2.2 验证环境是否配置正确的几个关键命令环境装完不能直接开跑,先花两分钟验证。我最常踩的情况是:pip list显示装好了,但import torch一运行就崩。验证命令其实很简单:python -c "import torch; print(torch.__version__); print(torch.cuda.is_available())"如果输出True,说明CUDA可用。再跑一个实际张量运算确认GPU真的在工作:python -c "import torch; x = torch.rand(1000,1000).cuda(); y = x @ x; print(y.sum())"这里有个细节:torch.cuda.is_available()返回True不代表CUDA计算真的没问题,可能是PyTorch自带的CPU实现兜底了。我做项目时遇到过驱动版本和CUDA runtime不匹配的情况,表面正常,但训练中途出现CUDA error: device-side assert triggered直接崩掉。所以强烈建议上面这个张量运算也要跑通。还有一个容易忽略的点:YOLOv5 4.0里很多工具脚本依赖wandb(Weights & Biases)做日志记录,第一次运行detect.py会让你登录,很烦人。可以在环境变量里禁用:export WANDB_MODE=disabled或者干脆在import wandb的地方注释掉。实测在Windows上禁用wandb能避免不少莫名其妙的网络超时问题。2.3 业务场景下的环境选择思路如果是给现场分拣设备配环境,我建议用Docker把环境打包成镜像,而不是在工控机上裸装。因为现场设备硬件参差不齐,今天这台是A卡、明天那台是N卡,用Docker + NVIDIA Container Toolkit可以把CUDA、PyTorch、代码一次性固化,换设备只需要拉镜像。具体做法是拉一个pytorch/pytorch:1.10.0-cuda11.3-cudnn8-devel镜像,把YOLOv5 4.0代码挂载进去,再pip安装依赖。这样开发环境、训练环境、现场推理环境三处完全一致,避免"在我电脑上能跑、到现场就跑不了"的尴尬。3. 数据集制作与标注实操3.1 从现场采集到筛选:垃圾数据集的特殊性数据是垃圾分拣项目的命根子。我见过很多团队在模型结构上花大功夫换各种Backbone,结果数据集就一两百张图,最后性能上不去,根子就在数据量不够。垃圾识别有个特点:同类物品在形态上差异极大,一个矿泉水瓶,有透明的、有绿色的、有压扁的、有带标签的,这都得当成同一种类来识别,需要模型学到足够泛化的特征。采集阶段我的建议是分三个场景拍:静态台面:物品静止摆放在不同背景、不同光照下,这是基础数据,让模型先学会基本特征。动态输送带:模拟真实分拣线的运动状态,用视频抽帧方式采集。这一步很关键,运动模糊对模型鲁棒性影响很大。遮挡堆叠:垃圾经常是互相压着的,要专门拍一些目标被部分遮挡的图片,让模型学会"看一部分认全部"。采集完不等于数据能用,要过筛。我筛数据看三点:模糊的不要、目标占比太小的不要、完全重复的不要。比如一帧画面里目标只有十几个像素,这种标注了也没意义,模型学不到有效特征,还容易产生噪声标签。3.2 使用LabelImg标注的核心操作要点标注工具我推荐LabelImg,轻量、跨平台、支持YOLO格式导出。虽然现在有一些自动化标注工具,但对垃圾这种类别多、目标密集的数据集,手工标注质量依然最可靠。标注前先规划类别清单,这一步要慎重,因为中途改类别名会非常痛苦。我的垃圾分拣项目第一版定了8类:plastic_bottle 塑料瓶 can 易拉罐 cardboard_box 纸箱 battery 电池 fruit_waste 果皮/厨余 glass_bottle 玻璃瓶 aluminum_foil 铝箔/锡纸 plastic_bag 塑料袋类别的命名尽量用英文小写加下划线,因为YOLO的类别文件就是纯文本记录,中文可能出现编码问题。LabelImg标注时有个小技巧:按住Ctrl+滚轮放大图片再打框,打出来的框更贴合目标边缘;对遮挡目标不要强行把整个物体框进去,而是框住可见区域,这样训练时模型学到的特征更准确。标注完保存,每张图片对应一个同名txt文件,内容格式是:类别序号 中心点x相对坐标 中心点y相对坐标 框宽相对坐标 框高相对坐标比如0 0.523 0.411 0.186 0.244,前面是类别ID,后面四个值都是0到1之间的归一化坐标。这里最容易出的问题是用绝对像素坐标,YOLO训练会直接报维度错误。LabelImg导出YOLO格式时自动生成归一化坐标,但如果你自己写脚本处理数据,一定要记得除以图片宽高。3.3 数据集目录结构与训练集划分策略YOLOv5 4.0对数据集目录结构有明确要求,目录得这样建:datasets/ ├── garbage_data/ │ ├── images/ │ │ ├── train/ # 训练图片 │ │ └── val/ # 验证图片 │ └── labels/ │ ├── train/ # 训练标签(txt) │ └── val/ # 验证标签(txt)注意train和val在images和labels下必须同名,否则训练时找不到对应标签。划分比例我一般用9:1,训练集占90%验证集占10%。有人说8:2更稳妥,但对垃圾场景数据本身就紧张了,验证集留10%足够判断拟合情况。划分的时候还要保证类别均衡,不能让训练集里塑料瓶占80%其它类一共20%。我的做法是统计每个类别的标注框数量,如果某一类特别少,就针对性补充采集或者做数据增强。这里补充一句数据增强的作用:YOLOv5 4.0训练时默认会自动做mosaic增强、随机旋转、缩放、平移、色彩抖动,所以不需要额外预处理。但如果你的数据集非常小(每类少于500个框),建议在模型配置里把增强强度调大,比如把hsv_h、hsv_s参数提高,让模型多适应颜色变化。4. 模型配置与训练核心流程4.1 修改配置文件:data.yaml和模型结构选择训练前要准备两个核心配置文件。第一个是数据集配置文件,比如garbage.yaml,放在YOLOv5的data目录下:train: datasets/garbage_data/images/train val: datasets/garbage_data/images/val nc: 8 names: ['plastic_bottle', 'can', 'cardboard_box', 'battery', 'fruit_waste', 'glass_bottle', 'aluminum_foil', 'plastic_bag']这里有两个容易被坑的地方。第一,路径必须是相对YOLOv5 4.0项目根目录的路径,不能写绝对路径,否则换机器跑就要改配置;但如果数据集和代码不在同一盘符,还得写绝对路径,这个要视情况判断。第二,nc一定要和names列表长度一致,少一个或多一个,训练时类别数量对不上,损失函数会直接报错。第二个是模型结构配置,通常直接复制官方现成的yolov5s.yaml,只改nc为8。选哪个模型结构取决于你的部署条件:模型参数量推理速度精度适用场景yolov5s7.2M最快适中现场工控机,推荐yolov5m21.2M较快较高显卡性能尚可yolov5l46.5M较慢高服务端离线识别我做过对比测试:在垃圾分拣场景,yolov5s和yolov5m的精度差距大概在2到3个mAP点,但推理速度差了将近一倍。实际分拣线帧率要求至少25FPS,所以我最终用yolov5s,把精度靠数据量和训练技巧补上去。4.2 训练命令与关键超参数调整环境就绪、数据就位、配置文件改好,就可以启动训练了。YOLOv5 4.0的训练命令是:python train.py --data garbage.yaml --cfg yolov5s.yaml --weights yolov5s.pt --batch-size 16 --epochs 100 --img-size 640几个参数我的建议:batch-size根据显存来定,如果显卡只有8GB显存,batch-size 16配--img-size 640极限了,再往上就显存溢出;epochs先跑100轮,看收敛情况再决定要不要加;--weights可以直接用官方在COCO上预训练的权重,迁移学习能省大量时间。训练过程中的输出信息重点看三个部分:GPU显存利用率、loss曲线、mAP指标。我见过有人只看mAP,不看loss曲线,结果模型根本没收敛。注意:YOLOv5 4.0里默认开启--multi-scale多尺度训练,但垃圾分拣场景目标大小分布比较稳定,我实际测试下来关闭多尺度训练能让训练速度提升20%,精度没有明显变化。在train.py参数里把--multi-scale去掉就行。还有一个参数容易被忽略:--cache。如果你内存足够大,加上这个参数会把图片全部缓存到内存,训练时省去反复读磁盘的时间,实测能提速将近三分之一。但要注意内存占用,3000张图大概需要20GB内存,别加了之后电脑直接卡死。4.3 训练进度监控与模型选型判断训练过程通常跑两三个小时到十几个小时不等,取决于数据量和硬件。YOLOv5 4.0会自动在runs/train/目录下生成exp文件夹,里面有训练曲线图和Best.pt、Last.pt两个权重文件。Best.pt是根据验证集mAP表现自动保存的最优模型,Last.pt是最后一轮的模型,两者都要保留。判断模型好坏不能只看训练集上的表现,要重点看验证集上的mAP和PR曲线。垃圾分拣场景我特别关注每个类别的单独AP值。经常出现整体mAP不错,但某一种类AP特别低的情况,比如电池因为体积小、样本少,AP只有50多,拉低了整体性能。解决类别性能差异大的办法有两个:一是增加该类别的训练样本数量,二是调整类别的采样权重。YOLOv5 4.0的官方代码里虽然没有直接暴露类别权重参数,但你可以通过修改数据集的txt文件,把少的类别做重复采样来变相实现。我在电池类别样本上做了3倍重复采样之后,AP从55提升到了70,效果很明显。5. 模型测试评估与业务流集成交付5.1 测试集评测与阈值调优模型训练完,用detect.py跑一遍看看效果:python detect.py --weights runs/train/exp/weights/best.pt --source 测试图片文件夹 --img-size 640 --conf-thres 0.4--conf-thres置信度阈值是个很讲究的参数。垃圾分拣现场我建议调成0.5起步,因为误检的成本很高——机械臂把不该抓的垃圾抓走了,比漏检更麻烦。但阈值也不能太高,否则一些低置信度的真实目标会被滤掉。我给现场服务的时候,用一套实际拍摄的测试视频来做评估,统计三个指标:检出率(该抓的抓到了多少)、误检率(不该抓的抓了多少)、单帧耗时。通过对比不同置信度阈值下的指标组合,找到最合适的阈值。这个调优过程比训练本身更花时间,但这是模型真正落地到业务流必须走的一步。5.2 推理脚本封装与业务系统对接检测模型不能直接用官方的detect.py跑业务,因为它是为演示设计的。我封装了一个轻量推理模块,核心逻辑是这样:import torch import cv2 from models.experimental import attempt_load from utils.general import non_max_suppression, scale_coords class GarbageDetector: def __init__(self, weights_path, device='cuda:0'): self.model = attempt_load(weights_path, map_location=device) self.model.eval() self.device = device def detect(self, frame): # 预处理:BGR转RGB、缩放、转Tensor img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) img = cv2.resize(img, (640, 640)) img_tensor = torch.from_numpy(img).float() / 255.0 img_tensor = img_tensor.permute(2, 0, 1).unsqueeze(0).to(self.device) # 推理 + NMS with torch.no_grad(): pred = self.model(img_tensor)[0] pred = non_max_suppression(pred, conf_thres=0.5, iou_thres=0.45) # 坐标还原到原图尺寸 results = [] if pred[0] is not None: for det in pred[0]: x1, y1, x2, y2, conf, cls = det.cpu().numpy() x1 *= frame.shape[1] / 640 x2 *= frame.shape[1] / 640 y1 *= frame.shape[0] / 640 y2 *= frame.shape[0] / 640 results.append({'box': [x1, y1, x2, y2], 'conf': float(conf), 'class': int(cls)}) return results和业务流对接时,输出格式要按现场PLC或机械臂控制系统的接口来定。一般用JSON序列化传过去,比如{"class": 0, "box": [120, 300, 240, 420], "conf": 0.92}。这里有个细节:机械臂抓取需要的是物体中心点坐标和大致尺寸,拿检测框中心点代替是可以的,但要注意传送带持续运动带来的偏移,需要在采集时刻加上运动补偿。5.3 现场部署的模型优化决策实际部署到现场后,我做了三个优化。第一是把模型用torchscript导出成静态图,推理速度比原本pytorch动态图快10%到15%;第二是降低输入分辨率从640到480,检测速度进一步提升,在垃圾场这样目标尺寸普遍较大的场景,精度损失很小;第三是开启半精度推理,在支持float16的显卡上能再快一倍。不过这些优化都建立在硬件有余量的前提下,如果现场设备本身就很弱,优先考虑换更强的工控机,而不是牺牲精度去迁就硬件。6. 常见问题与排查技巧实录6.1 训练阶段的高频故障速查训练过程中遇到的各种报错,如果能做成速查表,能省不少灰:现象可能原因处理方式训练时显存溢出OOMbatch-size过大 / 输入图过大减小batch-size,或者加--cache参数降低IO压力loss为nan学习率过大 / 数据集有非法标注降低初始学习率到0.001,检查txt标签是否有0或负值坐标mAP一直为0标签类别ID超出nc范围检查数据集labels里类别ID是否从0开始挨个排训练速度越来越慢显卡散热问题降频看nvidia-smi温度,超过75度需要清灰或加强散热import torch报错CUDA版本和驱动不匹配用nvidia-smi看驱动版本,对照官方兼容表重新装CUDA这里特别提一个我折腾最久的坑:标签文件里的坐标出现超出[0,1]范围的值,比如中心点x坐标写成1.2。训练不报错,但mAP曲线一直抖动上不去。排查方法是用脚本遍历所有txt文件,检查每个值是否在正常范围内:python -c " import os for root, _, files in os.walk('datasets/garbage_data/labels'): for f in files: path = os.path.join(root, f) for line in open(path): parts = line.strip().split() if len(parts) != 5: print('bad format', path) for p in parts[1:]: if not (0 <= float(p) <= 1): print('out of range', path) "6.2 推理阶段的业务问题排查模型跑起来以后出的问题,往往比训练阶段更考验排查能力。场景一:机械臂抓不准。这个大概率不是模型定位不准,而是坐标系映射问题。我当时的做法是在传送带旁边做一个固定标记点,用检测框中心和标记点的相对距离来标定相机坐标系到机械臂坐标系的变换矩阵,做完之后抓取准确率从70%提到了95%。场景二:不同时段识别率波动大。早晚光线变化导致模型性能下降,最简单的解决办法是在现场增加固定补光灯,把环境光变化降到最低。如果补光条件不具备,就在训练数据里加更多不同光照的样本。场景三:同一物体连续帧识别结果跳动。这个问题我建议在输出端加平滑滤波,只对时间序列做简单的中值滤波,把连续5帧的检测结果合并,框的位置取中值。这个操作不用重新训练模型,但对机械臂实际抓取的稳定性提升非常大。6.3 数据迭代与模型小版本更新垃圾分拣业务上线后,随着季节和时间推移,垃圾构成会变化。比如夏天饮料瓶多,春节后可能礼品盒多。所以模型要有迭代机制。我的做法是每两周从现场采集一批新数据,和旧数据合并重新训练一个版本,用新旧数据划分的验证集来A/B测试,确认新版本mAP不低于旧版本才上线。这个机制看着简单,但有个隐含要求:采集的原始图片必须持续标注。我一般让现场操作人员在日常工作时顺手拍摄,每天积累两三百张,两周下来就有两三千张新数据。标注工作可以交给兼职的标注人员,用LabelImg批量处理,成本可控。7. 写在最后的实战体会这套流程走下来,我个人最深的感受是:目标检测项目里最花时间的不是调模型,而是数据。花在采集、筛选、标注、平衡类别上的时间至少占整个项目的70%,模型训练反而是一个相对标准化的过程。YOLOv5 4.0的稳定性和工程化程度在这个场景下完全够用,与其追新版本,不如把手头版本吃透。最后还是提醒一句:任何公开教程里的超参数都只是起点,垃圾分拣项目的现场条件千差万别,传输带速度、相机安装角度、灯光布置这些变量都会对结果产生实际影响。真正可靠的做法是建立一套自己的评估流程,每次调整都对比同样的标准。按这个思路去做,哪怕现场条件再苛刻,模型也能逐步稳定下来。另外有一个最容易被小白忽略的小技巧:训练完成后别急着删除Last.pt。有时候best.pt是在验证集上表现最好的权重,但如果你发现现场某些类别误检比较多,可以再用Last.pt做推理对比,因为二者的收敛方向有细微差异,有时候反而Last.pt在实际场景的表现更稳。多留一个候选权重,上线时多一个选择,成本不过是几MB的磁盘空间。
搞了快一年的垃圾分拣项目,从最开始靠人工规则匹配,到后来上YOLOv5 4.0做视觉识别,我把从环境配置到模型上线跑通的全流程走了一遍。这篇就围绕YOLOv5 4.0版本,把环境配置、数据集制作、模型训练,以及最终接进垃圾分拣业务流的完整链路拉通来讲。很多人一上来就奔着"跑通官方demo"去,装完环境跑一次detect.py就以为完事了。实际做业务项目完全不是这么回事,尤其是垃圾分拣这种场景——目标种类多、外形差异大、现场光照复杂、还要考虑传输带上的实时速度,任何一个环节没处理好,模型精度再高也白搭。这篇文章我尽量把每一步为什么这么做、坑在哪、怎么判断结果对不对都讲清楚,希望能给准备上车目标检测的朋友省点时间。1. 项目背景与整体思路拆解1.1 为什么选YOLOv5 4.0版本做垃圾分拣先说我为什么锁定YOLOv5 4.0这个版本,而不是追最新版也不是回头用3.x。垃圾分拣业务的核心诉求是"实时识别+持续迭代",YOLOv5 4.0在2021年中发布时,已经具备非常成熟的CSPDarknet骨干网络和PANet颈部结构,检测速度和精度平衡做得非常好。拿实际数据说话:在普通消费级显卡上,YOLOv5s模型跑640×640输入,推理速度能做到5ms到10ms一帧,mAP在COCO类任务上能到37左右。对垃圾分拣这种场景,我们需要识别的是塑料瓶、易拉罐、纸箱、电池、果皮、玻璃瓶这些常见物品,类别数量不算多,但外形变化很大,YOLOv5 4.0的多尺度预测机制正好能覆盖大小目标混杂的情况。还有一点很关键:4.0版本的代码结构清晰、文档齐全,网上案例也多,出了问题容易排查。最新版虽然功能更多,但依赖变动大、踩坑成本高;更老的版本又没有Mish激活函数和自适应锚框这些改进。4.0正好是个"稳"字当头的版本,适合做工程落地。1.2 垃圾分拣业务流的核心链路与难点真正的垃圾分拣业务流不是"一张图丢进去、框出来"这么简单,完整的链路应该是这样:生产现场通过工位相机或传输带上的固定摄像头持续采集画面,画面经过目标检测模型推理,得到每个目标的类别和位置坐标,然后控制系统根据这些信息控制机械臂或分拣挡板,把对应垃圾推向不同的料仓。整个链路对检测模型的要求有三个:速度快、分类准、定位稳。速度快好理解,但"准"和"稳"是两回事。分类准要求模型能区分看起来很像的东西,比如透明矿泉水瓶和透明饮料瓶;定位稳要求同一件物体在连续几帧画面里,框的位置和大小不能跳来跳去,否则机械臂抓取的时候会扑空。我踩过的最大一个坑,是前期只追求测试集上的mAP,忽略了现场光照变化。厂房的灯光有频闪,传送带上的垃圾还会转动,同一个瓶子在不同角度下特征完全不同。所以整个项目从数据采集阶段就要围绕业务流设计,不能拍几张干净照片就完事。2. 环境配置全流程与版本搭配详解2.1 基础环境:Python、CUDA、PyTorch的版本搭配逻辑YOLOv5 4.0官方在README里给出的环境要求是Python 3.8+、PyTorch 1.7+,但我实测下来版本搭配有一些讲究。这是我最常用的组合,稳定不出幺蛾子:组件版本说明操作系统Ubuntu 20.04 / Windows 10均可,项目本身跨平台Python3.8.10太新版本可能兼容性问题,3.8最稳CUDA11.3向后兼容10.2,但11.x编译的算子更全cuDNN8.2.1和CUDA 11.3配套PyTorch1.10.0实测和4.0版本代码兼容性最好torchvision0.11.0必须和PyTorch版本严格对应为什么不推荐PyTorch 1.12以上?因为YOLOv5 4.0里有些针对老版本PyTorch写的算子,新版本里行为有变化,比如某些损失函数在不同版本下数值精度不一致,训练过程表现正常,但推理结果会差零点几个百分点,排查起来非常费劲。安装过程我用conda管理环境,照着执行就行:# 创建独立环境,避免污染系统Python conda create -n yolov5 python=3.8.10 conda activate yolov5 # 安装PyTorch,注意指定cuda版本 pip install torch==1.10.0+cu113 torchvision==0.11.0+cu113 -f https://download.pytorch.org/whl/torch_stable.html # 安装YOLOv5 4.0依赖 pip install -r requirements.txt提示:如果你机器上没有NVIDIA显卡,也可以用CPU版本做小规模测试,但训练速度会慢到怀疑人生。真实业务项目建议至少有8GB显存的显卡起步。2.2 验证环境是否配置正确的几个关键命令环境装完不能直接开跑,先花两分钟验证。我最常踩的情况是:pip list显示装好了,但import torch一运行就崩。验证命令其实很简单:python -c "import torch; print(torch.__version__); print(torch.cuda.is_available())"如果输出True,说明CUDA可用。再跑一个实际张量运算确认GPU真的在工作:python -c "import torch; x = torch.rand(1000,1000).cuda(); y = x @ x; print(y.sum())"这里有个细节:torch.cuda.is_available()返回True不代表CUDA计算真的没问题,可能是PyTorch自带的CPU实现兜底了。我做项目时遇到过驱动版本和CUDA runtime不匹配的情况,表面正常,但训练中途出现CUDA error: device-side assert triggered直接崩掉。所以强烈建议上面这个张量运算也要跑通。还有一个容易忽略的点:YOLOv5 4.0里很多工具脚本依赖wandb(Weights & Biases)做日志记录,第一次运行detect.py会让你登录,很烦人。可以在环境变量里禁用:export WANDB_MODE=disabled或者干脆在import wandb的地方注释掉。实测在Windows上禁用wandb能避免不少莫名其妙的网络超时问题。2.3 业务场景下的环境选择思路如果是给现场分拣设备配环境,我建议用Docker把环境打包成镜像,而不是在工控机上裸装。因为现场设备硬件参差不齐,今天这台是A卡、明天那台是N卡,用Docker + NVIDIA Container Toolkit可以把CUDA、PyTorch、代码一次性固化,换设备只需要拉镜像。具体做法是拉一个pytorch/pytorch:1.10.0-cuda11.3-cudnn8-devel镜像,把YOLOv5 4.0代码挂载进去,再pip安装依赖。这样开发环境、训练环境、现场推理环境三处完全一致,避免"在我电脑上能跑、到现场就跑不了"的尴尬。3. 数据集制作与标注实操3.1 从现场采集到筛选:垃圾数据集的特殊性数据是垃圾分拣项目的命根子。我见过很多团队在模型结构上花大功夫换各种Backbone,结果数据集就一两百张图,最后性能上不去,根子就在数据量不够。垃圾识别有个特点:同类物品在形态上差异极大,一个矿泉水瓶,有透明的、有绿色的、有压扁的、有带标签的,这都得当成同一种类来识别,需要模型学到足够泛化的特征。采集阶段我的建议是分三个场景拍:静态台面:物品静止摆放在不同背景、不同光照下,这是基础数据,让模型先学会基本特征。动态输送带:模拟真实分拣线的运动状态,用视频抽帧方式采集。这一步很关键,运动模糊对模型鲁棒性影响很大。遮挡堆叠:垃圾经常是互相压着的,要专门拍一些目标被部分遮挡的图片,让模型学会"看一部分认全部"。采集完不等于数据能用,要过筛。我筛数据看三点:模糊的不要、目标占比太小的不要、完全重复的不要。比如一帧画面里目标只有十几个像素,这种标注了也没意义,模型学不到有效特征,还容易产生噪声标签。3.2 使用LabelImg标注的核心操作要点标注工具我推荐LabelImg,轻量、跨平台、支持YOLO格式导出。虽然现在有一些自动化标注工具,但对垃圾这种类别多、目标密集的数据集,手工标注质量依然最可靠。标注前先规划类别清单,这一步要慎重,因为中途改类别名会非常痛苦。我的垃圾分拣项目第一版定了8类:plastic_bottle 塑料瓶 can 易拉罐 cardboard_box 纸箱 battery 电池 fruit_waste 果皮/厨余 glass_bottle 玻璃瓶 aluminum_foil 铝箔/锡纸 plastic_bag 塑料袋类别的命名尽量用英文小写加下划线,因为YOLO的类别文件就是纯文本记录,中文可能出现编码问题。LabelImg标注时有个小技巧:按住Ctrl+滚轮放大图片再打框,打出来的框更贴合目标边缘;对遮挡目标不要强行把整个物体框进去,而是框住可见区域,这样训练时模型学到的特征更准确。标注完保存,每张图片对应一个同名txt文件,内容格式是:类别序号 中心点x相对坐标 中心点y相对坐标 框宽相对坐标 框高相对坐标比如0 0.523 0.411 0.186 0.244,前面是类别ID,后面四个值都是0到1之间的归一化坐标。这里最容易出的问题是用绝对像素坐标,YOLO训练会直接报维度错误。LabelImg导出YOLO格式时自动生成归一化坐标,但如果你自己写脚本处理数据,一定要记得除以图片宽高。3.3 数据集目录结构与训练集划分策略YOLOv5 4.0对数据集目录结构有明确要求,目录得这样建:datasets/ ├── garbage_data/ │ ├── images/ │ │ ├── train/ # 训练图片 │ │ └── val/ # 验证图片 │ └── labels/ │ ├── train/ # 训练标签(txt) │ └── val/ # 验证标签(txt)注意train和val在images和labels下必须同名,否则训练时找不到对应标签。划分比例我一般用9:1,训练集占90%验证集占10%。有人说8:2更稳妥,但对垃圾场景数据本身就紧张了,验证集留10%足够判断拟合情况。划分的时候还要保证类别均衡,不能让训练集里塑料瓶占80%其它类一共20%。我的做法是统计每个类别的标注框数量,如果某一类特别少,就针对性补充采集或者做数据增强。这里补充一句数据增强的作用:YOLOv5 4.0训练时默认会自动做mosaic增强、随机旋转、缩放、平移、色彩抖动,所以不需要额外预处理。但如果你的数据集非常小(每类少于500个框),建议在模型配置里把增强强度调大,比如把hsv_h、hsv_s参数提高,让模型多适应颜色变化。4. 模型配置与训练核心流程4.1 修改配置文件:data.yaml和模型结构选择训练前要准备两个核心配置文件。第一个是数据集配置文件,比如garbage.yaml,放在YOLOv5的data目录下:train: datasets/garbage_data/images/train val: datasets/garbage_data/images/val nc: 8 names: ['plastic_bottle', 'can', 'cardboard_box', 'battery', 'fruit_waste', 'glass_bottle', 'aluminum_foil', 'plastic_bag']这里有两个容易被坑的地方。第一,路径必须是相对YOLOv5 4.0项目根目录的路径,不能写绝对路径,否则换机器跑就要改配置;但如果数据集和代码不在同一盘符,还得写绝对路径,这个要视情况判断。第二,nc一定要和names列表长度一致,少一个或多一个,训练时类别数量对不上,损失函数会直接报错。第二个是模型结构配置,通常直接复制官方现成的yolov5s.yaml,只改nc为8。选哪个模型结构取决于你的部署条件:模型参数量推理速度精度适用场景yolov5s7.2M最快适中现场工控机,推荐yolov5m21.2M较快较高显卡性能尚可yolov5l46.5M较慢高服务端离线识别我做过对比测试:在垃圾分拣场景,yolov5s和yolov5m的精度差距大概在2到3个mAP点,但推理速度差了将近一倍。实际分拣线帧率要求至少25FPS,所以我最终用yolov5s,把精度靠数据量和训练技巧补上去。4.2 训练命令与关键超参数调整环境就绪、数据就位、配置文件改好,就可以启动训练了。YOLOv5 4.0的训练命令是:python train.py --data garbage.yaml --cfg yolov5s.yaml --weights yolov5s.pt --batch-size 16 --epochs 100 --img-size 640几个参数我的建议:batch-size根据显存来定,如果显卡只有8GB显存,batch-size 16配--img-size 640极限了,再往上就显存溢出;epochs先跑100轮,看收敛情况再决定要不要加;--weights可以直接用官方在COCO上预训练的权重,迁移学习能省大量时间。训练过程中的输出信息重点看三个部分:GPU显存利用率、loss曲线、mAP指标。我见过有人只看mAP,不看loss曲线,结果模型根本没收敛。注意:YOLOv5 4.0里默认开启--multi-scale多尺度训练,但垃圾分拣场景目标大小分布比较稳定,我实际测试下来关闭多尺度训练能让训练速度提升20%,精度没有明显变化。在train.py参数里把--multi-scale去掉就行。还有一个参数容易被忽略:--cache。如果你内存足够大,加上这个参数会把图片全部缓存到内存,训练时省去反复读磁盘的时间,实测能提速将近三分之一。但要注意内存占用,3000张图大概需要20GB内存,别加了之后电脑直接卡死。4.3 训练进度监控与模型选型判断训练过程通常跑两三个小时到十几个小时不等,取决于数据量和硬件。YOLOv5 4.0会自动在runs/train/目录下生成exp文件夹,里面有训练曲线图和Best.pt、Last.pt两个权重文件。Best.pt是根据验证集mAP表现自动保存的最优模型,Last.pt是最后一轮的模型,两者都要保留。判断模型好坏不能只看训练集上的表现,要重点看验证集上的mAP和PR曲线。垃圾分拣场景我特别关注每个类别的单独AP值。经常出现整体mAP不错,但某一种类AP特别低的情况,比如电池因为体积小、样本少,AP只有50多,拉低了整体性能。解决类别性能差异大的办法有两个:一是增加该类别的训练样本数量,二是调整类别的采样权重。YOLOv5 4.0的官方代码里虽然没有直接暴露类别权重参数,但你可以通过修改数据集的txt文件,把少的类别做重复采样来变相实现。我在电池类别样本上做了3倍重复采样之后,AP从55提升到了70,效果很明显。5. 模型测试评估与业务流集成交付5.1 测试集评测与阈值调优模型训练完,用detect.py跑一遍看看效果:python detect.py --weights runs/train/exp/weights/best.pt --source 测试图片文件夹 --img-size 640 --conf-thres 0.4--conf-thres置信度阈值是个很讲究的参数。垃圾分拣现场我建议调成0.5起步,因为误检的成本很高——机械臂把不该抓的垃圾抓走了,比漏检更麻烦。但阈值也不能太高,否则一些低置信度的真实目标会被滤掉。我给现场服务的时候,用一套实际拍摄的测试视频来做评估,统计三个指标:检出率(该抓的抓到了多少)、误检率(不该抓的抓了多少)、单帧耗时。通过对比不同置信度阈值下的指标组合,找到最合适的阈值。这个调优过程比训练本身更花时间,但这是模型真正落地到业务流必须走的一步。5.2 推理脚本封装与业务系统对接检测模型不能直接用官方的detect.py跑业务,因为它是为演示设计的。我封装了一个轻量推理模块,核心逻辑是这样:import torch import cv2 from models.experimental import attempt_load from utils.general import non_max_suppression, scale_coords class GarbageDetector: def __init__(self, weights_path, device='cuda:0'): self.model = attempt_load(weights_path, map_location=device) self.model.eval() self.device = device def detect(self, frame): # 预处理:BGR转RGB、缩放、转Tensor img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) img = cv2.resize(img, (640, 640)) img_tensor = torch.from_numpy(img).float() / 255.0 img_tensor = img_tensor.permute(2, 0, 1).unsqueeze(0).to(self.device) # 推理 + NMS with torch.no_grad(): pred = self.model(img_tensor)[0] pred = non_max_suppression(pred, conf_thres=0.5, iou_thres=0.45) # 坐标还原到原图尺寸 results = [] if pred[0] is not None: for det in pred[0]: x1, y1, x2, y2, conf, cls = det.cpu().numpy() x1 *= frame.shape[1] / 640 x2 *= frame.shape[1] / 640 y1 *= frame.shape[0] / 640 y2 *= frame.shape[0] / 640 results.append({'box': [x1, y1, x2, y2], 'conf': float(conf), 'class': int(cls)}) return results和业务流对接时,输出格式要按现场PLC或机械臂控制系统的接口来定。一般用JSON序列化传过去,比如{"class": 0, "box": [120, 300, 240, 420], "conf": 0.92}。这里有个细节:机械臂抓取需要的是物体中心点坐标和大致尺寸,拿检测框中心点代替是可以的,但要注意传送带持续运动带来的偏移,需要在采集时刻加上运动补偿。5.3 现场部署的模型优化决策实际部署到现场后,我做了三个优化。第一是把模型用torchscript导出成静态图,推理速度比原本pytorch动态图快10%到15%;第二是降低输入分辨率从640到480,检测速度进一步提升,在垃圾场这样目标尺寸普遍较大的场景,精度损失很小;第三是开启半精度推理,在支持float16的显卡上能再快一倍。不过这些优化都建立在硬件有余量的前提下,如果现场设备本身就很弱,优先考虑换更强的工控机,而不是牺牲精度去迁就硬件。6. 常见问题与排查技巧实录6.1 训练阶段的高频故障速查训练过程中遇到的各种报错,如果能做成速查表,能省不少灰:现象可能原因处理方式训练时显存溢出OOMbatch-size过大 / 输入图过大减小batch-size,或者加--cache参数降低IO压力loss为nan学习率过大 / 数据集有非法标注降低初始学习率到0.001,检查txt标签是否有0或负值坐标mAP一直为0标签类别ID超出nc范围检查数据集labels里类别ID是否从0开始挨个排训练速度越来越慢显卡散热问题降频看nvidia-smi温度,超过75度需要清灰或加强散热import torch报错CUDA版本和驱动不匹配用nvidia-smi看驱动版本,对照官方兼容表重新装CUDA这里特别提一个我折腾最久的坑:标签文件里的坐标出现超出[0,1]范围的值,比如中心点x坐标写成1.2。训练不报错,但mAP曲线一直抖动上不去。排查方法是用脚本遍历所有txt文件,检查每个值是否在正常范围内:python -c " import os for root, _, files in os.walk('datasets/garbage_data/labels'): for f in files: path = os.path.join(root, f) for line in open(path): parts = line.strip().split() if len(parts) != 5: print('bad format', path) for p in parts[1:]: if not (0 <= float(p) <= 1): print('out of range', path) "6.2 推理阶段的业务问题排查模型跑起来以后出的问题,往往比训练阶段更考验排查能力。场景一:机械臂抓不准。这个大概率不是模型定位不准,而是坐标系映射问题。我当时的做法是在传送带旁边做一个固定标记点,用检测框中心和标记点的相对距离来标定相机坐标系到机械臂坐标系的变换矩阵,做完之后抓取准确率从70%提到了95%。场景二:不同时段识别率波动大。早晚光线变化导致模型性能下降,最简单的解决办法是在现场增加固定补光灯,把环境光变化降到最低。如果补光条件不具备,就在训练数据里加更多不同光照的样本。场景三:同一物体连续帧识别结果跳动。这个问题我建议在输出端加平滑滤波,只对时间序列做简单的中值滤波,把连续5帧的检测结果合并,框的位置取中值。这个操作不用重新训练模型,但对机械臂实际抓取的稳定性提升非常大。6.3 数据迭代与模型小版本更新垃圾分拣业务上线后,随着季节和时间推移,垃圾构成会变化。比如夏天饮料瓶多,春节后可能礼品盒多。所以模型要有迭代机制。我的做法是每两周从现场采集一批新数据,和旧数据合并重新训练一个版本,用新旧数据划分的验证集来A/B测试,确认新版本mAP不低于旧版本才上线。这个机制看着简单,但有个隐含要求:采集的原始图片必须持续标注。我一般让现场操作人员在日常工作时顺手拍摄,每天积累两三百张,两周下来就有两三千张新数据。标注工作可以交给兼职的标注人员,用LabelImg批量处理,成本可控。7. 写在最后的实战体会这套流程走下来,我个人最深的感受是:目标检测项目里最花时间的不是调模型,而是数据。花在采集、筛选、标注、平衡类别上的时间至少占整个项目的70%,模型训练反而是一个相对标准化的过程。YOLOv5 4.0的稳定性和工程化程度在这个场景下完全够用,与其追新版本,不如把手头版本吃透。最后还是提醒一句:任何公开教程里的超参数都只是起点,垃圾分拣项目的现场条件千差万别,传输带速度、相机安装角度、灯光布置这些变量都会对结果产生实际影响。真正可靠的做法是建立一套自己的评估流程,每次调整都对比同样的标准。按这个思路去做,哪怕现场条件再苛刻,模型也能逐步稳定下来。另外有一个最容易被小白忽略的小技巧:训练完成后别急着删除Last.pt。有时候best.pt是在验证集上表现最好的权重,但如果你发现现场某些类别误检比较多,可以再用Last.pt做推理对比,因为二者的收敛方向有细微差异,有时候反而Last.pt在实际场景的表现更稳。多留一个候选权重,上线时多一个选择,成本不过是几MB的磁盘空间。