简介:面向计算机、电子信息工程、数学等专业学生的无人机航拍瓷瓶目标检测数据集,压缩包内包含已标注图像与对应XML标注文件,可直接用于YOLO系列模型训练与验证。资源共143个文件,其中71张JPG航拍瓷瓶原图搭配72个XML标注文件,图像与标注一一对应,压缩包整体大小约402.42MB。数据均来自无人机航拍视角,覆盖不同高度与光线条件下的瓷瓶外观,场景多样,标注框贴合目标且类别清晰,质量较高,适合作为课程设计、期末大作业或毕业设计中目标检测部分的实验数据。目前已有334人学习下载。资源由资深算法工程师整理,标注规范可靠。下载后无需额外整理格式,即可接入YOLO训练流程,帮助使用者绕开自行采集与标注的繁琐环节,将精力集中于模型调优与结果分析;对于需要快速验证检测算法或完成课设任务的开发者而言,是一份即取即用的高效数据集。
1. 无人机航拍瓷瓶数据集直接可用的价值:省下的不只是打标签时间
做电力巡检算法的人,接到“瓷瓶检测”需求就会知道:无人机航拍图里的瓷瓶其实就是绝缘子,目标只有 20~40 像素,反光大、背景乱,自采数据加人工打标,两周时间轻松耗掉。这份数据集 zip 的稀缺之处就在“已标注”三个字——解压出来是 YOLO 能直接读的目录和标注 txt,不用补坐标框、不用重建类别表,直接进入训练环节。下面从解压后的第一眼核验讲起,走完目录盘点、data.yaml 编写、训练参数调优和标注文件三类常见坑,新手能跟步骤落地,熟手能少踩几个格式翻车点。
2. 瓷瓶检测任务拆解:两类主流场景与标注格式选型
2.1 航拍瓷瓶目标长什么样:小尺寸、强反光、异常样本稀缺
瓷瓶这个叫法在电力口很普遍,图纸上正式名是绝缘子。无人机巡线拍回来的画面里,一串瓷瓶挂在导线上,俯拍视角下目标占整张图的比例非常小。我接过几个野外巡线项目,飞行高度 30~60 米,1080p 画面里一串瓷瓶只有三四十像素宽,单只更小。更麻烦的是反光,复合绝缘子表面光滑,太阳一斜就过曝,标注员要放大三倍才看得清边界,模型学到的是高对比边缘特征,一旦光照变化就漏检。
从业务上看,瓷瓶检测通常分两类主流场景。第一类是只做定位,把所有瓷瓶串框出来,交给巡视班组确认;第二类是定位加异常分类,把自爆、缺片、异物覆盖这类缺陷也标出来。异常样本天然稀缺,如果这份数据只标了一类,先别失望——拿去做定位模型是够用的,后续加一个分类头,或者用“先检测后裁片再分类”的两级方案,都能把缺陷判断补上。
小目标多、异常样本少是这份数据最典型的两个特征。训练时如果不做针对性设置,模型很容易陷入“背景学得很好、目标学不清楚”的状态。所以拿到数据后要主动把输入分辨率提上去、把置信度阈值放低,并且用 mAP 分尺寸统计来验证小目标召回,这些后面都会落到具体命令里。
2.2 三类标注格式互换:先把标签统一成 YOLO txt
标题里说“对应已标注文件”,但实际解压出来的标注格式不一定统一。常见三种:YOLO txt 格式,每行是“类别 归一化中心x 归一化中心y 归一化宽 归一化高”;VOC xml 格式,坐标是绝对像素;COCO json 格式,坐标是绝对像素且按 annoatations 分组。Ultralytics YOLO 训练只认第一种,所以第一步是把所有标注统一成 txt。
如果解压后看到的是 COCO json,我一般用下面这个脚本做转换。例子只保留核心逻辑,生产环境可以把类别映射抽成字典,避免 category_id 和训练 names 对不上。
# coco json 转 yolo txt import json, os from PIL import Image def convert_coco_to_yolo(json_path, img_dir, out_dir): with open(json_path, 'r', encoding='utf-8') as f: data = json.load(f) # 建立 image_id -> 文件名 的映射 id2name = {im['id']: im['file_name'] for im in data['images']} # 类别映射:coco 的 category_id 往往不是 0/1/2 连续分布 cat_mapping = {} for i, cat in enumerate(data['categories']): cat_mapping[cat['id']] = i os.makedirs(out_dir, exist_ok=True) for ann in data['annotations']: img_id = ann['image_id'] name = id2name[img_id] img_path = os.path.join(img_dir, name) # 用真实图片宽高做归一化,不要轻信 json 里的宽高字段 w, h = Image.open(img_path).size x, y, bw, bh = ann['bbox'] x_c = (x + bw / 2.0) / w y_c = (y + bh / 2.0) / h bw_n = bw / w bh_n = bh / h cls_id = cat_mapping[ann['category_id']] txt_path = os.path.join(out_dir, os.path.splitext(name)[0] + '.txt') # 追加写入,一张图多个目标就写多行 with open(txt_path, 'a') as f: f.write(f"{cls_id} {x_c:.6f} {y_c:.6f} {bw_n:.6f} {bh_n:.6f}\n") convert_coco_to_yolo("annotations.json", "images", "labels")这段代码里最容易踩的是两处。一是用 PIL 重新读一遍真实尺寸,json 的 width/height 字段偶尔会和实际图不一致,一旦不一致,整批归一化坐标全部错位。二是 cat_mapping 把 COCO 的稀疏 id 映射成 0 开始的连续编号,否则类别表第一位和第二位对不上。转换完以后,用下文的标签核验脚本跑一遍再进训练。
2.3 模型选型:YOLOv8s 起步,YOLO11 与旧版接口差别不大
选模型版本这块,我建议直接走 ultralytics 这一套接口。YOLOv5 原仓库虽然经典,但后续维护不如 ultralytics 统一,yaml 写法、数据组织方式也要额外适配。现在你装最新版 ultralytics,YOLO11 也能用同一套 yolo 命令,哪怕是纯小白,按第 4 章的命令走,基本不会卡在环境上。
具体型号上,瓷瓶属于小目标场景,n 型号权重小、速度快,适合边缘盒子,但学术实验和正式交付我一般从 s 起步。s 在单张 2080Ti 上用 1200 分辨率训练几百张图,几个小时能出一版结果,性价比足够。等确认数据没问题,再回头微调。损失函数默认用 CIoU 就好,不要在开头就改 NWD、EIoU 这些变体——等 baseline 立住了,再用控制变量法去试对微小目标更友好的损失函数,否则指标坏了都分不清是哪一步引入的。
3. 数据集结构拆解:解压、核验、拆分到 data.yaml 配置
3.1 解压后先做三件事:目录核对、数量配对、后缀统一
拿到 zip 先别急着跑训练命令。我习惯先把压缩包解压到固定目录,然后做三件事:核对目录结构、统计图片和标注数量、检查文件名后缀。这三步做完,整个数据集心里就有底了。
unzip -q 瓷瓶数据集.zip -d ./insulator && cd ./insulator # 看两级目录结构,至少应该有 images 和 labels 两个目录 find . -maxdepth 2 -type d # 图片和标注数量分别统计,先保证成对 ls images | wc -l ls labels | wc -l # 抽查头和尾的文件名,确认后缀一致且不是大小写混用 ls images | head -5 ls labels | head -5数量不一致是最常见的问题。图片 1000 张、标注只有 980 个,那缺失的 20 个一定要揪出来,否则训练时 ultralytics 会跳过无标注的图,指标看着没问题,实际数据悄悄少了一截。后缀大小写混用也很恶心,images 里是 .JPG,labels 里全是 .jpg,文件名 stem 对不上,模型一张图都学不到。遇到这种情况,先统一后缀再做下一步。
3.2 标签核验:逐行检查坐标归一化与类别编号
目录数量对得上,还要核验标签内容本身。我写过一个轻量检查脚本,逐行读 txt,检查列数是否为 5、坐标是否在 0~1 范围内、宽高是否大于 0,顺手统计类别分布。
# 标签内容核验脚本 import glob, collections def check_labels(): problems = [] for p in glob.glob("labels/*.txt"): lines = [l.strip() for l in open(p) if l.strip()] if not lines: continue # 空标签后续单独处理 for i, line in enumerate(lines, 1): parts = line.split() if len(parts) != 5: problems.append((p, i, "列数不是5")) continue cls, cx, cy, bw, bh = map(float, parts) if not (0.0 <= cx <= 1.0 and 0.0 <= cy <= 1.0): problems.append((p, i, "中心坐标越界")) if not (0.0 < bw <= 1.0 and 0.0 < bh <= 1.0): problems.append((p, i, "宽高越界或为0")) # 类别分布,第一列数值对应的就是 data.yaml 里的下标 ids = collections.Counter() for p in glob.glob("labels/*.txt"): for line in open(p): if line.strip(): ids[int(line.split()[0])] += 1 print("类别分布:", dict(ids)) print("问题样本数:", len(problems)) for prob in problems[:10]: print(prob) check_labels()这段脚本的输出有两处要看。第一是类别分布,如果出现 0、1、2 之外的大数字,说明标签第一列用的不是从 0 开始的连续编号,得回去改 names 或改标签。第二是问题样本数,坐标越界通常发生在人工标注时手滑,这类标签不修,训练时损失会异常跳动。顺手提一句:偶尔的越界框模型能忍,但越界超过 10% 就要当成数据事故来修。
3.3 手写 data.yaml 与 train/val 拆分脚本
大部分数据集压缩包里是全部图片放一个目录、全部标注放另一个目录,没有 train/val 拆分。Ultralytics 要求 data.yaml 至少指定 train 和 val 两个路径,如果 val 直接指向全量数据,训练出来的指标会虚高,因为模型见过了验证集。我习惯先按 9:1 拆出验证集,再写 yaml。
# 按 9:1 拆分 train/val,移动图片和对应标注 import glob, os, random, shutil random.seed(42) imgs = glob.glob("images/*.jpg") # 按实际后缀调整 random.shuffle(imgs) os.makedirs("images/train", exist_ok=True) os.makedirs("images/val", exist_ok=True) os.makedirs("labels/train", exist_ok=True) os.makedirs("labels/val", exist_ok=True) for i, p in enumerate(imgs): stem = os.path.splitext(os.path.basename(p))[0] lab = f"labels/{stem}.txt" if not os.path.exists(lab): print("缺标注:", p) # 这一步能顺手揪出无标注的图 continue if i < len(imgs) * 0.9: shutil.move(p, "images/train/") shutil.move(lab, "labels/train/") else: shutil.move(p, "images/val/") shutil.move(lab, "labels/val/")拆分完之后写 data.yaml,路径建议写绝对路径,相对路径在换机器训练时最容易出问题。
# 数据根目录 path: /home/user/insulator train: images/train val: images/val # 类别顺序必须和标签第一列严格一致 names: 0: insulator 1: broken这里有个细节:yaml 里的 names 顺序要和标签第一列的编号一一对应。比如第一列 0 代表完好的瓷瓶,1 代表破损,那 names 里第 0 项必须是完好、第 1 项必须是破损。顺序反了不会报错,但训练出来 inference 时框上的类别名全是错的,这种错误特别隐蔽。
4. 用 Ultralytics YOLO 训这个数据集:最小命令和关键参数
4.1 环境配置:ultralytics 安装与版本验证
训练环境这一步是最劝退新手的,但实际要装的只有一个核心包。装完先验证 ultralytics 版本和 PyTorch 是否能调用 GPU,避免训练跑了一晚才发现用的 CPU。
pip install ultralytics # 验证版本和 GPU 可用性 python -c "import ultralytics; print(ultralytics.__version__)" python -c "import torch; print(torch.cuda.is_available())"如果 torch.cuda.is_available() 输出 False,通常不是 ultralytics 的问题,而是 PyTorch 和 CUDA 版本不匹配。常见做法是去 PyTorch 官网挑对应 CUDA 版本的命令重装 torch,而不是先重装 ultralytics。另外提醒一句,conda 环境一定要建独立的,别直接在 base 里装,依赖冲突后后悔药都没有。
4.2 训练命令:yolo detect train 与关键超参数
ultralytics 的训练入口统一是 yolo 命令,下面这条是最小可用版本。
yolo detect train \ model=yolov8s.pt \ data=data.yaml \ epochs=200 \ imgsz=1280 \ batch=16 \ patience=40 \ device=0 \ name=insulator_v8smodel 用 yolov8s.pt 预训练权重起手,比随机初始化收敛快得多。imgsz 我直接放到 1280 而不是默认 640,理由在 2.1 里说过:瓷瓶目标太小,640 分辨率下 20 像素的目标经过几次下采样就没了特征。显存不够就先把 batch 降到 8,不要动 imgsz。
几个关键参数的取值逻辑列成表:
| 参数 | 常见取值 | 说明 |
|---|---|---|
| epochs | 150~300 | 小数据集 200 足够,配合 patience 早停 |
| imgsz | 1280 | 小目标场景提分辨率收益最明显,代价是显存翻倍 |
| batch | 8~32 | 由显存决定,OOM 就先降 batch |
| patience | 30~50 | 验证指标连续多少轮不涨就停,防过拟合 |
| optimizer | auto / AdamW | 小数据集 AdamW 通常比 SGD 稳 |
| mosaic | 1.0 | 默认开启,小目标场景建议保留,但最后 10 轮关掉 |
训练启动后要盯两个东西:第一个是每轮输出的 mAP50 和 mAP50-95,第二个是损失曲线是否在 20 轮内出现断崖式下降又快速回升,那通常意味着学习率太大或标签有问题。先别改损失函数,CIoU 在这类任务上足够,等这一版结果跑出来再说。
4.3 首次推理:看框、看置信度、看漏检
训练结束后,用 best.pt 对验证集里几张典型图片跑一次推理,这一步能直观反映出数据质量。
from ultralytics import YOLO model = YOLO("runs/detect/insulator_v8s/weights/best.pt") results = model.predict( source="test_images/example.jpg", conf=0.25, # 初始置信度阈值 iou=0.45, imgsz=1280, save=True ) for r in results: print(r.boxes.cls) # 类别 id print(r.boxes.conf) # 置信度 print(r.boxes.xyxy) # 像素坐标首次推理最常见的现象是:置信度普遍偏低,比如 0.2~0.4 之间。这不一定代表模型差,而是小目标天然难判。把 conf 降到 0.15 再看漏检情况,如果框基本都在,只是置信度低,说明模型学到了但阈值卡太高;如果框东缺一个西缺一个,就要回数据里查那些样本的标注质量。
5. 标注文件避坑指南:三类翻车现场一次排净
5.1 类别编号从 1 开始导致全盘检测崩坏
现象:训练过程正常,loss 也在降,但验证集 mAP 一直很小,预测框输出的类别和实际完全对不上。
原因:标注文件第一列是 1、2 而不是 0、1,data.yaml 里 names 列表是从 0 开始数的。比如原数据集人工标注时习惯“1 代表完好、2 代表破损”,但 YOLO 训练时会把第一列的值当作 names 的下标,结果 1 对应了 names 里下标为 1 的那一项,全部错位。
解决:不要在写 yaml 时硬凑,直接把标签文件里的类别号改掉。一条 sed 命令就能把第一列的 1、2 改成 0、1。
# 把每个 txt 第一列的数字整体减 1 sed -i 's/^2/1/; s/^1/0/' labels/*.txt改完再跑一遍 3.2 的脚本,确认类别分布里只有 0 和 1,再进训练。
5.2 图片与标注文件错位:同名前缀对不上
现象:数据统计时图片数和标注数完全一致,但训练时发现模型什么都学不会,val 指标和随机猜测差不多。
原因:文件名不匹配。最常见的是 images 里叫 10001.JPG,labels 里叫 10001.jpg,后缀大小写不一致;或者原标注软件在 stem 后面加了 _annotation 之类后缀。ultralytics 加载时按 stem 找同名 txt,找不到就跳过,跳过比例一高,模型等于在纯背景图上训练。
解决:统一后缀和文件名,用 diff 快速比对两边的 basename。
ls images | sed 's/\.[^.]*$//' | sort > /tmp/img_names.txt ls labels | sed 's/\.[^.]*$//' | sort > /tmp/lab_names.txt diff /tmp/img_names.txt /tmp/lab_names.txtdiff 输出不为空,就说明有对不上的样本,先把它们揪出来重命名或删掉,再进训练。这一步是血泪经验,做数据项目第一晚往往就耗在这种肉眼看不出的错位上。
5.3 空标签与无目标的背景图拖低指标
现象:训练 loss 很低,mAP 却上不去,验证集里模型几乎不输出框。
原因:数据集里混着一批没有目标的背景图。无人机巡线时飞手经常顺手拍下大片导线和杆塔,这类图没有瓷瓶,标注文件是空 txt。大量空图会把模型往“输出零个框”的方向带,因为空标签不会产生正样本损失,负样本又太多。
解决:训练前统计空标签比例,超过 8%~10% 就把空图挑出来单放。
find labels -name "*.txt" -empty | wc -l挑出来的空图不要直接删,可以单独建一个 background 目录,后续做难例挖掘时会让模型对“没有目标”的场景更自信,减少误检。
5.4 小目标欠标注:框覆盖率不齐,模型学偏
现象:模型对大尺寸瓷瓶检得很准,对远端小目标几乎不漏检但框偏大,或者边界完全卡在目标中间。
原因:小目标标注难度大,标注员在放大三倍的画面里也很难标出紧凑的框,常见问题是框比目标大一圈,把背景也包进去了;或者只标了目标局部。当一张图里大量目标都是这种不紧实的框,模型学到的就是“模糊边界”。
解决:挑 20~30 张小目标密集的图,人工把框重新修一遍,单独放一个 hard_examples 目录,用于训练后期微调。如果框整体偏大且数量多,可以用标注工具批量把框向内收缩 5%,但收缩比例要控制,收紧过头会把目标切掉。这种修框的活没有捷径,但只修小目标部分,投入产出比很高。
5.5 用离线增强补一版数据:albumentations 组合
原始数据如果只有几百张,训练容易过拟合。与其靠训练时的在线增顽强撑,不如先用 albumentations 离线补齐一版数据,把光照变化、旋转、偏移这些在无人机巡检里真实出现的扰动提前加进去。
# 离线增强:生成一份 copy-paste 风格的数据副本 import albumentations as A import cv2, os aug = A.Compose([ A.RandomBrightnessContrast(p=0.8), A.HueSaturationValue(p=0.5), A.Rotate(limit=15, p=0.5), A.ShiftScaleRotate(shift_limit=0.05, scale_limit=0.1, p=0.4), ], bbox_params=A.BboxParams(format='yolo', label_fields=['labels'])) for name in os.listdir("images"): stem = os.path.splitext(name)[0] img = cv2.imread(f"images/{name}") boxes, labels = [], [] for line in open(f"labels/{stem}.txt"): cls, cx, cy, bw, bh = map(float, line.split()) boxes.append([cx, cy, bw, bh]) labels.append(int(cls)) out = aug(image=img, bboxes=boxes, labels=labels) cv2.imwrite(f"aug_images/{stem}_aug.jpg", out["image"]) with open(f"aug_labels/{stem}_aug.txt", "w") as f: for b, l in zip(out["bboxes"], out["labels"]): f.write(f"{l} {b[0]:.6f} {b[1]:.6f} {b[2]:.6f} {b[3]:.6f}\n")这段代码用的是 yolo 格式的 bbox_params, albumentations 内部会自动处理归一化坐标的变换。跑完后把 aug_images 和 aug_labels 并按 9:1 拆进 train/val,重新训练。注意增强数据只加进 train,不要把增强过的图塞进 val,否则验证集失真,指标再好看也代表不了现场。
6. 从能跑到好用:切片推理、小目标 AP 与 TensorRT 导出
模型在验证集上 mAP 能看了,离现场可用还差两步。我一般先做一次切片推理验证。思路很简单:无人机大图在 1280 分辨率下仍然有大量 20 像素的小目标,如果直接把整张图缩小到 640,小目标特征都丢了;把原图切成 512×512 的裁片,重叠 256 像素,再逐片推理,最后把坐标加回偏移量拼起来。切片后目标在片子里能占到 80 像素以上,识别率提升非常明显。切片推理不用改训练代码,只改推理端,但测试时必须用同一套切片逻辑评估,验证集 mAP 要单独分尺寸统计,重点看 32×32 以下目标那一栏。
第二步是把模型导出成 TensorRT engine,边缘盒子推理时用得上。命令行导出即可。
yolo export model=best.pt format=engine half=True imgsz=1280 device=0导出后要测一下延迟和显存占用,记住 TensorRT 的 engine 和训练时的 imgsz 强绑定,换分辨率得重新导出。
这组项目走下来,我最大的习惯变化是:任何数据集到手,第一晚只做核验和格式统一,不碰训练命令。翻车次数多了就明白,标注数据集的坑永远在看不到的文件名、类别编号和空标签里,模型反而是最听话的部分。希望帮到你。
本文还有配套的精品资源,点击获取