ARTICLE DETAIL

资讯详情

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

YOLO航空卫星图像目标检测:从数据准备到调优实战

YOLO航空卫星图像目标检测:从数据准备到调优实战 简介面向yolo系列算法目标检测任务的航空卫星图像数据集涵盖森林、公路、作物、河流、住宅、工业、果园、牧场及贫瘠土地等多类地物目标共1366张带标签图像。资源已按训练、验证、测试划分完成并附带data.yaml配置文件可直接用于yolov5、yolov7、yolov8、yolov9、yolov10、yolo11等主流版本训练与验证。包内同时提供YOLO格式与VOC格式两种标注文件前者的txt文件逐行记录类别索引与归一化后的中心点坐标、宽高信息后者的xml文件按Pascal VOC结构保存目标位置便于不同工具链切换使用。全部文件共2000个其中xml标注1230个、txt标注770个压缩包大小约60.51MB结构紧凑下载后即可展开训练。适合目标检测初学者快速上手也方便遥感领域研究者在多类别地物识别中直接复用标注数据目前已有42人学习下载可用于地物分类、农业监测、区域规划等应用研究。1. 1366张带标签航空卫星图YOLO训练的第一步不是写训练代码用YOLO做航空卫星图像的目标检测很多人第一反应是拿到数据后立刻解压、改个yaml、敲训练命令。但遥感图与自然图像差异很大1366张带标签的航空卫星数据类别覆盖森林、公路、作物、河流、住宅、工业、果园、牧场和贫瘠土地这9类地物在图像上的形态完全不同不是简单套用COCO预训练模型就能直接收敛的。常见的问题包括mAP虚高但实际漏检严重、后几类样本太少导致模型直接学不到、长条形地物如公路和河流被框切成多段。所以这篇直接把数据准备、格式转换、训练参数、评估排查和遥感特有的调优手段按一条线走通适合已经把YOLO基本流程跑通、但不熟悉遥感数据的人。2. 从zip到YOLO训练集目录结构、标注格式与类别检查拿到zip包先别急着解压训练。先扫目录确认标注格式再做可视化核对。遥感数据集的标注规范差别巨大有些是从公开遥感数据集转化的VOC格式有些是Labelme的JSON输出还有些是别人处理好的YOLO txt。三种格式的后续处理路径完全不同第一步是花十分钟看清楚而不是直接假设它是标准YOLO格式。2.1 解压后先扫清目录结构与标注类型我一般在Linux环境下先用find命令把目录结构拉出来同时统计文件后缀mkdir -p ~/aerial_dataset cd ~/aerial_dataset unzip ~/downloads/yolo算法-航空卫星图像数据集-1366张图像带标签-森林-公路-作物-河流-住宅的-工业-果园-牧场-贫瘠的土地.zip find . -maxdepth 2 -type d | sort find . -maxdepth 2 -type f | sed s/.*\.// | sort | uniq -c第一行是解压第二行看目录层级第三行统计文件后缀。-maxdepth 2把扫描范围控制在两层以内避免嵌套太深时输出刷屏uniq -c直接给出jpg、txt、xml、json各自的数量一眼就能判断标注格式。如果发现还有嵌套的压缩包先一并解压出来再继续。接着抽一个标注文件看内容。YOLO格式的txt每行是class_id x_center y_center width height全部是相对图像的归一化坐标VOC格式的xml则包含objectname和bndbox节点Labelme的json里有shapes和imageWidth/imageHeight。三种格式决定了后续要不要写转换脚本。find . -name *.txt | head -3 cat $(find . -name *.txt | head -1)如果txt文件是空的说明要么是背景图要么是标注遗漏。对于1366张图像的中小规模数据集空标签图是否保留要看你的任务想检测所有地物就删掉想做包含背景的分类就保留。我倾向于保留并单独建一个backgrounds目录后面训练时可以作为负样本加入。2.2 抽样可视化标签逐类核对标注质量格式确认后不要直接统计数量而是先做可视化。标注文件与图像是否对齐、框是否框在目标上、有没有整张图只标了一个点的情况这些问题只有人眼看过才能发现。我一般每类抽5张图把YOLO标签画出来检查import cv2 img_path images/0.jpg label_path labels/0.txt class_names [forest, highway, crop, river, residential, industrial, orchard, pasture, barren_land] img cv2.imread(img_path) h, w img.shape[:2] with open(label_path) as fp: for line in fp: parts line.strip().split() if len(parts) 5: continue cid, xc, yc, bw, bh map(float, parts) x1 int((xc - bw / 2) * w) y1 int((yc - bh / 2) * h) x2 int((xc bw / 2) * w) y2 int((yc bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, class_names[int(cid)], (x1, max(0, y1 - 6)), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2) cv2.imwrite(check_0.jpg, img)这段代码把归一化坐标乘回原图宽高画出矩形框和类别名。注意YOLO里xc, yc是中心点坐标绘制时要换算成左上角与右下角putText里的max(0, y1 - 6)防止类别名画到图像外。如果发现某些图标注框偏移半个身位说明标注质量不稳定后续训练时要看着办要么手动修正要么在loss权重上做处理。可视化要重点看三件事长条形目标是不是被一个巨大框包住、密集小目标是不是只标了部分、类别名称有没有和实际地物对不上。这些在mAP里很难暴露但在图上非常直观。2.3 统计类别分布先看类别ID映射是否连续接下来统计每个类别出现的实例数。遥感地物的共性是类别极不平衡森林、作物这类大块连片地物通常标注数量多河流可能只有几十条牧场和贫瘠土地面积大但实例少。这个统计直接决定后面训练时的采样策略和增强策略。import os from collections import Counter label_dir labels counter Counter() img_covered Counter() for f in os.listdir(label_dir): if not f.endswith(.txt): continue cls_in_img set() with open(os.path.join(label_dir, f)) as fp: for line in fp: parts line.strip().split() if len(parts) 5: continue cid int(parts[0]) counter[cid] 1 cls_in_img.add(cid) for c in cls_in_img: img_covered[c] 1 for cid in sorted(counter.keys()): print(fclass {cid}: instances{counter[cid]}, images{img_covered[cid]})counter统计实例总数img_covered统计每个类别至少出现一次的图像数。这两个数字分开看很重要某个类实例数多但只集中在少数几张图里和分散在几百张图里训练行为完全不同。结合标题中的9类地物类别映射关系大致如下class_id类别常见形态对YOLO的挑战0森林大块连片框内包含大量非目标区域1公路长条弯曲宽高比极端单框横跨多段2作物条块纹理与果园、牧场边界模糊3河流细长蜿蜒一个框无法覆盖连续体4住宅密集小方块小目标聚集漏检率高5工业大尺度规则建筑与住宅容易混淆6果园均匀纹理区与森林、作物边界交叠7牧场大片草地与贫瘠土地难以区分8贫瘠的土地低纹理裸土语义界线本身不清晰这张表后面会反复用到。所有类别ID必须从0开始连续编号如果原始数据里类别跳号或顺序不一致要先做一个重映射字典再写训练配置。2.4 VOC/JSON转YOLO txt的坐标换算与过滤规则如果标注是VOC格式转换成YOLO txt是绕不开的一步。VOC里的坐标是像素绝对值YOLO需要归一化换算公式是x_center ((xmin xmax) / 2) / img_width y_center ((ymin ymax) / 2) / img_height width (xmax - xmin) / img_width height (ymax - ymin) / img_height对应脚本import xml.etree.ElementTree as ET class_mapping { forest: 0, highway: 1, crop: 2, river: 3, residential: 4, industrial: 5, orchard: 6, pasture: 7, barren_land: 8 } def voc_to_yolo(xml_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 cid class_mapping.get(name) if cid is None: continue 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) if xmax xmin or ymax ymin: continue xc (xmin xmax) / 2 / img_w yc (ymin ymax) / 2 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cid} {xc:.6f} {yc:.6f} {w:.6f} {h:.6f}) return lines脚本里先过滤掉宽高非正的框防止某些标注工具导出时出现坐标颠倒。转换后建议再跑一遍可视化确认ID没有因为类别名拼写不同而掉进cid is None分支。为什么非要在转换后确认一遍因为遥感标注里的类别名经常带有空格、下划线或中文比如barren land和barren_land会被解析成两个键导致这一类别实例数直接归零。3. 基于这份1366张遥感数据训练YOLO配置文件、数据划分与超参数据准备好了才轮到训练。这里要强调一个前提1366张图、9类地物这个规模在遥感任务里属于中小型数据集选模型、设超参的思路和用几万张自然图像训练完全不一样。模型选大了会过拟合数据增强开猛了会破坏长条形目标patience设太短又会错过晚来的收敛点。3.1 模型选型YOLOv8s起步YOLO11留给对比实验对这种规模的数据集起步模型建议用YOLOv8s。为什么不选nn的参数量小、训练快但遥感小目标的语义特征提取能力偏弱m或l在这个数据量下容易把训练时间拉长且收益不稳定。YOLO11比v8在主干和训练策略上有改动但1366张图不足以把这部分优势发挥出来可以先跑通v8s的baseline再拿v8m做对比就够了。3.2 写data.yaml并做按类别分布的数据划分ultralytics的训练入口是yolo detect train数据侧只需要一个yaml文件path: /data/aerial train: images/train val: images/val names: 0: forest 1: highway 2: crop 3: river 4: residential 5: industrial 6: orchard 7: pasture 8: barren_landpath是数据集根目录train和val是相对路径names必须与2.3节的class_id一一对应。注意类别名里不要带空格barren land写成barren_land否则后面评估指标打印时排版会乱。数据划分不能简单random shuffle。遥感数据经常出现同一区域的多张图随机切分会让验证集和训练集包含同一片地物的不同视角导致mAP虚高。更稳妥的做法是先按文件名前缀或图像编号分组再把组切分。以纯随机划分为例import random, shutil from pathlib import Path root Path(/data/aerial) all_imgs sorted((root / images/all).glob(*.jpg)) random.seed(42) random.shuffle(all_imgs) split_idx int(len(all_imgs) * 0.8) for split, imgs in [(train, all_imgs[:split_idx]), (val, all_imgs[split_idx:])]: out_img_dir root / images / split out_lbl_dir root / labels / split out_img_dir.mkdir(parentsTrue, exist_okTrue) out_lbl_dir.mkdir(parentsTrue, exist_okTrue) for img in imgs: label root / labels/all / (img.stem .txt) if not label.exists(): continue shutil.copy(img, out_img_dir / img.name) shutil.copy(label, out_lbl_dir / label.name)random.seed(42)固定随机种子保证每次划分结果一致。if not label.exists()跳过没有对应标签的图像避免空标签进入训练。更细化的做法是按2.3节统计出的类别分布做分层抽样保证河流、牧场、贫瘠土地这些少样本类别在验证集里也有出现否则验证集的mAP会非常不稳定。3.3 训练命令与核心超参baseline训练命令cd /data/aerial yolo detect train \ dataaerial.yaml \ modelyolov8s.pt \ epochs100 \ imgsz640 \ batch8 \ patience20 \ mosaic0.8 \ fliplr0.5 \ scale0.3modelyolov8s.pt是COCO预训练权重遥感图和自然图像差异大但底层的边缘、纹理特征可以迁移1366张图的情况下从零训练收敛慢且容易陷入局部最优。patience20表示验证集mAP连续20个epoch不增长就早停遥感数据集小过拟合来得快没必要死等100个epoch。mosaic0.8是YOLOv8默认的mosaic增强概率对遥感数据要适当下调。mosaic把4张图拼成一张大面积的森林、作物会被切得支离破碎长条形公路和河流在拼接边界处语义断裂模型容易学到错误的上下文。scale0.3控制随机缩放的幅度遥感地物尺寸相对稳定不需要像自然图像那样做大幅缩放。各项建议参数如下参数默认值建议值原因imgsz640640起步显存充裕可试960小目标依赖分辨率但显存占用随分辨率平方上升batch168~16受显存限制batch太大会让BN统计不稳定epochs100100数据量小再长就过拟合patience5020遥感数据收敛窗口短早停更实际mosaic1.00.8降低大面积地物被切碎的概率fliplr0.50.5遥感图左右镜像语义保持不变hsv_h0.0150.005地物颜色有领域一致性过大的色彩扰动会让模型学错3.4 类别不平衡的应对复制少样本图而不是调loss权重直接调loss权重在YOLO里不容易生效因为YOLO训练是按图像采样的不是按实例采样。更直观的做法是让少样本类别对应的图像在训练集里多出现几次。操作上把包含河流、贫瘠土地的图像路径复制到训练列表中配合mosaic增强相当于提高了这些图像的采样概率。复制后图的总量增加了但模型输入尺寸不变训练代价增加有限。代价是要小心过拟合复制不是无限复制。我一般会把最少的类别复制到接近中位类别图像数的1.5倍就停同时注意验证集不要因为复制而被污染。另一个辅助手段是把少样本类别的实例区域裁剪出来通过Copy-Paste增强粘贴到不含该类别的图像上这个在遥感地物上效果比通用目标明显因为地物纹理与原图背景融合度天然较高。4. 训练完先别信mAP混淆矩阵、按类指标与遥感漏检排查训练结束后很多人直接看终端打印的总mAP就收工。遥感数据上这远远不够。9类地物形态差异大总mAP会被森林、作物这些好分的类别拉高河流、贫瘠土地的低分被掩盖。要按类检查结合混淆矩阵判断到底哪些地物在互相打架。4.1 从results.png与训练日志确认收敛状态训练完成后进runs/detect/train目录先看results.png。这张图包含box_loss、cls_loss、dfl_loss以及验证集mAP50和mAP50-95。注意区分两类现象如果cls_loss一直在降但val mAP不涨通常是验证集和训练集分布不一致或者验证集样本太少导致指标抖动如果box_loss降到0.8左右就下不去常见原因是标注框本身有偏差模型在学习一个不稳定的回归目标。遥感场景里值得额外留意的现象是mAP50高但mAP50-95低。这说明模型能把目标大致框出来但框的边界不够准。小目标在640分辨率下可能只占十几个像素IOU稍微偏移一点mAP50-95就掉得厉害。4.2 跑validation并直接读混淆矩阵用下面命令验证yolo detect val \ modelruns/detect/train/weights/best.pt \ dataaerial.yaml跑完后runs/detect/val目录下会生成confusion_matrix.png。这张图比mAP更直接。森林和果园往往整片混淆牧场和贫瘠土地互相误判河流经常被漏检到背景里。如果看到某两类横向纵向都有明显响应先别急着调模型回去看这两类的标注是否真的可分。牧场和贫瘠土地在视觉上确实没有明确边界标的人可能自己都拿不准这种情况模型学不好不是模型的错。4.3 用低置信度预测区分漏检和误检验证集上漏检怎么排查把置信度阈值调低重新预测看模型到底给不给框。如果给了框但置信度低说明模型学到了特征只是打分不够高如果低置信度下依然完全没有框才是真的漏学。from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) results model.predict( sourceimages/val, conf0.05, imgsz640, saveTrue, projectdebug_vis )conf0.05把阈值压得很低目的是把模型的所有预测都释放出来。逐个看保存下来的预测图重点看三类现象低置信度框是否刚好压在目标上判断是置信度校准问题还是特征学习不足同一目标是否出现多个互相重叠的半截框说明NMS参数或目标形态有问题长条形河流是否被切成几段框这是单框检测长条目标的固有缺陷。4.4 按类看AP找到真正拖后腿的类别验证集命令行输出的末尾会打印每一类的P、R和mAPClass Images Instances Box(P R mAP50 all 273 ... ... ... ... forest 273 ... ... ... ... highway 273 ... ... ... ...重点看R召回率低的类别。召回低意味着有大量目标没被检测出来在遥感地物里通常对应三类原因目标太小、训练样本太少、目标形态长宽比极端。河流和公路召回落典型原因是单个框很难覆盖整条连续地物训练时框的宽高比分布和自然图像差异太大。此时如果只是想提升这类指标可以在训练参数里开启rectTrue按宽高比分组batch减少padding带来的无效计算yolo detect train \ dataaerial.yaml \ modelyolov8s.pt \ epochs30 \ rectTruerectTrue会按图像的宽高比排序并分桶同一batch内的图pad到相近尺寸长条形遥感图的特征保留更好。注意它要求cacheTrue或允许预处理阶段重排数据部分旧版本下与mosaic增强不兼容。5. 遥感场景的YOLO调优手段瓦片切分、多尺度推理与ONNX导出遥感图像本身通常很大1366张图如果原图是几千像素见方全局resize到640会让小目标缩小到几乎不可见。解决这个问题最有效的手段是瓦片切分。5.1 瓦片切分与标签同步偏移把原始大图切成1024×1024的tile切分时保留重叠区域避免目标恰好落在切缝上。切图的同时必须把标签坐标同步偏移否则标注位置全部错位。以下函数处理标注偏移与过滤def filter_tile_boxes(boxes, x0, y0, tile_w, tile_h, min_cover0.6): keep [] for x1, y1, x2, y2 in boxes: inter_w max(0, min(x2, x0 tile_w) - max(x1, x0)) inter_h max(0, min(y2, y0 tile_h) - max(y1, y0)) inter_area inter_w * inter_h box_area (x2 - x1) * (y2 - y1) if box_area 0: continue if inter_area / box_area min_cover: nx1 max(x1, x0) - x0 ny1 max(y1, y0) - y0 nx2 min(x2, x0 tile_w) - x0 ny2 min(y2, y0 tile_h) - y0 keep.append((nx1, ny1, nx2, ny2)) return keepmin_cover0.6表示只有超过60%面积落在tile内的目标才保留避免大量半截框污染训练。推理时每个tile独立预测最后把所有框的坐标加上对应tile的偏移量再做一次全局NMS合并重叠区域。重叠区建议设10%~20%太少会让切缝两边的检测不稳定。5.2 多尺度与TTA训练完模型后用原始图像直接推理往往不是最佳方案。ultralytics的predict支持多尺度测试时增强yolo detect predict \ modelruns/detect/train/weights/best.pt \ sourcetest_images \ imgsz960 \ ttaTrue \ conf0.15ttaTrue会对输入做翻转和尺度变换后融合预测框对小目标召回有明显收益但推理时间会增加到原来的3倍左右。放在batch推理服务前要评估延迟预算。单独设imgsz960而不开TTA是一个性价比更高的中间档位。5.3 导出ONNX并固定输入尺寸模型验证没问题后一般要导出部署。先用ONNX导出yolo export \ modelruns/detect/train/weights/best.pt \ formatonnx \ dynamicTrue \ opset12如果部署端是固定尺寸输入dynamicTrue建议关闭固定比如640或960动态尺寸在TensorRT和OpenVINO上的兼容性开销不值得。导出后用onnxruntime验证一遍import onnxruntime as ort import numpy as np session ort.InferenceSession(best.onnx, providers[CPUExecutionProvider]) input_name session.get_inputs()[0].name img np.random.randn(1, 3, 640, 640).astype(np.float32) outputs session.run(None, {input_name: img}) for o in outputs: print(o.shape)如果输出shape与预期一致再把瓦片切分的tile推理、坐标偏移、NMS合并这三段串起来跑一遍完整测试图确认切到1024或960尺寸下漏检情况最轻。对这份1366张带标签的遥感数据把瓦片大小设成tile_size1024, overlap128配合imgsz960的TTA推理一般能同时兼顾小目标召回率和漏检边界切碎的问题。本文还有配套的精品资源点击获取
返回列表