
简介这份PDF文档面向遥感图像分析与计算机视觉方向的学习者、研究人员及工程实践者系统讲解如何将YOLOv11应用于卫星影像的地物分类与变化检测任务。文档共47页支持目录章节跳转与阅读器左侧大纲快速定位内容完整、条理清晰图表与文字显示正常。资源包为单一PDF文件大小约2.05MB便于下载后离线阅读与检索。目前已有68人学习浏览。文档从遥感图像分析基础、地物分类与变化检测方法综述切入深入剖析YOLOv11的网络结构、损失函数与训练流程并覆盖卫星影像辐射校正、几何校正、影像融合与噪声去除等预处理环节随后分别构建地物分类与变化检测模型给出数据集准备、架构调整、训练监控与评估优化方案最后通过实验对比、结果可视化及城市规划、农业、生态、灾害应急等应用案例帮助读者掌握从理论到落地的完整技术链路。1. 遥感图像分析遇上 YOLOv11卫星影像里的地物分类与变化检测到底怎么做手里有一批历史卫星影像想自动标出建筑、水体、植被还想知道两期影像之间哪里多了一栋楼、哪里退耕还林了——这是遥感图像分析里最典型的两个任务地物分类和变化检测。过去大家用 NDVI 阈值、面向对象分割规则一多就崩后来上深度学习又卡在标注贵、显存炸、小目标漏检。YOLOv11 把检测头和解耦结构做得更轻配合 LEVIR-CD 这类变化检测数据集能在单卡 12G 显存上跑通从训练到推理的全流程。这篇笔记面向想用 YOLOv11 落地卫星影像的工程师先讲清地物分类和变化检测在 YOLO 框架下怎么建模再给可复现的环境配置、数据转换、训练命令和推理保存脚本最后把踩过的坑摊开。适合有 Python 基础、摸过 PyTorch 但没系统做过遥感检测的人。2. 把卫星影像喂给 YOLOv11数据准备与格式转换的硬骨头2.1 地物分类和变化检测在 YOLO 里分别怎么建模地物分类在遥感里通常不是整图分类而是逐像素或逐目标分类。YOLOv11 是目标检测框架所以落地时要把「地物分类」转成「多类别目标检测」建筑、水体、植被、道路各算一类标注框住实例。这样做的代价是丢失像素级边界好处是能直接复用 YOLOv11 的预训练权重和训练管线对小目标优化也成熟。变化检测更绕。常见做法有两种一是双时相拼接成 6 通道输入让网络自己学差异二是分别推理两期影像再对检测框做匹配框位置和类别都一致视为未变化否则标记变化。前者需要改 YOLOv11 第一层卷积的输入通道后者不用改网络但依赖检测精度。我一般先用后者快速验证因为改动小、可解释等基线跑通再考虑通道拼接。LEVIR-CD 数据集是建筑物变化检测的常用基准提供同一地点两期影像和变化掩码。如果直接拿它做 YOLOv11 训练需要把掩码转成检测框对变化区域做连通域分析取外接矩形作为变化目标框。这一步的转换质量直接决定模型上限。2.2 从 LEVIR-CD 掩码到 YOLO 标注转换脚本与四个边界坑LEVIR-CD 的标注是 PNG 掩码白色为变化区域。转 YOLO 格式需要遍历掩码、找连通域、算归一化坐标。下面是我实际用的脚本依赖 opencv 和 numpy。import cv2 import numpy as np import os from pathlib import Path def mask_to_yolo_boxes(mask_path, img_w, img_h, min_area20): 把变化掩码转成 YOLO 格式的框类别固定为 0变化 mask cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE) if mask is None: return [] # 二值化LEVIR-CD 掩码白色为变化 _, binary cv2.threshold(mask, 127, 255, cv2.THRESH_BINARY) # 连通域分析只取面积大于阈值的区域过滤噪点 num_labels, labels, stats, _ cv2.connectedComponentsWithStats(binary, connectivity8) boxes [] for i in range(1, num_labels): # 0 是背景 x, y, w, h, area stats[i] if area min_area: continue # YOLO 格式中心点 x,y 和宽高全部归一化到 0-1 cx (x w / 2) / img_w cy (y h / 2) / img_h nw w / img_w nh h / img_h boxes.append(f0 {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}) return boxes def convert_split(mask_dir, img_dir, out_dir): os.makedirs(out_dir, exist_okTrue) for mask_name in os.listdir(mask_dir): if not mask_name.endswith(.png): continue mask_path os.path.join(mask_dir, mask_name) # 用 A 期影像的尺寸做归一化基准两期尺寸一致 img_path os.path.join(img_dir, mask_name.replace(_mask.png, .png)) img cv2.imread(img_path) if img is None: continue h, w img.shape[:2] boxes mask_to_yolo_boxes(mask_path, w, h) txt_name mask_name.replace(.png, .txt) with open(os.path.join(out_dir, txt_name), w) as f: f.write(\n.join(boxes)) if __name__ __main__: # 目录结构按 LEVIR-CD 官方组织A 期、B 期、label convert_split(LEVIR-CD/train/label, LEVIR-CD/train/A, datasets/levir_yolo/labels/train) convert_split(LEVIR-CD/val/label, LEVIR-CD/val/A, datasets/levir_yolo/labels/val)逻辑说明connectedComponentsWithStats返回每个连通域的外接矩形和面积min_area用来过滤掩码边缘的碎点设太小会生成大量无意义小框设太大会漏掉真实的小型变化。归一化用 A 期影像尺寸因为两期影像已经配准尺寸一致。参数说明min_area20是我在 1024×1024 影像上的经验值对应约 4×5 像素的变化区域。如果影像分辨率更高比如 0.3 米建筑变化面积大可以提到 50 到 100。connectivity8比 4 邻域更容易把相邻变化区域合并成一个框减少重叠框。四个边界坑第一掩码里可能有灰度值 128 的过渡像素直接0判断会把边缘算进去必须用 127 阈值二值化。第二连通域编号从 1 开始0 是背景循环里容易写成从 0 开始导致多一个全图框。第三归一化坐标要保留 6 位小数YOLO 训练时对精度敏感四舍五入到 2 位会引入偏移。第四A 期和 B 期影像文件名可能不完全对应转换前先做一次文件名匹配检查否则会生成空标注文件。2.3 数据集目录结构与 data.yaml 配置YOLOv11 用 Ultralytics 框架目录结构必须按它约定来。我一般这样组织datasets/levir_yolo/ images/ train/ # A 期影像文件名与 labels 对应 val/ labels/ train/ # 转换后的 txt val/data.yaml 内容path: datasets/levir_yolo train: images/train val: images/val nc: 1 names: [change]如果做地物分类多类别nc改成类别数names按顺序写建筑、水体、植被等。注意names的顺序必须和标注文件里的类别 id 一致否则训练时类别全乱。这个错误很隐蔽因为 loss 会正常下降但推理结果类别对不上。3. YOLOv11 环境配置与训练从权重文件到小目标优化3.1 环境配置适合零基础小白的可复现步骤Ultralytics 对环境要求不算苛刻但版本错配会出玄学问题。我固定用 Python 3.10 PyTorch 2.1 CUDA 11.8 这套组合在 3090 和 4090 上都稳。步骤如下# 创建虚拟环境避免污染系统 Python conda create -n yolo11 python3.10 -y conda activate yolo11 # 安装 PyTorchCUDA 11.8 版本 pip install torch2.1.0 torchvision0.16.0 --index-url https://download.pytorch.org/whl/cu118 # 安装 Ultralytics不要指定版本让它拉最新兼容版 pip install ultralytics # 验证安装能打印版本且不报错即可 yolo checksyolo checks会输出环境信息重点看 CUDA 是否可用、PyTorch 版本是否匹配。如果显示 CPU only说明 PyTorch 装成了 CPU 版重装时检查 index-url。权重文件下载YOLOv11 官方提供 n、s、m、l、x 五个尺度。遥感影像小目标多我一般从yolo11s.pt起步显存不够换 n精度不够换 m。权重文件在 Ultralytics 首次运行时自动下载到当前目录也可以手动从官方 release 页面获取。不要用来源不明的权重可能被篡改。3.2 训练命令与关键参数小目标优化怎么调训练命令一行就够但参数决定成败yolo detect train \ datadatasets/levir_yolo/data.yaml \ modelyolo11s.pt \ epochs100 \ imgsz1024 \ batch4 \ device0 \ projectruns/levir \ nameexp1 \ patience20 \ lr00.01 \ mosaic0.5 \ scale0.3 \ degrees0.0 \ fliplr0.5 \ cacheTrue参数逐个说imgsz1024是因为卫星影像分辨率高缩到 640 会丢小目标但 1024 显存占用大batch4是 12G 显存下的折中。patience20表示 20 轮没提升就早停避免过拟合。lr00.01是初始学习率遥感数据量通常几千张这个值比默认 0.01 略稳。mosaic0.5是马赛克增强概率对小目标有帮助但太高会让变化检测的时相关系混乱我设 0.5。scale0.3允许缩放增强degrees0.0关闭旋转因为卫星影像有固定方向旋转会引入不真实的方位关系。fliplr0.5水平翻转可以开垂直翻转对遥感也合理但 Ultralytics 默认只开水平。cacheTrue把图像缓存到内存加快训练但数据量大时注意内存占用。如果报内存不足改成cachedisk或关闭。小目标优化的核心在imgsz和 anchor。YOLOv11 是 anchor-free不需要手动设 anchor但输入尺寸直接决定小目标能否被检测到。1024 输入下8 像素的目标在特征图上还有 1 像素响应再小就丢了。如果目标普遍小于 8 像素要么提高输入到 1536要么用切片推理把大图切成小块分别检测再合并。3.3 训练过程监控与中断恢复训练启动后runs/levir/exp1/下会生成results.csv和权重文件。重点看metrics/mAP50和metrics/mAP50-95变化检测任务 mAP50 到 0.7 以上算可用0.5 以下说明标注或数据有问题。如果训练中断用resume恢复yolo detect train resume modelruns/levir/exp1/weights/last.ptlast.pt是最后一轮权重best.pt是验证集最优权重。推理用best.pt继续训练用last.pt。注意 resume 会读取原训练参数不要额外传data和model否则可能冲突。训练日志里如果box_loss不降先检查标注文件是否有空 txt空标注会让模型学不到东西。如果cls_loss不降检查类别 id 是否超出nc范围。4. 推理、保存结果与变化检测后处理把检测框变成可用图层4.1 推理命令与保存推理结果训练完用best.pt推理保存带框的图像和 txtyolo detect predict \ modelruns/levir/exp1/weights/best.pt \ sourcedatasets/levir_yolo/images/val \ imgsz1024 \ conf0.25 \ iou0.5 \ saveTrue \ save_txtTrue \ save_confTrue \ projectruns/predict \ namelevir_valconf0.25是置信度阈值变化检测里宁可低一点召回后续可以按面积过滤。iou0.5是 NMS 的 IoU 阈值变化区域通常不重叠0.5 够用。save_txtTrue会在labels子目录生成每张图的检测结果格式和训练标注一致方便后续处理。save_confTrue把置信度写进 txt便于按置信度筛选。推理结果默认画框和类别如果只要掩码式输出需要自己写后处理把检测框填充成二值图再和原始影像叠加。4.2 双时相变化检测的后处理框匹配与变化图生成如果采用分别推理两期影像再匹配的方案后处理是关键。下面脚本读取两期推理 txt做 IoU 匹配输出变化框。import numpy as np from pathlib import Path def load_boxes(txt_path): 读取 YOLO 格式 txt返回 [cx, cy, w, h, conf] 列表 boxes [] if not Path(txt_path).exists(): return boxes with open(txt_path) as f: for line in f: parts line.strip().split() if len(parts) 5: # 类别 id 忽略变化检测只有一类 boxes.append([float(x) for x in parts[1:5]] [float(parts[5]) if len(parts) 5 else 1.0]) return boxes def iou(box1, box2): 计算两个 YOLO 格式框的 IoU cx1, cy1, w1, h1 box1[:4] cx2, cy2, w2, h2 box2[:4] x1_min, x1_max cx1 - w1/2, cx1 w1/2 y1_min, y1_max cy1 - h1/2, cy1 h1/2 x2_min, x2_max cx2 - w2/2, cx2 w2/2 y2_min, y2_max cy2 - h2/2, cy2 h2/2 inter_xmin max(x1_min, x2_min) inter_ymin max(y1_min, y2_min) inter_xmax min(x1_max, x2_max) inter_ymax min(y1_max, y2_max) if inter_xmax inter_xmin or inter_ymax inter_ymin: return 0.0 inter_area (inter_xmax - inter_xmin) * (inter_ymax - inter_ymin) area1 w1 * h1 area2 w2 * h2 return inter_area / (area1 area2 - inter_area) def match_changes(boxes_a, boxes_b, iou_thresh0.3): A 期和 B 期框匹配未匹配上的视为变化 matched_b set() changes [] for ba in boxes_a: best_iou 0 best_j -1 for j, bb in enumerate(boxes_b): if j in matched_b: continue v iou(ba, bb) if v best_iou: best_iou v best_j j if best_iou iou_thresh: # A 期有、B 期没有匹配可能是拆除 changes.append((remove, ba)) else: matched_b.add(best_j) for j, bb in enumerate(boxes_b): if j not in matched_b: # B 期有、A 期没有匹配可能是新建 changes.append((add, bb)) return changes逻辑说明iou_thresh0.3是匹配阈值两期检测框 IoU 高于此值认为同一地物未变化。低于此值或找不到匹配标记为变化。remove表示 A 期有 B 期无add表示 B 期有 A 期无。这个后处理简单但有效缺点是依赖两期检测精度漏检会导致误报变化。参数说明iou_thresh设 0.3 比常规 0.5 低因为两期影像配准误差和检测框抖动会让同一地物的框不完全重合。如果配准精度高可以提到 0.5。conf阈值在推理时设 0.25后处理时可以再按置信度过滤比如只保留 conf0.4 的框减少误报。4.3 结果可视化与 GIS 图层导出检测结果最终要给 GIS 用导出 GeoJSON 或 Shapefile 是常见需求。用rasterio读取原始影像的仿射变换把像素坐标转成地理坐标import rasterio from rasterio.features import shapes import json def yolo_to_geojson(txt_path, img_path, out_geojson): 把 YOLO 检测框转成 GeoJSON坐标用影像地理参考 with rasterio.open(img_path) as src: transform src.transform w, h src.width, src.height features [] with open(txt_path) as f: for line in f: parts line.strip().split() if len(parts) 5: continue cx, cy, bw, bh [float(x) for x in parts[1:5]] # 转成像素角点 x_min (cx - bw/2) * w y_min (cy - bh/2) * h x_max (cx bw/2) * w y_max (cy bh/2) * h # 像素坐标转地理坐标 lon_min, lat_max transform * (x_min, y_min) lon_max, lat_min transform * (x_max, y_max) features.append({ type: Feature, properties: {class: change}, geometry: { type: Polygon, coordinates: [[ [lon_min, lat_min], [lon_max, lat_min], [lon_max, lat_max], [lon_min, lat_max], [lon_min, lat_min] ]] } }) with open(out_geojson, w) as f: json.dump({type: FeatureCollection, features: features}, f)注意transform * (x, y)返回的是(col, row)对应的地理坐标顺序不要搞反。如果影像有投影信息导出的 GeoJSON 可以直接在 QGIS 里叠加。5. 避坑与排查遥感 YOLOv11 训练里最容易翻车的五件事5.1 现象mAP 一直卡在 0.1 以下loss 不降原因标注文件类别 id 和 data.yaml 的names顺序不一致或者标注文件里有超出nc的类别 id。YOLOv11 不会报错但会把所有类别当背景学。解决用脚本统计所有 txt 里的类别 id 分布确认最大值小于nc。同时检查names列表顺序是否和标注生成时的类别映射一致。我习惯在转换脚本里把类别映射写成常量训练和推理共用。5.2 现象训练正常但推理结果全是框置信度都很低原因conf阈值设太低或者验证集和训练集分布差异大。遥感影像里云、阴影、季节变化会让模型把很多区域误判为变化。解决推理时把conf提到 0.4 到 0.5观察 mAP 变化。如果 mAP 下降不多但误报明显减少说明低置信度框大多是噪声。另外检查验证集是否包含训练集里没有的地物类型必要时补充负样本。5.3 现象小目标漏检严重大目标正常原因imgsz太小或者数据增强里的scale把目标缩得更小。1024 输入下小于 8 像素的目标在特征图上响应太弱。解决提高imgsz到 1536或者用切片推理把大图切成 512×512 带重叠的小块分别推理后合并。切片推理的代价是速度慢但小目标召回提升明显。另外把scale降到 0.1避免训练时目标被过度缩小。5.4 现象训练到一半 loss 突然变 NaN原因学习率太高或者数据里有损坏图像。遥感影像常见的问题是某些 tif 文件有异常值归一化后出现 inf。解决先把lr0降到 0.001 重跑如果还 NaN用脚本遍历所有图像检查像素值范围。正常影像归一化后应在 0 到 1 之间出现负值或大于 1 说明读取方式有问题。Ultralytics 默认用 OpenCV 读图16 位 tif 会被截断需要先转成 8 位或手动归一化。5.5 现象变化检测结果里大量「伪变化」同一地物两期都被检测但位置偏移原因两期影像配准误差或者检测框抖动。配准误差超过 2 像素时同一建筑的框在两期里 IoU 会低于 0.3被误判为变化。解决先做影像配准检查用rasterio读两期影像的 transform确认地理参考一致。如果配准没问题把匹配 IoU 阈值从 0.3 降到 0.2或者对检测框做形态学膨胀后再匹配。更彻底的做法是训练一个双时相输入的变化检测模型让网络直接学差异而不是依赖后处理匹配。6. 进阶技巧用切片推理和 TTA 把遥感小目标召回再提一档切片推理是我在遥感项目里最常用的技巧。卫星影像动辄 5000×5000 以上直接缩到 1024 会丢大量小目标。做法是把大图切成带重叠的小块每块单独推理再把结果映射回原图坐标做 NMS。重叠区域设 20% 到 30%避免边缘目标被切断。import cv2 import numpy as np from ultralytics import YOLO def sliced_predict(model, img_path, slice_size1024, overlap0.2, conf0.25): 切片推理返回原图坐标系下的检测框 img cv2.imread(img_path) h, w img.shape[:2] stride int(slice_size * (1 - overlap)) all_boxes [] for y in range(0, h, stride): for x in range(0, w, stride): x2 min(x slice_size, w) y2 min(y slice_size, h) x1 max(0, x2 - slice_size) y1 max(0, y2 - slice_size) patch img[y1:y2, x1:x2] results model(patch, confconf, verboseFalse) for r in results: for box in r.boxes: # 把 patch 坐标映射回原图 bx1, by1, bx2, by2 box.xyxy[0].tolist() all_boxes.append([bx1 x1, by1 y1, bx2 x1, by2 y1, box.conf[0].item()]) # 对所有框做 NMS合并重叠 if not all_boxes: return [] boxes_np np.array(all_boxes) indices cv2.dnn.NMSBoxes( boxes_np[:, :4].tolist(), boxes_np[:, 4].tolist(), conf, 0.5 ) return boxes_np[indices.flatten()].tolist()逻辑说明stride控制切片步长overlap0.2表示相邻切片有 20% 重叠。每个切片推理后把坐标加回切片偏移最后用 OpenCV 的 NMS 合并。cv2.dnn.NMSBoxes的输入格式是[x, y, w, h]而 YOLO 输出是[x1, y1, x2, y2]转换时注意。TTA测试时增强是另一个提召回的手段对同一张图做水平翻转、垂直翻转、多尺度缩放分别推理后合并结果。Ultralytics 推理时加augmentTrue就能开 TTA但速度会慢 2 到 3 倍。我的习惯是训练完先用普通推理看基线如果小目标召回不够再开 TTA 对比。TTA 对变化检测的提升不如对地物分类明显因为变化检测的误报也会被放大。验证方法上我一般留 10% 的验证集不参与训练训练完用yolo detect val跑一遍看mAP50和mAP50-95。变化检测还要额外算变化区域的像素级 IoU因为检测框的 mAP 不能完全反映变化区域的重合度。像素级 IoU 低于 0.5 时即使 mAP 高实际可用性也差。最后说个血泪教训遥感项目里数据质量比模型结构重要得多。我花过两周调 YOLOv11 的网络参数mAP 只涨了 2 个点后来回头清洗标注把误标和漏标的样本修了一遍mAP 直接涨了 15 个点。模型选型决定下限数据质量决定上限。每次训练前花半小时抽查标注比事后调参划算得多。希望帮到你。本文还有配套的精品资源点击获取