简介:本资源为NWPU VHR-10遥感目标检测数据集的完整标注版本,面向计算机、电子信息工程、数学等专业的学生及算法入门者,可直接用于YOLO目标检测模型的训练与课程设计、期末大作业、毕业设计等场景。数据集覆盖飞机、轮船、储罐、棒球场、网球场、篮球场、地面跑道、港口、桥梁和车辆共10个类别,每张图像均配有对应的XML标注文件,省去自行标注的繁琐环节。压缩包共1600个文件,包含800张jpg图像与800个xml标注文件,整体约73.71MB,图像与标注一一对应,目录结构清晰,便于直接接入YOLO训练流程或转换为其他格式。目前已有1395人学习下载,配套代码采用参数化编程,参数修改方便,注释详细,编程思路清晰,可帮助读者快速完成数据加载、类别统计与模型训练验证,是遥感检测方向较为实用的入门与实战素材。
1. 遥感目标检测落地:为什么我盯上了这份 NWPU VHR-10 已标注数据
做遥感目标检测的同学大概都有过这种体验:模型结构调了一整天,结果卡在数据上——要么找不到带标注的图,要么标注格式和 YOLO 对不上,要么类别定义模糊,飞机和跑道混在一起。我最近拆了一份 NWPU VHR-10 的已标注数据包,800 张遥感图像,配套 XML 标注文件,覆盖飞机、轮船、储罐、棒球场、网球场、篮球场、地面跑道、港口、桥梁、车辆共 10 个类别。这不是那种需要自己写爬虫、手动框选的半成品,而是可以直接喂给 YOLO 训练流程的成品数据。适合谁?做课程设计、期末大作业、毕业设计的学生,以及需要快速验证检测算法改进效果的工程师。遥感场景和自然图像不一样,目标小、方向任意、背景复杂,这份数据正好能把这些坑暴露出来。下面我从数据本身讲到训练落地,再到实际踩过的坑,一步步拆开。
2. NWPU VHR-10 数据解剖:10 类标注的分布与 YOLO 适配逻辑
2.1 数据目录结构与标注格式
拿到压缩包解压后,常见的目录组织是这样的:一个images文件夹放 800 张 JPG 图像,一个annotations文件夹放同名的 XML 文件。XML 是 PASCAL VOC 格式,每个文件对应一张图,里面记录了每个目标的类别名和边界框坐标(xmin, ymin, xmax, ymax)。文件名像000084.jpg、000311.jpg这种纯数字编号,没有多余前缀,省去了重命名的麻烦。
先确认一下目录层级,用一条命令就能看清:
# 查看解压后的目录结构,确认图像和标注是否一一对应 find ./NWPU_VHR-10 -maxdepth 2 -type d # 统计图像数量和XML数量,两者应该相等 ls ./NWPU_VHR-10/images/*.jpg | wc -l ls ./NWPU_VHR-10/annotations/*.xml | wc -l逻辑说明:find只看两层目录,避免输出太多文件;wc -l统计数量,如果图像和 XML 数量不一致,说明有缺失,需要先补齐再往下走。参数上没什么要调的,重点是确认配对关系。
2.2 10 个类别的分布特点与检测难点
这 10 个类别在遥感图像里的尺度差异很大。车辆和轮船往往只有几十个像素,储罐和飞机稍大,棒球场、网球场、篮球场这类场地目标能占到图像的三分之一。这种尺度跨度对 YOLO 的 anchor 设计是个考验。我一般会先跑一遍统计脚本,看看每个类别的实例数量和平均框面积:
import os import xml.etree.ElementTree as ET from collections import defaultdict # 统计每个类别的实例数量和平均边界框面积 class_stats = defaultdict(lambda: {'count': 0, 'area_sum': 0.0}) ann_dir = './NWPU_VHR-10/annotations' for xml_file in os.listdir(ann_dir): if not xml_file.endswith('.xml'): continue tree = ET.parse(os.path.join(ann_dir, xml_file)) root = tree.getroot() for obj in root.findall('object'): name = obj.find('name').text 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) area = (xmax - xmin) * (ymax - ymin) class_stats[name]['count'] += 1 class_stats[name]['area_sum'] += area for name, stat in sorted(class_stats.items()): avg_area = stat['area_sum'] / stat['count'] if stat['count'] > 0 else 0 print(f"{name}: 实例数={stat['count']}, 平均面积={avg_area:.1f} 像素")逻辑说明:用ElementTree解析 XML,遍历每个object节点,提取类别名和边界框坐标,累加实例数和面积。参数上,area是像素面积,能帮你判断哪些类别属于小目标。如果车辆的平均面积低于 32×32,就得考虑在 YOLO 里增加小目标检测层,或者调整输入分辨率。
2.3 从 VOC XML 到 YOLO TXT:转换脚本与类别映射
YOLO 训练需要的是每张图对应一个 TXT 文件,每行格式为类别索引 中心x 中心y 宽 高,且坐标要归一化到 0~1。类别索引从 0 开始,按你定义的类别顺序来。下面这个脚本我用了很多次,直接改路径就能跑:
import os import xml.etree.ElementTree as ET # 类别列表,顺序要和训练时的 data.yaml 一致 classes = ['airplane', 'ship', 'storage_tank', 'baseball_diamond', 'tennis_court', 'basketball_court', 'ground_track_field', 'harbor', 'bridge', 'vehicle'] def convert_annotation(xml_path, txt_path, img_w, img_h): tree = ET.parse(xml_path) root = tree.getroot() lines = [] for obj in root.findall('object'): name = obj.find('name').text if name not in classes: continue cls_id = classes.index(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) # 归一化并计算中心点、宽高 cx = (xmin + xmax) / 2.0 / img_w cy = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}") with open(txt_path, 'w') as f: f.write('\n'.join(lines)) # 假设图像尺寸已知,实际可从图像读取 img_w, img_h = 640, 640 # 这里需要根据实际图像尺寸修改 ann_dir = './NWPU_VHR-10/annotations' out_dir = './NWPU_VHR-10/labels' os.makedirs(out_dir, exist_ok=True) for xml_file in os.listdir(ann_dir): if xml_file.endswith('.xml'): txt_file = xml_file.replace('.xml', '.txt') convert_annotation(os.path.join(ann_dir, xml_file), os.path.join(out_dir, txt_file), img_w, img_h)逻辑说明:classes列表的顺序决定了 TXT 里的类别索引,必须和后续data.yaml里的names完全一致,否则训练时类别会错位。img_w和img_h要换成每张图的实际尺寸,如果所有图像尺寸统一,直接写死就行;如果不统一,得用 OpenCV 或 PIL 逐张读取。归一化后的坐标保留 6 位小数足够,YOLO 官方也是这个精度。
3. YOLO 训练配置:从 data.yaml 到超参数调优
3.1 数据集划分与 data.yaml 编写
800 张图不算多,按 7:2:1 划分训练集、验证集、测试集比较稳妥。划分时要注意类别均衡,别让某个类别在验证集里一个实例都没有。我一般写个简单脚本随机划分,然后生成 YOLO 需要的train.txt、val.txt、test.txt,每行是图像的绝对路径或相对路径。
import os import random # 按 7:2:1 划分数据集,生成 YOLO 所需的 txt 列表 img_dir = './NWPU_VHR-10/images' all_imgs = [f for f in os.listdir(img_dir) if f.endswith('.jpg')] random.seed(42) # 固定随机种子,保证可复现 random.shuffle(all_imgs) n = len(all_imgs) train_imgs = all_imgs[:int(0.7 * n)] val_imgs = all_imgs[int(0.7 * n):int(0.9 * n)] test_imgs = all_imgs[int(0.9 * n):] for name, subset in [('train', train_imgs), ('val', val_imgs), ('test', test_imgs)]: with open(f'./NWPU_VHR-10/{name}.txt', 'w') as f: for img in subset: f.write(os.path.join(img_dir, img) + '\n')逻辑说明:random.seed(42)保证每次划分结果一致,方便复现实验。路径写绝对路径或相对路径都行,YOLO 的data.yaml里会指定path根目录。划分比例不是死的,如果类别实例太少,可以适当增加训练集比例。
接着写data.yaml:
# NWPU VHR-10 数据集配置 path: ./NWPU_VHR-10 train: train.txt val: val.txt test: test.txt nc: 10 names: ['airplane', 'ship', 'storage_tank', 'baseball_diamond', 'tennis_court', 'basketball_court', 'ground_track_field', 'harbor', 'bridge', 'vehicle']参数说明:nc是类别数,必须和names长度一致。path是根目录,train、val、test是相对路径。如果图像和标签不在同一目录,YOLO 默认会去images同级找labels,所以目录结构最好是images/train和labels/train这种镜像结构。这份数据原始结构可能不是这样,需要手动整理一下。
3.2 YOLOv8 训练命令与关键参数
YOLOv8 是目前比较稳的选择,Ultralytics 的接口也简洁。假设你已经装好了ultralytics包,一条命令就能启动训练:
# 使用 YOLOv8n 预训练权重在 NWPU VHR-10 上微调 yolo detect train \ model=yolov8n.pt \ data=./NWPU_VHR-10/data.yaml \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ device=0 \ project=./runs/nwpu \ name=yolov8n_exp1参数说明:model选yolov8n.pt是因为数据量不大,小模型不容易过拟合;imgsz=640是遥感检测的常用输入尺寸,再大显存吃不消;batch=16在 8GB 显存上比较稳,显存小就降到 8;lr0=0.01是初始学习率,微调时通常比从头训练小一个数量级;patience=20表示验证集指标 20 轮不提升就早停,省时间。device=0指定第一块 GPU,没 GPU 就写cpu,但速度会慢很多。
训练过程中重点看mAP50和mAP50-95两个指标。遥感小目标多,mAP50-95通常比自然图像低不少,别一看数字低就以为模型崩了。如果mAP50在 0.6 以上,说明基本可用;低于 0.4 就得检查数据转换和类别映射有没有错。
3.3 推理验证与结果可视化
训练完先别急着导出模型,用验证集跑一遍推理,看看实际检测效果:
# 在验证集上评估模型性能 yolo detect val \ model=./runs/nwpu/yolov8n_exp1/weights/best.pt \ data=./NWPU_VHR-10/data.yaml \ imgsz=640 \ batch=16 \ conf=0.25 \ iou=0.5参数说明:conf=0.25是置信度阈值,低于这个值的框会被过滤;iou=0.5是 NMS 的 IoU 阈值,控制重叠框的合并程度。遥感目标密集时,iou可以适当调低到 0.4,避免漏检相邻目标。跑完会输出每个类别的精确率、召回率和 mAP,重点看车辆、轮船这些小目标的召回率,如果明显偏低,说明模型对小目标欠拟合。
想直观看看检测效果,可以用predict模式:
# 对单张图像或整个文件夹做推理,保存可视化结果 yolo detect predict \ model=./runs/nwpu/yolov8n_exp1/weights/best.pt \ source=./NWPU_VHR-10/images/000084.jpg \ conf=0.25 \ save=True \ project=./runs/nwpu/predict逻辑说明:source可以指向单张图、文件夹或视频。save=True会把带框的图存到project目录下。检查可视化结果时,重点看有没有把棒球场误判成篮球场,这两个类别在遥感图像里纹理相似,容易混淆。
4. 避坑与排查:遥感检测训练中常见的 5 个翻车点
4.1 类别名大小写不一致导致标注丢失
现象:转换脚本跑完,TXT 文件里某些类别的行数为零,训练时这些类别完全学不到。原因:XML 里的类别名可能是Airplane或airplane,而你的classes列表写的是airplane,if name not in classes直接跳过了。解决:在转换脚本里加一行name = name.lower().strip(),统一转小写并去空格。转换完再统计一遍每个类别的实例数,和原始 XML 对比,确认没丢。
4.2 图像尺寸不统一导致坐标归一化错误
现象:训练 loss 一直不降,或者检测框位置明显偏移。原因:脚本里写死了img_w=640, img_h=640,但实际图像尺寸可能是 500×500 或 1024×1024,归一化坐标全错了。解决:用 PIL 或 OpenCV 逐张读取图像尺寸,传给转换函数。如果图像尺寸差异大,建议先统一 resize 到 640×640,再重新生成标注。
4.3 训练集和验证集类别分布不均
现象:验证集 mAP 波动很大,某些类别时好时坏。原因:随机划分时没考虑类别均衡,某个类别可能全被分到训练集,验证集里一个实例都没有。解决:划分前先统计每个类别的实例列表,按类别分层抽样。简单做法是每个类别按比例抽取,保证验证集里每个类别至少有 5~10 个实例。
4.4 小目标漏检严重
现象:车辆、轮船的召回率低于 0.3,大目标检测正常。原因:YOLO 默认的 anchor 和特征层对小目标不友好,遥感图像里小目标像素太少。解决:把imgsz从 640 提高到 1024,或者改用 YOLOv8 的 P2 检测层(需要修改模型配置)。另外,数据增强里加上mosaic=1.0和scale=0.5,增加小目标的出现频率。
4.5 XML 文件编码问题导致解析失败
现象:转换脚本报ParseError,某些 XML 文件读不了。原因:XML 文件可能包含 BOM 头或非 UTF-8 编码。解决:用ET.parse时指定编码,或者先用open(xml_path, 'r', encoding='utf-8-sig')读入字符串再解析。批量处理时加个 try-except,把出错的文件名打印出来,单独处理。
5. 进阶技巧:用 TensorRT 加速推理与 mAP 验证
训练完的 PyTorch 模型在推理时速度一般,如果要做部署或者批量测试,导出 TensorRT 引擎能快不少。YOLOv8 支持直接导出:
# 导出 TensorRT 引擎,FP16 精度,适合 RTX 系列显卡 yolo export \ model=./runs/nwpu/yolov8n_exp1/weights/best.pt \ format=engine \ half=True \ imgsz=640 \ device=0参数说明:half=True启用 FP16 精度,速度提升明显,精度损失通常在 1% 以内;imgsz=640要和训练时一致,否则检测框会错位。导出完成后会生成.engine文件,用yolo detect predict时把model指向这个文件即可。
验证 TensorRT 引擎的精度有没有掉,最直接的方法是在验证集上跑一遍val,对比 PyTorch 模型的 mAP:
# 用 TensorRT 引擎在验证集上评估,对比精度 yolo detect val \ model=./runs/nwpu/yolov8n_exp1/weights/best.engine \ data=./NWPU_VHR-10/data.yaml \ imgsz=640 \ batch=16如果 mAP 掉了超过 2 个百分点,检查一下half和imgsz是否和训练配置匹配。另外,TensorRT 引擎和显卡架构绑定,换显卡后需要重新导出。
还有一个容易被忽略的点:遥感图像的通道顺序。有些数据集保存时是 RGB,有些是 BGR,YOLO 默认按 RGB 处理。如果推理结果颜色异常或者检测框偏移,用 OpenCV 读图后加一行cv2.cvtColor(img, cv2.COLOR_BGR2RGB)转一下。我一般会在推理脚本里固定这个操作,避免换数据集时翻车。
从那以后我每次拿到新的遥感数据集,都强制走一遍「统计类别分布 → 检查图像尺寸 → 转换标注 → 可视化抽查」这四步,确认无误再开训练。希望帮到你。
本文还有配套的精品资源,点击获取