资讯动态

YOLO目标检测实战:200张作物杂草图像的训练与部署全流程

发布时间:2026/10/9 7:15:44 来源:尧图企业网站定制
简介适用于YOLO系列算法的作物杂草检测数据集面向农业智能与精准农业应用可解决田间作物与杂草自动识别模型训练数据不足的问题。压缩包共601个文件大小约185.56MB含200张JPG原图、200个TXT格式标签YOLO格式与200个XML格式标签VOC格式并附带data.yaml数据集配置文件。标签内容为目标类别及归一化边界框坐标已按YOLO标准格式组织且训练、验证、测试集已预先划分可直接用于YOLOv5、YOLOv7、YOLOv8、YOLOv9、YOLOv10、YOLO11等主流目标检测框架的训练与测试。同时提供双格式标签便于在MMDetection、PaddleDetection等不同工具链间切换。目前已有106人学习下载适合农业AI开发者、科研人员及目标检测初学者使用。1. yolo算法遇到作物杂草200张带标签图像能做什么拿到这份「yolo算法-作物杂草数据集-200张图像带标签-.zip」大多数人的第一个念头是解压、开训、盯曲线。说实话200张标注图像放在动辄几万张的检测数据集面前不算大但在作物杂草这个垂直场景里它刚好卡在「练手够用、生产不够」的分界线上。它能帮你把yolo算法从原始图像一路跑到权重文件和推理验证的完整链路走通也能让你在投入真金白银采集数据之前先把标注格式、数据划分、过拟合、阈值调试这些硬骨头啃掉。文章按最小可行项目的路径展开先拆包体检再训练验证最后讲清楚这200张图像的天花板在哪里。适合刚做目标检测入门的学习者或者准备做农业视觉但手里还没有大量实拍数据的工程师。2. 先拆包体检目录结构、标签格式与数据质量检查2.1 images和labels是两套平行目录文件名必须严格对应不管这份压缩包是团队内部标注的还是从某个农业视觉项目里整理出来的YOLO生态里最常见的布局一定是这样weed_dataset/ ├── images/ │ ├── 001.jpg │ ├── 002.jpg │ └── ... └── labels/ ├── 001.txt ├── 002.txt └── ...image和label靠文件名的主干对应后缀不同。训练时YOLO不会同时读两个目录去比对它只负责按images/001.jpg这个路径查找同名.txt找不到就跳过但你不太容易察觉因为日志里不会明说哪张图漏了标签。所以第一步不是急着训练而是把压缩包里的图像和标签一一对应起来查一遍。我一般会把压缩包解压到固定目录不做任何原地修改。用unzip解压时别用带中文的父目录YOLO的训练器对中文路径的兼容性一直不太好Windows上尤其容易翻车。解压后先ls看一眼是不是有嵌套层有的压缩包会带一层与数据集同名的文件夹直接mv出来就能拍平。2.2 标签文件里的5个数字class id加归一化坐标打开任意一个.txt标签文件你会看到若干行每行5个数字0 0.6875 0.3125 0.125 0.0832 1 0.2450 0.4300 0.3200 0.4500这一行的含义拆开是第1个数类别id0对应names配置里排在第一个的类名1对应第二个以此类推第2、3个数目标框中心点的x、y坐标分别除以图像宽高后得到的归一化值取值0到1第4、5个数目标框的宽度、高度同样相对于图像宽高做了归一化很多刚上手的人会把第2、3个数当成框的左上角坐标去画框这是最常见的误解。哪怕训练器接收的格式确实是归一化中心坐标可视化验证的时候也必须先乘回图像原始宽高才能得到像素级矩形框。2.3 动手统计类别分布、空标签和越界坐标训练之前我建议先写一个几十行的体检脚本把问题暴露在前面。下面脚本会统计类别分布、框总数并检查标签文件和图像文件是否一一对应import os from collections import Counter from PIL import Image image_dir weed_dataset/images label_dir weed_dataset/labels class_counter Counter() total_boxes 0 problems [] for img_name in sorted(os.listdir(image_dir)): if not img_name.lower().endswith((.jpg, .jpeg, .png)): continue stem os.path.splitext(img_name)[0] label_path os.path.join(label_dir, stem .txt) if not os.path.exists(label_path): problems.append((img_name, 缺失标签)) continue # 检查图像是否能被正常解码 try: with Image.open(os.path.join(image_dir, img_name)) as im: im.verify() except Exception: problems.append((img_name, 图像损坏)) continue with open(label_path, r, encodingutf-8) as f: lines f.readlines() if not lines: problems.append((img_name, 空标签文件)) continue for line in lines: parts line.strip().split() if len(parts) ! 5: problems.append((img_name, 格式错误: line.strip())) continue cls_id int(parts[0]) x_c, y_c, w, h map(float, parts[1:]) # 归一化坐标检查越界说明标注工具或转换脚本有问题 if not (0 x_c 1 and 0 y_c 1 and 0 w 1 and 0 h 1): problems.append((img_name, 坐标越界)) continue class_counter[cls_id] 1 total_boxes 1 print(图像总数:, len(os.listdir(image_dir))) print(标注框总数:, total_boxes) print(类别分布:, sorted(class_counter.items())) for bad in problems[:20]: print(bad)逻辑说明脚本按文件名把image和label对齐对缺失标签、空标签、损坏图像、坐标越界四类问题分别记录。坐标越界特别值得警惕因为这类数据从标注工具导出后没做二次校验训练时轻则损失震荡重则直接NaN。参数说明im.verify()是Pillow的轻量解码检查不加载完整像素数据sorted(os.listdir(...))让输出顺序稳定方便和上一次体检对比。如果class_counter里出现预期之外的类别id说明names和本地类名表对不上先改这里再训练。还有一个细节如果图像和标签不是同一批来源经常出现「有些图像有标签有些标签没有对应图像」。标签目录里多出来、但图像目录里找不到主干的.txt也要清理掉否则YOLO不会报错只是给后续分析埋雷。3. 跑通yolo算法训练最小可复现流程与参数详解3.1 数据划分不是随手拖个文件这200张图像不划分就整体丢进去训练训练器也不会拦你但验证和测试就没有意义了。尤其是只有200张的小数据集划分方式直接决定你看到的mAP是真的泛化能力还是运气。常见划分比例是7:2:1分别对应训练、验证、测试。写一个不依赖图形界面、可重复运行的划分脚本比手动拖文件夹踏实。下面脚本按7:2:1随机划分并固定随机种子import os import random import shutil random.seed(42) # 固定种子每次运行得到同样划分 src_images weed_dataset/images src_labels weed_dataset/labels dst_root dataset files [f for f in os.listdir(src_images) if f.lower().endswith((.jpg, .png))] random.shuffle(files) n len(files) n_train int(n * 0.7) n_val int(n * 0.2) splits { train: files[:n_train], val: files[n_train:n_train n_val], test: files[n_train n_val:], } for split_name, split_files in splits.items(): img_out os.path.join(dst_root, images, split_name) lbl_out os.path.join(dst_root, labels, split_name) os.makedirs(img_out, exist_okTrue) os.makedirs(lbl_out, exist_okTrue) for f in split_files: shutil.copy2(os.path.join(src_images, f), os.path.join(img_out, f)) stem os.path.splitext(f)[0] label_src os.path.join(src_labels, stem .txt) if os.path.exists(label_src): shutil.copy2(label_src, os.path.join(lbl_out, stem .txt)) for split_name, split_files in splits.items(): print(split_name, len(split_files))逻辑说明shuffle打乱顺序后按比例切三段再把图像和标签同步拷到新目录形成dataset/images/train这类三级结构。为什么不用软链接Windows上软链接经常因为权限失败复制最稳200张图占不了多少空间。参数说明random.seed(42)是关键否则每次运行结果都不一样复现实验会很吃力。数据量涨到几千张时这个脚本依然适用改一个比例数字就行。划分时更要留意的是不能把同一来源的图像跨组。比如一块田里连拍的照片前几十帧进了训练集、后几帧进了验证集验证集看起来是没见过的其实模型早就见过这块地的纹理特征。这种数据泄漏在小数据集上会把mAP虚抬到0.9以上等换到另一块地的图像立刻原形毕露。所以划分之前能按拍摄批次、地块编号分就按批次分没有这些信息至少要检查文件名前缀是否重复把前缀相同的文件归到同一组再切。3.2 模型选型为什么200张图像适合用yolov8n而不是large模型标题里只写了「yolo算法」实际到训练这一步必须落成一个具体的权重结构。目前从业者最常用的入口是Ultralytics系YOLO一条yolo命令就能覆盖训练、验证、导出。它维护了n/s/m/l/x五个尺寸档位200张带标签图像上我只推荐nano。理由不复杂模型参数越少对数据量的要求越低。nano大约只有s版本三分之一的参数量在小数据集上过拟合更慢训练速度也快。拿yolov8x去训200张图大概率看到验证集loss涨、训练集loss降典型的过拟合曲线。反过来说如果作物杂草检测的目标是大田常见场景目标尺寸相对稳定nano的表达能力完全够用。3.3 训练命令与关键参数imgsz、epochs、batch到底怎么定先放训练用的配置文件。按两个类作物、杂草演示dataset.yaml长这样# dataset.yaml path: ./dataset train: images/train val: images/val test: images/test names: 0: crop 1: weed然后执行训练yolo detect train \ datadataset.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ patience20 \ projectruns \ nameweed_demo \ device0 \ pretrainedTrue命令拆开说modelyolov8n.pt直接在官方预训练权重上做迁移学习这是小数据集能收敛的底气。epochs100对200张图不算多但配了patience20连续20轮验证集mAP不涨就自动停。imgsz640是COCO预训练惯用的输入尺寸不折腾。batch16在常见16G显存上跑nano很轻松如果显存只有8G把batch降到8或者加一行cacheFalse。参数说明pretrainedTrue相当关键。不加载预训练权重200张图从头训练效果会明显差一截。device0指定第一张GPU没有N卡就改devicecpunano在CPU上也能跑完100轮200张图大概十几分钟到半小时。project和name决定权重输出位置runs/weed_demo/weights/best.pt。需要明确一点这份压缩包里实际的类别名我不掌握所以yaml里用crop和weed做演示。拿到数据后第一件事是打开几个标签文件确认id 0、id 1到底对应什么再改成自己的类名。类名本身不影响训练但影响部署时结果解析的可读性。3.4 训练过程看什么loss曲线、mAP和早停逻辑训练开始后终端会实时刷新box_loss、cls_loss、dfl_loss等指标。不要只看总lossyolo的损失函数实际是三部分加权和分类损失管「框里是不是这个类别」边界框回归损失管「框得准不准」分布焦点损失DFL管边缘定位的精细度。理解这一点排障时才有方向。对小数据集而言重点观察两件事。第一cls_loss在训练集上是否快速下降前几轮纹丝不动就怀疑标签类别id写错或者类别数量声明不对。第二验证集mAP50一旦连续多轮持平甚至下滑基本就是过拟合信号此时patience会让训练自动停在best.pt而不是最后一次的last.pt。训练结束后runs/weed_demo/下会生成results.png把训练和验证的所有曲线画在一起。建议只看验证集曲线训练集曲线再漂亮都不能代表泛化能力。4. 别只盯着训练曲线推理验证与模型导出4.1 推理脚本的最短写法训练完拿到best.pt很多人直接拿去预测一张目录效果不理想就怀疑模型实际上更常见的是推理参数没调对。先用一个最短脚本把验证集跑一遍同时落图片和txt文件from ultralytics import YOLO model YOLO(runs/weed_demo/weights/best.pt) results model.predict( sourcedataset/images/val, conf0.25, # 置信度阈值低于它的框直接丢掉 iou0.5, # NMS的IOU阈值 imgsz640, saveTrue, # 保存带框的可视化图 save_txtTrue, # 保存预测结果txt格式与标签文件一致 save_confTrue, # 在txt里追加置信度 projectresults, nameval_predict, line_width2, ) for r in results: print(r.path, 检测框数:, len(r.boxes)) for box in r.boxes: print(类别:, int(box.cls[0]), 置信度:, round(float(box.conf[0]), 3), 坐标:, [round(v, 1) for v in box.xyxy[0].tolist()])逻辑说明results是一个列表每个元素对应一张图。遍历时通过box.cls、box.conf、box.xyxy取类别、置信度和像素坐标。save_txtTrue会生成和YOLO标签同样格式的预测结果文件方便后续脚本把预测框和真实框做IoU对比而不只是肉眼看图。参数说明conf0.25是yolo官方默认推理阈值。小数据集训练出来的模型往往置信度整体偏低如果框少得可怜把conf降到0.1再试反过来如果框多到互相重叠把conf提到0.4。iou0.5控制NMS去重时判定「重叠」的阈值作物杂草这种目标密集场景可以适当降到0.4。4.2 判定模型是否可用的三个指标不是只有mAP预测跑完打开results/val_predict/文件夹不要只看标注框画得对不对按三个维度判断。第一目标漏检率。杂草密集区域模型有没有漏掉大半杂草小目标漏检在200张图的小模型里太常见改善手段不是盲目加训练轮数而是把imgsz从640提到960再做一次推理看漏检是否缓解。如果确实缓解说明模型对小目标编码能力不足后续训练可以考虑加大输入。第二类别混淆。有没有把作物框成杂草、杂草框成作物这类错误在二分类里对mAP影响不大但对实际除草执行机构是致命的——把作物当杂草打掉损失的是产量。如果混淆严重先检查训练集的类别分布再考虑给杂草类增加权重。第三定位偏移。框是画上了但框住的是叶尖而不是整株。这种错位属于边界框回归不够精确小数据集里常见。不要马上归咎于模型先用conf把低置信度框筛掉剩下的定位误差多数来自标注框本身画得偏紧。4.3 导出ONNX并做一致性验证本地验证通过后下一步往往是部署到边缘设备或服务端。YOLO官方export一行就能转ONNXmodel.export(formatonnx, imgsz640, opset12, dynamicFalse)参数说明dynamicFalse固定输入尺寸换来更好的部署兼容性推理端如果只会用640输入就按固定尺寸导出。opset12兼容多数onnxruntime版本太新的算子在某些老设备上反而跑不了。导出后别以为转换成功就完事先做一致性验证用onnxruntime对同一张图跑一版输出再和best.pt的预测结果对比。import onnxruntime as ort import numpy as np import cv2 session ort.InferenceSession( runs/weed_demo/weights/best.onnx, providers[CPUExecutionProvider], ) input_name session.get_inputs()[0].name img cv2.imread(a_test_image.jpg) img cv2.resize(img, (640, 640)) # YOLO是RGB输入OpenCV默认读BGR要转换并归一化 rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) tensor np.transpose(rgb, (2, 0, 1)).astype(np.float32) / 255.0 tensor np.expand_dims(tensor, 0).copy() outputs session.run(None, {input_name: tensor}) print(输出张量形状:, outputs[0].shape)逻辑说明这段代码把图像解码、缩放、BGR转RGB、归一化到0到1然后拼成[1,3,640,640]的张量喂给模型。.copy()很有必要numpy的转置视图往往不是连续内存onnxruntime遇到这种情况会直接报错。输出张量形状一般形如[1, 4类别数, 8400]其中8400是不同特征图尺度下的预测框总数拿回来还需要后处理解析坐标。这部分不是本文重点但要明白model.predict()和ONNX原始输出的区别——predict帮你把后处理做完了ONNX给的是一堆未解码的预测值。一致性验证通过后模型才算真正具备部署条件。5. 200张数据集避坑指南5个真实翻车现场5.1 现象验证集mAP高达0.93实测另一块地的图像几乎框不出杂草原因这种翻车九成是数据泄漏。划分train/val时直接随机切同一个地块或同一次拍摄的图像被分到两边模型记住的是地块纹理背景不是杂草本身。验证集和训练集太像mAP自然虚高。解决划分前按文件名的批次前缀归组或按拍摄时间切分同一田块的图像只出现在一个split里。另外单独留一批完全无关的图像做盲测mAP曲线再漂亮都不如盲测一张图有说服力。5.2 现象跑了十几轮box_loss不降反升甚至出现NaN原因标签文件里有异常值。最常见两种标注时目标拖出了图像边界导出后坐标越界标签行混入空行或非数字字符。yolo的损失函数对这类脏数据没有任何防御能力。解决回到第2章的体检脚本把越界坐标、非5列的行、NaN全查出来修完数据再重训。不要试图靠调小学习率掩盖数据问题那是拿玄学当工程。5.3 现象训练集和验证集里出现了同一张图原因划分脚本没写错是原始目录里本身存在重复图像。同一张jpg从不同渠道拷贝时被改成不同文件名比如IMG_001.jpg和1.jpg视觉上相同md5也相同。解决先对图像目录做一次md5去重再执行划分脚本。200张的规模用Python的hashlib全量算一遍只要几秒不要省这个步骤。5.4 现象作物类检测得很好杂草类一个也框不出来原因类别不平衡。如果200张图里杂草目标又小又稀疏模型很容易把小杂草当成背景噪声学掉。标注时人也会习惯性漏掉远处的小草留下的标签本身偏「大而清晰」。解决先可视化标注确认杂草框是否覆盖所有小草目标。再在训练时给少数类别加损失权重Ultralytics训练配置里有cls相关的权重项可以调。或者直接对杂草样本做复制增强拼版进训练集。实在不行把图像裁成四个patch分别训练对小目标往往有奇效。5.5 现象训练到一半CUDA out of memory原因多数不是batch的错是imgsz拉得太大。很多人为了让小目标更清楚把imgsz直接设1280显存立刻爆掉。nano模型在1280下batch8也可能OOM。解决保持imgsz640起步先把链路跑通。确认需要提分辨率时batch降到4同时检查是否有其它进程占显存。更省事的做法是缩小batch多跑几轮性价比比盲目提分辨率高得多。6. 拿到这200张图之后迁移学习、数据增强、合成数据三条路在大田作物和杂草检测这个方向200张带标签图像的真实定位是让你在一天内跑通yolo算法从数据到部署的完整链路顺带把标注、划分、训练、验证的每处细节摸清楚。但想拿到能支撑决策的精度200张远远不够。见过有人拿200张训练完直接去田间演示效果不理想就否定算法这种判断本身就不成立。小数据集的正确用法是把流程价值榨干再把积累的问题转化成下一轮采集方案。三条路可以并行。第一是迁移学习上面命令里的pretrainedTrue只是第一层后续可以尝试在更大作物检测模型上继续微调比从零开始强很多。第二是数据增强yolo内置mosaic、翻转、旋转等增强手段200张图能撑起100轮训练不重样但增强强度过猛会让模型学到假纹理比如杂草叶片被旋转90度后和真实生长姿态完全不符反而误导泛化。第三是合成数据对特定杂草用透明背景叶片贴图随机拼到作物背景上成本远低于人工标注。我自己现在拿到任何小数据集都不会急着调参而是先做一遍全链路体检脚本、划分脚本、nano训练、推理落图、导出ONNX。这套流程走完哪里数据不够、哪里标注不规范心里都有底。然后带着结论去扩数据比盲目多标几百张有用得多。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑