ARTICLE DETAIL

资讯详情

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

苍蝇检测数据集:VOC与YOLO双格式标注及YOLOv8训练实战

苍蝇检测数据集:VOC与YOLO双格式标注及YOLOv8训练实战 简介面向目标检测任务的数据集资源专为苍蝇检测场景设计适合计算机视觉初学者与算法工程师用于训练和评估YOLO、Faster R-CNN等模型。图片素材经百度爬取、人工删重并使用labelImg工具完成矩形框标注仅flies一个类别共532张图片、689个标注框标注内容准确合理。压缩包共1598个文件包含532张jpg原图、532个Pascal VOC格式xml标注文件以及534个txt文件以YOLO格式标注为主整体大小16.67MB数据格式可直接适配主流检测框架。目前已有242人学习使用这份数据可帮助开发者免去繁琐的数据采集与标注环节快速搭建苍蝇检测训练流程适用于害虫监测、卫生防疫等应用场景。1. 苍蝇检测数据集VOC和YOLO双格式标注值得直接用于训练做目标检测的同行应该都有过这种经历模型结构选好了、训练脚本写好了结果卡在数据集上。尤其是苍蝇这类小目标检测自己去网上爬图、清洗、用labelImg一张张画框五百多张图搞下来至少两三个整天标完还要转格式、划分数据集光是xml和txt之间的字段对应关系就够绕一阵子。这份苍蝇检测数据集图片数量532张标注框数689个类别只有flies一类VOC格式的xml和YOLO格式的txt各532个文件是直接对标好的成品下载解压就能喂给YOLOv5、YOLOv8或者MMDetection训练。适合的人群很明确做害虫监测、食品安全检测、环境卫生分析相关视觉项目的算法工程师或研究生也包括刚接触目标检测、想拿一个小而干净的数据集跑通全流程的新手。这套数据的标注画的是矩形框工具是labelImg来源是百度图片爬取后做了删重处理。单类别的好处在于不用纠结类别不平衡689个框分布相对集中适合用来做迁移学习或者验证检测管线是否跑通。2. 数据包结构剖析文件组成与标注格式的对应关系2.1 解压后的文件清单和命名规律拿到压缩包解压后第一件事不是急着训练而是先搞清楚目录结构。常见做法是把所有jpg、xml、txt都平铺在同一个文件夹里命名像cangying_78.jpg、cangying_89.jpg这样前缀统一、按序号递增。这种命名方式有个好处三个文件通过主文件名直接关联cangying_78.jpg对应cangying_78.xml和cangying_78.txt不需要额外查映射表。建议解压后先跑一个脚本确认三个文件的数量是否一致防止拷贝过程中丢文件。按项目说明jpg、xml、txt各532个如果实际数量有出入先找压缩包是否完整再检查是否被安全软件拦了部分文件。2.2 VOC格式的xml里到底存了什么VOC格式的xml是Pascal VOC项目定的标注标准labelImg默认保存格式就是它。打开一个xml文件核心字段就是这些annotation folderJPEGImages/folder filenamecangying_78.jpg/filename size width640/width height480/height depth3/depth /size object nameflies/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin152/xmin ymin98/ymin xmax301/xmax ymax240/ymax /bndbox /object /annotation这里需要说明几个字段的实际含义filename是图片文件名size存储图片宽高和通道数object里的name就是类别名这里固定是flies。bndbox四个值是目标框的左上角和右下角像素坐标xmin/ymin在前xmax/ymax在后。truncated表示目标是否被图片边界截断difficult表示是否难分这两项在标注小目标时经常被忽略但训练时如果遇到loss异常波动回头检查这两个值是有必要的。注意坐标是像素绝对值不是归一化值。这是VOC和YOLO格式最核心的区别转换时如果忘记除以图片宽高训练时边界框会全部跑到图片外面去。2.3 YOLO格式的txt和VOC的对应关系YOLO格式的txt每一行代表一个目标五个值用空格隔开类别ID、中心点x坐标、中心点y坐标、框宽、框高。注意后面四个值都是归一化到0到1之间的浮点数不是像素值。0 0.3452 0.4826 0.2187 0.2958拿上面那个xml文件举例xmin152、ymin98、xmax301、ymax240、图片宽640、高480那么中心点x就是(152301)/2/6400.3539中心点y是(98240)/2/4800.3521宽是(301-152)/6400.2328高是(240-98)/4800.2958。我一般会写个Python脚本用xml里读到的坐标和宽高去验证txt里的数值避免有个别文件坐标转换错误。2.4 用脚本批量核对标注文件直接上手检查是对数据集质量最有效的兜底。下面这个脚本读xml解析出图片尺寸和目标框再和txt里的内容比对看是否一致。import xml.etree.ElementTree as ET from pathlib import Path xml_paths list(Path(annotations/xmls).glob(*.xml)) txt_paths list(Path(annotations/txts).glob(*.txt)) def parse_xml(xml_path): tree ET.parse(xml_path) root tree.getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) boxes [] for obj in root.iter(object): name obj.find(name).text bbox obj.find(bndbox) xmin int(bbox.find(xmin).text) ymin int(bbox.find(ymin).text) xmax int(bbox.find(xmax).text) ymax int(bbox.find(ymax).text) boxes.append((name, xmin, ymin, xmax, ymax)) return w, h, boxes mismatch [] for xml_path in xml_paths: stem xml_path.stem txt_path Path(annotations/txts) / f{stem}.txt w, h, xml_boxes parse_xml(xml_path) with open(txt_path) as f: txt_lines [line.strip().split() for line in f if line.strip()] if len(xml_boxes) ! len(txt_lines): mismatch.append((stem, count, len(xml_boxes), len(txt_lines))) continue for (name, xmin, ymin, xmax, ymax), line in zip(xml_boxes, txt_lines): cls_id 0 x_center (xmin xmax) / 2 / w y_center (ymin ymax) / 2 / h bw (xmax - xmin) / w bh (ymax - ymin) / h diff abs(float(line[1]) - x_center) if diff 1e-3: mismatch.append((stem, x_center, float(line[1]), x_center)) if mismatch: print(发现问题文件:, mismatch[:10]) else: print(全部对应关系正常)这段脚本的逻辑分三层先统计xml里的目标数和txt行数是否一致逐个比对类别ID是否正确再对比归一化坐标和宽高是否吻合。比对精度做到小数点后三位就够了因为YOLO格式本身是浮点存储labelImg转出来的值本身就带浮点误差差异超过0.001才需要警惕。我的实际习惯是新拿到任何数据集都先过一遍这个验证脚本花不了十分钟但能把后续训练过程中的大部分莫名其妙问题提前消掉。3. 从VOC到YOLO转换脚本与标签使用全流程3.1 为什么双格式都有还要自己写转换很多人问既然txt和xml都有了为什么还要自己写转换脚本直接拿现成的txt去训练不就行了。这里有两个实际问题第一是训练YOLO系列时需要单独划分训练集和验证集而原数据集只提供全量标注文件没有train.txt、val.txt这些索引文件第二是要确认txt文件里的类别ID和模型配置文件里的类别顺序一致数据集只有flies一个类别类别ID固定为0但配置里如果写错成从1开始loss会一直掉不下去。我在处理这个数据集时习惯的做法是先保留xml不动写一个转换脚本同时完成格式校验、数据划分和生成YOLO训练所需的索引文件。3.2 完整转换与划分的Python脚本import os import random from pathlib import Path import xml.etree.ElementTree as ET # 配置路径 IMG_DIR images # 存放jpg XML_DIR xmls # 存放xml LABEL_DIR labels # 输出txt SPLIT_RATIO 0.85 # 训练集占比 random.seed(42) # 类别映射只有flies一类ID是0 class_names [flies] # 创建输出目录 os.makedirs(LABEL_DIR, exist_okTrue) def convert_xml_to_yolo(xml_path, output_dir): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) lines [] for obj in root.iter(object): cls_name obj.find(name).text if cls_name not in class_names: continue cls_id class_names.index(cls_name) bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 坐标归一化并计算YOLO格式的四个值 x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) out_path Path(output_dir) / f{Path(xml_path).stem}.txt with open(out_path, w) as f: f.write(\n.join(lines)) # 处理所有xml xml_files list(Path(XML_DIR).glob(*.xml)) for xml_file in xml_files: convert_xml_to_yolo(xml_file, LABEL_DIR) # 按比例划分训练集和验证集 image_files sorted(Path(IMG_DIR).glob(*.jpg)) random.shuffle(image_files) split_idx int(len(image_files) * SPLIT_RATIO) train_files image_files[:split_idx] val_files image_files[split_idx:] def write_txt(file_list, output_path): with open(output_path, w) as f: for img_path in file_list: # 注意这里写绝对路径训练时省去路径拼接的麻烦 f.write(str(img_path.resolve()) \n) write_txt(train_files, train.txt) write_txt(val_files, val.txt) print(f训练集数量: {len(train_files)}, 验证集数量: {len(val_files)})脚本里两个值得细说的点一是random.seed(42)固定随机种子保证每次运行划分结果一致这样多轮实验之间对比指标才能排除数据划分的干扰二是train.txt里写的是图片绝对路径这样在不同机器上训练时只要不移动图片目录就不用重新生成索引文件。划分比例我选了0.85532张图大约452张训练、80张验证。单类别检测任务验证集80张足够反映模型表现但如果你的业务场景要求更高泛化指标建议改成0.8甚至0.9之后用交叉验证看稳定度。3.3 类别配置文件的写法YOLOv8训练时需要一份data.yaml来告诉框架类别名和路径写法如下# data.yaml path: /your/absolute/path/dataset train: train.txt val: val.txt nc: 1 names: [flies]这里最容易翻车的点是path、train、val三个字段的组合方式。YOLOv8的data.yaml里train和val既可以写绝对路径也可以写相对path的路径但如果写train: train.txt且path设置了完整路径框架会拼成/path/to/dataset/train.txt。前后必须确保目录结构一致一旦train.txt放的位置和path配不上启动训练后不到一分钟就会报FileNotFoundError。我在第一次跑这个数据集的时候就踩过这个坑后来统一改成train和val都写绝对路径一劳永逸。4. YOLOv8实测从配置到训练苍蝇检测模型4.1 环境准备和预训练权重选择YOLOv8在这类单类别小目标数据集上表现很稳。环境建议用ultralytics当前稳定版配合PyTorch 2.x显存8G以上的显卡就能跑没有显卡用CPU也行就是训练时间会长很多。pip install ultralytics预训练权重我一般选yolov8n.pt或者yolov8s.pt一个轻量一个精度稍高先跑通再升级模型。n模型在这个数据量级上大约一两分钟就能完成一个epoch适合前期调参。4.2 训练命令和关键参数调整yolo detect train datadataset/data.yaml modelyolov8n.pt epochs100 batch16 imgsz640 patience20参数都不算陌生但有几个值针对苍蝇检测这种小目标场景需要特殊考虑。imgsz设640是因为原图尺寸接近这个值放大到640再训练能保留更多小目标细节batch设为16是在8G显存下的稳妥选择显存不够就降到8。epochs先跑100轮配合patience20做早停如果第40轮后val精度不再上升自动停在最佳权重。这里特别要说明的是苍蝇在图片里通常占比很小属于典型的小目标检测场景如果发现recall偏低建议把imgsz升到960试一轮对比。4.3 训练输出的解读训练完成后ultralytics会在runs/detect/train目录下生成weights文件夹best.pt和last.pt各一份。best.pt是按验证集指标保存的最优权重last.pt是最后一轮的结果做推理和测试优先用best.pt。输出的results.csv里包含每一轮的precision、recall、mAP50、mAP50-95这些指标直接打开看趋势就行。我在跑这个数据集时一个batch16、imgsz640的配置下50轮左右mAP50就稳定在0.85以上了效果是可以直接用的。如果发现mAP50上不去0.6问题大概率不在训练参数上回到第3节的脚本检查你有没有把坐标归一化这一步写错。4.4 拿训练好的权重做推理验证from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) results model.predict(test_img/cangying_12.jpg, conf0.25) boxes results[0].boxes for box in boxes: print(f类别ID: {int(box.cls)}, 置信度: {float(box.conf):.2f}) print(f坐标: {box.xyxy.tolist()})这里的conf阈值0.25是YOLO系列的默认值实际部署时可以根据你对误检的容忍度往上调比如食品安全场景要求宁缺毋滥就调到0.5以上。预测结果的xyxy坐标是像素值可以直接用来画框或者做后续的目标计数。5. 数据集使用避坑指南四个翻车现场与排查方法5.1 图片与标注文件数量对不上现象解压后发现jpg有532张但xml只有531个训练时提示找不到某个xml文件。原因下载过程中压缩包损坏或者拷贝时文件被安全软件隔离个别文件丢失。数据包里文件数量多解压时偶尔会出现这种玄学问题。解决重新解压原始压缩包解压前先校验压缩包完整性。解压后写个一行脚本对比文件名集合from pathlib import Path jpg_names [p.stem for p in Path(images).glob(*.jpg)] xml_names [p.stem for p in Path(xmls).glob(*.xml)] print(set(jpg_names) - set(xml_names)) print(set(xml_names) - set(jpg_names))输出非空集合就说明有文件缺失或冗余缺哪个文件单独去原压缩包里找回。5.2 训练时loss直接变为nan现象训练到几个batch后loss输出nan模型权重全部失效。原因最常见的是标注框坐标出现0值或者负值比如xmax写成了0归一化后宽高为负数YOLO的损失函数计算时对负数取log直接就nan。另一个可能是图片本身损坏或全黑加载进来像素值异常。解决批量清洗数据把坐标非法和图片文件损坏的样本全部过滤掉。一个快速的检查脚本是读取txt里每行的宽和高的值只要小于等于0的直接剔除from pathlib import Path bad_files [] for txt_path in Path(labels).glob(*.txt): with open(txt_path) as f: for line in f: parts line.strip().split() if float(parts[3]) 0 or float(parts[4]) 0: bad_files.append(txt_path.stem) break print(有问题的文件:, bad_files)另外损坏的图片用PIL打开时如果抛出异常也需要一并处理。这轮清洗做完再重新训练损耗的只是几分钟时间但能省下半天排查精力。5.3 验证集mAP很高但实际检测效果差现象训练时mAP50到了0.9但拿一张没见过的场景图去推理框的位置明显偏了或者漏检一大片。原因数据分布问题。这个数据集图片来源于百度爬取删重之后同场景相似图可能仍有不少如果划分训练集和验证集时没有做充分的随机化验证集里可能存在和训练集高度相似的图片指标自然虚高。这是所有爬图数据集最容易踩的坑。解决划分数据时不要一次性random.shuffle然后切分建议先用图片的文件名哈希取模做粗划分或者用感知哈希算法去重后再划分。更实用的做法是把训练集和验证集按来源特征分组比如同一时间段爬的相似背景图尽量放到同一侧。简单脚本层面可以用文件名序号奇偶划分试试大概率不会比随机划分差。5.4 单类别数据集训练时类别ID写错现象训练正常启动loss正常下降但推理时提示classifier预测错误输出的类别名和预期不符。原因数据集txt里的类别ID是0因为只有flies一个类。但如果你把labels里的txt拿去和另一个多类别数据集合并训练而自己的data.yaml里类别顺序和txt里的ID没有对齐就会出这种问题。解决严格保证txt里的ID和data.yaml里names列表的索引一一对应。当前数据集类别只有fliesnames写成[flies]ID固定是0。如果将来要合并其他类别比如把苍蝇和蚊子放一起训练那么txt里原来为0的行表示flies还是mosquito取决于names列表怎么排。我习惯在合并后的数据集上再跑一遍第5.1节那个对比脚本确保没有漏改的地方。6. 验证与进阶从训练指标到可视化工具的交叉确认训练完看指标只是第一步真正确认模型可用一定得拿具体图片做人工检查。我的习惯是写一个批量的推理脚本把验证集80张图的预测结果画出来存成一张拼贴图快速阅览。import cv2 from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) image_paths list(Path(images).glob(*.jpg))[:80] # 对验证集图片批量推理并画框 for img_path in image_paths: img cv2.imread(str(img_path)) results model.predict(img_path, conf0.25) for box in results[0].boxes: x1, y1, x2, y2 map(int, box.xyxy[0].tolist()) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite(fvis/{img_path.name}, img)把可视化的结果翻一遍重点看之前提到的两类问题一是漏检图片里肉眼可见的苍蝇模型没框出来说明模型recall不够考虑提高imgsz或加大epochs二是误检背景里的深色物体被框成苍蝇说明背景和前景区分度不够这种在爬取的数据里很常见比如深色石子或树叶阴影。苍蝇在图片中通常只占几十个像素标注框小模型天然学得不如大目标好所以小目标检测的trick在这里都适用把imgsz从640提到960、训练时开启mosaic增强、或者用SAHI这类切片推理工具做推理时增强。如果确认模型需要更高精度一个简单的做法是先用当前模型对未标注图片做伪标注然后人工修正后加入训练集。这个数据集本身标注质量是可用的再扩充两三倍后模型表现会有明显提升。从做工程的角度我一直坚持一个原则模型的坑很多靠数据能填数据不够时先别急着换模型结构。之后我每次用下载的数据集训练都会强制走一遍格式校验、类别核对、数据划分固定随机种子这三步哪怕说明文档写得很完整也照做一次。自己跑一遍心里有底后面出了问题往回查也容易定位。这套流程从VOC到YOLO格式的数据集都适用希望帮到你。本文还有配套的精品资源点击获取
返回列表