ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

猕猴桃检测数据集实战:VOC转YOLO格式与训练全流程

猕猴桃检测数据集实战:VOC转YOLO格式与训练全流程 简介面向猕猴桃检测任务的标注数据集覆盖1838张图片每张均提供VOC格式xml和YOLO格式txt标注文件共1个类别、19278个目标框可直接用于常见目标检测模型的训练与验证。压缩包大小约84.07MB文件总数为两千个其中包含1838个xml标注文件与162个YOLO格式txt文件结构清晰解压后即可按目录使用。数据采用labelImg工具手工画矩形框完成标注类别为猕猴桃框选规则统一能保证训练数据的准确性和一致性。已有七百余人浏览或学习适合计算机视觉初学者、算法工程师及农业智能化项目开发者可用于YOLO系列、Faster R-CNN、SSD等模型的训练、验证与对比实验。同时兼顾VOC与YOLO两种格式可减少格式转换成本便于多框架快速迭代数据规模适中既适合入门练习也可作为特定场景检测任务的补充数据。1. 先看懂猕猴桃检测数据集的“1838张、1类别”到底意味着什么拿到手的是一个 1838 张图、只有猕猴桃一个类别、同时带 VOC 和 YOLO 两套标注的压缩包。这类数据集解决的不是“模型选型”问题而是“数据能不能直接用”的问题标注格式不统一、目录打散、坐标算错才是检测项目里最消耗时间的地方。1838 张对单类别检测来说不算大但足够把 YOLO 训练、验证、导出、部署的最小闭环跑通适合做算法验证、毕设基线或者产线初版模型。7z 压缩比高解压后通常还带原始 XML 和转换好的 txt方便你核对转换逻辑是否可靠。这篇文章就沿着“解压 → 看懂标注 → 转格式 → 整理目录 → 训练 → 可视化核验”这条路径把这个数据集从头到尾用起来。2. 认识数据集里的 VOC 与 YOLO 两套标注字段、坐标与目录布局很多拿到双格式数据集的人会直接跳过 VOC 只看 YOLO txt结果一旦训练报错、标签对不上图又得回头翻 XML。先花十分钟把两套标注的字段和坐标系统对清楚后面每一步都能省时间。2.1 VOC 标注里真正要被读走的只有三段信息VOC 的 XML 文件里塞了不少元信息文件夹名、文件名、图像来源、分割标记等真正喂给训练流程的只有三块size里的width、height、depthobject里的name以及bndbox里的xmin、ymin、xmax、ymax。一张图上每有一个猕猴桃就会多一个object节点所以解析时要遍历而不是只取第一个。import xml.etree.ElementTree as ET tree ET.parse(Annotations/kiwi_0001.xml) root tree.getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) for obj in root.findall(object): name obj.find(name).text box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) print(w, h, name, xmin, ymin, xmax, ymax)这里逐行解析出每个目标的类别名和像素坐标。findall(object)保证多目标场景不会漏框size里的宽高是后面做坐标归一化的分母不要自己拿 PIL 重新读图尺寸去替换。两套数据如果来自同一批原始图XML 里的尺寸和图片实际像素理应一致但转换脚本里最好还是以 XML 为准避免图片被预处理缩放过导致坐标错位。2.2 YOLO 标注的五个数字和边框坐标换算YOLO txt 每行只有五个数字类别索引、归一化中心点 x、归一化中心点 y、归一化宽、归一化高。它和 VOC 最大的区别是坐标系完全不同VOC 存的是左上角加右下角的像素绝对值YOLO 存的是中心点加宽高、且全部除以图像宽高映射到 0 到 1 之间。换算公式固定为x_center ((xmin xmax) / 2) / widthbox_w (xmax - xmin) / widthy 方向同理用height。写转换脚本时最容易搞错的不是除法而是把xmax - xmin写成xmin xmax或者忘记除以图像宽高直接用像素值写进 txt这样训练时模型会一直收敛不到稳定损失。这个数据集既然同时给了 VOC 和 YOLO建议随手抽几个同名文件对照同一个目标在 XML 里的bndbox和 txt 里的五个数字用上面的公式双向验算一次能立刻发现标注是不是经历过裁图、缩放或者坐标越界。尤其是“YOLO 边框坐标”这个热词对应的操作很多教程只给公式不给验证方法实际操作时对比验算才是排查标注异常的最快路径。2.3 两套格式的目录布局与对应关系对比项VOC XMLYOLO txt类别表达字符串如kiwifruit整数索引如0坐标系统像素绝对坐标左上角右下角归一化相对坐标中心点宽高一图多目标多个object节点一行一个目标多行并列辅助信息含尺寸、来源、分割等只有 5 列数字无冗余解析成本需解析 XML 结构split()后转 float 即可解压后常见的目录形态是JPEGImages/放图、Annotations/放 XML、labels/放 txt也可能直接就是images/加labels/。无论哪种文件命名规则通常是同名不同后缀kiwi_0001.jpg、kiwi_0001.xml、kiwi_0001.txt。这个命名对应关系决定了后面所有批处理脚本的写法也决定了数据划分时能不能把图片和对应标签“绑”在一起移动。如果发现某个.jpg找不到同名.txt这就是一个必须处理的脏数据点不能靠训练时的exist_ok参数糊弄过去。3. 用 7z 解压并校验猕猴桃检测数据集先过完整性再谈训练拿到.7z压缩包后的第一反应多半是解压看内容但“解压成功”和“文件完整”是两回事。压缩包在传输过程中可能损坏、被截断甚至标注文件在打包前就已经缺失。先校验、再解压、再数文件数量这三步做完后续训练才不会被奇怪的数据问题反复打断。3.1 Linux 下解压 7z 文件的最小命令7z 格式的压缩率高但解压工具不是系统标配。Debian/Ubuntu 系需要先装p7zip-full才能拿到7z命令否则只会提示找不到命令。装好后最常用的解压命令是sudo apt install p7zip-full 7z x kiwifruit_dataset.7z -o/home/user/datasets/kiwix表示解压并保留压缩包内的目录结构-o指定输出目录注意-o后面直接跟路径不能写成-o /path那样带空格。压缩包体积不大时可以直接解压到当前目录但对这种标注数据我一般会单独建一个数据集根目录避免 XML、txt、图片散落在一堆工程文件里。Windows 下用 7-Zip 的图形界面右键解压即可命令行场景下用7z.exe x参数和 Linux 完全一致便于写自动化脚本。3.2 解压后先做数量对齐而不是急着可视化解压完第一件事是数文件数量判断“1838 张”这个数字是否真的对应到了三套文件上。图可能同时是.jpg、.jpeg、.png命名后缀不统一所以要用通配符枚举而不是只数某一种后缀。find . -name *.jpg -o -name *.jpeg -o -name *.png | wc -l find . -name *.xml | wc -l find . -name *.txt | wc -l如果图片是 1838而 XML 或 txt 少于这个数说明标注有缺失如果标注比图片多说明存在孤儿标注文件。最理想的形态是三个数字完全相等。这里不建议直接开始统计标注框数量因为一张图可能有多个目标框总数不等于文件数先确认“一图一标注文件”的对应关系再谈内容质量。3.3 用哈希值确认压缩包完整无损坏压缩包在网盘、聊天工具、对象存储之间来回传输很容易出现字节级损坏。解压报错时大家会意识到文件坏了但更隐蔽的情况是压缩包结构完好、内部个别文件内容被改过。所以在解压前甚至解压后都建议对.7z文件本身算一次哈希值和下载来源给出的摘要比对。这对应到的就是“7z 压缩文件获取哈希值”这个操作不是 7z 格式特有而是所有分发文件都应该做的完整性确认。sha256sum kiwifruit_dataset.7z输出是一个 64 位的十六进制字符串长度固定。只要源页面给出的哈希值能和本地算出来的一致就能确认文件在传输链路中没有被修改或截断。Windows PowerShell 下对应命令是Get-FileHash -Algorithm SHA256 kiwifruit_dataset.7zmacOS 的shasum -a 256和 Linux 的sha256sum输出格式也基本一致。哈希校验通过后再去解压能过滤掉相当一部分“训练到一半突然报图片解码失败”的诡异问题。4. 用 Python 把 VOC 标注转成 YOLO txt 并顺手清理脏数据数据集自带的 YOLO 格式通常可以直接用但在实际项目里你拿到的更可能是只有 VOC XML 的老数据、从 KITTI 或 COCO 转过来的混合格式或者这个数据集的 txt 版本本身就有坐标越界问题。与其轻信现成 txt不如自己写一遍转换脚本用统一的规则重新生成 YOLO 标签同时把脏数据捞出来。4.1 转换脚本XML 解析、归一化与写入一个最小可用的 VOC 转 YOLO 脚本核心逻辑就是把bndbox的像素坐标换算成归一化中心点坐标。下面这个版本额外做了类别名到索引的自动映射并限制了输出小数位避免写入超长浮点。import xml.etree.ElementTree as ET from pathlib import Path def voc_to_yolo(xml_path, out_dir, class_map): tree ET.parse(xml_path) root tree.getroot() img_w float(root.find(size/width).text) img_h float(root.find(size/height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_map: class_map[name] len(class_map) box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) x_center ((xmin xmax) / 2.0) / img_w y_center ((ymin ymax) / 2.0) / img_h box_w (xmax - xmin) / img_w box_h (ymax - ymin) / img_h lines.append(f{class_map[name]} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) out_path Path(out_dir) / (Path(xml_path).stem .txt) out_path.write_text(\n.join(lines)) if __name__ __main__: class_map {} for xml_file in Path(Annotations).glob(*.xml): voc_to_yolo(xml_file, labels_yolo, class_map) print(class_map)root.find(size/width)这种 XPath 写法可以少写一级变量直接取到嵌套字段class_map用字典动态建索引这样即使数据集后续追加新类别也不用改代码。真正决定训练能不能跑通的是lines里的坐标数值转换时用img_w、img_h做归一化分母而不是用640这类固定值。如果有人先统一 resize 了图片再拿原 XML 转格式坐标就整体错位了这类问题在可视化那一步才会暴露。4.2 单类别时也要把类别清单写下来虽然只有一个类别但 YOLO 训练要求data.yaml里的names顺序和 txt 第一列的数字严格对应。这个数据集转换后第一列永远是0对应names: [kiwifruit]。不要写names: [猕猴桃]这类中文名Ultralytics 的工具链对中文类别名在部分版本会输出乱码日志虽然不影响训练但排查问题时会多一层干扰。另外把类别清单单独存一份成classes.txt每行一个类别名这种文件在很多标注工具和可视化脚本里是标准输入格式后续做数据集交接、二次标注都用得上。4.3 越界坐标与空标注的自检逻辑手工标注的数据几乎不可避免会出现两种问题一是框坐标超出图像边界比如xmax大于图像宽度二是某个object节点里坐标值为空或为负数。转换脚本里可以直接加一道检查遇到异常就打印文件名而不是悄悄跳过。for line in lines: parts list(map(float, line.split())) if len(parts) ! 5: print(bad line in, xml_path) if parts[1] 0 or parts[1] 1 or parts[2] 0 or parts[2] 1: print(center out of range in, xml_path) if parts[3] 0 or parts[4] 0: print(non-positive box size in, xml_path)归一化之后坐标落在0~1之外说明原始标注的像素框就画出了图片边界这类样本轻则拉低精度重则在增强阶段抛异常。框宽高小于等于 0 的样本通常是标注工具误操作生成训练时会让损失值出现 NaN。自检日志打印的每个文件名都要人工确认不要侥幸过滤掉因为一张图多标注一个目标框在 1838 张的小数据集里对 mAP 的影响比想象中大。4.4 转换后验收数量一致是最低标准转换脚本跑完后labels 目录里的 txt 数量必须和 Annotations 里的 XML 数量一致。用两行命令就能完成最终验收ls Annotations | wc -l ls labels_yolo | wc -l如果数量不一致先查是不是 XML 文件名带了额外空格或大小写差异再查是不是转换脚本里某个样本解析抛异常被跳过了。这一步验收通过之后再进入第 5 章的目录整理和训练环节才不会带着隐藏的数据缺口往下走。5. 用 yolo 训练自己的数据集目录整理、data.yaml 与首次训练数据集本身带了 YOLO 格式标签理论上可以直接开训但实际还差一个标准目录结构和一个数据配置文件。Ultralytics YOLO 系列对数据集目录的要求不算苛刻但一旦目录结构不符合预期报错信息往往只提示“找不到图片”不会告诉你正确结构长什么样所以最好一次性摆对。5.1 整理成 YOLO 标准目录结构标准做法是把数据集拆成train和val两个子集每个子集下分images和labels两类文件。不要把所有图片一个目录、所有标签一个目录直接丢给训练器YOLO 需要的是同名图片和标注一一配对并分层存放。mkdir -p dataset/train/images dataset/train/labels mkdir -p dataset/val/images dataset/val/labels然后按 8:2 或 9:1 划分。下面这个循环把图片随机抽到训练集同时把同名 txt 一并移过去注意扩展名替换时.jpg、.jpeg、.png要分别处理for img in $(ls images | shuf -n 1470); do mv images/$img dataset/train/images/ base${img%.*} mv labels/$base.txt dataset/train/labels/ doneshuf -n 1470表示从全部 1838 张里随机抽 1470 张作为训练集剩余文件自然会落在验证集里。这里最容易踩的坑是只移动了图片而忘了移动同名标签或者用了*.jpg替换但原图是.jpeg导致找不到标签文件。划分完之后再数一遍两边文件数量确认图片数和标签数相等再开始写配置文件。5.2 data.yaml 的正确写法YOLO 训练通过一个 yaml 文件告诉训练器数据在哪、有几个类别以下是这个数据集对应的最小配置path: /home/user/datasets/kiwi/dataset train: train/images val: val/images nc: 1 names: - kiwifruitpath建议写绝对路径避免相对路径在不同工作目录下解析出不同结果train和val填的是相对于path的目录写到images这一层不要写train/images/*.jpg这种展开形式也不要额外写labels字段YOLO 会自动把images替换成labels去找同名 txt。nc必须和names列表长度一致这里类别名是唯一的字符串所以nc: 1如果 txt 里出现了1这个类别索引而nc只配了 1训练会直接报类别索引越界。5.3 训练命令与关键参数用 Ultralytics YOLO 生态训练这个数据集最小命令长这样yolo detect train datakiwi.yaml modelyolov8n.pt epochs100 imgsz640 batch16 device0参数这个数据集的建议值说明modelyolov8n.pt或yolov8s.pt数据量小先跑轻量骨架别一上来就用 x 版epochs100单类别 1838 张100 轮足够看到收敛趋势imgsz640和预训练权重输入尺寸一致起步最稳batch16按显存调整跑不起来就降到 8device0指定第一块 GPUCPU 训练可以去掉或设cpu训练过程中的box_loss、cls_loss曲线是判断模型有没有学进去的最直接依据。这个数据集只有一个类别cls_loss的波动会比多类别小但如果发现box_loss前 20 轮几乎不下降先回头检查标签坐标是不是异常而不是盲目堆 epochs。训练结束后框架会自己处理后处理流程里 NMS 和阈值筛选这些环节这些内置的后处理不需要手动实现。5.4 训练阶段的三种常见报错与快速定位第一种是训练日志提示大量图片没有标签典型输出是类似 “found no labels” 的警告。用下面的命令把缺标签的图片列出来find dataset/train/images -name *.jpg | while read f; do base$(basename ${f%.jpg}) [ ! -f dataset/train/labels/$base.txt ] echo $f done第二种是类别索引越界报错文本里会出现class id或max class id字样。用grep -rn ^1 labels/找出所有第一列数字为 1 的 txt 行检查是不是转换时类别映射写错了。第三种是图片路径或文件名带中文Ultralytics 在部分环境中会对非 ASCII 路径抛异常最稳妥的做法是训练前统一把数据集根目录、图片目录、文件名全部改成英文。6. 训练前最后一步把 YOLO 边框坐标画回原图核验标注前面所有检查和转换都是在“信任代码输出”的前提下做的但标注数据最怕的是坐标对、语义错比如框画到了叶子上、标注框漏了一半果实。与其等训练完再通过 mAP 间接推断不如直接把 txt 里的五个数字画回原图肉眼扫一遍。6.1 用 PIL 把 txt 标注画到图上YOLO txt 存的是归一化相对坐标画图时要把中心点和宽高换算回像素坐标。换算公式是x1 (x_center - box_w/2) * img_width乘回宽高这一步经常被漏掉画出来的框要么挤在角落要么大小完全不对。from PIL import Image, ImageDraw img Image.open(dataset/train/images/kiwi_0001.jpg) draw ImageDraw.Draw(img) w, h img.size with open(dataset/train/labels/kiwi_0001.txt) as f: for line in f: cid, xc, yc, bw, bh map(float, line.split()) x1 (xc - bw / 2.0) * w y1 (yc - bh / 2.0) * h x2 (xc bw / 2.0) * w y2 (yc bh / 2.0) * h draw.rectangle([x1, y1, x2, y2], outlinered, width3) img.save(check_0001.jpg)这个脚本批量跑完所有验证集图片后逐张翻看最明显的两类问题框是否紧贴果实边缘、框之间是否存在大面积重叠或漏检区域。单类别数据集的标注质量主要就看框的贴合度如果 30% 以上的框都比果实大一圈后续训练出来的模型预测框也会偏好偏大。6.2 双向核对缺失文件防止“有标签没图”和“有图没标签”除了坐标质量还要从文件层面双向核对一遍。很多转换脚本只检查了 XML 到 txt 的完整性但图片和 txt 的配对关系没人验证。用comm命令可以快速找出两边差异ls dataset/train/images | sed s/\.[^.]*$// | sort imgs.txt ls dataset/train/labels | sed s/\.[^.]*$// | sort labels.txt comm -23 imgs.txt labels.txtcomm -23输出只在第一个文件里出现的行也就是有图没有标签的部分把参数换成comm -13就能找出有标签没有图的孤儿文件。这一步过滤掉的问题在训练时报错信息往往非常不直观会以FileNotFoundError或空标签警告的形式出现。6.3 只有一类目标时mAP50-95 比 mAP50 更值得盯单类别检测有个容易误判的点mAP50 这类宽松阈值下的指标通常很高因为背景干扰少、类别单一即使框偏一点也能命中。真正衡量框精度的指标是 mAP50-95它把 IoU 阈值从 0.5 一路拉到 0.95 取平均。猕猴桃这类果实目标边缘清晰、形态固定如果这个数据集训练出来的 mAP50-95 低于 0.6大概率不是模型能力问题而是标注框本身不贴边或数据划分时训练集和验证集同源分布差异大。建议在训练命令里加上plotsTrue生成预测结果对比图把验证集上的预测框和真实标签画在同一张图上比只看终端输出的数字更能定位问题是出在召回还是出在定位精度。本文还有配套的精品资源点击获取
返回列表