简介目标检测是计算机视觉的核心任务之一而数据集的规范组织直接影响模型训练的效率与效果。在众多目标检测框架中YOLOv5凭借高效性与易用性成为广泛应用的选择其标准数据集目录结构images/labels划分、yaml配置是训练流程的基石。通过图像采集、标注、格式转换等步骤构建高质量数据集能够显著提升模型在真实场景中的泛化能力。农业质检、粮库筛选等场景对颗粒状农产品的实时检测需求日益增长玉米粒好坏检测正是典型的应用案例。以玉米粒好坏检测为切入点完整梳理从图像采集、标注实操、目录格式整理到训练前验证的流程重点介绍YOLOv5标准目录结构的搭建方法与注意事项帮助开发者快速构建属于自己的目标检测数据集少走弯路。玉米粒好坏检测数据集从零搭建YOLOv5标准目录格式的完整实践做目标检测项目最难的不是模型选型不是训练参数调优而是你有没有一份靠谱的数据集。尤其是农业方向的项目公开数据集本来就少想找到一份“玉米粒好坏检测”这种细分类的现成数据基本不太可能。所以大多数人的路径都是自己动手做一份标注数据集顺便把目录格式整理成YOLOv5可以直接训练的样子。这篇文章就围绕“玉米粒好坏检测”这个具体场景完整梳理一遍数据集从图像采集、标注、格式整理到质量验证的全过程。我做的这份数据集包含2个类别好粒、坏粒分好了训练集和验证集目录结构直接对标YOLOv5的官方要求放进去就能开训。如果你也想做类似的目标检测数据集或者你只是被数据格式折腾过希望这篇能帮你少走一些弯路。1. 为什么选玉米粒好坏检测数据集的定位和价值先聊聊这个项目本身。玉米粒好坏检测听起来是个特别窄的场景但它背后其实代表了一类很典型的农业质检需求在传送带或者料斗里实时识别出变质、发霉、破损的玉米粒把合格和不合格的颗粒分开。这个需求在粮库、饲料厂、种子筛选设备里都很常见而目标检测模型恰好可以做这种实时定位识别。这个项目的核心难点在于“坏粒”不是一个完全均质的类别。发霉的、霉变的、破损的、虫蛀的视觉特征差异很大但如果类别标签定义得太细数据量根本撑不住。所以实际做的时候我采用了最常用的二分法好粒good和坏粒bad。这类二分类目标检测数据集表面上看只是一个简单的标注任务实际上涉及到很多值得仔细抠的细节。从技术角度来说这个数据集的价值有几个方面第一它是一个非常适合入门和验证流程的场景。玉米粒本身是小型目标形态相对统一背景可以控制训练起来不会太吃力模型迭代速度也快。第二它可以作为数据生产流程的模板。学会了“图像采集-标注-划分-验证”这条链路以后不管是做小麦、大米、花生还是其他颗粒状农产品的检测基本套路都是通用的。第三它是脱离公开数据集依赖的一次实战。很多人的第一个目标检测项目用的是网上现成的数据集训练完精度很高但到实际场景一测就崩原因就是数据分布不匹配。自己采集、自己标注的数据集天然更贴近真实部署环境模型泛化能力也更好。2. 图像采集阶段的选择要不要考虑传感器与光照如果你以为做数据集就是随便拍几张照片然后开始标注那就大错特错了。图像采集是整个项目的地基采集方式直接决定了后续模型的部署效果。2.1 用手机拍摄还是用工业相机效果差在哪里我的这份数据集用的是普通手机摄像头拍的分辨率在1200万像素以上就够用了。因为玉米粒的尺寸不大但也不算特别小对于YOLOv5的输入尺寸默认640x640来说只要玉米粒在画面中占据一定比例检测是没有问题的。使用工业相机固定光源拍摄的最大好处是光照均匀、背景干净但坏处也很明显数据太“干净”了。训练出来的模型换到真实环境比如传送带、阳光直射的车间很容易失效。所以我在采集的时候故意加入了不同光线条件的变化——上午的自然光、下午的侧光、室内灯光甚至还有几组阴影遮挡的样本。让模型见过更复杂的光照变化实际部署的时候才不会“见光死”。2.2 背景和环境的选择要贴近真实使用场景背景方面有几种典型的选择纯色纸板、黑色绒布、白色托盘还有接近真实传送带的灰色橡胶垫。我建议不要全用一种背景。如果模型只见过黑色背景部署时遇到白色输送带就会误检漏检。这个数据集里每张图像大约6到10粒玉米尽量让每粒玉米的边界清楚不要大面积重叠。个别图像可以有少量堆叠以增强模型的抗遮挡能力但训练集里叠得太多的图片要控制比例不然模型学到的特征容易偏向轮廓边缘而不是表观特征。2.3 数据量的把控好坏类别的比例设计玉米粒好坏检测的目标类别有两个good好粒和 bad坏粒。一个容易踩的坑是训练集中两个类别的实例数量偏差太大。好粒采集容易坏粒相对难找有些人图省事就只拍了几十个坏粒样本就开搞结果训练出来的模型对坏粒的召回率极低。我的做法是训练集中好粒实例约2000个坏粒实例约1600个尽量让两个类别的数量比例控制在1.5:1以内这样模型不会因为类别极度不平衡而偏科。验证集则单独切出来不参与训练数量大约是训练集的1/5左右。注意这里说的实例数量是标签文件中出现的框的数量不是图片张数。一张图里有6粒玉米就有6个目标框。统计类别数量时千万不要按图片张数算。3. 数据标注实操YOLO格式标签的每一步细节标注是整个数据集制作过程中最耗时间的环节也是决定模型最终效果的上限。算法工程师常说一句话模型效果的上限由数据处理决定标注质量就是这个上限的直接体现。3.1 标注工具的选择LabelImg依然是最省事的选项现在可选的目标检测标注工具有很多比如LabelImg、Labelme、Roboflow、X-AnyLabeling等。对于YOLO格式数据集来说我仍然推荐老牌的LabelImg原因很简单轻量、稳定、开箱即用。LabelImg安装没有什么难度Python环境跑一下pip install就能装好。如果你在Windows上不想折腾Python环境也可以直接下载打包好的exe版本。界面很简洁左边是标注工具栏右边是图像区用矩形框把目标框起来选好类别标签保存就自动生成对应的txt文件。3.2 YOLO标签格式的坐标解析这是整个数据集制作中最重要的知识点。YOLOv5的标签格式不是Pascal VOC那种x_min, y_min, x_max, y_max而是归一化后的中心点坐标加宽高class_id center_x center_y width height每个值之间用空格隔开其中center_x, center_y, width, height全部是相对于图像宽高的比例值取值范围在0到1之间。class_id从0开始计数。比如一张宽1000像素、高800像素的图片某个玉米粒的检测框左上角在(200, 150)右下角在(400, 350)那对应的YOLO格式就是0 0.3 0.3125 0.2 0.25计算过程中心点x (200 400) / 2 / 1000 0.3中心点y (150 350) / 2 / 800 0.3125宽 (400 - 200) / 1000 0.2高 (350 - 150) / 800 0.25LabelImg会自动完成这个过程你只需要框选就行。但理解这个格式很重要因为后面做数据增强、写脚本检查标签的时候你都要直接和这些数值打交道。3.3 标注边界与类别判定好粒和坏粒的模糊地带标注的时候最头疼的不是怎么框而是怎么判断某个玉米粒到底算好还是算坏。这个问题的答案直接决定了你的标注一致性。我的判定标准是这样的表面光滑、颜色金黄均匀、形态饱满的标为good。有明显霉斑、发黑、干瘪、破损露出内部淀粉、虫蛀痕迹的标为bad。处于临界状态只有一点点瑕疵的统一标为bad。宁可让模型对轻微的坏粒“敏感”一些也不要漏检。这个判定标准要在标注开始之前就定下来最好是写进一个标注说明文档里让参与标注的人统一参考。否则不同人标注的标准不一致训练出来的模型精度再高也是虚的因为标签本身就是不一致的。提示标注过程中建议每标注完二三十张图退回来看一遍已生成的标签文件。看看有没有越界的坐标比如width或height大于1有没有类别名写错的情况。早期发现修正成本很低等到几十个文件都标注完再统一检查改起来就麻烦了。3.4 训练集和验证集不要重复常见错误的根源这里单独说一个非常关键的点训练集和验证集的数据千万不能有重叠。有些人从同一段视频里抽帧先按顺序抽了一组做训练集又顺手在后面抽了一组做验证集如果视频里玉米粒的位置移动不大验证集里的图像可能和训练集高度相似甚至同一粒玉米在不同帧里出现在几乎相同的位置。这种数据划分会导致验证集loss参考价值严重失真。正确的做法是先把所有标注完成的图片全部汇总到一个总文件夹里打乱顺序再按比例划分训练集和验证集从源头避免同源图像被切到两个集合里。最简单的实现就是用Python随机划分而不是手工从文件夹里挑。4. 目录格式搭建YOLOv5数据集标准结构的完整落地好标注完成之后就到了最核心的环节把零散的图片和标签文件整理成YOLOv5官方要求的目录结构。4.1 YOLOv5标准目录结构拆解YOLOv5的训练程序是通过读取yaml配置文件来加载数据集的目录结构需要遵循约定。官方的标准格式如下dataset/ ├── images/ │ ├── train/ │ │ ├── 001.jpg │ │ ├── 002.jpg │ │ └── ... │ └── val/ │ ├── 101.jpg │ ├── 102.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── 001.txt │ │ ├── 002.txt │ │ └── ... │ └── val/ │ ├── 101.txt │ ├── 102.txt │ └── ... └── corn_data.yaml有几个容易被忽略的要点第一images和labels必须放在同一个父目录下图片文件名和标签文件名必须完全一致只是扩展名不同。比如001.jpg对应的标签就是001.txt。如果名字对不上YOLOv5训练时会直接跳过这张图而且不会报错只在日志里显示一个很隐晦的警告。第二train和val两个子目录里都要有images和labels不能只把训练集分出来验证集格式随意放。第三yaml配置文件中的path、train、val路径最好用相对路径或者以数据集根目录为基准的路径不建议在yaml里写死绝对路径。否则换一台电脑训练路径就失效了。4.2 yaml配置文件的编写我的corn_data.yaml内容如下path: ../datasets/corn_data # 数据集根目录 train: images/train # 训练集图片 val: images/val # 验证集图片 nc: 2 # 类别数量 names: [good, bad] # 类别名称列表特别注意names的列表顺序必须和标注时class_id的含义严格一致。如果你标注的时候设定0是good、1是bad那么在yaml里names[0]必须是goodnames[1]必须是bad。搞反了的话训练不会报错但预测结果会张冠李戴。4.3 用脚本划分数据不用手工复制粘贴如果你已经把所有图片和标签都放进一个大文件夹里了可以把下面这个脚本稍微改一下一条命令完成划分import os import random import shutil # 总数据集路径所有图片和标签都在这里 all_images_dir all_data/images all_labels_dir all_data/labels # 输出路径 output_dir datasets/corn_data train_img_dir os.path.join(output_dir, images, train) val_img_dir os.path.join(output_dir, images, val) train_lab_dir os.path.join(output_dir, labels, train) val_lab_dir os.path.join(output_dir, labels, val) for d in [train_img_dir, val_img_dir, train_lab_dir, val_lab_dir]: os.makedirs(d, exist_okTrue) # 获取所有图片文件 image_files [f for f in os.listdir(all_images_dir) if f.endswith((.jpg, .jpeg, .png))] random.seed(42) # 固定随机种子保证可复现 random.shuffle(image_files) # 按9:1划分训练集和验证集 val_ratio 0.1 val_count int(len(image_files) * val_ratio) val_images image_files[:val_count] train_images image_files[val_count:] for img in train_images: shutil.copy(os.path.join(all_images_dir, img), os.path.join(train_img_dir, img)) label_file img.rsplit(., 1)[0] .txt label_src os.path.join(all_labels_dir, label_file) if os.path.exists(label_src): shutil.copy(label_src, os.path.join(train_lab_dir, label_file)) else: print(f警告缺少标签文件 {label_file}) for img in val_images: shutil.copy(os.path.join(all_images_dir, img), os.path.join(val_img_dir, img)) label_file img.rsplit(., 1)[0] .txt label_src os.path.join(all_labels_dir, label_file) if os.path.exists(label_src): shutil.copy(label_src, os.path.join(val_lab_dir, label_file)) else: print(f警告缺少标签文件 {label_file}) print(数据集划分完成) print(f训练集图片数{len(train_images)}) print(f验证集图片数{len(val_images)})这个脚本看起来很基础但有两个细节值得说。一是文件名匹配的问题。如果你的图片格式不统一比如有些是.jpg有些是.png脚本里的endswith判断会把它们都认出来但标签文件统一替换成.txt就行所以没问题。二是缺失标签文件的提示。我故意在这里加了一个显式的提示而不是静默跳过。因为缺标签文件是数据集中最容易发生又最难排查的问题宁可让脚本在这里停下来提醒你也不要等训练的时候才发现。4.4 数据划分比例的考量9:1还是8:2对于玉米粒检测这种样本量在几百张图片级别的数据集我建议划分比例在9:1到8:2之间。如果数据集总量很大比如超过5000张图片验证集可以少一些9:1就够但如果只有两三百张验证集建议留到20%否则验证集太小评估出来的mAP波动会很大没有参考意义。还有一个很常见的建议是数据集量特别小的时候可以考虑用K折交叉验证来评估模型。不过对于初学或者快速验证流程的场景先按8:2或9:1划分跑通整个训练流程更重要。5. 训练前的最后一道工序格式验证与常见坑把目录整理好、yaml文件写好后千万别急着开训。至少有四件事值得在训练前花十几分钟做掉能帮你省掉后面无数排查的麻烦。5.1 标签内容合法性检查YOLOv5对标签格式是零容忍的。标签文件里如果出现了大于1的坐标值、负数坐标或者类别id超出nc范围训练过程中轻则丢样本重则直接报错导致训练中断。我写了一个小检查脚本遍历所有标签文件把异常值都列出来import os label_root datasets/corn_data/labels nc 2 for split in [train, val]: label_dir os.path.join(label_root, split) for f in os.listdir(label_dir): if not f.endswith(.txt): continue file_path os.path.join(label_dir, f) with open(file_path, r) as fp: for idx, line in enumerate(fp.readlines()): parts line.strip().split() if len(parts) ! 5: print(f{file_path} 第{idx1}行格式错误{line.strip()}) continue class_id int(parts[0]) cx, cy, w, h map(float, parts[1:]) if class_id nc: print(f{file_path} 类别id超出范围{class_id}) if cx 0 or cy 0 or w 0 or h 0 or w 1 or h 1: print(f{file_path} 坐标值异常{line.strip()})这个脚本不需要一次跑出所有问题重点是跑完一遍没有输出基本可以放心。5.2 图片和标签数量核验这一步容易被忽略但非常重要。用下面两条命令就能快速检查# 检查train目录图片数量 find datasets/corn_data/images/train -name *.jpg | wc -l # 检查train目录标签数量 find datasets/corn_data/labels/train -name *.txt | wc -l两边数量一致是最基本的要求。如果标签数量比图片少就说明有图片没被标注到如果标签比图片多多半是重复生成或者命名冲突。还有一个更深层的检查方式用YOLOv5自带的检查脚本。在yolov5项目目录下运行python train.py --data corn_data.yaml --weights yolov5s.pt --epochs 1如果数据集有严重格式问题这一步就会直接暴露出来。5.3 可视化验证肉眼检查标注框检查标签文件数值正常不代表标注框的位置正确。建议在训练前把标注框可视化到图片上随机抽查几十张看看框是否紧贴目标、类别是否正确。YOLOv5项目里自带一个可视化工具路径是utils/plots.py调用它就能把标签画到图上。也可以在Python里快速手动完成import cv2 import os img_dir datasets/corn_data/images/val label_dir datasets/corn_data/labels/val for f in os.listdir(img_dir)[:20]: img_path os.path.join(img_dir, f) img cv2.imread(img_path) h, w img.shape[:2] label_path os.path.join(label_dir, f.rsplit(., 1)[0] .txt) with open(label_path, r) as fp: for line in fp.readlines(): parts line.strip().split() cls int(parts[0]) cx, cy, bw, bh map(float, parts[1:]) x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) color (0, 255, 0) if cls 0 else (0, 0, 255) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, str(cls), (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1) cv2.imwrite(fcheck_{f}, img)看一看生成的图片如果发现某个框明显偏离了玉米粒的位置就要回到LabelImg里修正标注。5.4 数据集大小与训练时间的关系YOLOv5s是YOLOv5系列里比较轻量的版本一个几百张图片的玉米粒数据集GPU训练不到一小时就能跑完。但如果你不小心用了YOLOv5x这种超大模型同样数据量可能要训练好几个小时。所以刚起步的时候先用YOLOv5s跑通整个流程确认数据集没问题再考虑是否用更大模型提升精度。6. 从数据集到模型训练第一轮训练的适配与调优当目录结构、标签文件、yaml配置都准备好之后就可以正式进入训练阶段。这部分的经验同样重要因为很多问题虽然在训练阶段暴露但根源其实在数据集这一层。6.1 超参数设置epochs和batch size的选择对于玉米粒这种小型目标、类别少、场景固定的数据集一些YOLOv5的默认超参数已经是合理的选择并不需要大动干戈。初学容易犯的错误是盲目追求epoch数设置成500甚至更多结果模型在训练集上表现很好验证集mAP却迟迟上不去——这是过拟合的典型信号。我的建议是先用300个epoch跑一轮观察训练结果。如果mAP已经在比较早的阶段进入平台期后面基本不怎么涨了说明300足够甚至150、200也行。batch size方面如果不清楚自己的GPU显存能支撑多大的batch可以直接用默认的batch-1让YOLOv5自动探测。这个参数在训练日志里会显示如果显存不够不要强行调大。6.2 模型选型YOLOv5s是验证流程的首选验证数据集阶段不需要一上来就用复杂的模型。YOLOv5s是速度和精度的均衡点网络结构相对简单训练速度快在玉米粒这种难度不高的任务上mAP一般可以达到90%以上。如果后续要在边缘设备上部署可以试试YOLOv5n或者YOLOv5s的量化版本如果要追求更高的检测精度再考虑YOLOv5m或YOLOv5l。但无论如何第一次训练走通流程yolov5s都是最合适的选择。拿我自己跑的结果来说用这个玉米粒数据集训练YOLOv5s验证集上的mAP0.5可以达到92%左右mAP0.5:0.95在70%上下。这个水平对一个二分类检测任务来说已经够用了。6.3 训练完成后如何检查数据集的“健康度”训练结束后不要只看最终mAP。建议打开train_batch0.jpg、val_batch0_labels.jpg这些可视化文件看看实际样本中的标注框和预测框的差异。还有results.png里的loss曲线如果训练集loss下降但验证集loss不下降甚至上升说明模型过拟合除了考虑增加数据量也要回头审视数据集是否有问题——标签噪声、类别不平衡、图像重复都可能导致这种现象。比如你在验证集上发现某个坏粒样本的预测框置信度特别低去查看原始标注发现这个样本的框画得偏大把旁边的好粒也包进去了那模型学到的特征就是混乱的预测结果自然不好。7. 基于这套流程的个人经验总结做玉米粒好坏检测数据集这整个流程下来我有几点体会想分享。第一数据集质量比数量重要。很多人执着于收集几千上万张图反而忽略了标注一致性和场景覆盖度。对于玉米粒这种形态相对固定的目标500张图片如果标注精确、场景丰富效果往往好过2000张但标注粗糙、背景单一的数据。第二一定要重视验证集划分的随机性。固定随机种子可以保证每次实验的结果可复现别小看这个细节。我自己早期不设随机种子每次重新划分数据集训练出来的模型精度都不一样后来才意识到是划分方式在变。第三多留一些“脏数据”未必是坏事。可能有人会花大量时间去剔除那些略微遮挡、光线偏暗的图片但在我看来这些反而是提升模型鲁棒性的关键数据。只要标注准确保留一定比例的复杂场景是有益的。最后再分享一个实用技巧做完这份玉米粒数据集后我把它同时存了一份COCO格式的版本。万一以后想切换模型框架比如从YOLOv5换成MMDetection或者别的工具库COCO格式的通用性更强不需要重新标注。做数据的时候多留一手后面会省很多事。目前这个数据集已经用YOLOv5跑通了训练和验证效果稳定。如果后续有时间我打算往里面补充不同品种的玉米样本、增加传送带动态模糊场景、尝试加入一些不同分辨率图像来测试模型的尺度泛化能力。数据集的迭代是一个长期过程永远不要觉得第一版就是终点。本文还有配套的精品资源点击获取