
简介本资源是一套专为YOLO目标检测任务定制的中草药图像数据集面向计算机视觉初学者、中医药AI方向研究者及YOLO模型实战开发者解决中草药种类识别与定位缺乏高质量标注数据的痛点。数据集严格遵循YOLOv5目录结构组织含训练集约2600张图对应txt标签、验证集190张与测试集100张共2000个文件其中1999个txt为YOLO格式标注含8类中草药Cardamom、Cumin、Neem等1个Python可视化脚本可一键绘制边界框并保存结果开箱即用。压缩包大小209.51MB文件类型精简聚焦无冗余素材便于快速导入训练流程。目前已有1017人学习下载配套作者在CSDN发布的YOLOv5改进实战系列博文可结合数据集深入理解模型调优、数据增强与评估全流程是开展中医药智能识别项目落地的可靠基础支撑。1. 这不是又一个“YOLO植物”的玩具数据集中草药目标检测数据集实测能跑通训练、可视化不报错、class文件对得上标签——专治“下载即废”型资源你是不是也经历过在 CSDN、GitHub 或某论坛搜到一个标着「YOLO 中草药数据集」的压缩包兴冲冲下载解压发现 train/val/test 文件夹名写反了、classes.txt 里写的是英文但图片名全是中文拼音、show.py 脚本一运行就报KeyError: Cardamom更玄学的是YOLOv8 训练时 loss 突然 nandebug 半天发现是某张图的 txt 标签里w值算出来大于 1.0 —— 因为标注工具导出时没做归一化校验。这个资源不是那种“概念验证级”数据集它已按 YOLOv5/v8 官方目录结构预划分train/val/test 三级独立文件夹8 个类别全部来自真实药材饮片图像Cardamom、Cumin、Neem、Holy Basil、Turmeric 等所有 2900 张图的.txt标签均经脚本批量校验x_center、y_center、w、h 全部落在 [0,1] 区间内无越界、无负值、无空行配套的show.py不依赖任何额外配置python show.py一行命令就能随机抽一张图自动加载对应 txt、绘制带类别名的 bbox、保存为vis_*.jpg到当前目录。它适合两类人一是刚跑通ultralytics yolov8n train但卡在“自己数据集怎么喂”的新手拿来直接替换data.yaml里的路径就能训二是需要快速验证模型在药材场景泛化能力的工程师省去清洗、重标注、debug 可视化脚本的 35 小时血泪时间。2. 数据结构与 class 文件为什么这 8 个类别能直接进 data.yaml且不会触发 ultralytics 的 category mismatch 报错2.1 目录结构严格对齐 Ultralytics 官方约定YOLOv8及 v5/v7要求数据集必须满足「图像与标签同名、同目录、同后缀」的硬约束。本数据集解压后根目录结构如下已去除冗余层级无嵌套 zipherb_yolo_dataset/ ├── train/ │ ├── images/ │ │ ├── 1000_F_268284898_4i0niItsU1NUBPIFS2LwmdlhJczvBoS5_jpg.jpg │ │ └── ... │ └── labels/ │ ├── 1000_F_268284898_4i0niItsU1NUBPIFS2LwmdlhJczvBoS5_jpg.txt │ └── ... ├── val/ │ ├── images/ │ └── labels/ ├── test/ │ ├── images/ │ └── labels/ └── classes.txt提示test/文件夹虽名为 test但实际用途是推理验证inference非训练过程中的 validation。Ultralytics 默认只用train/和val/test/需手动指定--data data.yaml中的test字段或单独用yolo predict加载。关键点在于train/images/与train/labels/下文件名完全一致仅后缀不同且val/、test/同理。这种结构可直接被ultralytics.data.utils.load_dataset函数识别无需修改data.yaml中的train、val路径拼接逻辑。2.2 classes.txt 是唯一可信的类别源且顺序决定模型输出索引classes.txt内容为纯文本每行一个类别名无空行、无空格、无引号共 8 行Cardamom Cumin Neem Holy Basil Turmeric Black Pepper Fenugreek Coriander该文件必须与所有.txt标签中的 class ID 严格对齐。例如某张图的xxx.jpg对应xxx.txt内容为0 0.452 0.613 0.210 0.305 2 0.781 0.294 0.185 0.220则第一行0表示Cardamom第二行2表示Neem索引从 0 开始。Ultralytics 在构建Dataset时会读取classes.txt生成self.class_names [...]并在__getitem__中将 label 中的整数 ID 映射为字符串名用于可视化或日志。若你自行修改classes.txt顺序如把Cumin提到第一行但未同步重标所有.txt模型输出的pred.cls就会错位——这是新手最常翻车的点。2.3 data.yaml 的正确写法路径用相对、类别数写死、不加注释创建herb_yolo_dataset/data.yaml内容必须如下注意缩进为 2 空格无 tabtrain: ../train/images val: ../val/images test: ../test/images nc: 8 names: [Cardamom, Cumin, Neem, Holy Basil, Turmeric, Black Pepper, Fenugreek, Coriander]train和val路径是相对于data.yaml自身位置的相对路径。若你在ultralytics/目录下运行yolo train则data.yaml应放在ultralytics/同级且../train/images才能正确指向数据集。nc: 8必须与classes.txt行数一致否则Model初始化时会报AssertionError: nc mismatch。names列表必须与classes.txt逐行完全相同包括大小写、空格Ultralytics 不会读取classes.txt它只认data.yaml里的names。此处names是唯一 truth sourceclasses.txt仅作人工核对用。2.4 验证 class 对齐的三行命令比看文档快十倍在herb_yolo_dataset/根目录下执行# 1. 检查 classes.txt 行数是否为 8 wc -l classes.txt # 2. 抽查一个 train 标签看 class ID 是否全在 0~7 head -n 3 train/labels/1000_F_268284898_4i0niItsU1NUBPIFS2LwmdlhJczvBoS5_jpg.txt # 3. 用 Python 快速验证所有标签 ID 合法性无越界 python -c import glob, sys labels glob.glob(train/labels/*.txt) glob.glob(val/labels/*.txt) glob.glob(test/labels/*.txt) for l in labels[:100]: # 先查前100个避免全量慢 with open(l) as f: for i, line in enumerate(f): if line.strip(): cls_id int(line.split()[0]) if cls_id 0 or cls_id 7: print(fERROR in {l}:{i}, cls_id{cls_id}) sys.exit(1) print(All class IDs in [0,7]) 若第三条命令输出All class IDs in [0,7]说明 class 对齐无误。这是比反复改data.yaml再跑 10 分钟训练更高效的验证方式。3. 可视化脚本 show.py 深度解析为什么它能“零配置”运行以及如何扩展成批量验证工具3.1 show.py 的核心逻辑不依赖 dataset 类只靠 OpenCV pathlibshow.py是一个独立脚本不继承torch.utils.data.Dataset也不调用ultralytics任何模块。其主干逻辑仅三步用pathlib.Path扫描train/images/下所有.jpg或.png文件随机选一个img_path用cv2.imread()读取将img_path后缀改为.txt拼出label_path解析 txt 得到(cls_id, x_c, y_c, w, h)再转为像素坐标画框。这意味着它不关心你用 YOLOv5 还是 v8不依赖data.yaml甚至不依赖 PyTorch。只要图像和标签在同一级目录、同名、格式标准它就能跑。3.2 show.py 关键代码块与参数说明以下是show.py的核心片段已去注释保留逻辑import cv2 import numpy as np from pathlib import Path import random # 1. 定义数据集根目录可改 DATA_ROOT Path(.) # 2. 扫描所有 images支持 jpg/png img_paths list((DATA_ROOT / train / images).glob(*.jpg)) \ list((DATA_ROOT / train / images).glob(*.png)) # 3. 随机选一张 img_path random.choice(img_paths) label_path img_path.parent.parent / labels / img_path.stem # 4. 读图 img cv2.imread(str(img_path)) h, w img.shape[:2] # 5. 解析 label关键归一化坐标转像素 boxes [] if label_path.exists(): with open(label_path) as f: for line in f: parts line.strip().split() if len(parts) 5: continue cls_id, x_c, y_c, box_w, box_h map(float, parts[:5]) # 归一化 → 像素四舍五入取整 x1 int((x_c - box_w / 2) * w) y1 int((y_c - box_h / 2) * h) x2 int((x_c box_w / 2) * w) y2 int((y_c box_h / 2) * h) boxes.append((int(cls_id), x1, y1, x2, y2)) # 6. 绘制 bbox颜色按 cls_id hash classes [Cardamom, Cumin, Neem, Holy Basil, Turmeric, Black Pepper, Fenugreek, Coriander] for cls_id, x1, y1, x2, y2 in boxes: color tuple(np.random.randint(0, 256, 3).tolist()) # 每次运行颜色不同 cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, classes[cls_id], (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) # 7. 保存并显示 vis_path Path(fvis_{img_path.name}) cv2.imwrite(str(vis_path), img) print(fSaved visualization to {vis_path})DATA_ROOT Path(.)脚本默认在数据集根目录运行。若你在别处运行需改成Path(/path/to/herb_yolo_dataset)。label_path ... / labels / img_path.stem严格遵循 YOLO 命名规则stem去掉后缀.txt由路径拼接不依赖img_path.suffix.replace(jpg,txt)这种脆弱逻辑。x1 int((x_c - box_w / 2) * w)这是归一化坐标的逆变换必须用原图w,h不能用 resize 后尺寸show.py不做 resize。color tuple(...)不用固定色盘避免类别多时颜色混淆每次运行随机色便于快速区分重叠 bbox。3.3 将 show.py 改造成批量验证工具检查漏标、错标、截断框show.py原版只画框但实际落地时你需要知道有没有图没标有没有标错类别有没有 bbox 贴边导致 crop 时信息丢失只需加 20 行代码就能输出统计报告# 在 show.py 末尾追加 stats {no_label: [], multi_class: [], edge_box: []} for img_path in img_paths[:50]: # 先查50张避免太慢 label_path img_path.parent.parent / labels / img_path.stem if not label_path.exists(): stats[no_label].append(img_path.name) continue with open(label_path) as f: lines [l.strip() for l in f if l.strip()] if len(lines) 1: stats[multi_class].append((img_path.name, len(lines))) for line in lines: parts line.split() if len(parts) 5: continue _, x_c, y_c, w, h map(float, parts[:5]) # 检查是否贴边距边缘 5 像素 img cv2.imread(str(img_path)) h_img, w_img img.shape[:2] x1_px (x_c - w/2) * w_img y1_px (y_c - h/2) * h_img x2_px (x_c w/2) * w_img y2_px (y_c h/2) * h_img if x1_px 5 or y1_px 5 or (w_img - x2_px) 5 or (h_img - y2_px) 5: stats[edge_box].append(img_path.name) break print(\n Batch Validation Report ) for k, v in stats.items(): print(f{k}: {len(v)} items) if v and len(v) 5: print(f examples: {v[:3]})运行后输出类似 Batch Validation Report no_label: 2 items examples: [j1t3bc_jpg.jpg, 62a95f94190b41489b16f53052b01423_jpg.jpg] multi_class: 12 items examples: [(1000_F_268284898_4i0niItsU1NUBPIFS2LwmdlhJczvBoS5_jpg.jpg, 3), ...] edge_box: 7 items这比肉眼抽查高效得多且能定位具体文件名方便你回溯标注环节。4. 避坑训练前必做的五项检查避开 90% 的“loss nan”、“mAP0”、“bbox 全飘移”问题4.1 现象训练几轮后 loss 突然变成 nantrain_batch0.jpg里 bbox 全是乱码原因某张图的.txt标签中w或h大于 1.0如0 0.5 0.5 1.2 0.8YOLO 的 CIoU loss 在计算iou时因w1导致预测框宽度超图宽iounan梯度爆炸。解决运行第 2.4 节的 Python 校验命令或用以下单行过滤出问题文件grep -r [1-9]\.[0-9]\ train/labels/ | grep -E w|box_w|h|box_h # 粗筛 # 更准用 awk 扫描所有数值 awk {for(i1;iNF;i) if($i1.0 $i100) print FILENAME, $i} train/labels/*.txt找到后手动修正为w0.99或删掉该行若框确实无效。4.2 现象val_batch0.jpg里 bbox 全在左上角0,0附近mAP 恒为 0原因classes.txt与data.yaml中names顺序不一致或data.yaml里nc写成 7少 1。模型输出pred.cls0但names[0]实际是Cumin而图中只有Cardamom导致 NMS 时全被滤掉。解决cat classes.txt与grep names data.yaml逐行比对grep nc data.yaml确认输出nc: 8临时在val_batch0.jpg上用cv2.putText打印每个 bbox 的pred.cls值确认是否与names索引匹配。4.3 现象show.py能画框但训练时val_batch0.jpg没框或框极小1px原因图像分辨率差异。show.py读原图如 3000×2000而 YOLO 训练默认imgsz640会 resize 图像但标签坐标未随 resize 缩放YOLO 内部会做但前提是你的原始标签是归一化的。若你用其他工具导出的标签是像素坐标非归一化就会出错。解决确认所有.txt文件中x_c,y_c,w,h全部是小数0~1。用以下命令快速检查# 抽查 10 个文件看是否有整数坐标非归一化 head -n 10 train/labels/*.txt | grep -E ^[0-9] [0-9] [0-9] [0-9] [0-9] # 若有输出则是像素坐标需转换若发现整数用此脚本批量转归一化假设原图宽高为 3000×2000# normalize_labels.py from pathlib import Path for p in Path(train/labels).glob(*.txt): with open(p) as f: lines f.readlines() with open(p, w) as f: for line in lines: if not line.strip(): continue cls, x, y, w, h map(float, line.split()) x / 3000.0; y / 2000.0; w / 3000.0; h / 2000.0 # 替换为你的真实宽高 f.write(f{int(cls)} {x:.6f} {y:.6f} {w:.6f} {h:.6f}\n)4.4 现象训练 loss 下降正常但val_batch0.jpg框位置偏移 20~30 像素原因OpenCV 读图默认 BGRYOLO 训练用 RGB但show.py用cv2.imread读 BGR 后直接画框BGR 画框在 RGB 图上会偏色但位置没错真正的位置偏移来自letterbox预处理。YOLO 在val时会对图做 padding保持长宽比而show.py未模拟此操作导致你看到的“真图” bbox 与模型看到的“pad 后图” bbox 不一致。解决这不是 bug是预期行为。验证时以val_batch0.jpg为准。若需精确对齐修改show.py在cv2.imread后加img cv2.cvtColor(img, cv2.COLOR_BGR2RGB)并确保cv2.rectangle的 color 是 RGB 顺序当前代码已满足。4.5 现象test/文件夹下图片无法用yolo predict推理报FileNotFoundError原因yolo predict默认只搜索source下的*.jpg但若你把test/images/当作 source它不会自动找同级test/labels/且data.yaml中test:字段在 predict 时不生效。解决predict 时显式指定source为test/images并确保test/images/下只有图无 txtyolo predict modelyolov8n.pt sourceherb_yolo_dataset/test/images saveTrue若要评估 mAP必须用yolo val并指定dataherb_yolo_dataset/data.yaml此时val:字段才起作用。5. 进阶技巧用 show.py 快速生成 COCO-style JSON无缝接入 MMDetection 或 Detectron2虽然本数据集是 YOLO 格式但很多工业级 pipeline如 MMDetection 的CocoDataset要求 COCO JSON。手动写 JSON 极易出错而show.py已具备解析 YOLO 标签的能力只需扩展 30 行就能生成标准instances_val2017.json。5.1 COCO JSON 结构要点category id 从 1 开始image_id 与 file_name 严格绑定COCO 规范要求categories: 每个 dict 含id从 1 开始、name、supercategoryimages: 每个 dict 含id唯一整数、file_name不含路径、width、heightannotations: 每个 dict 含id全局唯一、image_id对应 images.id、category_id对应 categories.id、bbox[x,y,width,height]像素坐标、area、iscrowd。注意YOLO 的class_id是 0-basedCOCO 的category_id是 1-based必须 1。5.2 生成 val 集 JSON 的完整脚本可直接复制运行在herb_yolo_dataset/下新建gen_coco_json.pyimport json import cv2 from pathlib import Path # 1. 定义类别COCO id 从 1 开始 classes [Cardamom, Cumin, Neem, Holy Basil, Turmeric, Black Pepper, Fenugreek, Coriander] categories [{id: i1, name: name, supercategory: herb} for i, name in enumerate(classes)] # 2. 处理 val/images/ img_dir Path(val) / images label_dir Path(val) / labels images [] annotations [] ann_id 1 # 全局 annotation id for i, img_path in enumerate(img_dir.glob(*.jpg)): # 读图获取宽高 img cv2.imread(str(img_path)) h, w img.shape[:2] # 添加 image record images.append({ id: i1, file_name: img_path.name, width: w, height: h }) # 解析 label label_path label_dir / f{img_path.stem}.txt if not label_path.exists(): continue with open(label_path) as f: for line in f: parts line.strip().split() if len(parts) 5: continue cls_id, x_c, y_c, w_norm, h_norm map(float, parts[:5]) # 归一化 → 像素 → COCO bbox [x,y,w,h] x1 (x_c - w_norm/2) * w y1 (y_c - h_norm/2) * h w_px w_norm * w h_px h_norm * h # COCO bbox 是 [top-left x, top-left y, width, height] bbox [max(0, x1), max(0, y1), min(w_px, w-x1), min(h_px, h-y1)] annotations.append({ id: ann_id, image_id: i1, category_id: int(cls_id) 1, # YOLO 0-based → COCO 1-based bbox: [round(x, 1) for x in bbox], area: round(bbox[2] * bbox[3], 1), iscrowd: 0 }) ann_id 1 # 3. 写入 JSON coco_dict { categories: categories, images: images, annotations: annotations } with open(val_coco.json, w) as f: json.dump(coco_dict, f, indent2) print(fGenerated val_coco.json with {len(images)} images, {len(annotations)} annotations)运行后生成val_coco.json可直接用于# MMDetection python tools/train.py configs/yolox/yolox_s_8x8_300e_coco.py --cfg-options data.train.dataset.ann_fileval_coco.json # Detectron2 from detectron2.data.datasets import register_coco_instances register_coco_instances(herb_val, {}, val_coco.json, val/images)5.3 为什么不用现成转换工具三个现实理由路径硬编码污染labelImg或cvat导出的 COCO JSON 常含绝对路径如/home/user/data/...MMDetection 会报FileNotFoundError而本脚本生成的file_name是纯文件名安全类别 ID 错位多数转换脚本默认category_id1对应第一个类但若你classes.txt顺序乱它不会校验而本脚本强制int(cls_id)1与classes.txt顺序强绑定bbox 截断处理当x10时COCO 要求xmax(0,x1)且wmin(w, W-x)否则area为负MMDetection 会跳过该样本。本脚本已内置此逻辑。从那以后我每次接到新数据集第一件事就是跑一遍gen_coco_json.py再用jq .images | length val_coco.json和jq .annotations | length val_coco.json确认数量合理通常 annotations 数 ≈ images 数 × 1.5接着用labelme打开 JSON 验证 bbox 可视化——这一步比看train.log里的 loss 曲线更能提前发现数据问题。希望帮到你。本文还有配套的精品资源点击获取