
简介面向航拍屋顶检测任务的数据集适用于训练YOLO、Faster R-CNN等目标检测模型也可用于遥感图像分析、城市建筑提取等场景。全套共1376个文件压缩包约132.59MB核心包含458张JPG原图、458个Pascal VOC格式XML标注文件及458个YOLO格式TXT标注文件此外还有少量辅助TXT文件可直接接入主流检测框架。标注由labelImg完成采用矩形框形式共覆盖3个类别roof2451框、roof_asb349框、roof_asb_notsure89框总框数2889个类别不均衡情况清晰可见便于针对性数据增强。目前已有428人下载学习适合需要航拍屋顶样本用于算法验证、模型微调或数据扩充的研究者与开发者。1. 航拍屋顶检测数据集458张VOCYOLO双格式三类目标直接开训做目标检测的人拿到一份数据集通常先卡在格式上VOC的XML标注、YOLO的txt归一化坐标转换脚本写不好训练入口就进不去。这份航拍屋顶检测数据集一共458张图三个类别同时带VOC和YOLO两套标注解压就能用。它解决的不只是“有数据可用”而是把从标注格式到训练配置之间最容易翻车的那段路提前铺平了。适合两类人刚上手YOLO、想拿小规模真实场景数据练手的新手以及要快速验证航拍视角下检测方案的从业者。屋顶检测在航拍场景里很有代表性——目标小、背景杂、正射视角多这些特性后面会反复影响你的参数选择。2. VOC和YOLO两种标注格式结构差异与转换逻辑2.1 VOC格式XML标注文件逐字段拆解VOC格式把每张图的标注信息存在同名XML文件里文件名和图片文件名一一对应。打开一个XML核心字段就那几个filename指向图片名size记录宽高和通道数object块里是每个目标的类别名和外包框坐标。annotation folderimages/folder filenameroof_001.jpg/filename size width1920/width height1080/height depth3/depth /size object nameroof/name bndbox xmin320/xmin ymin240/ymin xmax850/xmax ymax620/ymax /bndbox /object /annotationXML里的坐标是像素绝对值左上角为原点x向右、y向下。这个坐标系和大部分标注工具一致人眼直接能核对所以VOC格式的优点是直观、好排查。但缺点也很明显训练YOLO模型时模型读取的是归一化坐标XML里的绝对值不能直接进训练。这也是为什么数据集要附一份YOLO格式标注——省去自己转换的功夫。2.2 YOLO格式txt归一化坐标与类别编号规则YOLO格式每个文件对应一张图片文件名同名但后缀是txt。每一行代表一个目标内容依次是类别编号、归一化后的中心点x坐标、中心点y坐标、框宽度、框高度。2 0.4932 0.4711 0.1623 0.2012 0 0.7421 0.5834 0.2451 0.1823 1 0.3412 0.3621 0.1087 0.0912类别编号从0开始不是从1开始。如果数据集里三个类别分别是平屋顶、坡屋顶、小型建筑那么编号就是0、1、2。归一化坐标的计算方式是中心点x除以图片宽度框宽除以图片宽度中心点y和框高除以图片高度最终数值范围在0到1之间。注意框宽高也做了归一化不是像素值。前面XML里那个(320, 240, 850, 620)的框图片宽1920高1080时对应的YOLO行应该是中心点x(320850)/2585归一化后585/1920≈0.3047中心点y(240620)/2430归一化后430/1080≈0.3981宽850-320530归一化后530/1920≈0.2760高620-240380归一化后380/1080≈0.3519。这套规则容易在类别编号上出问题训练时data.yaml里写了names: [roof, solar_panel, chimney]这种顺序如果图片中某个目标的编号写成3训练直接报错或者静默丢样本。之前见过有人把VOC里的name类别/name对应错位导致整个数据集质量拉低后面会专门讲这个坑。2.3 双格式互转一份能直接套用的转换脚本VOC转YOLO是检测训练里最高频的操作这份数据集自带两种格式但如果你后续自己标注数据还是要会转。给出转换脚本前提是XML和图片在同一级目录XML里已经有正确的name和bndbox。import xml.etree.ElementTree as ET import os CLASSES [flat_roof, sloped_roof, small_building] def convert_voc_to_yolo(xml_file, out_dir): tree ET.parse(xml_file) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) out_lines [] for obj in root.iter(object): cls_name obj.find(name).text if cls_name not in CLASSES: continue cls_id CLASSES.index(cls_name) 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 / img_w y_center (ymin ymax) / 2 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h out_lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) base_name os.path.splitext(os.path.basename(xml_file))[0] with open(os.path.join(out_dir, base_name .txt), w) as f: f.write(\n.join(out_lines)) xml_dir annotations_voc out_dir labels_yolo os.makedirs(out_dir, exist_okTrue) for f in os.listdir(xml_dir): if f.endswith(.xml): convert_voc_to_yolo(os.path.join(xml_dir, f), out_dir)逻辑说明脚本先解析size拿到图片宽高作为归一化分母再遍历每个object取出类别名和bndbox四个角点。类别名映射到编号用的是CLASSES列表的索引位置顺序必须和训练配置一致。每个目标转成一行文本浮点数保留6位小数足够精度。参数说明CLASSES列表的顺序就是类别编号顺序增删类别时同步修改这里xml_dir是VOC标注所在目录out_dir是输出txt的目录。注意脚本没做坐标裁剪如果标注框超出了图片边界转换结果会大于1训练时会被YOLO自动忽略。3. 把数据喂给YOLOv8目录结构、data.yaml与数据划分3.1 目录结构怎么摆让YOLO直接认账的标准布局YOLOv8对数据集目录有一定要求但不算苛刻常见做法是train和val分开每个文件夹下有images和labels两个子目录。这份数据集的458张图建议按8:2划分训练集和验证集目录结构如下dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yaml对应图片的标注txt文件必须放在同名的labels目录下文件名保持一致。比如images/train/roof_001.jpg对应labels/train/roof_001.txt。如果某张图片没有目标它的txt文件可以为空但文件要存在否则训练时YOLO会报找不到标注。这个细节很多人忽略结果是训练到一半突然警告虽然不致命但会让你怀疑数据有问题。3.2 data.yaml逐项配置路径、类别名、类别数一个都不能错yaml文件是YOLO训练的人口配置错误会在训练启动阶段直接报错。path: D:/datasets/roof_detection train: images/train val: images/val names: 0: flat_roof 1: sloped_roof 2: small_buildingpath是数据集根目录的绝对路径train和val是相对path的子目录路径。真正容易出问题的是names它的索引就是txt标注里的类别编号顺序不能错。注意yaml里不要把names写成[flat_roof, sloped_roof]这种列表缩写显式带索引会减少歧义。额外说一个检查习惯训练前把categories.txt或yaml里的类别顺序和某个txt标注文件手工对一遍看第一行的类别编号是否落在names的key范围内。3.3 训练集/验证集划分别让同一栋建筑的图像同时出现在两边划分数据不能随机乱切。航拍图像存在空间相关性同一片区域拍摄的多张图从不同角度覆盖了同一栋屋顶。如果随机划分模型在验证集上看到的屋顶很可能在训练集里已经见过类似视角评估结果虚高换到新区域就掉点。常见做法是按场景或按拍摄区域划分确保一个地理范围内的图全部进入训练集或全部进入验证集不做交叉。import os import random from shutil import copy2 random.seed(42) img_dir all_images label_dir all_labels train_img dataset/images/train valid_img dataset/images/val train_lbl dataset/labels/train valid_lbl dataset/labels/val for d in [train_img, valid_img, train_lbl, valid_lbl]: os.makedirs(d, exist_okTrue) all_files os.listdir(img_dir) random.shuffle(all_files) split_idx int(len(all_files) * 0.8) for i, fname in enumerate(all_files): base os.path.splitext(fname)[0] src_img os.path.join(img_dir, fname) src_lbl os.path.join(label_dir, base .txt) if i split_idx: copy2(src_img, train_img) copy2(src_lbl, train_lbl) else: copy2(src_img, valid_img) copy2(src_lbl, valid_lbl)逻辑说明脚本按固定随机种子打乱文件顺序前80%进训练集、后20%进验证集。copy2保留文件元数据对图片和txt都适用。实际工作中我会在划分前先看一眼图片的文件名规律如果文件名里带着拍摄编号或区域编号优先按这些分组。参数说明random.seed(42)固定随机种子保证可复现split_idx int(len(all_files) * 0.8)控制划分比例可以根据数据量调整数据多可以到0.9数据少用0.7。注意这里假设所有图片都有对应标注文件如果存在某些图没标注的情况要先过滤否则copy2会报错。4. 训练与评估YOLOv8参数怎么设三类指标怎么读4.1 训练参数在458张小规模数据上怎么选初始值小数据集上训练参数设置比大数据集更敏感。458张图规模批量大小、迭代轮数、输入尺寸这三个参数直接决定训练能否收敛。yolo detect train datadataset/data.yaml modelyolov8n.pt epochs150 batch16 imgsz640 patience30 device0modelyolov8n.pt是YOLOv8的nano版本参数量最小适合小数据集不容易过拟合。epochs150不是必须跑满配合patience30做早停连续30轮验证集指标不提升就自动停。batch16在常见显卡上都跑得动显存8G以上没有压力。imgsz640是YOLOv8默认输入尺寸。有一个常用技巧先冻结主干训练前50轮再解冻微调。命令加一行参数yolo detect train datadataset/data.yaml modelyolov8n.pt epochs150 batch16 imgsz640 freeze10freeze10冻结前10层主干部分的预训练权重不被破坏专注训练检测头。这个做法对屋顶检测这种小样本任务比较友好能减少因为数据量不足导致的特征提取层漂移。4.2 评估指标mAP、混淆矩阵与损失曲线怎么看训练结束后YOLOv8会生成results.csv和一堆图表。主要看三个东西mAP0.5、mAP0.5:0.95和混淆矩阵。mAP0.5表示IoU阈值0.5下的平均精度是相对宽松的评估口径。屋顶检测任务中框的IoU超过0.5就算命中这个指标能到0.8以上说明模型基本可用。mAP0.5:0.95是IoU从0.5到0.95每隔0.05取一次的平均更严格小目标在这项上容易掉分。混淆矩阵看的是类别间的互相误检。屋顶检测里最常见的误检模式是坡屋顶和小型建筑互相混如果类别里有这类干扰以及屋顶边缘和外墙线被误框。处理逻辑上如果混淆矩阵里某两个类别互相高响应常见做法有两种一是叠加训练数据让模型看到更多这类相似目标的区分特征二是检查标注框是否描得太松框的边界把相邻背景也包进去了导致模型学到的特征不纯。损失曲线方面box_loss和cls_loss在训练集上持续下降、在验证集上不再下降时就说明模型容量已经到头了继续加epoch只会过拟合不会提升泛化。4.3 导出部署从pt到onnx一步操作训练完的best.pt可以直接做推理但如果要部署到没有PyTorch环境的地方通常导出成onnxyolo export modelruns/detect/train/weights/best.pt formatonnx imgsz640导出时注意imgsz要和训练时一致否则推理端要重新处理输入尺寸。onnx导出后可以用onnxruntime做推理速度比PyTorch原生的快不少。这里提一句有时候导出的onnx在TensorRT里精度会有轻微下降尤其在batch size变化时小目标检测对这类数值波动更敏感测试用的输入尺寸最好固定。5. 常见坑与排查从标注到训练的五条血泪记录5.1 类别编号错位mAP为零但训练不报错现象训练正常跑完loss正常下降但验证集的mAP一直是0检测结果全部为空。原因txt标注里的类别编号和data.yaml中names的索引对不上。比如VOC转换脚本里CLASSES顺序是[flat_roof, sloped_roof, small_building]但yaml里写成了[small_building, flat_roof, sloped_roof]。模型学习时把编号0和类别名绑定推理时编号0对应错了名字预测框即使位置正确类别匹配不上也计为误检。解决训练前写一段脚本扫描所有txt统计每个类别编号出现的次数再和yaml的names手动对照。import os label_dir dataset/labels/train count {} for f in os.listdir(label_dir): if not f.endswith(.txt): continue with open(os.path.join(label_dir, f), r) as fp: for line in fp: cls_id line.split()[0] count[cls_id] count.get(cls_id, 0) 1 print(count)输出{0: 312, 1: 241, 2: 67}这样的统计结果核对每个编号对应的类别是否和yaml一致。这一步30秒的事能省掉一整天排查时间。5.2 小目标漏检航拍视角下屋顶占比太小现象模型能检出大面积的厂房顶但居民区里的小型屋顶漏检严重尤其当图片里建筑密集时。原因屋顶在航拍图里像素占比小经过640×640的输入缩放后小目标的长边可能只剩十几个像素特征在多次下采样后丢失。这在小样本数据集上尤其明显。解决三个常用手段按优先级试。第一是切图推理把原图切成四块或九块分别推理再拼接结果直接放大目标的有效像素占比。第二是训练时用更大的imgsz比如imgsz960模型能看到更多细节。第三是检查标注框是否贴得够紧如果框把屋顶附近的空地也包进去了等于让模型学了错误的目标边界缩小标注框能直接提升小目标AP。切图推理有一个注意点推理结果要过滤掉切片边缘的框否则拼接时同一个目标在边界上被重复检出。5.3 坐标归一化越界转换脚本没做边界裁剪现象训练日志里出现WARNING: ignoring corrupt label对应图片在损失计算时被跳过一张图里的某些目标直接不参与训练。原因标注框超出了图片边界转换时没做裁剪归一化坐标出现大于1或小于0的值。YOLO训练时对这些非法标注直接忽略。数据集中手工标注的框偶尔会画出边界几个像素人眼看不出来但坐标一归一化就暴露了。解决转换脚本里加边界裁剪把xmin、ymin限制在0到宽高范围内同时保证xmax xmin、ymax yminxmin max(0, float(box.find(xmin).text)) ymin max(0, float(box.find(ymin).text)) xmax min(img_w, float(box.find(xmax).text)) ymax min(img_h, float(box.find(ymax).text)) if xmax xmin or ymax ymin: continue这段裁剪放在归一化之前能过滤掉无效框。处理完后重新统计一次坐标范围确认所有归一化值都在0到1之间。5.4 训练中断后不知道还能续跑现象训练到80轮时显卡过热或断电重启后只能从头再跑浪费了已经收敛一半的模型。原因不熟悉YOLO的断点续训机制。runs/detect/train下存在last.pt这是每轮结束保存的最新权重。解决只用加一个参数就能从断点继续yolo detect train resumeTrue它会自动读取runs/detect/train/weights/last.pt恢复学习率和优化器状态。这里有个注意点如果修改了epochs参数续训时会按新值重新计算可能和原计划不一致。所以续训时不要动参数直接用resume让它按last.pt里的状态继续跑。5.5 某类别训练样本过少AP波动剧烈现象三个类别里有一个类别只有几十张训练图每次训练它的AP忽高忽低很不稳定。原因样本极少时模型对该类别的特征学习不充分初始化和随机采样带来的波动被放大。458张总量三个类别分布不均很正常这是小样本数据集的通病。解决先看类别统计确认是不是真的不平衡。如果某类确实很少优先考虑增大该类的数据增强概率。YOLOv8里可以用hsv_h、hsv_s、degrees这些增强参数但它们是全局生效的。针对单类增强更实际的做法是单独收集该类别的公开数据或者对该类别所在图片做复制增强。另外提示一个选择如果数据集据描述已包含特定类别分布考虑使用官方的迁移学习方式先在大规模预训练模型权重基础上微调能缓解少量类别的波动。6. 最后一招手动验证把预测框回贴原图比数字更可信训练结束后各项指标再好看最终也要落实到“框画得准不准”这件事上。写一个可视化脚本把标注和预测结果同时画到原图上按文件名逐张翻能直观发现表格里体现不出的问题——比如某个类别框整体偏大、某类目标总被漏检、两栋相邻建筑的框糊在一起。import cv2 import numpy as np from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) img_path dataset/images/val/roof_052.jpg img cv2.imread(img_path) results model.predict(img, conf0.35, imgsz640) for r in results: for box in r.boxes: x1, y1, x2, y2 map(int, box.xyxy[0].tolist()) cls_id int(box.cls[0]) conf float(box.conf[0]) label f{model.names[cls_id]} {conf:.2f} cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, label, (x1, y1 - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) cv2.imwrite(pred_roof_052.jpg, img)参数说明conf0.35是置信度阈值可视化时放低一点可以看到模型“犹豫”的输出如果0.35以下还有大量合理框说明模型其实学了更多但被阈值挡掉了。imgsz640要和训练时保持一致否则预测框的坐标精度会受影响。model.names从yaml里读取类别名画图时直接显示文本。怎么做视觉排查顺序翻看验证集图片重点关注三类现象。一是框和屋顶边缘的贴合度如果框整体偏向一侧或明显外扩说明标注本身画得松训练出来的框自然不准二是同一张图里某个类别频繁漏检说明该类别和背景的区分度不足三是相邻目标框重叠严重常发生在坡屋顶密集区域如果预测框覆盖两个目标可能需要调整NMS参数。这一轮视觉检查通过后再回头调整训练参数或标注方向会明确很多。从那以后我每次训练完都会强制自己跑一遍可视化循环至少翻20张验证集图片再决定要不要发版。指标可以骗人图片不太会。希望帮到你。本文还有配套的精品资源点击获取