ARTICLE DETAIL

资讯详情

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

卫星图像目标检测实战:数据检查、YOLO训练参数与避坑全指南

卫星图像目标检测实战:数据检查、YOLO训练参数与避坑全指南 简介NWPU VHR-10卫星图像数据集完整版含800张高分辨率遥感图像为遥感目标检测任务提供高分辨率影像及完整标注覆盖飞机、轮船、储罐、棒球场、网球场、篮球场、地面跑道、港口、桥梁和车辆共10个类别。图像与标注一一对应可直接用于YOLO等目标检测模型的训练和验证适合高校学生在课程设计、期末大作业和毕业设计中快速搭建视觉识别实验。压缩包共2612个文件以jpg图像、xml标注和txt标签为主体辅以缓存与说明文件整体大小约61.15MB目录组织清晰便于按需取用。已有321人学习下载适合具有一定深度学习基础、需要真实卫星场景数据开展实践的读者也可用于遥感图像分析相关教学与科研场景。资源免去了自行采集、清洗和标注的繁琐流程标注格式规范可帮助使用者把精力集中在模型调优与结果分析上。1. 卫星图像目标检测决定成败的是数据不是模型拿到一个写着“YOLO目标检测卫星图像数据集已标注可以直接使用800张图像对应已标注文件.rar”的压缩包多数人第一反应是解压、建个yolo训练脚本、直接开跑。但我建议先停一下。卫星图像目标检测和普通目标检测有个本质差异目标尺寸小、背景纹理复杂、单类样本往往极不均衡。800张图像加对应标注文件对遥感场景来说恰好够做一次迁移学习、够验证一个可行性基线却远没到可以盲目开训的程度。真正决定你是拿到可用模型还是白跑几天显卡的不是模型结构有多新而是“已标注”这三个字背后到底有没有坑。标注格式、类别顺序、坐标合法性、图像和标签配对关系每一项都可能让训练结果从 baseline 变成翻车现场。这篇笔记按拿到数据后的真实操作顺序来写先检查再转换然后训练、排查、验证落地每一步都给出可复现的命令和参数。2. 拿到已标注数据集先做检查三类问题不查清就跑不踏实2.1 解压后先看目录结构文件名乱码和路径分层最容易被忽略这份数据是.rar压缩包服务端解压常用7z不要直接用unzip。在 Linux 下先把工具装好再解压到固定目录避免中文压缩包名在命令行里带来转义问题# 安装解压工具Debian/Ubuntu 系 sudo apt install p7zip-full # 解压到 data 目录-o 后面不接空格 7z x YOLO目标检测卫星图像数据集已标注可以直接使用800张图像对应已标注文件.rar -odata # 看两层目录结构确认 images 和 labels 的放置方式 find data -maxdepth 2 -type d | sort常见的数据集布局有两种一种是images/和labels/平级另一种是每个子目录里同时放.jpg和.xml/.json。用上面的命令一眼就能分清。这里我先提醒一个问题Windows 上打包的 rar在 Linux 下解压偶尔会出现中文文件名乱码。现象是图像文件名变成一串问号或者乱码字符后面写配对脚本时要么找不到文件要么把\u转义当成真实路径。遇到乱码最省事的办法是在本地 Windows 先解压把文件名统一改成英文或拼音再重新打包传服务器。这一步看着琐碎但直接影响后面所有脚本的路径映射。目录结构确认后还需要统计真实样本数。用标题里写的是“800张图像”但解压出来可能多几张少几张别一上来就把它当成固定超参数写死在代码里。先数清楚再说# 统计图片数量和标注文件数量 find data -type f \( -name *.jpg -o -name *.png -o -name *.tif \) | wc -l find data -type f \( -name *.xml -o -name *.json -o -name *.txt \) | wc -l卫星影像常见格式是.tif但 YOLO 系列读.tif不一定顺畅如果发现大量.tif建议先用opencv或PIL批量转成.jpg。转格式时注意别把地理坐标信息当图像数据读进来普通目标检测只需要像素矩阵不需要波段堆叠。2.2 识别标注格式XML、JSON 还是直接就是 YOLO 的 TXT“已标注”这个词在不同数据集里含义差别很大。我见过标成 Pascal VOC XML 的见过 COCO JSON 的也见过直接就是 YOLO 格式 TXT 的。这几种预处理路径完全不同。先用file命令看清楚# 抽查前几个标注文件的真实格式 file data/annotations/* | head -20 # 如果是 XML直接看标签内容 head -30 $(find data/annotations -name *.xml | head -1) # 如果是 TXT查看前几行内容 head -5 $(find data/annotations -name *.txt | head -1)判断依据很简单XML 文件根节点一般是annotation里面是object和bndboxJSON 文件通常是images和annotations两个大数组TXT 文件每行是class_id x_center y_center width height这种归一化坐标而且是纯文本。如果原始标注已经是 YOLO 格式的 TXT别高兴太早这部分反而最容易藏问题。TXT 里没有类别名只有数字编号所以必须搞清楚编号对应的真实类别也就是拿到一份classes.txt或者data.yaml。同样编号0在 A 数据集里是飞机在 B 数据集里可能是船舶。没有类别清单的 YOLO 标注基本等于半个黑匣子训练出来的模型你根本不知道它在识别什么。2.3 统计类别分布和空标签数据均衡性决定训练策略确认标注格式后我做一次全量统计。用 Python 解析 XML 是最通用的方式因为 VOC 格式在所有标注工具里最基础import glob import xml.etree.ElementTree as ET xml_files glob.glob(data/annotations/*.xml) category_stats {} empty_labels [] for xml_file in xml_files: root ET.parse(xml_file).getroot() names [obj.findtext(name) for obj in root.iter(object)] if not names: empty_labels.append(xml_file) for name in set(names): category_stats[name] category_stats.get(name, 0) 1 print(类别分布:, category_stats) print(空标签文件数量:, len(empty_labels))这段代码的关键是obj.findtext(name)。有些标注工具导出的 XML 里name节点带前缀或者大小写不一致比如airplane和Airplane会被统计成两个类实际是同一种目标。遇到这种情况先统一小写再统计。空标签文件必须特别关注。YOLO 训练时允许没有标注的图片作为负样本但前提是labels目录里存在对应的空.txt文件而不是干脆没有这个文件。如果 XML 存在但没有object节点转换时也要生成内容为空的目标 txt否则训练过程会报“no labels found”的警告而且负样本随机性就没法控制。类别分布方面卫星图典型任务如飞机、舰船、储油罐、车辆、桥梁通常存在严重的类别不均衡。如果某个类别的目标只有三五十个训练时这个类大概率学不出来。看到这种分布后续需要针对性做复制粘贴增强或者调整损失权重而不是直接开训。这一步统计完数据能不能用基本就有数了。3. 把标注转成 YOLO 格式坐标归一化脚本与四个必须处理的边界3.1 VOC XML 转 YOLO TXT脚本本身不难难在细节处理假设数据集是 Pascal VOC 格式也就是每个图像对应一个 XML。标准 YOLO TXT 要求每行class_id x_center y_center width height全部用归一化坐标。我的转换脚本如下import cv2 import xml.etree.ElementTree as ET from pathlib import Path IMG_DIR Path(data/images) XML_DIR Path(data/annotations) OUT_DIR Path(data/labels) CLASSES [airplane, ship, oil_tank, vehicle] # 严格按 data.yaml 顺序 def voc2yolo(img_path: Path, xml_path: Path, out_path: Path): # 用 OpenCV 读取真实图像尺寸不能直接信 XML 里的 width/height img cv2.imread(str(img_path)) if img is None: print(f图像读取失败: {img_path}) return h, w img.shape[:2] root ET.parse(xml_path).getroot() lines [] for obj in root.iter(object): name obj.findtext(name).strip().lower() if name not in CLASSES: continue cls_id CLASSES.index(name) box obj.find(bndbox) xmin float(box.findtext(xmin)) ymin float(box.findtext(ymin)) xmax float(box.findtext(xmax)) ymax float(box.findtext(ymax)) # 1. 越界坐标直接剪裁到图像范围内 xmin max(0, min(xmin, w)) xmax max(0, min(xmax, w)) ymin max(0, min(ymin, h)) ymax max(0, min(ymax, h)) # 2. 过滤过小目标宽高小于 2 像素的直接丢弃 if xmax - xmin 2 or ymax - ymin 2: continue # 3. 归一化计算 x_center (xmin xmax) / 2.0 / w y_center (ymin ymax) / 2.0 / h box_w (xmax - xmin) / w box_h (ymax - ymin) / h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) out_path.parent.mkdir(parentsTrue, exist_okTrue) out_path.write_text(\n.join(lines), encodingutf-8) for xml_file in XML_DIR.glob(*.xml): img_file IMG_DIR / (xml_file.stem .jpg) if not img_file.exists(): print(f缺少对应图片: {xml_file.stem}) continue out_file OUT_DIR / (xml_file.stem .txt) voc2yolo(img_file, xml_file, out_file)这里有几个值得细说的点。第一图像尺寸一定要自己读不能直接用 XML 里size节点记录的宽高因为某些标注工具在导出时会把图像的 EXIF 旋转信息丢掉导致宽高和真实矩阵不一致。第二越界坐标要剪裁而不是报错。标注工具允许画框时稍微超出图像边缘这种框在训练时会引发边界框损失计算异常。第三过滤小于 2 像素的目标这类目标在损失函数里会被当成噪声处理。类别顺序问题更隐蔽。脚本里CLASSES的顺序必须和后续训练配置文件data.yaml里的names完全一致否则同一个编号在不同阶段对应不同类别训练出的模型完全错乱损失函数看起来正常但验证结果是废的。这个坑我见过太多人踩所以把它单独强调一次。3.2 回读验证把 TXT 标签画回图片上人工抽查转换脚本写完不是结束必须回读验证。做法是把生成的 YOLO 标签画到原图上随机抽几十张看一眼确认框的位置、大小、类别和原图对得上import cv2 import random from pathlib import Path IMG_DIR Path(data/images) LABEL_DIR Path(data/labels) OUT_VIS Path(data/check_vis) OUT_VIS.mkdir(exist_okTrue) image_files list(IMG_DIR.glob(*.jpg)) random.seed(42) sample_files random.sample(image_files, 20) for img_path in sample_files: img cv2.imread(str(img_path)) if img is None: continue h, w img.shape[:2] txt_path LABEL_DIR / (img_path.stem .txt) if not txt_path.exists(): print(f缺少标签文件: {txt_path.name}) continue for line in txt_path.read_text().strip().splitlines(): cls_id, x_c, y_c, bw, bh map(float, line.split()) x1 int((x_c - bw / 2) * w) y1 int((y_c - bh / 2) * h) x2 int((x_c bw / 2) * w) y2 int((y_c bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, str(int(cls_id)), (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) out_path OUT_VIS / (img_path.stem _check.jpg) cv2.imwrite(str(out_path), img)抽查时重点看三类问题一是框是否整体偏移二是类别编号是否和想象中的目标一致三是小目标框是否漏掉。如果前三张图就有问题说明转换脚本哪里不对这时候回头查代码成本最低。如果抽查几十张都正常再进入训练阶段。3.3 归一化坐标的分布统计提前暴露小目标和大宽高比问题转换并回读合格后我还习惯做一次坐标分布统计。把全部标签的宽高、中心点位置输出成直方图能直观看出这个数据集小目标占比有多高、目标是否集中在图像中心区域import numpy as np from pathlib import Path LABEL_DIR Path(data/labels) all_wh [] for txt_file in LABEL_DIR.glob(*.txt): for line in txt_file.read_text().strip().splitlines(): parts line.split() if len(parts) 5: continue bw, bh float(parts[3]), float(parts[4]) all_wh.append((bw, bh)) all_wh np.array(all_wh) print(目标宽度均值比例:, all_wh[:, 0].mean()) print(宽度小于 0.01 的目标占比:, (all_wh[:, 0] 0.01).mean()) print(高度小于 0.01 的目标占比:, (all_wh[:, 1] 0.01).mean())这里的关键判断是归一化宽度小于 0.01意味着这个目标在原图里只占 1% 的宽度。如果图像是 1000 像素宽那目标就只有 10 个像素YOLOv8 的模型在标准 640 输入下几乎不可能稳定检出这种尺寸的目标。看到这种分布就需要在训练时把输入分辨率拉高到 1280 甚至更高或者做滑窗切块。这个统计值直接影响后面训练参数的选择也是判断“这个数据集标注质量到底怎么样”的硬指标。4. 训练参数怎么设遥感小目标数据集的五个关键配置4.1 data.yaml 路径和类别顺序写错一个数字全盘皆输训练配置文件中path必须是绝对路径或者相对yolo命令运行目录的路径train和val是相对path的子目录。names的顺序与前面转换脚本里的CLASSES列表必须完全一致这是硬约束。# dataset.yaml path: /data/satellite train: train val: val names: 0: airplane 1: ship 2: oil_tank 3: vehicle在这儿我要额外说明一点很多人在网上下载的模型权重是在 COCO 上预训练的COCO 的names顺序是 person、bicycle、car……你在yolo detect train时用modelyolov8n.ptUltralytics 会自动读取你传入data的类别数并替换检测头所以不存在“必须匹配 COCO 类别顺序”的问题。但如果你在代码里手动加载权重后再改model.names那就容易翻车。直接用 CLI 命令训练让框架自己处理类别数替换是最稳的做法。4.2 imgsz、batch、lr0 的联动卫星图像不能照搬 640这是整篇笔记里我认为最值得反复推敲的地方。普通目标检测任务默认imgsz640但遥感图像目标小、幅面大直接缩放到 640 等于把原本就微小的目标进一步压缩。我一般会这样设置参数取值建议说明imgsz1280 或 1536卫星图像建议从 1280 起调显存充足时用 1536batch8 或 161280 分辨率下 batch8 约需 12-16G 显存不够就往下减lr00.003迁移学习微调用 0.003 起步比默认 0.01 更稳mosaic0.5保留马赛克增强但降低概率避免语义混乱close_mosaic10最后 10 个 epoch 关闭马赛克让模型适应真实分布yolo detect train \ modelyolov8n.pt \ datadataset.yaml \ imgsz1280 \ batch8 \ epochs200 \ lr00.003 \ mosaic0.5 \ close_mosaic10 \ patience30 \ projectruns \ namesatellite_baseline这段命令里patience30表示验证指标连续 30 个 epoch 不提升就早停。对于 800 张这种小样本量早停是性价比最高的防过拟合手段。project和name把训练产物放到独立目录后面多个实验对比时逻辑清晰。lr00.003的适用范围是加载预训练权重后的微调。如果从零开始训练0.003 会收敛得极慢。而用yolov8n.pt做迁移预训练骨干已经具备通用特征提取能力学习率降低一些反而稳定。显存不够时不要只想着把batch降到 1batch1的 BN 统计在遥感小数据集上非常不稳定我建议优先降imgsz降完还不行再减batch无论如何不要低于 4。4.3 增强参数调整旋转角度开大但马赛克要收敛卫星图像的拍摄视角是俯视目标没有重力方向的概念飞机可以朝任意方向停靠舰船可以沿任何航向航行。这和自然图像里“人不会倒着走”完全不同所以旋转增强的幅度可以放开yolo detect train \ modelyolov8n.pt \ datadataset.yaml \ imgsz1280 \ batch8 \ epochs200 \ flipud0.5 \ fliplr0.5 \ degrees90 \ scale0.2 \ translate0.1 \ hsv_h0.015 \ hsv_s0.7 \ hsv_v0.4degrees90的意思是每张图以不超过 90 度的随机角度旋转。对卫星图来说这和上下左右翻转不冲突模型学到的是“目标在任意方向下都要能被识别”。但注意scale不建议开大scale0.2代表尺度抖动范围是正负 20%已经足够。小目标数据集如果scale开太大模型会看到大量被缩小到接近噪声的目标训练损失容易震荡。马赛克增强mosaic0.5和close_mosaic10是配套用的。马赛克处理的优势是在小样本下合成四张图的上下文增加目标数量和多样性但卫星图像每张图的拍摄条件、光照、分辨率差异很大四张完全不同的遥感图拼在一起可能出现跨图的建筑物边缘断裂、全景语义失控。最后 10 个 epoch 关掉马赛克是为了让模型在大脑里“忘掉”拼接痕迹只适应真实单图分布。5. 训练避坑排查四类让你翻车的真实现场5.1 类别映射错位loss 正常下降但 mAP 为 0现象训练日志里 loss 降得很顺畅分类损失也在降但每个 epoch 结束后的验证 mAP 始终是 0或者预测框里类别和实际物体对不上。原因数据转换脚本里的CLASSES列表顺序和训练data.yaml里的names顺序不一致。比如你在转换时把 ship 放在索引 0训练配置里却按 airplane、ship 的顺序排列那编号 0 在数据里是 ship在模型里是 airplane整个训练过程就是学习一套错误映射。解决把CLASSES变成唯一数据源data.yaml里的names直接从同一个列表生成不要在两个文件里手抄两遍。训练开始前用 3.2 小节的回读脚本抽查 20 张图人眼确认编号和物体的对应关系后再开跑。5.2 训练时大量图片报 no labels found现象命令行输出大量WARNING no labels found in ...但标签文件确实存在用编辑器打开也有内容。原因常见的有两种。一是labels目录和images目录不同名YOLO 框架默认要求images/train/xxx.jpg对应labels/train/xxx.txt目录层级不对就找不到二是图像文件名和标签文件名不一致XML 的.stem是img_001图片却叫IMG_001.jpg大小写或前后缀对不上。解决用 3.2 节的回读脚本把所有校对过配对的图像和标签复制到统一目录再按 8:2 切分train和val。我的做法是不用os.listdir手动切而是直接写一个把文件名列表写入train.txt的脚本YOLO 框架读到这个文件列表后逐个校验校验不通过会打印出具体缺失的文件名比默写目录结构更快定位问题。5.3 小目标完全漏检真凶是输入分辨率不是模型能力现象模型能检出大尺寸建筑和大型舰船但小目标如车辆、小型船只几乎全部漏检召回率低得可怜把验证图片放大之后手动标注也是小框密密麻麻。原因输入分辨率过低。卫星图像里一个车辆目标可能只有 12×12 像素缩放到 640 后只剩 6-8 像素在 YOLOv8 的 P3 特征图上只占不到一个网格模型压根看不到它。解决优先把imgsz提到 1280 或 1536让目标在输入图上至少占 16 像素以上。显存不够时就做滑窗切块把大图切成 512 或 640 的窗口再训练相当于变相放大目标。同时对验证集要区分大、中、小目标分别算 AP这样能判断漏检集中在哪个尺度而不是只看一个综合的 mAP。5.4 大图直接训练 OOM显存爆掉后 batch 自动回落现象torch.cuda.OutOfMemoryError直接中断训练或者框架自动把 batch 降到 2 上下训练速度骤降。原因卫星图动辄几千乘几千即便imgsz1280如果原图长宽比悬殊letterbox会在上下或左右填充大量灰色像素实际参与计算的有效分辨率依然很高。这里其实不完全是参数量的问题而是填充区域的浪费。解决我一般会打开rectTrue这个参数允许一个 batch 内的图像保持原始宽高比而不是强制缩放成正方形能显著减少填充浪费。同时关闭cacheTrue不要在内存里缓存全部 800 张大图否则数据加载阶段就把内存占满了。如果显存依然不够最终方案是预处理阶段就把大图切块成固定尺寸的小图切块后的数据集再训练这也能顺带解决 5.3 的小目标漏检问题。6. 验证训练成果混淆矩阵和滑窗推理缺一不可6.1 用验证命令快速产出指标和混淆矩阵训练结束后先用验证命令拿到一整套评估指标而不是看着 last.pt 就当完成yolo detect val \ modelruns/satellite_baseline/weights/best.pt \ datadataset.yaml \ imgsz1280 \ batch8 \ plotsTrueplotsTrue会在runs/satellite_baseline/下生成混淆矩阵和曲线图。我验证时更关注mAP50-95而不是mAP50因为 mAP50 只要求 IoU 达到 0.5对定位精度不敏感卫星目标边缘要求高mAP50-95 更能反映框得准不准。如果 mAP50 高但 mAP50-95 低说明框的位置基本对但边缘贴合粗糙这时候可以考虑用更高分辨率输入再做一轮微调。6.2 把验证结果还原到整幅大图滑窗推理的坐标回填模型训练好之后实际使用场景是整幅大图推理而不是切好的小图。这里有个很容易遗漏的细节滑窗推理的输出框坐标是相对于窗口的必须换算回原始大图坐标。我的常见做法是按窗口大小 640、重叠率 30% 滑遍整幅图每个窗口跑一次模型得到检测框后执行坐标回填将x offset_x、y offset_y还原。重叠区域里同一个目标可能被检出两次再做一次 NMS 去重。这个流程看着简单但在卫星大图上能明显减少漏检和重复框的问题。验证时我习惯把这个流程也跑一遍和直接用小图验证的结果对比确认滑窗没有引入坐标偏移。这套流程完整走下来800 张卫星图能跑出一个可靠且问题清楚的 baseline。我习惯在项目开始时就把它做成固定脚本后面换数据集、换模型只需要调整路径和类别清单。数据检查、格式转换、参数设置这些环节省不掉省掉的最后都会在训练日志里以诡异的方式还回来。希望这篇笔记里的避坑记录能帮你少走几趟弯路训练顺利。本文还有配套的精品资源点击获取
返回列表