ARTICLE DETAIL

资讯详情

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

植物盆栽检测数据集:COCO2017筛选4624图与YOLO/VOC双格式训练指南

植物盆栽检测数据集:COCO2017筛选4624图与YOLO/VOC双格式训练指南 简介植物盆栽检测数据集共4624张JPEG图片标注目标类别为potted plant全部筛选自COCO2017数据集可直接用于YOLO系列目标检测模型的训练与验证。标签同时提供txt与xml两种格式txt文件记录类别和归一化边界框坐标适合YOLO快速读取xml文件采用PASCAL VOC结构便于Faster R-CNN等基于VOC格式的框架导入满足不同工具链需求。资源包总大小约719MB共13873个文件其中4624张jpg图像、4625个txt标签、4624个xml标签并保留COCO原始图像ID命名方式方便追溯源图与标注。图像内容覆盖多种真实场景包括不同角度、光照和背景下的盆栽植物有助于提高模型在实际环境中的泛化能力。已有181人学习使用适合目标检测入门者、算法工程师或科研人员作为盆栽场景的公开基准数据省去自行采集标注的成本可直接融入训练、验证及模型部署流程。1. 植物盆栽检测数据集4624张图从COCO2017里筛出来的单类检测起点做目标检测的都知道数据集的干净程度往往比模型结构更影响最终结果。这份植物盆栽检测数据集是把COCO2017里所有包含potted plant标注的图片抽出来归成一份独立数据共4624张图标签同时转成了YOLO训练的txt格式和VOC风格的xml格式类别只有一个potted plant。它的定位很明确拿来就能训训完就能验证不用再折腾格式转换和标注清洗。对刚开始跑YOLO训练全流程的新手它能帮你省掉写转换脚本、排查标注错位这一大段弯路对想快速验证某个检测头或训练策略的熟手这份数据够小、够干净几十个epoch就能出一轮结果。市面上散落的各种XX检测数据集里这种从COCO抽出来的子集反而是最靠谱的——源数据经过多年验证标注质量经得起推敲不像某些私人标注数据集那样存在大量漏标和错标。2. 数据底细COCO2017筛选逻辑与4624张图的样本构成2.1 potted plant在COCO里的类别位置COCO2017一共有80个类别potted plant排在categories.json里的类别编号58supercategory是indoor。这个类别在国内习惯叫盆栽植物但要注意COCO的标注边界它标的是整盆植物包括花盆、土壤和所有枝叶不是只标叶子部分。这个细节决定了训练出来的检测框一定是包住整盆的矩形如果你希望模型只输出叶子区域就得重新标注直接拿这份数据是做不到的。从原版标注里能观察到一个明显特征potted plant经常和椅子、桌子、沙发这些室内家具出现在同一张图里而且实例通常偏小属于中小目标。COCO官方对potted plant的标注框面积中位数大概在整张图面积的2%到6%之间比person、car这些大目标小一个量级。这对检测器是个不小的挑战——目标小、遮挡多、背景杂。所以这份数据集训练出的模型直接拿到室外绿植场景去测mAP会明显下降这是数据分布决定的不是模型或训练参数的问题。认识这一点很重要否则你会误以为是自己的训练流程出了问题。2.2 4624张图的构成与标注统计这份数据集的图片文件名完整保留了COCO2017的12位数字编号比如000000210065.jpg、000000389159.jpg。train2017和val2017里所有含potted plant实例的图片都被抽取了出来。注意4624是图片文件的数量不是标注实例的数量——一张图里可能同时出现好几盆植物所以实例总数会略大于4624。项目数值图片总数4624类别数1potted plant标签格式YOLO txt VOC xml数据来源COCO2017 train2017 val2017坐标体系txt为中心点归一化xml为绝对像素图片格式JPG分辨率不统一我建议你拿到数据后先做一次实例密度统计也就是数一数每张txt里有几行。用一行命令就能完成wc -l labels/*.txt | awk {print $1} | sort -n | uniq -c输出会告诉你有多少张图只有1个实例有多少张有2个、3个甚至更多。从COCO的实际分布看大部分图里只有1到2盆植物少数图有4盆以上。这个分布对训练的影响很直接模型对单盆场景会拟合得很好对多盆密集场景容易漏检。后面第4章讲数据划分时我会给一个按实例数做分层划分的思路就是为了避免这种分布不均导致评估结果失真。2.3 两种标注格式的实际样子建议你打开一张典型样本把同名txt和xml放在一起对照着看。txt文件是YOLO原生格式每一行代表一个目标共5个数字0 0.483154 0.512344 0.183012 0.276919这一行的意思是类别0potted plant目标框中心点在图片宽度的48.3%、高度的51.2%处框宽占整张图宽的18.3%框高占整张图高的27.7%。全部数值都在0到1之间这就是归一化坐标它跟图片分辨率无关所以YOLO训练时不管你输入640还是1280标注都不用改。xml文件则用绝对像素描述同一个目标。它包含图片名、尺寸和bndbox边界坐标annotation filename000000210065.jpg/filename size width640/width height480/height depth3/depth /size object namepotted plant/name bndbox xmin95/xmin ymin123/ymin xmax212/xmax ymax256/ymax /bndbox /object /annotation两种格式描述的是同一个目标区别只在坐标系。为什么建议两个格式都保留因为YOLO原生吃txt但很多数据增强工具、可视化脚本、评估工具只认VOC的xml。比如你想用LabelImg复核标注它读xml最方便想用某个数据增强库对图片和标注一起做随机裁剪、旋转不少库的接口是VOC格式。双格式意味着后面无论接哪条工具链都不用再自己写转换省下的时间够你多跑几十个epoch。3. 标签双格式转换COCO坐标到YOLO txt再到XML的逐行脚本3.1 COCO json转YOLO txt归一化坐标的计算这份数据集的txt和xml不是凭空生成的源头是COCO2017的instances_train2017.json和instances_val2017.json。COCO在json里存的bbox是[x, y, width, height]其中x和y是目标框左上角的绝对像素坐标width和height是框的像素宽高。这个格式和YOLO的差异是双重的一是坐标系不同COCO用左上角YOLO用中心点二是量纲不同COCO是绝对像素YOLO是归一化比例。转换的核心公式就四个x_center (x width/2) / image_width y_center (y height/2) / image_height box_width width / image_width box_height height / image_height对应脚本如下import json import os # 从COCO标注文件里筛出 potted plant转成YOLO格式的txt coco_category_name potted plant with open(instances_train2017.json, r, encodingutf-8) as f: coco json.load(f) # 建立 图片id - 文件名 / 尺寸 的索引 img_id_to_file {img[id]: img[file_name] for img in coco[images]} img_id_to_size {img[id]: (img[width], img[height]) for img in coco[images]} # 找到 potted plant 在COCO里的类别id原始编号是58 cat_id None for cat in coco[categories]: if cat[name] coco_category_name: cat_id cat[id] break # 按图片聚合标注只保留 potted plant anns_by_img {} for ann in coco[annotations]: if ann[category_id] ! cat_id: continue img_id ann[image_id] anns_by_img.setdefault(img_id, []).append(ann) # 逐图写出YOLO txt out_dir labels os.makedirs(out_dir, exist_okTrue) for img_id, anns in anns_by_img.items(): file_name img_id_to_file[img_id] img_w, img_h img_id_to_size[img_id] txt_name os.path.splitext(file_name)[0] .txt with open(os.path.join(out_dir, txt_name), w) as f: for ann in anns: x, y, w, h ann[bbox] # COCO: 左上角 宽高 # 转YOLO中心点归一化坐标 x_center (x w / 2) / img_w y_center (y h / 2) / img_h box_w w / img_w box_h h / img_h # 裁剪到[0,1]防止边缘越界 x_center min(max(x_center, 0.0), 1.0) y_center min(max(y_center, 0.0), 1.0) box_w min(max(box_w, 0.0), 1.0) box_h min(max(box_h, 0.0), 1.0) f.write(f0 {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}\n)这段代码有几个关键点必须说清楚。第一COCO的bbox是左上角坐标转YOLO必须加上width/2和height/2得到中心点再归一化少了这一步整个框会往左上角偏移四分之一个框的距离训练出来的模型框全偏。第二归一化必须用图片真实宽高不能写死成640因为COCO原图分辨率不统一写死会直接导致这一步的标注全部错位。第三写入格式用6位小数精度足够训练文件体积也更小。第四这里class_id直接写成0因为这份数据集只有一个类别如果以后合并了其他类别class_id要对应data.yaml里的索引编号不能想当然。3.2 YOLO txt转VOC xml反向还原像素坐标如果你手头只有txt想转回xml做可视化复核或者接LabelImg工具链逻辑就是反推。从归一化中心坐标还原出左上角和右下角的像素坐标再组织成XML结构。这个方向平时用得不多但一旦需要手写就是半小时的事。import os from xml.etree import ElementTree as ET from xml.dom import minidom from PIL import Image def txt_to_xml(txt_path, img_path, out_dir, class_namepotted plant): img Image.open(img_path) img_w, img_h img.size root ET.Element(annotation) folder ET.SubElement(root, folder) folder.text os.path.dirname(img_path) filename ET.SubElement(root, filename) filename.text os.path.basename(img_path) size ET.SubElement(root, size) width ET.SubElement(size, width) width.text str(img_w) height ET.SubElement(size, height) height.text str(img_h) depth ET.SubElement(size, depth) depth.text 3 with open(txt_path, r) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue cls_id, cx, cy, bw, bh parts cx, cy, bw, bh float(cx), float(cy), float(bw), float(bh) # 反推像素坐标 xmin int((cx - bw / 2) * img_w) ymin int((cy - bh / 2) * img_h) xmax int((cx bw / 2) * img_w) ymax int((cy bh / 2) * img_h) # 边界裁剪防止越界 xmin max(0, xmin) ymin max(0, ymin) xmax min(img_w, xmax) ymax min(img_h, ymax) if xmax xmin or ymax ymin: continue obj ET.SubElement(root, object) name ET.SubElement(obj, name) name.text class_name bndbox ET.SubElement(obj, bndbox) xmin_el ET.SubElement(bndbox, xmin) xmin_el.text str(xmin) ymin_el ET.SubElement(bndbox, ymin) ymin_el.text str(ymin) xmax_el ET.SubElement(bndbox, xmax) xmax_el.text str(xmax) ymax_el ET.SubElement(bndbox, ymax) ymax_el.text str(ymax) xml_str ET.tostring(root, encodingunicode) pretty_xml minidom.parseString(xml_str).toprettyxml(indent ) out_path os.path.join(out_dir, os.path.splitext(os.path.basename(txt_path))[0] .xml) with open(out_path, w, encodingutf-8) as f: f.write(pretty_xml) return out_path反推时的符号最容易写反。xmin (cx - bw/2) * img_wymax (cy bh/2) * img_h一旦把减号写成加号框会直接横跨整张图。写完后务必做边界保护因为COCO原始标注偶尔会出现框略微超出图片边缘的情况不做clamp的话xml里会出现xmax大于width的非法值YOLO训练时这类框会被直接丢弃或者造成loss异常。3.3 批量处理与一致性校验上面两个脚本只写了单个文件的处理逻辑实际面对4624张图得包一层目录遍历。常见做法是遍历images目录下所有jpg对每个jpg找同名txt不存在同名txt的记录到missing.txt方便后续排查。批量转换时注意几个参数要一致class_name统一用potted plant不要有的文件写成potted_plant有的写成plant类别名字符串不一致会导致xml解析后类别对不上。输出目录结构保持images和labels平行YOLO训练时按目录配对读文件。转换完做一次行数统计比较txt总行数和xml里object节点总数不一致说明有文件被跳过或坐标非法被过滤。一致性检查命令find labels -name *.txt | xargs wc -l | tail -1 grep -c object labels/000000210065.xml我一般会在转换后随机抽10张图用OpenCV把gt框画出来人工扫一遍。这一步很土但能挡住90%的坐标错位问题。特别是第一次用这份数据集时一定要做确认无误再进训练流程。4. 开训练数据集划分、data.yaml配置与YOLO参数设置4.1 按7:2:1划分同时做实例均衡拿到4624张图后第一件事不是直接训练而是重新划分数据集。COCO原始自带train和val的划分但那是按COCO全类别设计的单类场景下你最好自己划成train/val/test三份比例7:2:1。test集先冻结起来训练和调参全程不碰最后才拿出来评估一次这样得到的泛化指标才可信。最简单的随机划分脚本import os import random import shutil random.seed(42) all_files [f for f in os.listdir(images) if f.endswith(.jpg)] random.shuffle(all_files) n_total len(all_files) n_train int(n_total * 0.7) n_val int(n_total * 0.2) train_files all_files[:n_train] val_files all_files[n_train:n_train n_val] test_files all_files[n_train n_val:] for split, files in [(train, train_files), (val, val_files), (test, test_files)]: os.makedirs(fimages/{split}, exist_okTrue) os.makedirs(flabels/{split}, exist_okTrue) for f in files: shutil.copy(fimages/{f}, fimages/{split}/{f}) shutil.copy(flabels/{f[:-4]}.txt, flabels/{split}/{f[:-4]}.txt)参数说明random.seed(42)保证每次划分结果一致调试时可复现0.7和0.2分别对应train和val剩下的自然就是test用shutil.copy而不是move保留原始文件备份之后想调整划分比例可以重新跑不用再下载一次数据。随机划分有一个隐患有些图实例多有些图实例少随机划分可能导致val集全是简单样本评估结果虚高。更稳的做法是按实例数分层划分。先统计每张txt的行数也就是每张图的实例数量然后按实例数排序隔三份取一份分别进train、val、test。这样三个集合的实例密度分布接近评估结果更能反映真实水平。4.2 data.yaml配置与容易踩的细节YOLOv5和YOLOv8都吃data.yaml字段不多但每个字段都可能埋坑train: ./images/train val: ./images/val test: ./images/test nc: 1 names: 0: potted plant几个细节必须注意。train和val路径建议用相对路径并且从项目根目录出发。千万别写绝对路径——换机器跑的时候绝对路径直接作废还得改配置。nc必须和names长度一致nc写成1但names里写两个类别训练大概率会报类别索引越界。names的key从0开始不要写成1。有人习惯从1编号但YOLO内部索引从0开始写成1会导致第一个类别完全没有样本参与训练模型退化成瞎猜。还有一个细节类别名potted plant中间有空格。YOLO本身是支持的因为names在yaml里是字符串不会被拆词。但如果后面要导TensorRT或者接某些C部署框架空格可能引发输出层解析问题这个我放在第6章专门讲。4.3 训练命令与日志观察配置没问题就可以开训了以YOLOv8为例yolo detect train \ datadata.yaml \ modelyolov8s.pt \ epochs100 \ imgsz640 \ batch16 \ workers4 \ lr00.01 \ optimizerSGD参数选择逻辑如下。model用yolov8s而不是n或mn太小盆栽这种中小目标容易欠拟合m太大4624张图一个epoch要跑较长时间s是效率和质量平衡点。imgsz640COCO原图大部分接近这个分辨率不需要强行拉大拉大到1280显存需求翻四倍收益很小。batch16在8G显存左右能跑动显存不够就减到8或4别硬撑OOM一次白跑半小时。lr00.01是YOLOv8默认值如果换成Adam优化器建议降到0.001。训练日志重点看三个指标box_loss、cls_loss、mAP50。box_loss前10个epoch应该从2.0左右快速降到0.5以下之后缓慢下降如果box_loss一直不降大概率是标注文件读错了回到第3章检查坐标转换公式。mAP50在epoch 20以后应该超过0.8如果始终低于0.6检查数据划分是否泄露——比如train和val里出现了同一张图的不同增强版本。训练中断了不用慌YOLO每10个epoch自动保存last.pt续训命令是yolo detect train resumeTrueresume会自动读取runs/detect/train/weights/last.pt前提是你没改项目目录名。这个机制我在长训练里用过很多次比重新训节省大量时间。5. 避坑与排查标注错位、类别ID冲突、漏检的五个典型问题5.1 现象训练loss能降但预测框全部偏左上原因txt里的坐标不是中心点而是左上角直接归一化。也就是转换时忘了加width/2和height/2把COCO的x、y直接除以图片宽高了。这个错误在loss曲线上几乎看不出来因为模型照样能拟合只是学到的框永远往左上角偏。解决重跑第3.1节的脚本确认公式用的是(x w/2) / img_w。改完重新生成labels再训练。判断依据很简单拿一张测试图叠加预测框如果框的中心落在花盆中心偏右下的位置基本就是标注问题而不是模型问题。我建议在训练前用OpenCV把gt框画出来看十张图这一步几十秒能省掉一整轮白训。5.2 现象mAP一直为0或第一个epoch就报class index out of range原因data.yaml里nc和names不匹配或者names的key不是从0开始。常见的是写成names: {1: potted plant}而txt里的类别编号是0索引直接越界。解决把names改成从0开始。如果是多类别场景记住一条铁律txt里的class_id必须小于nc。写个小命令扫描所有txt找出第一列大于等于nc的行awk $1 1 {print FILENAME, $0} labels/*.txt | head这条命令输出所有类别ID不为0的行及其文件名。单类数据集里所有行都应该是0出现1就说明转换时class_id没有重置成0回到第3.1节检查写入逻辑。5.3 现象用LabelImg打开xml框位置正确但解析报错原因xml里出现了越界坐标。COCO的bbox偶尔会把框的右边界恰好贴在图边缘转换时浮点误差导致xmax比图片宽度大一个像素或者xmin出现负值。某些严格的XML解析器遇到这种情况直接抛异常。解决在转xml时对xmin、ymin、xmax、ymax做clamp代码在第3.2节已经写好了就是那四行max和min。另外注意xml里类别名potted plant带空格部分旧版LabelImg对带空格的名字处理有兼容性问题。真遇到了把类别名全局改成potted_plant保持txt、xml、yaml三处一致问题就消失了。5.4 现象训练时大量Warning提示找不到标注文件原因images目录和labels目录的文件数量对不上。常见是筛选时候把图片复制过来了但txt没生成或者某些图在COCO里本身就是空标注——没有potted plant实例但图片文件还是被当成了正样本目录的一员。解决先统计两个目录的数量差ls images/train/*.jpg | wc -l ls labels/train/*.txt | wc -l如果images比labels多把多出来的图片移到unlabeled目录。YOLO对无标注图片只会Warning不会报错但空标签会参与训练等于喂了大量负样本会明显压低recall。这个问题在4.1节的划分脚本里要顺手处理掉不要在训练时才想起来。5.5 现象同一盆植物被检测出两个重叠框一个大一个小原因这不一定是数据问题可能有两个来源。一是盆栽的叶子边界本身模糊标注员之间对整盆植物的边界判定存在主观差异导致gt框大小不一致二是模型对中小目标定位不稳NMS时没有正确合并。解决先看val集上gt框和预测框的一致性。如果gt框本身大小差异大说明标注边界不统一训练时把iou_t设成0.7让模型更聚焦框中心回归。如果gt框一致就是推理参数问题把NMS的iou阈值从默认的0.45调到0.6能去掉大部分重叠框。我在这份数据上实测0.6是比较舒服的折中既不会漏掉密集排列的多盆植物也不会留下大量重复框。6. 验证与进阶从mAP评估到把盆栽检测用起来6.1 用val集跑标准评估训练完先不看训练日志的曲线直接在冻结的val集上跑一次干净评估yolo detect val \ modelruns/detect/train/weights/best.pt \ datadata.yaml \ imgsz640 \ conf0.001 \ iou0.6conf0.001是为了测出模型在低置信度下的真实召回能力。评估要用低置信度部署时再调高到0.25或0.5。输出会给出mAP50、mAP50-95、Precision、Recall四个核心指标。这个数据集上正常结果应该是mAP50在0.85以上mAP50-95在0.55以上。如果mAP50-95明显低于0.4说明框定位不准优先回查坐标转换。6.2 误检分析把植物和花盆分开看单类模型最大的问题是无法区分有土的活植物和空花盆假植物。打开YOLO输出的混淆矩阵图误检集中在两类一是纹理接近叶子的物体——窗帘、绿植墙、编织物二是只露出花盆边缘、植物部分被严重遮挡的案例。三个提升思路第一训练时开mosaic和mixup增强强制模型学叶子纹理而不是颜色。第二把类别拆成potted plant和flower pot两个类重新标注一部分数据——这个数据集只给单类标注你要做就得自己补标但工作量可控。第三用多尺度训练盆栽大量是中小目标imgsz从640提到960对小目标的recall有明显改善。显存不够时可以用tta推理时自动做多尺度加权。6.3 部署时的一个细节类别名空格的处理前面提过names里potted plant带空格训练没问题但导出TensorRT engine时某些版本的onnx导出工具会把类别名拼进输出节点名空格会导致节点名异常。所以导出前把data.yaml的names改成potted_plant重新导出onnx再用trtexec转engine。这个改动不影响训练权重只影响导出工具的解析。yolo export modelbest.pt formatonnx imgsz640 trtexec --onnxbest.onnx --saveEnginebest.engine --fp16fp16精度在盆栽检测这个任务上几乎不掉点推理速度能快一倍。我在这个数据集上实测单张640x640的图在T4上能跑到接近实时这个路径是验证过的。如果最后要做实时巡检或者边缘部署直接照这个流程走省掉不少试错。从那以后我每次拿到别人的数据集第一件事就是跑一遍坐标一致性检查把txt和xml互转一遍再比对数值确认无误才开始配yaml。数据转换这种事翻车一次就长记性了——公式都对但符号写反或者类别ID没重置都是几分钟能修的错却能让你白训好几个小时。这份植物盆栽检测数据集本身把最繁琐的格式转换做完了希望你也能避开这些坑把时间花在真正有用的调参和验证上希望帮到你。本文还有配套的精品资源点击获取
返回列表