ARTICLE DETAIL

资讯详情

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

NWPU VHR-10遥感目标检测实战:YOLO格式转换与训练避坑指南

NWPU VHR-10遥感目标检测实战:YOLO格式转换与训练避坑指南 简介这份资源面向计算机、电子信息工程、数学等专业的学生与算法初学者提供一套可直接投入训练的遥感目标检测数据集解决自建数据标注耗时、格式不统一的问题。数据源自NWPU VHR-10覆盖飞机、轮船、储罐、棒球场、网球场、篮球场、地面跑道、港口、桥梁和车辆共10个类别图像与标注一一对应省去清洗与转换环节。压缩包共1600个文件由800张jpg图像与800个xml标注文件组成xml可直接对接YOLO等主流检测框架的解析流程整体约73.71MB体积轻便便于本地加载与迁移。目前已有1395人学习下载适合课程设计、期末大作业与毕业设计等场景读者可据此快速搭建训练与验证流程把精力集中在模型调参、结构改进与结果分析上也可作为遥感检测入门到进阶的练手素材。1. 遥感目标检测为什么总在“小目标”上翻车从 NWPU VHR-10 说起拿到一份标注好的遥感数据集第一反应往往是“直接上 YOLO 跑一遍不就行了”。真跑起来才发现飞机、储油罐、棒球场这些目标在 800 张图像里分布极不均衡小目标密集、背景复杂、尺度跨度大模型很容易把操场认成网球场把港口里的船漏掉一半。NWPU VHR-10 就是这样一个典型的遥感检测数据集10 个类别包含飞机、舰船、储油罐、棒球场、网球场、篮球场、田径场、港口、桥梁、车辆图像来自高分辨率卫星和航空影像每张图都有对应的 xml 标注文件。它适合谁适合已经跑通过 COCO 或 VOC 上 YOLO 流程、想切进遥感目标检测方向的工程师也适合做毕设或课程项目、需要一份“拿来就能训”的标注数据的学生。这一章先把这份数据的脾气讲清楚后面再动手。2. 把 NWPU VHR-10 的 xml 喂给 YOLO格式转换与目录组织2.1 先看清 xml 里到底标了什么NWPU VHR-10 的标注文件是标准 Pascal VOC 风格的 xml每个文件对应一张图像内部结构大致如下annotation folderpositive/folder filename001.jpg/filename size width1024/width height768/height depth3/depth /size object nameairplane/name bndbox xmin120/xmin ymin45/ymin xmax260/xmax ymax180/ymax /bndbox /object !-- 可能还有多个 object -- /annotation关键点有三个一是name里的类别名是英文且不同来源的 xml 可能大小写不一致二是bndbox的坐标是左上角和右下角的绝对像素值不是归一化坐标三是有些图像可能包含多个同类目标转换时要逐个 object 处理不能只取第一个。我一般会先写一个统计脚本把 800 张图里每个类别的框数量、宽高分布、是否有空标注跑一遍心里有数再转格式。2.2 转成 YOLO 需要的 txt脚本与四个边界坑YOLO 训练需要的是每张图一个 txt每行class_id x_center y_center width height全部归一化到 0~1。下面这个脚本是我常用的转换逻辑直接改路径就能跑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] CLASS_MAP {name: i for i, name in enumerate(CLASSES)} def convert_xml_to_txt(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.strip().lower() # 处理常见别名避免类别对不上 if name storagetank: name storage_tank if name not in CLASS_MAP: print(f跳过未知类别: {name} in {xml_path}) continue cls_id CLASS_MAP[name] bnd obj.find(bndbox) xmin float(bnd.find(xmin).text) ymin float(bnd.find(ymin).text) xmax float(bnd.find(xmax).text) ymax float(bnd.find(ymax).text) # 边界裁剪防止标注越界导致归一化后为负 xmin max(0, min(xmin, img_w - 1)) ymin max(0, min(ymin, img_h - 1)) xmax max(0, min(xmax, img_w - 1)) ymax max(0, min(ymax, img_h - 1)) if xmax xmin or ymax ymin: print(f跳过无效框: {xml_path}) continue 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}) with open(txt_path, w) as f: f.write(\n.join(lines)) # 批量处理时图像尺寸从 xml 的 size 里读不要硬编码逻辑说明先建立类别名到 id 的映射遇到未知类别直接跳过并打印避免训练时标签越界。坐标裁剪是血泪经验遥感标注里偶尔有框超出图像边界不裁剪会导致归一化后出现负数YOLO 训练时直接报错或学出诡异框。参数方面CLASSES的顺序一旦确定就不要改否则之前训的权重全废x_center保留 6 位小数足够再多了也没意义。2.3 目录结构怎么摆才不返工YOLO 官方推荐的结构是 images 和 labels 分开train/val 各自成对。我一般会这样组织dataset/ images/ train/ (约 640 张) val/ (约 160 张) labels/ train/ val/ data.yamldata.yaml内容如下path: ./dataset train: images/train val: images/val nc: 10 names: [airplane, ship, storage_tank, baseball_diamond, tennis_court, basketball_court, ground_track_field, harbor, bridge, vehicle]注意names的顺序必须和转换脚本里的CLASSES完全一致差一个顺序模型学到的就是错位的类别。划分 train/val 时不要随机打散遥感图像里同一区域的多张图如果同时出现在训练和验证集验证指标会虚高。我一般按图像来源或地理位置做分组划分没有来源信息时至少保证同一场景的图不跨集。3. 用 YOLOv8 在 NWPU VHR-10 上跑通第一个 baseline3.1 环境配置与最小训练命令环境这块Ultralytics 的 YOLOv8 是目前上手最快的选择pip 装完就能用。我一般会建一个干净的 conda 环境避免和之前的 torch 版本打架conda create -n nwpu_yolo python3.10 -y conda activate nwpu_yolo pip install ultralytics装完后直接命令行训练yolo detect train \ data./dataset/data.yaml \ modelyolov8s.pt \ epochs100 \ imgsz640 \ batch16 \ device0 \ projectruns/nwpu \ namebaseline参数说明modelyolov8s.pt是预训练权重遥感数据量不大时从预训练起步比从头训收敛快很多imgsz640是默认输入尺寸NWPU VHR-10 原图普遍在 1000 像素以上直接缩到 640 会丢小目标细节后面会讲怎么调batch16在 8G 显存上比较稳显存不够就降到 8 或 4device0指定第一块 GPUCPU 训练会慢到怀疑人生。3.2 训练日志里该盯哪几个数跑起来之后控制台会输出每一轮的 box_loss、cls_loss、dfl_loss 和 mAP50、mAP50-95。遥感检测里最该盯的是 mAP50-95 和每一类的 AP因为小目标类比如 vehicle的 AP 往往远低于大目标类比如 ground_track_field。如果 box_loss 一直不降先检查标签格式是不是有负数或超出 1 的值如果 cls_loss 震荡可能是类别不均衡太严重车辆和飞机的框数量可能差一个数量级。我一般会在训练中途用yolo detect val单独跑一次验证看看混淆矩阵里哪些类互相误判比如网球场和篮球场在低分辨率下确实容易混。3.3 推理与可视化确认模型到底学到了什么训练完拿一张验证集图像跑推理yolo detect predict \ modelruns/nwpu/baseline/weights/best.pt \ source./dataset/images/val \ conf0.25 \ saveTrueconf0.25是默认置信度阈值遥感小目标建议先调到 0.1 看看召回再根据误报情况往上加。推理结果会保存在runs/detect/predict下重点看两类问题一是密集小目标有没有被漏掉二是大目标有没有被重复框。如果发现储油罐这种圆形目标框得歪歪扭扭说明数据里的标注本身可能就不够紧这时候要么清洗标注要么在训练时加一点旋转增强。4. 遥感小目标检测的避坑与排查清单4.1 现象mAP 看着还行但小目标全漏原因imgsz640把原图缩小后车辆、小船这类目标只剩十几个像素YOLO 的 stride 下采样后特征几乎消失。解决把imgsz提到 1024 或 1280同时batch相应降到 4 或 2如果显存实在不够可以用切片推理把大图裁成重叠的小块分别检测再合并。4.2 现象训练 loss 正常验证 mAP 为 0原因data.yaml里的names顺序和转换脚本里的CLASSES不一致或者 labels 目录路径写错导致读不到标签。解决先跑一遍yolo detect train看它打印的数据集统计确认nc和每类实例数再用几行 Python 检查一个 txt 文件里的 class_id 是否在 0~9 之间。4.3 现象模型把港口认成桥梁原因这两个类在遥感图像里都有长条形结构且训练样本中港口和桥梁的上下文高度重叠。解决不要只靠 YOLO 的默认增强加入 mosaic 和 mixup 的同时针对性地对这两类做难例挖掘把误判的图单独拿出来重新标注或加权重。4.4 现象训练到一半显存爆了原因imgsz调大后没有同步降batch或者workers开太多导致内存泄漏。解决按imgsz翻倍、batch减半的原则调整workers在 Linux 上设 8 左右Windows 上设 0 或 2避免多进程读图卡死。4.5 现象验证集指标远高于测试集原因划分数据时同一区域的图被分到了两边模型其实在“背答案”。解决按图像来源或地理区块做分组划分确保验证集里的场景在训练集中没有出现过。如果数据量实在少至少做 5 折交叉验证看指标是否稳定。5. 把 NWPU VHR-10 用出更多价值从 baseline 到可交付模型5.1 用类别权重和损失函数调优小目标YOLOv8 默认的分类损失是 BCE对类别不均衡没有额外处理。如果车辆类的 AP 明显拖后腿可以在训练时给不同类别加权重或者换用 focal loss 的思路。Ultralytics 没有直接暴露类别权重参数但可以通过复制车辆类样本、或在数据集中对车辆密集区域做过采样来间接实现。我一般会先跑一版 baseline看每类 AP 的差距再决定要不要动损失函数而不是一上来就改。5.2 验证模型是否真的可交付三个检查动作第一拿一批完全没有参与训练的遥感图跑推理看漏检和误报是否在可接受范围第二用yolo export formatonnx导出 ONNX确认推理速度和精度损失第三把模型放到实际业务的分辨率下测试比如原始影像不缩放直接切片推理看小目标召回是否达标。这三个动作做完才能说这个模型不是“只在验证集上好看”。5.3 一个具体技巧切片推理合并框遥感图像往往很大直接缩放会丢小目标。我常用的做法是把原图按 640×640 切片重叠 128 像素每片单独推理再把所有框映射回原图坐标最后用 NMS 合并。这样小目标的召回能提升一大截代价是推理时间线性增加。如果业务对实时性要求不高这个技巧性价比很高。import cv2 import numpy as np from ultralytics import YOLO model YOLO(runs/nwpu/baseline/weights/best.pt) img cv2.imread(test_large.jpg) h, w img.shape[:2] tile, overlap 640, 128 all_boxes [] for y in range(0, h, tile - overlap): for x in range(0, w, tile - overlap): crop img[y:ytile, x:xtile] if crop.shape[0] tile or crop.shape[1] tile: crop cv2.copyMakeBorder(crop, 0, tile-crop.shape[0], 0, tile-crop.shape[1], cv2.BORDER_CONSTANT, value(0,0,0)) results model(crop, conf0.15, verboseFalse) for box in results[0].boxes: x1, y1, x2, y2 box.xyxy[0].tolist() all_boxes.append([x1x, y1y, x2x, y2y, box.conf[0].item(), box.cls[0].item()]) # 这里接一个 NMS 做全局合并IoU 阈值建议 0.5逻辑说明切片时保证每块和相邻块有重叠避免目标被切一半导致漏检conf调低到 0.15 是为了先拿高召回后面再用 NMS 压误报。参数上tile和overlap要根据目标尺寸调车辆多就减小 tile飞机多可以适当放大。这个方案我在地理信息相关的项目里反复用过比直接缩放整图稳得多。最后说个习惯每次拿到新的遥感数据集我都会先花半小时把标注可视化一遍随机抽 20 张图把框画出来看。这一步能提前发现大量标注错误和类别混淆比训完再回头查省事得多。希望帮到你。本文还有配套的精品资源点击获取
返回列表