中草药图像目标检测实战:YOLO数据集解构与训练避坑指南
发布时间:2026/9/28 15:49:23来源:尧图网络
简介面向目标检测与计算机视觉学习者的中草药图像数据集基于YOLOv5标准目录格式整理可直接用于训练中草药识别模型或体验YOLO系列算法从数据准备到模型评估的完整流程。数据集涵盖8个常见类别包括Cardamom、Cumin、Neem等典型中草药图片已划分训练集约2600张、验证集约190张、测试集约100张每张图片均对应txt格式的标准YOLO标注文件记录类别id与归一化的框中心坐标、宽度和高度。包内另附类别class文件与数据可视化脚本无需修改代码随机传入一张图片即可绘制边界框并保存至当前目录方便快速核查标注质量与模型输入。资源共2000个文件以txt标注文件为主另有1个py可视化脚本压缩包整体约209.51MB目录结构清晰可直接被YOLOv5训练脚本加载。目前已有1017人学习下载特别适合需要现成中草药数据集完成目标检测实验、课程设计或算法复现的学生与研究者。1. 中草药图像目标检测为什么这份 YOLO 数据集能直接进训练做目标检测的都知道数据集准备比调模型更耗时间。YOLO 训练本身跑几个小时就完事但标注几千张图、划分训练验证测试集、写类别文件、调格式这些杂活随便就能磨掉两三天。这份中草药图像检测数据集的价值在于它已经按 YOLOv5 的标准目录结构分好了训练集、验证集、测试集标注全部是 YOLO 相对坐标格式8 个类别Cardamom、Cumin、Neem 等总共两千八百多张图片解压就能直接开训。附带一个 show.py 可视化脚本随机传一张图进去就能把检测框画出来用来核对标注质量非常方便。适合手里有检测需求、但不想从零标注的工程师和研究者——不管你是跑 YOLOv5 还是 YOLOv8这份数据集的目录规范都能直接吃进去省掉的正好是准备数据的那段脏活累活。2. 数据集结构与标注格式先看懂 YOLO 目录和归一化坐标再说训练2.1 目录划分逻辑datasets 下的 images 和 labels 为什么必须平行拿到数据集后第一件事不是解压就跑而是先把目录结构看明白。YOLOv5 的数据组织方式很固定images 和 labels 两个目录必须平行存在同名文件一一对应图片放 images标注 txt 放 labels。这份数据集在 datasets 下分成 train、val、test 三个子集每个子集里再分 images 和 labels这就是 YOLOv5 默认的读取方式yolov5 官方仓库的 detect.py、train.py 都按这个约定找文件。datasets/ ├── images/ │ ├── train/ # 约 2600 张 │ ├── val/ # 约 190 张 │ └── test/ # 约 100 张 └── labels/ ├── train/ # 对应的 txt 标注 ├── val/ └── test/注意测试集的图片和标签数量差了一倍这是因为标注过程中部分图片被过滤掉了训练前最好自己数一遍两个目录里的文件数是否一致。我拿到数据的第一反应就是跑find datasets -type f | wc -l去核对数量这一步能避免后面训练时莫名其妙报AssertionError: Label not found。2.2 标注文件里的五行数字类别、中心点、宽高全是相对坐标每个 txt 文件对应一张图片里面的每一行代表一个目标框格式是class x_centre y_centre width height。它不是像素坐标而是归一化之后的相对坐标范围在 0 到 1 之间除以图片实际宽高得到。比如一张 640x640 的图片里某个目标的中心点在 (320, 160)宽 80高 160那么 txt 里写的就是0 0.5 0.25 0.125 0.25。0 0.512830 0.442786 0.276430 0.219460 1 0.683710 0.510420 0.183900 0.154710第一列的数字是类别索引必须和类别文件里的顺序严格对应——classes.txt 里第一个类对应索引 0第二个对应索引 1依此类推。如果训练时你改了类别顺序但没改标注文件模型会把这些框全部认错。我在实际项目中吃过这个亏换数据集时忘了同步 classes 文件结果模型把 Cardamom 全识别成了 CuminLoss 还降得挺快最后检查才发现是类别错位。2.3 classes 文件与 dataset.yaml训练前必须手动对齐的两处配置这份数据集的类别文件通常是 classes.txt包含 Cardamom、Cumin、Neem 等 8 个类别。但 YOLOv5 训练时真正读的不是 classes.txt而是 dataset.yaml 里的 names 列表。你需要手写这个 yaml把它和 classes.txt 的类别顺序完全对齐。# dataset.yaml train: datasets/images/train val: datasets/images/val test: datasets/images/test nc: 8 names: [Cardamom, Cumin, Neem, Turmeric, Holy Basil, Fenugreek, Pepper, Ginger]这里有个容易翻车的细节yaml 里的train和val路径可以写绝对路径也可以写相对路径但相对路径的基准是运行 train.py 时所在的目录不是 dataset.yaml 所在目录。我习惯把路径写成相对 datasets 的完整路径然后把数据集的根目录放到和 yolov5 仓库平级的位置这样无论从哪启动训练都不会找不到文件。还有一点val 和 test 不要写同一个目录否则你最后的模型评估会失真训练时看不到真实分布。3. 用可视化脚本检查数据show.py 的机制与实际操作3.1 show.py 做了什么从文件命名规律看出它的读取逻辑这个数据集的标注文件名都是长哈希串比如1000_F_268284898_xxx_jpg.rf.ac1ed67819a98e6ec0188f3ad7abf1ba.txt对应图片名是1000_F_268284898_xxx_jpg.rf.ac1ed67819a98e6ec0188f3ad7abf1ba.jpg。show.py 做的就是传入图片路径 → 读取同名 txt → 解析每行的类别和归一化坐标 → 按图片宽高还原成像素坐标 → 用 OpenCV 画框和类别标签 → 保存到当前目录。自己写一个也不难核心逻辑就几行。下面是一个等效的最小实现逻辑和 show.py 一致import cv2 import numpy as np import sys import os def draw_yolo_boxes(image_path, classes_fileclasses.txt): # 读取类别名称 with open(classes_file, r) as f: classes [line.strip() for line in f.readlines()] # 读取图片并获取宽高 img cv2.imread(image_path) h, w img.shape[:2] # 同名 txt图片路径去掉扩展名后拼接 .txt txt_path os.path.splitext(image_path)[0] .txt with open(txt_path, r) as f: lines f.readlines() for line in lines: parts line.strip().split() if len(parts) ! 5: continue cls_id int(parts[0]) x_center float(parts[1]) * w # 相对坐标还原为像素 y_center float(parts[2]) * h box_w float(parts[3]) * w box_h float(parts[4]) * h x1 int(x_center - box_w / 2) y1 int(y_center - box_h / 2) x2 int(x_center box_w / 2) y2 int(y_center box_h / 2) label classes[cls_id] if cls_id len(classes) else fclass_{cls_id} cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, label, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) out_path visualized_ os.path.basename(image_path) cv2.imwrite(out_path, img) print(f可视化结果已保存: {out_path}) if __name__ __main__: draw_yolo_boxes(sys.argv[1])坐标还原的关键在于把归一化值乘以图片的实际宽高再换算左上角和右下角。注意 OpenCV 的putText在画中文类别名时会乱码所以要么类别文件里用英文要么用 PIL 替代 OpenCV 去绘制否则保存出来的图上标签全是问号。我用这段脚本逐张抽查了大概 30 张图片确认了标注框和中药草叶片位置基本吻合没有明显偏移。3.2 随机抽查策略几十张图就能看出数据集能不能用可视化不是每张图都跑一遍那样太费时间。我的习惯是训练集里随机挑 20 到 30 张按类别分层抽确保每个类至少看到 2 到 3 个实例。先抽验证集的因为数据量小一屏能看个大概再抽训练集的重点看边缘案例——叶片重叠、背景杂乱、目标特别小的图。看到框紧贴叶片边缘、类别标签没串号这份数据的标注质量基本就有底了。# 从训练集中随机抽 30 张图片出来逐个可视化 find datasets/images/train -name *.jpg | sort -R | head -n 30 sample_list.txt while read img; do python show.py $img; done sample_list.txt跑完后批量看输出的visualized_*.jpg不需要每张都点开用文件管理器缩略图扫一遍就够。我一般还会额外做一步把可视化图片的文件名按类别名前缀批量重命名方便按类别归类检查。如果发现某个类别的框明显比真实目标大一圈或者小一圈说明标注时框的边界打得不紧训练时会影响 mAP这类图要么删掉要么重新标。3.3 直接训练yolov5 仓库拉下来就能跑的最小命令数据集验证没问题之后直接拿官方 yolov5 仓库训练。这一步的关键是把 dataset.yaml 的路径配好然后运行 train.py指定模型大小、batch size 和训练轮数。2600 张图的话一张 1080Ti 或 3060 显卡batch 16 跑 100 轮大概两到三小时。git clone https://github.com/ultralytics/yolov5 # 若已有仓库则跳过 cd yolov5 pip install -r requirements.txt python train.py \ --data /path/to/dataset.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --cache--cache参数会把图片提前加载进内存省去每轮训练时的磁盘 I/O速度快很多但吃内存如果显存和内存都够就开。--img 640是输入分辨率如果你的中草药图片本身细节密集可以试 960 或 1280但显存占用会明显上涨训练时间也几乎翻倍。先用 640 跑基线然后看验证集的 mAP 再决定要不要提分辨率。4. 避坑指南标注、训练、可视化里最容易翻车的五个地方4.1 可视化脚本画不出框现象show.py 运行不报错但保存出来的图上一个框都没有。原因txt 文件和图片文件名不匹配。很多目标检测数据集的标注是 Roboflow 导出的文件名带有.rf.前缀和哈希串如果图片被重命名过os.path.splitext(image_path)[0] .txt就找不到对应的标注文件程序静默跳过一张空图保存出来。解决先打印txt_path看看文件是否存在或者用os.path.exists(txt_path)做个判断缺失时直接抛异常提醒自己。写脚本时加一行判断能省掉后面排查的一堆时间。4.2 训练时 Loss 变成 NaN现象训练到十几轮时 loss 突然变成 nan然后一直 nan模型权重废掉。原因最常见的是学习率太大或 batch 里出现极端数据另外显卡的混合精度在某些情况下也会导致数值溢出。中草药图片里如果有全黑或全白的图归一化后像素值集中在极值附近前向传播时容易产生异常梯度。解决先把--amp关掉YOLOv5 的默认训练开 AMP换成--amp False再把学习率从默认的 0.01 降到 0.001。最后排查数据写个脚本遍历图片检查像素方差把方差接近 0 的图删掉或补入噪声。这三个动作按顺序做Nan 基本就解决了。4.3 BN 崩溃训练中模型输出全部变成常数现象训练到中后期验证集的 mAP 突然掉到接近 0预测时所有目标都被识别成同一个类别。原因这是 BNBatch Normalization层崩溃的典型表现通常是因为某个 batch 里样本分布极端导致 BN 层的 running_mean 和 running_var 被污染。YOLOv5 里默认开启--sync-bn是多卡训练用的单卡时不会触发但如果你用了自定义的数据加载器shuffle 设置不当会让连续多个 batch 都来自同一个类别。解决训练时加--shuffle确认数据加载是随机打乱的如果自定义了 Dataset在__getitem__里做随机翻转和色调抖动增加 batch 内的数据多样性。另一个保底办法是训练前用--cache预加载并做一次数据分布检查。4.4 测试集和验证集混在一起现象训练时 val loss 很低mAP 很高但换到新数据上效果一塌糊涂。原因这份数据集的测试集和验证集目录名称长得很像都是datasets/images/test和datasets/images/val一不小心就在 dataset.yaml 里把test和val指向了同一个目录。验证集本身参与了模型挑选如果再拿它做最终评估指标理所当然好看。解决dataset.yaml 里三个路径必须严格区分训练过程中只参考 val 的指标调整参数模型训练完后再用 test 集跑一次val.py以这个结果作为最终效果。这是必须养成的习惯——验证集是用来调参的测试集是用来交差的混在一起就是自欺欺人。4.5 小目标漏检严重现象模型对叶片大、背景干净的中草药识别很好但图片里目标只占几十个像素时基本漏检。原因中草药图像里很多叶片是远距离拍摄的主目标大但边缘可能还有小的叶片或种子。YOLOv5 默认的锚框尺寸针对 COCO 数据集优化对中小目标并不友好。另一个原因是下采样倍数太大特征图分辨率不够小目标的特征在深层特征图中已经丢失。解决训练时加--multi-scale让模型在训练过程中随机缩放输入尺寸增强对不同目标尺寸的适应力如果小目标占比很高可以把输入分辨率提到 960 或 1280或者换 YOLOv5 的 P6 模型yolov5s6.pt它多了一个更大的特征输出层专门改善小目标检测。5. 从这份数据集出发扩充样本、转换格式、适配 YOLOv8 的完整链路5.1 中草药类别 vs 通用数据集要不要做数据增强中草药和常见物体的区别很大同一个物种的不同生长期叶片颜色、纹理差异明显拍摄角度、背景泥土、桌面、手掌也千差万别。直接拿原始数据集去训练模型容易过拟合到背景而不是目标本身。YOLOv5 自带了一系列数据增强在 hyp.scratch.yaml 里控制重点调三个参数hsv_h色调偏移、flipud垂直翻转和 mosaic。# hyp.scratch.yaml 片段 hsv_h: 0.015 # 色调偏移 hsv_s: 0.7 # 饱和度偏移 hsv_v: 0.4 # 明度偏移 flipud: 0.0 # 垂直翻转概率 mosaic: 1.0 # mosaic 增强中草药图像不像车辆检测那样要求“头朝上”垂直翻转不会破坏语义建议把 flipud 调到 0.5。mosaic 保持 1.0 就行它把四张图拼一起训练对小目标和中草药这种需要看纹理的类别帮助很明显。注意 hsv_v 不要调太高中草药的颜色是重要特征明度变化太大会让模型把颜色信息当噪声。5.2 外部数据扩充如何把自己拍的图变成 YOLO 格式假设你自己拍了一些中草药的图片要补进训练集最直接的方案是用 LabelImg 标注然后导出 YOLO 格式。LabelImg 保存的 txt 本身就是 YOLO 相对坐标格式和这份数据集的格式一致直接丢进 labels 目录就能用。但如果你手里的是 VOC 格式的 XML 标注就得转换。import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_path, out_dir, classes): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) yolo_lines [] for obj in root.iter(object): cls_name obj.find(name).text if cls_name not in classes: continue cls_id classes.index(cls_name) box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) x_center (x1 x2) / 2 / img_w y_center (y1 y2) / 2 / img_h w_norm (x2 - x1) / img_w h_norm (y2 - y1) / img_h yolo_lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w_norm:.6f} {h_norm:.6f}) out_path os.path.join(out_dir, os.path.basename(xml_path).replace(.xml, .txt)) with open(out_path, w) as f: f.write(\n.join(yolo_lines)) # 用前确保 classes 列表和数据集 classes.txt 完全一致 classes [Cardamom, Cumin, Neem, Turmeric, Holy Basil, Fenugreek, Pepper, Ginger] voc_to_yolo(a.xml, labels, classes)VOC 坐标是左上角和右下角的绝对像素值转换时要先算中心点再归一化注意x_center (x1 x2) / 2 / img_w的运算顺序——先加后除别把括号丢了。还要注意 VOC 里可能有difficult标记的目标这种目标应该跳过不转否则会当作正常目标参与训练拉低精度。5.3 迁移到 YOLOv8目录不用改yaml 和训练命令要动YOLOv8 的数据组织方式继承了 YOLOv5 的约定images 和 labels 平行结构不变所以这份数据集给 YOLOv8 用不需要迁移目录。但 dataset.yaml 的写法不同YOLOv8 要求path指定数据集根目录train和val写相对路径另外 YOLOv8 没有--cache参数改成了cacheTrue训练命令也变成了yolo train。# yolov8 用的 dataset.yaml path: /absolute/path/to/datasets train: images/train val: images/val test: images/test nc: 8 names: [Cardamom, Cumin, Neem, Turmeric, Holy Basil, Fenugreek, Pepper, Ginger] # 训练命令注意 --weights 改成了 model 参数 yolo train modelyolov8s.pt datadataset.yaml epochs100 imgsz640 batch16YOLOv8 和 YOLOv5 的标注格式完全兼容不需要转换。如果你之前用 YOLOv5 跑过这个数据集换到 YOLOv8 时可以直接沿用 classes 顺序和 yaml 里的 names只要调整一下 yaml 结构就行。一个细节YOLOv8 的默认类别名不包含背景类nc直接设 8别多写一个 background否则训练时类别索引全部错位。6. 模型训完怎么验证从 mAP 到可视化结果的一整套确认流程训练结束后不要直接看 loss 曲线就说“好了”完整的验证流程应该是跑一遍测试集推理 → 输出 mAP 和混淆矩阵 → 可视化检测结果 → 对照标注框定位系统性错误。先跑验证命令数据集的 test 目录在 yaml 里配好之后直接指定--task testpython val.py \ --data dataset.yaml \ --weights runs/train/exp/weights/best.pt \ --img 640 \ --task test \ --conf-thres 0.1 \ --iou-thres 0.5注意这里的--conf-thres 0.1只影响验证时的置信度阈值不是最终部署阈值。验证结果里重点看两个东西mAP0.5 和 mAP0.5:0.95。前者代表定位大致准确的比例后者对框的精确度要求更高。如果 mAP0.5 很高但 mAP0.5:0.95 偏低说明框打得不紧——中草药叶片边缘不规则标注框和真实轮廓缝隙大的话这个差距会很明显。然后是关键的定性验证把测试集的图片随机抽出来跑 detect.py保存可视化结果。这一步看的是模型在未知数据上的实际表现比任何指标都直接。python detect.py \ --weights runs/train/exp/weights/best.pt \ --source datasets/images/test/ \ --conf-thres 0.3 \ --save-txt检测结果存在runs/detect/exp里把图导出来浏览一遍重点看两个场景一是框的置信度是否虚高模型对纹理类似但没有目标的位置打出 0.8 以上的误检二是多个目标重叠时框是否互相吞并。如果发现误检集中发生在某个类别回到数据集里看这个类别的样本量和标注一致性通常就是标注框质量差或样本不够的问题。我从这次中草药数据集训练里养成的习惯是任何数据集到手先可视化 30 张图、核对类别文件顺序、确认 train/val/test 数量然后才允许自己启动训练命令。这套流程每次训练固定走一遍不跳过也不简化——在那之后我几乎没再遇到过训练中途才发现数据问题的糟心事。希望这份拆解对你有帮助省掉你排查数据集的时间把精力花在真正难啃的模型调优上。本文还有配套的精品资源点击获取
网站建设高端定制企业官网