
简介猕猴桃检测数据集是一份面向目标检测任务的专业标注数据适合计算机视觉入门者与农业AI开发者用于训练猕猴桃识别模型。数据同时提供Pascal VOC格式的XML标注和YOLO格式的TXT标注覆盖1838张猕猴桃图像共包含2000个文件其中1838个XML标注文件与162个TXT文件压缩包整体约84MB文件命名规律便于脚本批量读取。类别仅有‘mihoutao’一种标注框总数达19278个全部由labelImg工具按矩形框规则勾画标注准确且合理适合单类目标检测场景的快速落地。已有722人学习下载使用时可省去格式转换和手工标注时间直接用于YOLO、SSD、Faster R-CNN等模型训练同时规范的目录结构和统一的标注规则也让这份数据成为学习构建检测数据集、开展数据增强或模型对比实验的实用参考对农业采摘机器人、水果分级系统等应用开发具有直接帮助。1. 这个单类猕猴桃检测数据集好在哪要快速验证 yolo目标检测流程真正卡人的不是训练命令而是找不到标注干净、格式可对比的数据。这个猕猴桃检测数据集把“画框”做完了1838张jpg图片配齐VOC格式xml和YOLO格式txt双套标注单类别mihoutao19278个矩形框平均每张图约10.5个目标是典型的高密度果实检测场景。做采摘机器人视觉或果园计数的工程师可以用它快速搭baseline第一次接触目标检测的学习者也能借这份数据把格式解析、数据划分、模型训练、指标验证整条链路走通。不管之后选YOLOv8、YOLO11还是换检测头这个体量都足够跑出一个有参考意义的结果。2. VOC与YOLO双格式的字段对齐与坐标换算2.1 解压、哈希校验与文件数核对这个数据集压成了7z包先别急着解压有几个动作比解压本身更重要。7z压缩文件的坑往往不是内容错而是传输过程中文件损坏直接解压出来的图可能缺一半。我拿到.7z文件后的习惯是先算哈希、再解压、最后对数。Linux下用7zip命令Windows下除了7-Zip图形界面还可以用PowerShell里的certutil -hashfile。sha256sum firc_mihoutao.7z 7z x firc_mihoutao.7z -o/home/user/data/kiwi -ysha256sum输出的64位十六进制字符串要和文件来源给出的哈希比对一致后再继续这一步可以排除“下载到一半文件已坏”这类问题。7z x表示解压-o指定输出根目录注意-o和目标路径之间不要留空格-y是遇到同名文件自动覆盖避免命令在交互提示处卡住。解压路径尽量用绝对路径相对路径在后续切换工作目录时容易引发标签加载错误。解压完成后先做一次文件数核对看三套文件是否数量一致find . -name *.jpg | wc -l find . -name *.xml | wc -l find . -name *.txt | wc -l三个数字都应该是1838这样才能说明图片、VOC标注、YOLO标注三套文件一一对应。像firc_mihoutao_18.jpg、firc_mihoutao_18.xml、firc_mihoutao_18.txt这样的同名文件后缀不同但主名必须一致。如果实际解压后txt数量少于jpg说明标注过程有漏存直接拿去训练会在数据集加载时静默丢图训练损失曲线会莫名抖动这种问题通常比模型结构更难排查。2.2 VOC格式xml解析与字段语义VOC格式的每张图对应一个xml文件里面按object节点存放每一个标注框。labelImg画矩形框导出的VOC标注文件里名称字段是mihoutao框坐标由xmin、ymin、xmax、ymax四个标签给出它们都是原图上的像素绝对值。一个典型的object节点内容大致如下object namemihoutao/name bndbox xmin101/xmin ymin27/ymin xmax290/xmax ymax222/ymax /bndbox /object这个例子里框的像素宽是xmax - xmin像素高是ymax - ymin对应原始图像中的实际目标区域。用xml.etree.ElementTree解析不需要额外安装依赖代码也比较直白import xml.etree.ElementTree as ET def parse_voc(xml_path): tree ET.parse(xml_path) root tree.getroot() boxes [] for obj in root.iter(object): bnd obj.find(bndbox) boxes.append({ name: obj.findtext(name), xmin: int(bnd.findtext(xmin)), ymin: int(bnd.findtext(ymin)), xmax: int(bnd.findtext(xmax)), ymax: int(bnd.findtext(ymax)) }) return boxesroot.iter(object)会递归找出所有object节点比findall更稳健因为不同版本的labelImg生成xml时嵌套层级不完全一致。数值转int前最好确认标签里没有空字符串否则int(None)会抛TypeError这种脏数据在后续校验脚本里要单独归类。解析结果里保留name字段是有意的后续校验类别名时直接比对即可。2.3 YOLO格式txt的归一化坐标与互转YOLO格式的标注文件和VOC最大的不同是坐标全部归一化。每个txt文件放多个框一行一个字段结构是类别id cx cy w h。例如0 0.4531 0.3244 0.2013 0.1598表示目标类别索引是0目标中心位于图片宽高的53.1%和32.4%处框宽约20.1%框高约16.0%。归一化的好处是标签与输入图片分辨率解耦不管训练时把图缩放到640还是1280标签都能直接用。从VOC格式转换到YOLO格式时容易写错的点是中心点公式。中心点不是直接拿(xminxmax)/2还要再除以图片宽高def voc_to_yolo(xml_path, txt_path, img_w, img_h): boxes parse_voc(xml_path) lines [] for b in boxes: cx (b[xmin] b[xmax]) / 2.0 / img_w cy (b[ymin] b[ymax]) / 2.0 / img_h bw (b[xmax] - b[xmin]) / img_w bh (b[ymax] - b[ymin]) / img_h lines.append(f0 {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) with open(txt_path, w, encodingutf-8) as f: f.write(\n.join(lines))这段函数里的img_w和img_h必须是原图尺寸不是训练时的输入尺寸。如果不确定可以用cv2.imread读图后取shape[1]和shape[0]。类别id这里固定写0因为这个数据集只有一个mihoutao类别如果后续自己合并了其他种类id要与模型yaml里的names顺序严格对应不然模型训练出来的类别名全部错位。VOC与YOLO两种格式的差异我常列成一张对照表给团队里新人看维度VOC xmlYOLO txt坐标基准原图像素绝对值归一化相对坐标框的表示方式xmin/ymin/xmax/ymaxcx/cy/w/h类别表示名称字符串整数索引常见存放目录annotations/labels/适用读取方标注工具、评测协议主流检测框架原生加载两种格式同时存在的最大价值是互相校准同一张图的xml和txt解析出来的框应当完全一致这也是下一章校验脚本的核心思路。3. 标注合规性校验、框分布统计与train/val划分脚本3.1 为什么对“别人标好的数据”还要再校验数据集页面写着“提供准确且合理标注”但真实的网络下载场景里这类描述只代表对方尽力了不代表没有脏数据。从网上下载的数据集常见三类问题xml和txt数量对不上、某个框坐标越界、图片本身损坏。这些问题单张很难发现却会在训练时表现为loss不下降或验证指标异常波动排查起来更耗时。尤其是 yolo目标检测流程 里数据加载出错往往不会直接中断训练而是跳过坏图看起来只是曲线抖动。单类别数据集的校验有一个特殊性不用处理类间不平衡但框的质量直接影响yolo损失函数。一个坐标越界的框在loss里会被模型当作大误差目标强行学习造成附近位置的预测也偏移。所以训练前把每个框的坐标合理性检查一遍比调模型参数更划算。3.2 图片、xml、txt三套文件的一致性检查我一般写一个脚本一次性完成检查不引入额外依赖opencv-python就足够。脚本逻辑分成三块图片能否正常读、txt每行能否解析成合法的归一化坐标、xml和txt是否一一存在。校验时不需要解析完整xml只查对应文件在不在因为标注框数值是否合理统一看txt就够。import cv2 from pathlib import Path img_dir Path(images) txt_dir Path(labels) xml_dir Path(annotations) errors [] for img_path in sorted(img_dir.glob(*.jpg)): img cv2.imread(str(img_path)) if img is None: errors.append(fbroken image: {img_path.name}) continue txt_path txt_dir / (img_path.stem .txt) xml_path xml_dir / (img_path.stem .xml) if not txt_path.exists() or not xml_path.exists(): errors.append(fmissing label: {img_path.name}) continue for line_no, line in enumerate(txt_path.read_text(encodingutf-8).splitlines(), 1): parts line.strip().split() if len(parts) ! 5: errors.append(f{txt_path.name}:{line_no} col!5) continue try: cid, cx, cy, w, h map(float, parts) except ValueError: errors.append(f{txt_path.name}:{line_no} not float) continue if cid ! 0: errors.append(f{txt_path.name}:{line_no} class_id{cid}) if not (0 cx 1 and 0 cy 1 and 0 w 1 and 0 h 1): errors.append(f{txt_path.name}:{line_no} coord range) print(images checked:, len(list(img_dir.glob(*.jpg)))) print(error lines:, len(errors)) for e in errors[:50]: print(e)脚本里对yolo坐标的判断比较严格中心点cx、cy必须在0到1之间宽w、高h必须大于0且不超过1。这里有一个容易忽略的细节0 w 1要求宽不能等于0因为xml里xmax等于xmin时转换出的w就是0这样的框在训练时会被当作无效框最好是提前发现而不是让框架悄悄过滤。如果错误列表为空三套文件1838张齐整这份数据才能进入下一步。3.3 框尺寸分布与密度分析格式通过校验后我还会做一次更精细的统计看框的尺寸分布和图片内部的密集程度。具体做法是读取所有txt把每一行的归一化宽高收集起来批量计算均值和分位数import numpy as np all_wh [] for txt_path in txt_dir.glob(*.txt): for line in txt_path.read_text(encodingutf-8).splitlines(): parts line.strip().split() if len(parts) 5: all_wh.append((float(parts[3]), float(parts[4]))) wh np.array(all_wh) print(total boxes:, len(wh)) print(mean w/h:, wh[:, 0].mean(), wh[:, 1].mean()) print(median w/h:, np.median(wh[:, 0]), np.median(wh[:, 1]))这个统计主要解决两个判断目标尺度和目标密度。如果中位框宽在0.05以下说明目标普遍偏小训练时imgsz640对每个目标的像素占比不足可能要考虑imgsz896或做切片推理如果框宽度中位数在0.2以上说明目标在画面里占比很大网络很容易学后处理NMS的阈值要适当调高。这份数据总框数19278、图片数1838平均每张约10.5个框属于密集场景模型输出端会同时出现大量候选框需要关注NMS的IoU阈值设置。3.4 train/val划分脚本与随机种子训练检测模型前要先把数据划分开。这个数据集没有附带划分用的txt文件所以自己准备脚本是绕不开的一步。划分原则只有一条以图像为单位分不能把同一张图的多个框跨到训练集和验证集。一张图平均10.5个框如果按框行数随机切分会把同一张图的信息泄露到验证集评估出来的mAP虚高完全没有参考意义。import random, shutil from pathlib import Path random.seed(42) imgs sorted(Path(images).glob(*.jpg)) random.shuffle(imgs) val_cnt int(len(imgs) * 0.15) val_imgs, train_imgs imgs[:val_cnt], imgs[val_cnt:] for split, img_list in [(train, train_imgs), (val, val_imgs)]: for img in img_list: dst_img_dir Path(fsplit/{split}/images) dst_lab_dir Path(fsplit/{split}/labels) dst_img_dir.mkdir(parentsTrue, exist_okTrue) dst_lab_dir.mkdir(parentsTrue, exist_okTrue) shutil.copy2(img, dst_img_dir / img.name) label_src Path(labels) / (img.stem .txt) shutil.copy2(label_src, dst_lab_dir / label_src.name)脚本用copy2而不是move是为了保留原始目录在出错时可以重新切分。random.seed(42)固定随机序列确保每次执行脚本得到的train/val组合完全一样这对后续模型对照实验是必须的。若是复用同一个数据集调参两次划分不同会导致训练集与验证集分别变化最后比较模型A和模型B时分不清差异是模型带来的还是数据划分带来的。划分后的组成可以用一张表表达划分比例估算张数用途train85%1562更新权重val15%276早停与选best.pttest需要时再划分50~100最终结果对比单类别数据集不需要按类别做分层抽样如果后面换成一个多类别数据集我会用sklearn的train_test_split加stratifyy保持类别比例在划分前后一致否则少数类可能全部掉进训练集或验证集。4. YOLOv8训练自己的数据集猕猴桃单类检测配置与参数4.1 写一份干净的数据集yaml把yolo训练链路整理顺畅第一步是准备一个最简单的yaml文件。这个数据集单类内容非常清晰# mht.yaml path: /home/user/data/mihoutao/split train: images/train val: images/val nc: 1 names: 0: mihoutaopath字段指向数据集根目录train和val是相对路径分别指向上一章划分出来的split目录。nc是类别数names给出类别名列表。写yaml时注意两点冒号后面必须有空格path建议用绝对路径因为训练脚本如果由其他目录启动相对路径会根据当前工作目录变化而找不到数据。如果使用的是YOLOv8或者更新的YOLO11这个yaml可以直接被DataLoader读取即使是更早的YOLOv5结构也大体一致。类别名必须和数据集中的标注对得上这里是mihoutao如果写成kiwi训练不会报错但模型输出的标签名会变成kiwi视觉上会很奇怪。4.2 训练命令与参数选择单类别检测的训练命令我一直用yolo CLI方式简洁且日志输出完整。加载COCO上的预训练权重做fine-tune比从随机初始化训快得多这也是 yolov8训练自己的数据集 时最常用的做法yolo detect train \ datamht.yaml \ modelyolov8s.pt \ epochs120 \ imgsz640 \ batch16 \ device0 \ patience20 \ projectruns/mihoutao \ nameexp1modelyolov8s.pt指定的预训练权重如果本地不存在yolo会自动下载如果环境无法访问外网就手动下好放到当前目录。参数选择上这个数据集的目标是中等偏大19278个框分散在1838张图里用yolov8s作为主干已经足够直接上yolov8x在小数据集很容易过拟合。epochs设120配合patience20做早停val指标连续20轮不升自动停止实际训练出来的最佳轮次通常在60到100之间。提示显存不够时优先把batch从16改成8不要先降imgsz。输入尺寸直接决定小目标的分辨率表达能力batch降低只是增加训练时长不太影响最终精度。训练过程中常见的问题集中在路径身上而不是模型参数。yaml里的path写错了训练会报Dataset not foundWindows路径带反斜杠时yaml解析容易把转义符吃掉我一般会在代码里先print(yaml.safe_load(open(mht.yaml)))检查一遍。batch大小改成8或4时学习率不需要手动调yolo默认会自动按batch缩放这点和早期YOLOv5保持一致。各参数的微调参考总结成一张表参数建议值调整方向modelyolov8s.pt数据少用s/m数据多用l/xepochs120观察val_loss再增减imgsz640小目标多时升896batch8~16以显存不溢出为准device0/cpu多卡时0,1patience20防止过拟合plotsTrue生成results曲线4.3 单类别场景下yolo损失函数的观察重点训练日志里会实时输出box_loss、cls_loss、dfl_loss三项损失这是理解 yolo损失函数 最容易上手的入口。box_loss使用的是CIoU类损失负责回归预测框与标注框的位置cls_loss是分类损失因为这里只有一个类别通常前10轮就快速收敛到一个很小的值dfl_loss全称是Distribution Focal Loss负责边框回归的分布学习对边界细节影响比较大。对这个数据集来说真正要盯的是box_loss和dfl_loss曲线的分离程度。如果train/box_loss持续下降但val/box_loss在第70轮后掉头向上说明模型开始记住训练集里果实的角度和光照过拟合已经发生。这时把epochs从120降下来或者在yaml里加weight_decay0.0005做L2约束比继续硬跑更有效。单类场景下如果看到cls_loss迟迟不降优先去查标注txt的类别id是不是混入了其他数字。训练完成后在runs/mihoutao/exp1目录下会自动保存最后权重和最佳权重一般评估和部署都用best.pt而不是last.pt。算法对比实验只要保证固定随机种子、固定预训练权重和固定数据划分跑出来的结果才有横向可比性。5. 验证指标解读与两个提高标注可信度的技巧5.1 用val命令评估而不是只看训练loss训练日志的loss只能说明模型在训练集上的拟合情况最终可不可用要单独跑一遍验证集。常用命令是yolo detect val \ modelruns/mihoutao/exp1/weights/best.pt \ datamht.yaml \ batch16输出会给出每个类别的precision、recall、mAP50和mAP50-95。对这个数据集mAP50应该在0.9以上mAP50-95相对低一些是正常的因为它对IoU更敏感。如果mAP50低于0.85先回去翻校验脚本重点检查是不是有一部分标注框明显比真实果实小一圈这种情况在手工标注矩形框时最容易发生在遮挡果实上。5.2 用预测框与标注框叠加检查边界验证指标正常后我还习惯把预测结果和原始标注画在同一张图上抽查几十张。代码可以直接用ultralytics输出预测框再解析VOC的xml把原始标注框画一遍import cv2 import xml.etree.ElementTree as ET from pathlib import Path from ultralytics import YOLO model YOLO(runs/mihoutao/exp1/weights/best.pt) img_path split/val/images/firc_mihoutao_1513.jpg img cv2.imread(img_path) res model.predict(img_path, conf0.25)[0] for box in res.boxes: x0, y0, x1, y1 map(int, box.xyxy[0]) cv2.rectangle(img, (x0, y0), (x1, y1), (0, 255, 0), 2) xml_path Path(annotations) / (Path(img_path).stem .xml) root ET.parse(xml_path).getroot() for obj in root.iter(object): bnd obj.find(bndbox) x0 int(bnd.findtext(xmin)); y0 int(bnd.findtext(ymin)) x1 int(bnd.findtext(xmax)); y1 int(bnd.findtext(ymax)) cv2.rectangle(img, (x0, y0), (x1, y1), (0, 0, 255), 2) cv2.imwrite(check_1513.jpg, img)绿色框是模型预测红色框是原始标注。xml_path 这里指向原始annotations目录如果划分时把xml也复制进split/val就换成对应路径。大量对比后如果发现预测框总比标注框大一圈往往是标注时把果实的边界框收得过紧模型学到的是真实边缘的保守位置。更隐蔽的问题是xml文件名不匹配路径写错时ET.parse直接抛出FileNotFoundError说明目录组织方式和脚本预期不一致需要回解压目录核对实际结构。5.3 7z归档的哈希校验与重新打包最后分享一个和资源本身直接相关的工作习惯。7z压缩文件获取哈希值不只是解压前校验用实验结束后回传数据时也应该归档一次并把这个哈希写进实验记录确保后续复现实验下载的副本和当前训练用的数据完全一致。重新打包并计算哈希一行就可以完成7z a mihoutao_splits.7z split/ -mx5 sha256sum mihoutao_splits.7z-mx5是压缩等级参数取值范围0到90是纯存储不压缩9是极限压缩。对图片数据集没有必要用9压缩时间和解压时间都会明显变长-mx5是比较平衡的选择如果只是存档不常解压-mx1解压速度会更快。归档前再把jpg、xml、txt各自计数一次和1838对上这个副本就可以作为实验基线长期保存。本文还有配套的精品资源点击获取