简介:这套数据集面向YOLO目标检测任务,专门提供中草药图像标注样本,已按YOLOv5所需的datasets目录结构划分训练、验证与测试集,可直接进入模型训练流程,适合目标检测学习者、算法调试人员以及需要现成中医药视觉样本的开发者使用。资源共2000个文件,其中1999个为txt标注文件,另有1个Python可视化脚本,压缩包整体约209.51MB;txt文件记录类别与归一化后的边界框坐标(x_centre、y_centre、w、h),与YOLOv5默认要求一致,清晰规范便于解析。当前已有1019人学习下载,数据配套工具也经过实际验证。数据覆盖Cardamom、Cumin、Neem等8个中草药类别,训练集约2600张、验证集约190张、测试集约100张,图片与标签一一对应;自带的show脚本可随机读取图片并绘制边界框、将结果保存到当前目录,无需修改即可运行,能帮助使用者快速检查标注质量、无缝衔接YOLO系列模型的训练与评估环节。
1. 中草药图像检测,最需要的不是模型而是能用的数据集
想做中草药识别的开发者,多半会先撞上同一堵墙:模型随便换,数据集没法抄。YOLO 的通用预训练权重里没有“金银花、连翘、薄荷”这些类,想落地一个草药识别功能,最耗时间的不是训练,而是采集、标注、划分、排查标注错误这一整条数据集链路。这个标题给的是一个已经处理好的包:划分好的数据集、类别 class 文件和数据可视化脚本,拆开看就是三件事——标注长什么样、训练配置怎么写、训练前怎么检查数据。下面按这条链路往下走,适合正在做中药饮片识别、药材分拣或智能药柜的开发者,也适合第一次接触 YOLO 数据集、不想在数据准备上反复返工的新手。
2. 数据集里到底有什么:目录结构、标注格式与划分逻辑
拿到手先别急着训练。先搞清楚这个数据集的目录结构、标注格式和划分方式,这三样东西决定了你后续所有的踩坑方向。一个合格的数据集包通常有根目录,里面放着 images、labels、classes.txt(或 class 文件)和可视化脚本,部分包还会帮你把 train/val/test 三个集合的划分文件写好。
2.1 一张图看懂包内结构:images、labels 与 classes 各管什么
常见的目录结构如下,我用 tree 命令列出来:
herbal_yolo/ ├── images/ │ ├── train/ # 训练集图像 │ ├── val/ # 验证集图像 │ └── test/ # 测试集图像 ├── labels/ │ ├── train/ # 训练集标注 │ ├── val/ │ └── test/ ├── classes.txt # 类别文件,一行一个类名 ├── visualize.py # 数据可视化脚本 └── data.yaml # 部分包自带,没有就自己写images 和 labels 是成对存在的,同一张图的图像路径和标注路径除了扩展名以外必须完全一致。比如 images/train/gold_001.jpg 对应 labels/train/gold_001.txt,如果文件名对不上,训练时会直接跳过这张图,这类问题用可视化脚本最容易发现。
classes.txt 的行序就是 YOLO 的类别 ID,第 1 行对应 ID 0,第 2 行对应 ID 1,以此类推。labels 文件里每一行的第一列就是这个 ID,所以 class 文件一旦变动,前面所有标注都会错位。检查类别文件时建议逐行看一遍,确认没有重复类名、没有空行;空行会让后续读取出的类别列表多出一个空字符串,训练时不会报错,但预测结果会把类别名显示成空白。
判断配对是否完整,可以先做一步快速体检:
#!/bin/bash # 快速核对 images/labels 是否一一对应 for split in train val test; do img_count=$(ls images/$split/*.jpg 2>/dev/null | wc -l) lab_count=$(ls labels/$split/*.txt 2>/dev/null | wc -l) echo "$split: images=$img_count labels=$lab_count" done这段脚本分别统计三个划分集合下的图像数与标注文件数,两个数字通常应该一致。如果 labels 明显少于 images,优先去检查那些没有对应标注的图:可能是漏标、导出异常,也可能是空标注文件被放在了别处。空标注文件在训练里会被视为背景图,本身合理,但如果是误操作产生的,会让模型学出一些不必要的背景特征。
2.2 YOLO 标注格式的底层逻辑:归一化坐标与类别 ID
打开任意一个 labels 文件,每行是五个数字:类别 ID、x_center、y_center、width、height,全部是相对于图像宽高的归一化值。例如:
0 0.512345 0.678901 0.123456 0.098765这一行表示:目标类别 ID 是 0,目标中心位于图像横向 51.23%、纵向 67.89% 的位置,目标宽度占图像宽度的 12.35%,高度占图像高度的 9.88%。所有数值必须在 0 到 1 之间,如果标注框超出图像边界,训练时会出警告甚至影响损失计算,这也是可视化脚本要重点检查的内容。
为什么要归一化?因为 YOLO 的输入分辨率是固定的,比如 640×640,而不同来源的图片尺寸差异很大。归一化之后,标注不随图像缩放而改变,训练时可以随意做缩放、裁剪增强而不用重新计算坐标。你改 imgsz 的时候不需要动标注文件,这就是归一化带来的便利。
检查标注有没有越界,用一小段 Python 就能完成:
import os from pathlib import Path label_dir = Path("labels/train") bad_files = [] for txt in label_dir.glob("*.txt"): for line in txt.read_text().strip().splitlines(): parts = line.split() if len(parts) != 5: bad_files.append((txt.name, "格式错误", line)) continue _, x, y, w, h = map(float, parts[:5]) if not (0 <= x <= 1 and 0 <= y <= 1 and 0 <= w <= 1 and 0 <= h <= 1): bad_files.append((txt.name, "越界", line)) for file, err, line in bad_files[:20]: print(file, err, line)这段代码逐行解析每个标注文件,检查是否恰好五个字段,并判断中心点坐标与宽高是否都在合法区间。常见的越界原因是标注工具导出时勾选了“允许超出图像范围”,或者手工修改坐标时算错比例。越界框会让模型在训练时被迫去拟合一个“不存在”的目标区域,对收敛没有好处,尽早清掉。
2.3 划分数据集为什么是关键一步:避免验证集“虚高”
标题强调“划分好的数据集”,这件事在很多自采数据集里恰恰是最容易被忽略的。很多人把图片随机打乱后按 8:1:1 切就完事,结果同一株草药的多张不同角度照片被同时分到训练集和验证集,验证集 mAP 刷到 0.95,换到实地拍摄却掉到 0.6 以下。这个现象在草药场景特别明显,因为同一株植物的照片背景、光线非常相似,模型吃的其实是“记忆”不是“泛化”。
我一般会要求:划分至少要按“采集批次”或“植物个体”进行,即同一次采集、同一株个体的所有图片只能落在一个集合中。如果数据集已经划分好,先看一下 val 和 train 里有没有来自同一来源的图像,最简单的方法是看文件名前缀,很多采集者在命名时都带了批次号或个体号。
test 集常常被忽略,但它才是模型真实水平的裁判。很多教程只分 train 和 val,但对中草药这种环境差异极大的场景,强烈建议留一块 test 出来,哪怕只有几十张。这个集合里的图像训练和验证都接触不到,只在最后做一次评估,它反映的是模型在“没见过的环境”下的表现,而不是在熟悉样本上的复读。
3. 把 class 文件变成可训练配置:data.yaml 与训练参数
class 文件只是类别清单,训练时并不会直接读它,真正被加载的是 data.yaml。这个文件把 images、labels 的路径和类别列表串起来。标题里单独强调 class 文件,其实就是在提醒你:拿到 class 文件后,稍作改动就能拼出一份自己的 data.yaml。
3.1 从 class 文件到 data.yaml:路径、类别数与绝对路径陷阱
data.yaml 的标准格式如下:
path: /home/user/herbal_yolo # 改成你自己的数据集根目录 train: images/train val: images/val test: images/test nc: 10 names: 0: 金银花 1: 连翘 2: 薄荷 3: 枸杞 4: 黄芪 5: 当归 6: 甘草 7: 三七 8: 丹参 9: 陈皮几个细节值得强调。首先,train、val、test 推荐写成相对于 path 的路径,而不是绝对路径;其次,names 的索引顺序必须和 classes.txt 一致,一个常见的低级错误是 classes.txt 里第 1 行是“金银花”,但 data.yaml 里 0 写成了“连翘”,训练时不会报错,但预测结果全部错位。nc 可以省略,框架会自动数 names 的个数;但如果 class 文件里混入空行,读取出的类别列表可能多出空字符串,此时 nc 自动加 1,类别数就对不上了。
绝对路径陷阱也很常见。如果数据集是在某台 Windows 机器上整理的,data.yaml 里可能留着盘符路径,换到 Linux 环境就会报“路径不存在”。正确做法是把根目录独立写在 path 字段,其余全部用相对路径。我习惯在每个新环境跑训练命令前先执行一次路径检查:
import yaml from pathlib import Path data = yaml.safe_load(open("data.yaml")) root = Path(data["path"]) for split in ["train", "val", "test"]: p = root / data[split] print(split, p, "存在" if p.exists() else "不存在")读入 data.yaml,把 path 和相对子目录拼接成完整路径,逐个检查存在性。这一步能挡掉绝大多数“目录找不到”的报错。yaml 解析后的字段名要和文件里完全一致,大小写、拼写都不能差,否则输出会是 None。
3.2 训练命令与参数解读:一次落地配置
数据集和配置就绪后,训练命令可以这样写:
yolo detect train \ data=data.yaml \ model=yolov8n.pt \ epochs=150 \ imgsz=640 \ batch=16 \ device=0 \ workers=4 \ cache=Truedata 指定配置文件,model 用预训练权重做迁移学习,epochs 在数据集规模不大时建议给到 150 到 200,imgsz=640 是平衡点,batch 受显存限制,device 指定显卡编号,workers 控制数据加载线程数,cache=True 把图像缓存进内存、减少磁盘读取等待。
参数怎么调,按场景来:
| 参数 | 推荐值 | 适用说明 |
|---|---|---|
| epochs | 150~300 | 数据集小就 150,val 损失还在降就往上加 |
| imgsz | 640 / 960 | 小目标多就 960,常规场景 640 够用 |
| batch | 16 / 8 | 显存不够先减 batch,不建议低于 4 |
| device | 0 | 多卡可写 0,1,CPU 训练不推荐 |
| cache | True | 首次训练省时间,内存紧张就关掉 |
train 和 val 损失都是每轮输出一次,重点看 val/box_loss 是否还在走低。如果 mAP50 已经到 0.9 但 mAP50-95 只有 0.5,说明检测框位置不够准,问题往往出在标注框质量或 NMS 参数上,而不是数据量不够。mAP50 反映的是“框得差不多就算对”的松指标,mAP50-95 对框的精确度更敏感,后者才是调优阶段的主要参考。
3.3 模型选型:n/s/m 怎么选,为什么中草药场景先试小模型
YOLO 系列同一代模型按规模分为 n、s、m、l、x 几档。中草药图像检测有个特点:目标数量不多、单类目标尺寸中等,但类别间相似度高。真正难的不是“找得到”而是“分得清”,模型容量大确实有助于学更细的纹理差异,但对数据量的要求也更高。如果数据集只有几百到两三千张图,建议先上 n 或 s 档,因为过拟合带来的问题远比模型容量不足更麻烦。
怎么选?第一轮用 n 档跑通全流程,确认数据没问题、指标在合理区间后,再换 s 或者 m 做第二轮。直接上最大的 x 档,在这个场景里很少能一次成功:数据质量还没验证就引入最大的模型变量,一旦指标不理想,根本分不清是数据问题还是模型容量问题。
小模型还有个实际好处:中草药识别类应用很多要跑在移动端或边缘设备上,换成 n 档部署,推理速度能快出好几倍,而精度损失通常在可接受范围内。我的建议是先跑 n 档拿到一个“数据质量基线”,再决定要不要为了几个点的 mAP 换更大的模型。
4. 可视化脚本的正确用法:标注检查与脏数据排查
标题里特意放了“数据可视化脚本”,这是很多人容易忽略的价值点。可视化脚本不是用来发配图的,而是用来在训练前把数据集的脏东西挑出来。中草药图像采集环境复杂,背景里经常有相似叶片、手指、桌面纹理,标注错误率比常规目标检测数据集高不少,跑一遍可视化是性价比最高的体检。
4.1 标注叠加绘制:30 秒内发现漏标、错标和越界框
先把标注框原样画到图上,这是最基础也最有效的一步。以下脚本读取图像和同名标注文件,绘制矩形框与类别名称:
import cv2 from pathlib import Path img_dir = Path("images/train") label_dir = Path("labels/train") class_names = Path("classes.txt").read_text().splitlines() save_dir = Path("visual_check") save_dir.mkdir(exist_ok=True) for img_path in img_dir.glob("*.jpg"): txt_path = label_dir / (img_path.stem + ".txt") if not txt_path.exists(): print("缺标注:", img_path.name) continue img = cv2.imread(str(img_path)) h, w = img.shape[:2] for line in txt_path.read_text().strip().splitlines(): cid, x, y, bw, bh = line.split() cid = int(cid) x, y, bw, bh = map(float, (x, y, bw, bh)) # 还原为像素坐标 x1 = int((x - bw / 2) * w) y1 = int((y - bh / 2) * h) x2 = int((x + bw / 2) * w) y2 = int((y + bh / 2) * h) color = (0, 255, 0) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, class_names[cid], (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) cv2.imwrite(str(save_dir / img_path.name), img) print("可视化结果已保存到", save_dir)这段脚本做四件事:检查图像是否有对应标注文件、按归一化坐标反算像素框、在图上画矩形和类名、批量保存到新目录。画完之后翻一遍 visual_check 目录,重点看三类问题:框是否包住完整目标、框是否明显偏大偏小、类别名是否与目标一致。几百张图翻完大概十几分钟,但这十几分钟能省下后面好几轮无效训练。
参数说明:cv2.rectangle 的线宽设为 2,分辨率特别高的图可能看不清,可以改成 3 或 4;字体尺寸 fontScale 也可以按图像实际尺寸等比调整。color 用的是 BGR 的绿色,如果想按类别区分颜色,可以把 color 换成类别调色板,看到同一目标区域出现两种框色时,往往就是漏标和错标同时存在的信号。
4.2 目标尺寸分布统计:判断你的场景是不是“小目标地狱”
中草药检测一个高频问题是叶片目标太小,一张 6000×4000 的原始图里,单片叶子可能只占几百像素,缩到 640 之后几乎成噪点。可视化脚本里最好带一个目标尺寸分布统计,输出每个目标相对图像的宽高占比分布:
import numpy as np from pathlib import Path label_dir = Path("labels/train") areas = [] wh_ratios = [] for txt in label_dir.glob("*.txt"): for line in txt.read_text().strip().splitlines(): _, x, y, w, h = map(float, line.split()) areas.append(w * h) wh_ratios.append(w / h) areas = np.array(areas) # 小目标判定:面积占比小于 0.01 x 0.01 small_ratio = (areas < 0.0001).mean() print(f"样本数: {len(areas)}") print(f"目标面积占比 中位数: {np.median(areas):.5f}") print(f"小于 1% x 1% 的小目标占比: {small_ratio*100:.1f}%")逻辑是:目标面积占比(w×h)反映目标在整幅图中的视觉大小。若中位数低于 0.001,说明一半以上的目标占图面积不到千分之一,按 640 输入算只有 20×20 像素左右,属于典型小目标场景。此时第一轮训练用 640 很可能效果很差,需要切图或提高输入分辨率。
small_ratio 的阈值 0.0001 对应 100×100 像素的目标在 10000×10000 全图里的比例,你可以根据实际图片分辨率调整。wh_ratios 分布也值得看:如果绝大多数框的宽高比集中在一个区间,说明目标形态比较固定,后续调 anchor 时应该优先照顾这个区间;如果宽高比特别分散,反而说明标注框有大量无效区域。
如果小目标占比超过三成,常见做法是先按 50% 重叠率把原图切成 640×640 的小块,再对每块单独做标注转换。切图会成倍增加样本数,但要注意块与块之间不能被切断太多,目标被切断面积超过一半的直接丢弃,否则模型学到的是各种残缺叶片,反而干扰收敛。
4.3 类别样本量统计:提前知道哪些类会训练崩
可视化脚本的第三项功能是统计各类别样本量,这一步看起来简单,却是判断类别不平衡最直接的依据:
from pathlib import Path from collections import Counter label_dir = Path("labels/train") counter = Counter() for txt in label_dir.glob("*.txt"): for line in txt.read_text().strip().splitlines(): cid = int(line.split()[0]) counter[cid] += 1 class_names = Path("classes.txt").read_text().splitlines() for cid in sorted(counter): print(f"{class_names[cid]}: {counter[cid]}")统计结果如果出现“金银花 3000 个框、当归 80 个框”这种数量级差异,训练时模型会严重偏向多样本类别,少数类的 mAP 往往在 0.2 以下。应对方式有三条:第一,对少数类做过采样,把它的图像复制进训练集或重复参与每个 epoch;第二,增加增强强度,尤其是对少数类做随机旋转、平移、尺度扰动;第三,调整损失权重或改成按类别采样。最不推荐的做法是直接删多数类的样本——中草药识别本身就缺数据,删数据等于自断一臂。
5. 中草药检测的 4 个必踩坑:从验证集虚高到类别崩坏
这一章要写的几条,全是拿训练时长换来的血泪经验。标题里的数据包和脚本能帮你解决一部分,但有几个坑是工具之外、依赖意识和习惯的,单独列出来。
5.1 验证集 mAP 高达 0.95,实拍却全部漏检——划分泄漏
现象:训练过程非常顺利,val mAP50 一路爬到 0.95,但拿着训练好的权重去拍了一张真实场景照片,模型什么都没检测出来。
原因:数据集划分时没有按采集批次隔离。同一株草药的几十张不同角度照片被随机分到了 train 和 val,模型在验证时看到的是“见过的个体”,分数自然虚高;真实场景里的植株、花期、光照全变了,模型立刻露馅。
解决:重新划分数据集,按文件名前缀、采集日期或采集地点分组,确保同一个体的照片只出现在一个集合中。如果数据包已经划分好,就抽 20% 的 val 图片和 train 做肉眼对比,看是否有相似来源。还有一种更隐蔽的情况:数据增强版本泄漏,同一张原图切出来的重叠块被同时分到两个集合,这种情况要用文件去重把完全相同的图像剔除。
5.2 叶片太小,模型学到的是“纹理噪声”——小目标失效
现象:训练收敛正常,mAP50 有 0.7 左右,但可视化预测结果时发现,模型总是把叶片局部纹理误检成目标,大叶片也只在边缘处画框。
原因:输入分辨率 640 时,原始大图里的小叶片缩成了十几个像素的色块,模型没有足够像素去区分“真叶片”和“背景纹理”,于是开始学习局部高频特征,产生大量误检。这时候如果你急着调模特参数,大概率会翻车,因为问题根本不在模型侧。
解决:先跑 4.2 的尺寸统计确认小目标占比,然后做切图(50% 重叠率),同时把 imgsz 提到 960。如果条件允许,把训练数据里的原图统一缩放到固定短边后再标注,避免同一批数据里既有 6000 像素大图又有 800 像素小图,尺度差异过大会让模型学得很分裂。
5.3 相似叶片互相误判——形态近似导致类别边界模糊
现象:薄荷和荆芥、金银花和连翘这类形态接近的类别,混淆矩阵里互相误判的比例特别高,单独看每一类的 mAP 还可以,但实际输出经常张冠李戴。
原因:中草药很多是近缘植物,叶片形状、纹理差异极小,而数据集里拍摄角度大多为俯拍叶片,模型缺少能区分的判别特征。这个坑最容易在数据增强上翻车:过强的旋转、缩放会让本来就难分的叶片变得更像。
解决:针对易混类别补充侧视、花期、干制品等不同状态的正样本,让模型学到更多判别特征。做这类项目时,对易混类别额外采集“局部特写 + 全株环境”两种尺度,相当于人为拓宽类内多样性,比单纯增加数量更有效。训练参数上,易混场景建议关掉或降低 mixup 强度,因为它会把两张不同类别的图叠在一起,加剧边界模糊。
5.4 某一类 mAP 始终塌陷——类别不平衡与标注质量双重问题
现象:训练多次,其他类 mAP50 都能到 0.85 以上,唯独某一类始终在 0.3 附近徘徊,增加 epoch 也没有改善。
原因:先看 4.3 的类别统计,这类样本量往往极少;再翻可视化输出,会发现这类目标存在大量框偏大或框偏小的问题,标注不一致让模型无所适从。中草药标注里常见的“习惯性框大”很隐蔽,人眼看不出来,但模型对框的回归目标非常敏感。
解决:先修标注再做平衡。把该类的所有标注框重新检查一遍,删除明显错误的框,再做过采样或复制粘贴增强,让该类的有效样本量提上来。如果标注框质量太差,修完标注的收益远大于堆数据,不要急着先扩数据。跑第二轮训练时,单独打印这个类别的混淆矩阵,确认误判主要是和哪个类混在一起,再有针对性地补样本。
6. 落回真实场景:用 best.pt 做单图验证与阈值调优
训练完成后,跑一次单图预测是验证“这个数据集到底行不行”的最终标准。别把训练过程当黑匣子,验证这一步会给整个链路一个明确的交代。
6.1 单图预测命令与输出解析
yolo detect predict \ model=runs/detect/train/weights/best.pt \ source=test_real.jpg \ conf=0.25 \ iou=0.45 \ save=Truepredict 子命令会输出标注框、类别名和置信度。关键参数是 conf 和 iou:conf 是置信度阈值,调低会有更多误检漏出,调高会丢掉低置信度的正确目标;iou 是 NMS 的 IoU 阈值,调低让重叠框更少,调高让密集目标更容易保留。中草药场景如果目标密集,比如一丛薄荷,iou 可以适当放宽到 0.5 以上,但要接受误检增加。
6.2 一套适合中草药场景的阈值调整方法
我的习惯是先以 conf=0.25、iou=0.45 跑一遍,看误检与漏检的比例:误检多,调高 conf 到 0.35 或 0.4;漏检多,先别急着调低 conf,回头检查是不是 5.2 的小目标问题。验证时不要只挑拍得好的图,专门挑几张光照差、背景杂的图来“找茬”,模型在这几张图上的表现才接近真实水平。
我自己做中草药检测项目时,每个数据集包到手,第一件事永远是跑可视化脚本而不是训练,这个习惯帮我避开好几次返工。数据集可以越扩越大,但脏数据只会越攒越乱,前期多花一小时检查,后期就能少熬几个通宵。希望帮到你。
本文还有配套的精品资源,点击获取