简介:面向深度学习目标检测入门与实战人群,这是一套基于YOLOv8的多类别瓶子识别检测系统源码包。系统使用YOLOv8的detect目标检测分支,附带训练好的模型权重,平均准确率达0.95,覆盖bottle类别,并配有完整部署教程、数据集配置YAML及各类评估指标曲线,可直接用于工业质检、垃圾分类、生产线瓶体计数等场景。压缩包共488个文件,约89.27MB,以Python源码(py)、说明文档(md)、配置文件(yaml/yml)为主,辅以模型权重(pt)、测试图片与结果图表(jpg/png),并包含Dockerfile、C++推理demo等跨平台部署辅助文件。源码基于ultralytics-main,兼容Windows、macOS、Linux,环境搭建、训练与推理均有清晰步骤,支持多GPU训练;训练完成后自动生成runs/detect结果目录,便于查看PR曲线、混淆矩阵等指标并快速替换模型进行图片或视频推理。已有2653人学习下载,适合希望快速上手YOLOv8训练与部署、完成瓶子检测项目的学生和工程师。
1. 拿到瓶子检测源码包:先搞清楚这个包在解决什么问题
一瓶矿泉水、一罐可乐、一个玻璃药瓶混在一起,产线要按类别计数、回收站要分拣、实验室要盘库——这类场景用基于YOLOv8的瓶子识别检测系统,是当下见效最快的路线。标题里的压缩包,本质上给你三样东西:部署教程、训练好的模型权重、训练过程中生成的各种评估指标曲线。你拿到手第一步不是读代码,而是把环境配好、把模型在图片上跑出检测框,再决定要不要用自己的瓶子数据做二次训练。这篇笔记就按这个顺序推进:先讲压缩包内部结构和YOLOv8环境配置,再讲推理和部署,然后是yolov8训练自己的数据集,最后把坑和进阶技巧一起列出来。
2. 压缩包内部结构与YOLOv8环境配置:先跑通再谈优化
2.1 源码包里通常有什么:按用途给文件归类
这类打包不会只丢一个权重文件给你,我习惯先按用途把文件分四组看。第一组是训练好的模型,通常是best.pt和last.pt,前者是验证集指标最好的权重,后者是最后一轮保存的权重,日常推理和部署一般用best.pt。第二组是评估指标曲线,常见的有results.png、PR_curve.png、confusion_matrix.png、F1_curve.png,它们长在runs/detect或runs/train目录下,YOLOv8 训练完会自动生成。第三组是训练配置,包括data.yaml和args.yaml,前者记录数据集路径和类别名,后者记录训练超参。第四组是推理入口脚本和说明文档,部署教程一般以 Markdown 或者 README 形式出现。
拿到压缩包后,我一般先看一眼data.yaml里的nc值和names列表,这决定了后面所有命令的类别参数。如果压缩包里的模型是在 3 个瓶子类别上训练的,但你自己有 5 类瓶子,直接拿来推理没问题,想再继续训练就必须改data.yaml并重新标注数据。文件清单不需要背,理解每个文件在训练和部署链条里扮演什么角色,比记住目录结构重要得多。
常见目录结构如下表,具体打包方式会有出入,思路是通用的。
| 文件/目录 | 用途 | 什么时候会用到 |
|---|---|---|
best.pt | 验证集表现最好的权重 | 推理、导出、部署的默认选择 |
last.pt | 最后一轮训练权重 | 断了恢复训练、对比过拟合情况 |
results.png | loss 曲线、mAP 曲线汇总图 | 判断训练是否收敛、有没有过拟合 |
PR_curve.png | 精确率-召回率曲线 | 选置信度阈值 |
data.yaml | 类别数和类别名 | 训练、验证、导出都要读它 |
args.yaml | 训练超参数快照 | 复现训练、排查异常曲线 |
README.md/部署教程 | 环境命令和数据集说明 | 第一次启动前先读一遍 |
2.2 从零配置 YOLOv8 环境:conda 加 ultralytics 一次跑通
YOLOv8 环境配置是很多新手卡住的第一道坎,主要是 PyTorch 和 CUDA 版本的匹配问题。我的固定做法是用 conda 单独建一个 Python 3.10 环境,先装 ultralytics 本体,再单独装带 CUDA 支持的 PyTorch,顺序不能反。如果直接pip install ultralytics,pip 很可能给你装一个 CPU 版本的 torch,推理慢到怀疑人生。
# 创建独立环境,避免污染系统 Python conda create -n bottle python=3.10 -y conda activate bottle # ultralytics 是 YOLOv8 的官方包,会自动带 opencv、numpy 等依赖 pip install ultralytics -i https://pypi.tuna.tsinghua.edu.cn/simple # 单独装 CUDA 版 PyTorch,cu118 对 GTX 16 系列和 RTX 20/30 系列都友好 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118环境配好后,先做两个验证再继续。第一是确认 PyTorch 能不能调用显卡,第二是确认 ultralytics 版本能加载模型。用下面这一段 Python 做体检。
import torch from ultralytics import YOLO # 显卡不可用时,后面所有训练都要回到 CPU,慢几十倍 print("CUDA available:", torch.cuda.is_available()) # 用一个最小的官方权重测试加载流程,顺便看一眼网络结构 model = YOLO("yolov8n.pt") print(model.model)如果torch.cuda.is_available()返回False,大概率是 PyTorch 装成了 CPU 版,或者 NVIDIA 驱动太老。驱动不用追新,但 CUDA 版本和 PyTorch 的编译版本要对上,cu118 对应 CUDA 11.8,cu121 对应 CUDA 12.1。GTX 1660 Ti 这类老卡我建议直接用 cu118,兼容性最好。模型能够加载之后,再切回压缩包自带的best.pt,环境这一步就算过关了。
2.3 用一颗验证图确认权重没损坏:shape、类别、一次推理
加载了best.pt之后,先别急着训练,先确认这个权重文件是完好的、类别数和你预期一致。最稳妥的做法是输出模型的输入尺寸和类别映射,再拿一张没参与过训练的瓶子照片试推一次。
from ultralytics import YOLO model = YOLO("runs/detect/train/weights/best.pt") # 打印模型结构,重点看 Detect 头的 nc(类别数) model.info() # 推理一把,save=True 会把画好框的结果存到 runs/detect/predict 下 results = model.predict( source="bottle_test.jpg", save=True, conf=0.25, imgsz=640 )这段代码里的conf=0.25是关键参数,意思是只有置信度大于 0.25 的框才会被画出来。瓶子检测场景里,如果画面里反光严重,0.25 可能漏检,我会先调低到 0.15 看全部候选框,再根据评估指标曲线决定最终阈值。imgsz=640是输入分辨率,先用默认值跑通,后面发现小瓶子漏检再往上提。
如果推理结果里没有检测框,先看model.names输出的类别名是不是瓶子的英文,再看图片本身是不是高清正面照。权重能正常出框,说明压缩包里训练好的模型是能用的,后面所有定制化工作都从这个起点展开。
3. 把训练好的模型跑起来:图片、视频、边缘设备部署的完整链路
3.1 单张图片和目录推理:三行命令覆盖 80% 的验证场景
YOLOv8 的推理入口非常统一,命令行和 Python 都支持。日常快速验证我直接用yolo detect predict命令,它背后就是 ultralytics 的预测引擎,不需要写 Python 脚本。
# 单张图片推理,结果会输出到 runs/detect/predict 目录 yolo detect predict model=best.pt \ source=bottle_test.jpg \ save=True \ conf=0.25 # 整个目录的图片一口气跑完,适合批量验证评估指标 yolo detect predict model=best.pt \ source=./test_images/ \ save=True \ conf=0.2 \ imgsz=640命令行的参数和 Python 调用是同一套,model指定权重,source指定图片或目录,save决定是否把画框图片落盘,conf是置信度阈值,imgsz是输入尺寸。批量推理时我会把conf放低到 0.15 到 0.2,目的不是追求精度,而是把所有可能的候选框都找出来,再去看哪些是误检,哪些是模型确实没学会的特征。
3.2 视频和摄像头实时检测:关键参数是stream和max-det
传送带场景基本都是视频流或者 RTSP 摄像头,YOLOv8 对视频推理做了封装,source里传视频路径或摄像头地址就行。这里有个容易忽略的参数stream=True,在 Python 里调用时一定要写,否则视频帧会被一次读入内存,长时间跑会爆内存。
from ultralytics import YOLO model = YOLO("best.pt") # stream=True 会按帧迭代,不断释放前一帧的内存 results = model.predict( source="rtsp://192.168.1.10:554/stream1", stream=True, conf=0.2, imgsz=640, max_det=20 ) for frame_result in results: # 每帧返回一个 Results 对象,boxes 里有坐标、置信度、类别 boxes = frame_result.boxes print(f"这一帧检测到 {len(boxes)} 个物体")实时场景里max_det=20值得加上,它限制每一帧最多输出 20 个检测框。瓶子检测通常一帧也就十来个目标,加这个参数能避免在目标堆叠时出现大量低质量冗余框,也顺带压低了 NMS 的耗时。还有一个参数是vid_stride,控制每隔多少帧做一次推理,比如vid_stride=2就是跳帧检测,帧率能翻一倍,代价是运动目标偶尔会被跳过。
3.3 部署到 RK3588 等边缘设备:先导出 ONNX 再转 RKNN
很多生产环境不是拿 PC 跑检测,而是要部署到 RK3588 这类边缘盒子。YOLOv8 导出的链路很清晰,先在 PC 上把 PyTorch 权重导出成 ONNX,再用 RKNN-Toolkit2 转换成 RKNN 格式,最后用 C 接口加载推理。
from ultralytics import YOLO model = YOLO("best.pt") # opset=12 是 RKNN 工具链比较稳妥的兼容版本 # half=True 导出 FP16 权重,RK3588 的 NPU 对 FP16 支持更完整 model.export(format="onnx", imgsz=640, opset=12, half=True)导出之后你会得到一个.onnx文件,用 RKNN-Toolkit2 转 RKNN 时注意两点。第一,输入尺寸要固定为 640,动态尺寸在 NPU 上要么不支持,要么性能很差,我一般都直接--fix_input锁定。第二,预处理里的 BGR 转 RGB、归一化到 0-1,这些操作要挪到 RKNN 的量化配置里去做,不要在推理代码里重复做一遍,否则颜色通道对不上,检测框会整体乱飘。边缘部署最大的坑不是模型精度,而是预处理和后处理在 C 代码里重写时和 Python 版不一致。
4. 训练自己的数据集:从标注到读懂评估指标曲线的完整流程
4.1 先搭数据集目录:标注工具与目录划分的固定套路
压缩包自带的模型是基于公开数据集或作者自己的瓶子图片训练的,搬到你的产线、你的光源、你的摄像头角度下,大概率要重新训练。第一步是整理数据集,我习惯把图片和标签分开两个大目录,再按 train 和 val 划分。
datasets/bottle/ ├── images/ │ ├── train/ # 训练集图片 │ └── val/ # 验证集图片 ├── labels/ │ ├── train/ # 和图片同名的 txt 标签 │ └── val/ └── data.yaml标注工具用 LabelImg 就行,标注格式选 YOLO,它会直接生成和图片同名的 txt 文件。每个 txt 文件里每一行对应一个目标,格式是class_id x_center y_center width height,四个坐标值都是归一化到 0-1 的小数。数据量上,我的血泪经验是:一个类别最少 150 到 200 个目标,并不是要 200 张图,而是这 200 个目标要覆盖不同角度、不同光照、不同遮挡程度。瓶子这类形状规整的目标容易低估数据多样性需求,结果就是训练完之后换个角度就漏检。
4.2 写 data.yaml:类别 ID 从 0 开始,路径用绝对路径
YOLOv8 训练时读取的data.yaml是数据集的唯一入口,这个文件写错会让整个训练白跑。最常见两个问题:一是路径用了相对路径,换个机器直接找不到图片;二是类别 ID 从 1 开始标,而 YOLO 要求从 0 开始。
# 推荐全部用绝对路径,训练和验证都方便定位 path: D:/datasets/bottle train: images/train val: images/val nc: 3 names: 0: plastic_bottle 1: glass_bottle 2: metal_cannc是类别数,names是类别名列表,这里的索引必须和 txt 标签里的 class_id 一一对应。如果你标注的时候把玻璃瓶标成 class 2,但 names 里索引 2 写的是金属罐,训练不会报错,但评估曲线和推理结果会乱套。检查方式很简单,随机打开一个 txt 文件,看第一个数字有没有超出nc-1,再看对应的英文名对不对。
4.3 GTX 1660 Ti 也能跑的启动训练参数:显存不够就减 batch
训练入口同样是yolo detect train,重点在超参数的选择。GTX 1660 Ti 显存一般是 6GB,跑 YOLOv8s 合理,硬上 YOLOv8m 或 YOLOv8l 会爆显存。
yolo detect train \ model=yolov8s.pt \ data=D:/datasets/bottle/data.yaml \ epochs=100 \ imgsz=640 \ batch=8 \ lr0=0.01 \ patience=20 \ device=0 \ project=runs/bottle \ name=train_v1model=yolov8s.pt是预训练权重,用它做初始化能显著加速收敛,比随机权重快得多。batch=8在 6GB 显存下是安全值,如果显存报 OOM,就降到 4。lr0=0.01是初始学习率,这个值对迁移训练是安全的,但如果你发现 loss 曲线反复震荡,下一步就是把它降到 0.001。patience=20的意思是连续 20 轮验证集 mAP 不涨就早停,能省时间,也能避免过拟合。训练过程会实时打印 box_loss、cls_loss、mAP 这些指标,同时往runs/bottle/train_v1目录写results.png和权重文件。
4.4 读懂评估指标曲线:results.png 里每个子图都在说什么
训练完成后,results.png里是一组小图,从上到下依次是训练 loss、验证 loss、精确率、召回率、mAP50、mAP50-95 这几条曲线。读图时我主要看两个地方。第一,训练 loss 和验证 loss 之间的距离,训练 loss 一直降但验证 loss 在提升,说明过拟合了,早停或者加数据增强都能压住。第二,mAP50 曲线是否进入平台期,正常情况 mAP50 先快速上升,然后慢慢趋平,如果平台期 mAP50 还在 0.8 以下,说明数据量或特征区分度不够,不是单纯调参能解决的问题。
| 曲线名 | 判断标准 |
|---|---|
| train/box_loss | 持续下降说明模型在拟合样本 |
| val/box_loss | 和 train 差距过大则有过拟合风险 |
| metrics/mAP50(B) | 0.9 以上算优秀,0.8 到 0.9 可接受 |
| metrics/mAP50-95(B) | 比 mAP50 更严格,用来对比模型提升 |
| PR_curve.png | 曲线越靠右上角越好,还用来选置信度阈值 |
mAP50 和 mAP50-95 这两个指标的差异要特别说清楚。mAP50 是 IoU 阈值为 0.5 时算的平均精度,适合判断检测框大概位置对不对;mAP50-95 是 IoU 从 0.5 到 0.95 取平均,对框的贴合程度要求苛刻得多。瓶子这类边界清晰的目标,mAP50 很容易做到 0.95 以上,但 mAP50-95 能到 0.8 就算不错了,如果你的 mAP50 高而 mAP50-95 偏低,说明框的位置偏了,优先去调标注质量而不是网络结构。
5. 避坑清单:瓶子检测从训练到部署的五个高频翻车点
5.1 训练时报 "class labels missing" 或 loss 固定在某个值不动
现象是训练刚开始几轮,日志里出现类似WARNING labels not found in ...或AssertionError: Class labels missing,然后 loss 值长时间不变。原因是训练集里存在空的 txt 文件,或者标签里出现了nc范围外的类别 ID。我常用的排查命令是遍历所有标签文件,检查每行的第一个数字是否在0到nc-1之间。解决方法是把空文件删掉,把越界的类别 ID 修正回来,重新检查目录结构后再启动训练。
5.2 检测框把托盘和瓶盖背景也框进去
现象是检测结果里除了瓶子,还有大量重叠在托盘边缘、瓶盖反光点上的误检框。原因通常是训练数据里正样本背景太单一,模型把背景纹理当成了瓶子的特征。解决思路有两个,一个是给训练集补充带托盘、带传送带边缘、带高光点的负样本图片,目录下放一批没有目标的图片到训练集里,YOLOv8 会自动把它们当背景学习;另一个是调整hsv_h、hsv_s这两个数据增强参数,压缩颜色扰动强度,避免模型过度依赖某个高光特征。
5.3 细长瓶和矮胖瓶混在一起时,细长瓶漏检严重
现象是矿泉水瓶、长颈酒瓶这类长宽比大的目标,竖着能检测到,横放或倾斜时很容易漏。原因是降采样到 640 分辨率后,细长目标占据的网格太少,特征在小分辨率下被抹掉了。解决方法是把imgsz从 640 提到 960 或 1280,代价是显存占用和推理耗时同步上升。GTX 1660 Ti 如果显存不够,就把batch降到 2,或者换 YOLOv8s 的小分辨率版本重新训练。这一步是典型的精度和速度取舍,部署到 RK3588 这类边缘设备时更要慎重,分辨率升高会导致换算后的耗时成倍上涨。
5.4 val loss 曲线下降后反弹,越训越差
现象是训练 loss 一直在降,但results.png里 val loss 和 mAP50 在第 50 轮左右开始反向恶化。原因是训练时间过长、lr0过大,模型在验证集上开始过拟合。解决方法是先把lr0降到 0.001,开启cos_lr=True让学习率按余弦周期衰减;同时把patience设为 15 到 20,让程序在验证指标不再提升时自动停掉。早停时保存的best.pt是验证集上表现最好的权重,相当于给你留了一颗后悔药,哪怕最后训练曲线很难看,也能拿它去推理。
5.5 部署到 RK3588 后帧率个位数,卡在预处理而不是模型
现象是移植到边缘设备后,同样的模型在 PC 上有 30 FPS,在 NPU 上只有 5 FPS。原因大概率不在卷积推理,而在数据预处理和后处理。PyTorch 里的图像读取、缩放、归一化在 Python 下跑本来就慢,而且 NPU 推理是异步的,CPU 预处理跟不上就会拖慢整体节奏。解决方法是把图像缩放、通道转换、归一化全部用 C 或 C++ 的 OpenCV 实现,推理后处理里的解码和 NMS 也手动实现,不要调 Python 侧的ultralytics库。做推理优化时,我会先单独测一次纯模型推理耗时,再把预处理和后处理的耗时分别打点,就能定位瓶颈在哪一段。
6. 进阶:把评估指标曲线变成你的调参依据,再加一个计数后处理
6.1 用 F1-Confidence 曲线挑置信度阈值
训练完的目录里有F1_curve.png,横轴是置信度阈值,纵轴是 F1 分数。它帮你回答一个具体问题:conf应该设 0.1 还是 0.4。曲线的峰值对应的就是最佳阈值,比如峰值在 0.35,那么推理时conf=0.35能让精确率和召回率最平衡。如果你更在意别漏检,可以把conf再往左挪 0.05;如果更在意误检率,就往右挪 0.05。我第一次做瓶子检测时直接默认 0.25,结果误检一堆,后来对照 F1 曲线才发现这个数据集的最佳阈值在 0.4 附近。
我一般还会打开PR_curve.png看一眼,当曲线右上角有明显的下坠段时,说明某些类别的召回率在低置信度区域才有收益,需要单独检查那一类的标注质量。
6.2 用已有的 best.pt 继续做增量训练,控制回归风险
改进了数据集之后,不需要从预训练的yolov8s.pt从头开始,直接拿上次训练的best.pt继续训练,相当于把历史学习结果保留下来。增训时我把学习率放低一两个数量级,锁定前几层冻结特征提取器。
yolo detect train \ model=runs/bottle/train_v1/weights/best.pt \ data=D:/datasets/bottle/data.yaml \ epochs=50 \ imgsz=640 \ batch=8 \ lr0=0.0001 \ freeze=10 \ project=runs/bottle \ name=finetune_v2freeze=10的意思是冻结模型前 10 层的参数,它们主要负责通用特征提取,改动数据分布时不需要重新学习。增训后对比train_v1和finetune_v2的评估指标曲线,如果 mAP 反而下降了,说明新数据里存在和旧标注冲突的样本,这时候回滚到best.pt,把冲突样本挑出来重新标注再训。增量训练是调优效率最高的路,却也最容易因为标注不一致翻车,对比曲线是最直接的验证手段。
6.3 给检测结果接一个计数后处理,产线场景直接能用
模型输出检测框之后,产线计数还需要统计每一类瓶子的数量。YOLOv8 的boxes对象里已经带了类别和置信度,后处理逻辑本身很简单,主要是考虑重复计数的问题。
from ultralytics import YOLO model = YOLO("best.pt") counts = {} # stream=True 按帧迭代,适合视频计数场景 results = model.predict("conveyor.mp4", stream=True, conf=0.35) for frame in results: for box in frame.boxes: cls = int(box.cls[0]) label = model.names[cls] # 只统计瓶子相关类别,忽略误检的背景 if label in ("plastic_bottle", "glass_bottle", "metal_can"): counts[label] = counts.get(label, 0) + 1 print("统计结果:", counts)这里我刻意没有做追踪,所以每一帧的同一个瓶子会被重复累加。真实产线场景需要配合 ByteTrack 或 DeepSORT 做目标追踪,才能做到一个瓶子只计一次。如果只是做静态图片批量计数,这个简单版本就够了。
第一次跑通瓶子检测项目时,我犯的最蠢的错是拿到best.pt后先去做部署,结果边缘设备上模型精度不错、帧率也签收,用户说漏检,最后发现是我自己训练数据里压根没放带高光的玻璃瓶样本。后来养成一个习惯:任何数据集改动,先去和results.png、F1_curve.png对照一遍,确认评估指标和现场现象对得上再动手调参。这套流程下来,你手上的数据集质量、模型效果、部署性能都会是能交代清楚的。希望这次记录能帮到你。
本文还有配套的精品资源,点击获取