☰
航拍屋顶检测数据集:VOC与YOLO格式转换及YOLOv8训练实战
2026/10/2 3:16:45 网站建设 项目流程

简介:面向航拍屋顶检测任务的数据集,适用于训练YOLO、Faster R-CNN等目标检测模型,也可用于遥感图像分析、城市建筑提取等场景。全套共1376个文件,压缩包约132.59MB,核心包含458张JPG原图、458个Pascal VOC格式XML标注文件及458个YOLO格式TXT标注文件,此外还有少量辅助TXT文件,可直接接入主流检测框架。标注由labelImg完成,采用矩形框形式,共覆盖3个类别:roof(2451框)、roof_asb(349框)、roof_asb_notsure(89框),总框数2889个,类别不均衡情况清晰可见,便于针对性数据增强。目前已有428人下载学习,适合需要航拍屋顶样本用于算法验证、模型微调或数据扩充的研究者与开发者。

1. 航拍屋顶检测数据集:458张VOC+YOLO双格式,三类目标直接开训

做目标检测的人拿到一份数据集,通常先卡在格式上:VOC的XML标注、YOLO的txt归一化坐标,转换脚本写不好,训练入口就进不去。这份航拍屋顶检测数据集一共458张图,三个类别,同时带VOC和YOLO两套标注,解压就能用。它解决的不只是“有数据可用”,而是把从标注格式到训练配置之间最容易翻车的那段路提前铺平了。适合两类人:刚上手YOLO、想拿小规模真实场景数据练手的新手,以及要快速验证航拍视角下检测方案的从业者。屋顶检测在航拍场景里很有代表性——目标小、背景杂、正射视角多,这些特性后面会反复影响你的参数选择。

2. VOC和YOLO两种标注格式:结构差异与转换逻辑

2.1 VOC格式:XML标注文件逐字段拆解

VOC格式把每张图的标注信息存在同名XML文件里,文件名和图片文件名一一对应。打开一个XML,核心字段就那几个:filename指向图片名,size记录宽高和通道数,object块里是每个目标的类别名和外包框坐标。

<annotation> <folder>images</folder> <filename>roof_001.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>roof</name> <bndbox> <xmin>320</xmin> <ymin>240</ymin> <xmax>850</xmax> <ymax>620</ymax> </bndbox> </object> </annotation>

XML里的坐标是像素绝对值,左上角为原点,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=(320+850)/2=585,归一化后585/1920≈0.3047;中心点y=(240+620)/2=430,归一化后430/1080≈0.3981;宽=850-320=530,归一化后530/1920≈0.2760;高=620-240=380,归一化后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_ok=True) 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_building

path是数据集根目录的绝对路径,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_ok=True) 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 data=dataset/data.yaml model=yolov8n.pt epochs=150 batch=16 imgsz=640 patience=30 device=0

model=yolov8n.pt是YOLOv8的nano版本,参数量最小,适合小数据集,不容易过拟合。epochs=150不是必须跑满,配合patience=30做早停,连续30轮验证集指标不提升就自动停。batch=16在常见显卡上都跑得动,显存8G以上没有压力。imgsz=640是YOLOv8默认输入尺寸。

有一个常用技巧:先冻结主干训练前50轮,再解冻微调。命令加一行参数:

yolo detect train data=dataset/data.yaml model=yolov8n.pt epochs=150 batch=16 imgsz=640 freeze=10

freeze=10冻结前10层,主干部分的预训练权重不被破坏,专注训练检测头。这个做法对屋顶检测这种小样本任务比较友好,能减少因为数据量不足导致的特征提取层漂移。

4.2 评估指标:mAP、混淆矩阵与损失曲线怎么看

训练结束后,YOLOv8会生成results.csv和一堆图表。主要看三个东西:mAP@0.5、mAP@0.5:0.95和混淆矩阵。

mAP@0.5表示IoU阈值0.5下的平均精度,是相对宽松的评估口径。屋顶检测任务中,框的IoU超过0.5就算命中,这个指标能到0.8以上说明模型基本可用。mAP@0.5:0.95是IoU从0.5到0.95每隔0.05取一次的平均,更严格,小目标在这项上容易掉分。

混淆矩阵看的是类别间的互相误检。屋顶检测里最常见的误检模式是坡屋顶和小型建筑互相混(如果类别里有这类干扰),以及屋顶边缘和外墙线被误框。处理逻辑上,如果混淆矩阵里某两个类别互相高响应,常见做法有两种:一是叠加训练数据,让模型看到更多这类相似目标的区分特征;二是检查标注框是否描得太松,框的边界把相邻背景也包进去了,导致模型学到的特征不纯。

损失曲线方面,box_loss和cls_loss在训练集上持续下降、在验证集上不再下降时,就说明模型容量已经到头了,继续加epoch只会过拟合,不会提升泛化。

4.3 导出部署:从pt到onnx,一步操作

训练完的best.pt可以直接做推理,但如果要部署到没有PyTorch环境的地方,通常导出成onnx:

yolo export model=runs/detect/train/weights/best.pt format=onnx imgsz=640

导出时注意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,比如imgsz=960,模型能看到更多细节。第三是检查标注框是否贴得够紧,如果框把屋顶附近的空地也包进去了,等于让模型学了错误的目标边界,缩小标注框能直接提升小目标AP。切图推理有一个注意点:推理结果要过滤掉切片边缘的框,否则拼接时同一个目标在边界上被重复检出。

5.3 坐标归一化越界:转换脚本没做边界裁剪

现象:训练日志里出现WARNING: ignoring corrupt label,对应图片在损失计算时被跳过,一张图里的某些目标直接不参与训练。

原因:标注框超出了图片边界,转换时没做裁剪,归一化坐标出现大于1或小于0的值。YOLO训练时对这些非法标注直接忽略。数据集中手工标注的框偶尔会画出边界几个像素,人眼看不出来,但坐标一归一化就暴露了。

解决:转换脚本里加边界裁剪,把xmin、ymin限制在0到宽高范围内,同时保证xmax > xmin、ymax > ymin:

xmin = 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 resume=True

它会自动读取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, conf=0.35, imgsz=640) 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)

参数说明:conf=0.35是置信度阈值,可视化时放低一点,可以看到模型“犹豫”的输出,如果0.35以下还有大量合理框,说明模型其实学了更多但被阈值挡掉了。imgsz=640要和训练时保持一致,否则预测框的坐标精度会受影响。model.names从yaml里读取类别名,画图时直接显示文本。

怎么做视觉排查:顺序翻看验证集图片,重点关注三类现象。一是框和屋顶边缘的贴合度,如果框整体偏向一侧或明显外扩,说明标注本身画得松,训练出来的框自然不准;二是同一张图里某个类别频繁漏检,说明该类别和背景的区分度不足;三是相邻目标框重叠严重,常发生在坡屋顶密集区域,如果预测框覆盖两个目标,可能需要调整NMS参数。这一轮视觉检查通过后,再回头调整训练参数或标注,方向会明确很多。

从那以后,我每次训练完都会强制自己跑一遍可视化循环,至少翻20张验证集图片再决定要不要发版。指标可以骗人,图片不太会。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询