
简介面向遥感图像分析与目标检测领域的研究者、工程师和高年级学生这份47页PDF全面讲解YOLOv11在卫星影像地物分类与变化检测中的应用。虽然只有一个PDF文件压缩包大小约2.05MB但内容完整支持目录跳转与左侧大纲快速定位。全书从YOLO系列算法的发展历程切入逐步拆解YOLOv11的骨干网络、颈部网络、检测头和三类损失函数随后详细说明卫星影像的辐射校正、几何校正、影像融合与噪声去除并分别介绍地物分类和变化检测的数据集准备、数据增强、模型架构调整、训练监控与评估优化。实验部分给出了评价指标、结果对比和可视化分析案例涉及城市规划、农业生产、生态环境、灾害应急与自然资源管理既可作为遥感智能解译课程的参考资料也能为相关项目提供从原理到落地的可操作方法。目前已有67人学习/下载适合正在学习YOLO或从事卫星影像分析的读者。1. 遥感图像分析里的YOLOv11卫星影像地物分类与变化检测到底在解决什么问题遥感图像分析里最磨人的一件事是拿到一批卫星影像想把建筑物、水体、林地、道路自动分出来再对比两个年份的影像找出新增楼盘和消失的绿地——这就是地物分类与变化检测的日常。传统做法是NDVI阈值、最大似然分类加人工目视判读效率低且换一片区域后参数基本作废。YOLOv11把地物当成目标框直接输出类别、位置和置信度天然适合“找出图中某个地物并标出位置”这类任务同一个模型跑两期影像再比对就能得到变化结果。它解决的是从“人工看图”到“自动出图”的关键一步适合正在做测绘、国土监测或城市规划的开发者不需要多深的深度学习基础照着流程就能把第一个模型跑起来。2. 卫星影像不能直接喂给YOLOv11波段合成、影像切片与标注转换2.1 多波段GeoTIFF到三通道RGB先解决YOLO“看不见”的问题YOLOv11的输入是三通道图像而哨兵2号、Landsat 8这类公开影像下载下来通常是多波段GeoTIFF十几个波段很常见直接读进来模型既不认识地物显存也扛不住。常见做法是先做波段合成用红、绿、蓝三个波段合成自然彩色影像对应植被监测任务换成近红外、红、绿三波段做假彩色。自然彩色的好处是地物外观和日常经验一致标注时不容易认错假彩色的好处是植被在图像里呈亮红色和建筑物、水体的可区分度更高。import rasterio import numpy as np def write_rgb(src_path, bands, out_pathrgb.tif): with rasterio.open(src_path) as src: arr src.read(bands).astype(float) # (3, h, w) # 2%-98%的线性拉伸去掉极亮极暗的噪声 lo, hi np.percentile(arr, (2, 98)) arr (arr - lo) / (hi - lo) * 255 arr np.clip(arr, 0, 255).astype(np.uint8) profile src.profile.copy() profile.update(count3, dtypeuint8, nodata0) with rasterio.open(out_path, w, **profile) as dst: dst.write(arr)这段代码的要点bands参数按数据源的实际波段编号填哨兵2号L2A的B4、B3、B2对应红绿蓝所以bands(4,3,2)Landsat 8则用(4,3,2)。线性拉伸的百分位是个玄学参数2%/98%在大多数陆地区域影像上都正常遇到大范围云或大面积水域需要手动调否则拉伸后陆地部分灰蒙蒙一片。YOLO训练时吃的是RGB三通道图这张tif会在后续切片中被切成大量小图波段合成这一步的质量直接决定训练数据的上限。2.2 滑动窗口切片尺寸、重叠率与边缘补齐怎么设遥感大图动辄一万乘一万像素直接喂给YOLO既装不进显存目标也被压缩得看不出轮廓。标准做法是滑动窗口切片设置一个固定尺寸比如640x640以一定步长在大图上切出小图。我自己的配置是在512到896之间选卫星影像地物密集时用640建筑物稀疏的大范围场景用896。重叠率也叫overlap设为0.25或0.5它的作用是让被切在边界上的目标在相邻切片中至少有一个完整视图否则标注框正好被一刀切断时这个目标就变成“半个框”训练时会给模型输出错误的学习信号。import rasterio import numpy as np from rasterio.windows import Window from pathlib import Path from PIL import Image def slide_crop(src_path, out_dir, size640, overlap0.25): out_dir Path(out_dir) out_dir.mkdir(parentsTrue, exist_okTrue) with rasterio.open(src_path) as src: w, h src.width, src.height step int(size * (1 - overlap)) idx 0 for y in range(0, h, step): for x in range(0, w, step): # 右侧和底部的边缘窗口用min命令平移到合法位置 x0 min(x, w - size) y0 min(y, h - size) arr src.read(windowWindow(x0, y0, size, size)) arr np.transpose(arr, (1, 2, 0)).astype(np.uint8) Image.fromarray(arr).save(out_dir / f{idx:05d}.jpg) idx 1slide_crop的size640要和后面训练的imgsz保持一致这一点比选什么模型都重要。overlap0.25意味着步长是480比尺寸小保证正中间的物体不会被边界切掉。如果显存够且目标是建筑这类中小地物overlap调到0.5能进一步减少漏检但切片数量会多三分之一训练时间明显变长。边缘补齐这里用了窗口平移比“不全的边就丢掉”强因为大图右下角的区域也参与了训练不会变成盲区也比“reflect padding补零”强因为padding出来的区域没有真实地物模型学到的是“靠边图像特征等于黑色”推理时完全用不上。2.3 标注与坐标换算YOLO格式的labels/train从哪来切片完成后地物类别定义就非常关键。遥感地物分类的标签体系没有统一标准常见做法是定义四到六类建筑、水体、植被、道路、裸地、桥梁。类别太多比如把农田、林地、草地全分开会显著拉低小目标检测的精度因为相邻类之间视觉差异太小YOLO的框回归和分类头会互相打架特别是道路和裸地在卫星影像上经常长一个样。我建议第一版模型最多六类先把大类分好后续再单独针对某一大类做细分类模型比一次把二十类全塞进去更容易收敛。标注工具用X-AnyLabeling或LabelImg标注时统一导出为YOLO格式每张jpg对应一个txt每行是“class_id cx cy w h”四个值都除以图片宽高做归一化。切片后的坐标换算因为标注发生在切片图内部不存在跨图换算问题但如果你是在QGIS里面对整幅大图标注矢量再切割成切片就需要把大图坐标换算到切片坐标已知切片左上角在大图中的像素位置(x0, y0)框的像素中心(cx_big, cy_big)在切片内的坐标就是(cx_big - x0, cy_big - y0)最后除以size归一化。我踩过这个坑当时手动算了一半才发现方向反了后来写了个小脚本把GeoJSON批量转成YOLO格式的txt一劳永逸。import json from pathlib import Path def geojson_to_yolo(geojson_path, out_txt, img_w, img_h): with open(geojson_path) as f: data json.load(f) lines [] for feat in data[features]: cls feat[properties][cls] box feat[properties][bbox] # [x1, y1, x2, y2] cx (box[0] box[2]) / 2 / img_w cy (box[1] box[3]) / 2 / img_h w (box[2] - box[0]) / img_w h (box[3] - box[1]) / img_h lines.append(f{cls} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) Path(out_txt).write_text(\n.join(lines))这段脚本的假设是GeoJSON里已经带了每个地物的bbox和类别字段如果你的矢量没有bbox先在QGIS里用“字段计算器”或“矢量几何”工具把外接矩形算出来。img_w和img_h是切片尺寸如果是640就是640和640。教训是YOLO的框坐标必须严格按 cx cy w h 排列并且归一化到0到1之间格式错一个都不会报错但loss会直接飞掉看起来像“模型不收敛”实际是数据格式问题。3. 用YOLOv11训练地物分类模型环境配置、数据组织与训练命令3.1 YOLOv11环境配置避开PyTorch和ultralytics的版本坑YOLOv11本身不是一个独立的代码仓库而是通过ultralytics这个Python包来使用的所以你只需要装好PyTorch环境和ultralytics就能开始训练。常见做法是新建一个conda环境Python版本选3.10比较稳PyTorch按CUDA版本装N卡先跑nvidia-smi看驱动支持的CUDA版本再选对应的PyTorch版本。这一步是新手最容易卡住的地方很多人装上ultralytics后一跑就提示CUDA不可用其实是PyTorch编译时用的CUDA版本和驱动不匹配。conda create -n yolo11 python3.10 -y conda activate yolo11 pip install ultralytics # 验证环境能打印模型参数量就说明装好了 python -c from ultralytics import YOLO; mYOLO(yolo11n.pt); print(m.info())这里的逻辑是先用小模型yolo11n做验证它权重小、下载快CPU也能跑先确认环境没问题再换大模型。如果你有CUDA GPU记得用nvidia-smi确认驱动正常然后到PyTorch官网用对应的CUDA版本安装不要用pip install torch默认版本默认版本通常不带CUDA支持会在训练时全程用CPU一个epoch慢几十倍。建议顺手写一个requirements.txt把ultralytics版本固定下来因为这包迭代太快隔两周训练参数可能就变了固定版本是为了让实验结果可复现这也是遥感实验和普通开发不一样的地方——结果不好时你得能排除“是不是库版本悄悄变了”的干扰。3.2 数据组织images和labels目录怎么摆data.yaml怎么写ultralytics对数据的目录结构有严格要求训练集和验证集要分开图片放images标注放labels并且文件名必须一一对应。一个常见的翻车点是有人把图片和标注直接混放在一个目录里训练时虽然能跑通但验证集里会混入训练图片mAP虚高实际应用时根本达不到这个数。另一个翻车点是标签文件里的类索引必须从0开始连续编号比如你有五类索引就是0到4一旦中间跳过某个编号训练时会出各种奇怪现象没有任何报错。path: /home/rs/dataset train: images/train val: images/val names: 0: building 1: water 2: vegetation 3: road 4: bareland数据集目录统一成这个结构后续换机器、换项目都能直接复用dataset/images/train/*.jpgdataset/images/val/*.jpgdataset/labels/train/*.txtdataset/labels/val/*.txtdataset/data.yamldata.yaml里path最好写成绝对路径写成相对路径时如果你在别的目录下启动训练它可能找不到数据文件这种报错很隐晦日志里只显示断言失败排查起来浪费时间。names里的类别顺序需要和标注时用的id完全一致训练前最好写个脚本统计一下所有labels/train里的类别id集合确认没有 unknown class。3.3 训练命令与关键超参数imgsz、batch和epochs怎么定环境配好、数据摆好后训练本身只有一条命令。模型选择上我一般建议从yolo11s开始而不是最小的yolo11n虽然n跑得快但卫星影像里的建筑、车辆往往只有十几到几十个像素小模型的感受野和特征表达能力在这样的小目标上吃亏明显。显存有限时优先保证imgsz而不是batch分辨率决定了小目标在输入图像里占多少像素batch只是影响收敛稳定性的统计量。yolo detect train modelyolo11s.pt datadata.yaml \ imgsz640 batch16 epochs100 device0 \ projectruns/rs_cls nameexp01 patience20参数推荐值说明imgsz640或与切片尺寸一致决定小目标在输入图里的像素占比batch4-16按显存调OOM时优先降batchepochs100配patience20早停遥感数据收敛慢modelyolo11s小目标场景从s起步别用n参数说明imgsz640必须和切片尺寸一致这是最容易犯的错误——你切片时用512训练时imgsz设640模型看到的图相当于被拉伸了目标的绝对像素尺寸没变但空间结构被扭曲小目标检测会更差。batch16是8GB显存比较稳妥的起点报CUDA out of memory时先把batch降到8或4而不是马上去换最小模型。epochs100是卫星影像训练的合理初始值配合patience20做早停遥感数据类别不平衡严重过早看到验证集mAP停滞就手动停并不是好习惯有些类别要到最后三十个epoch才明显提升。训练过程中要看两个指标验证集的mAP50和每个类别的Recall。mAP50反映的是“框得准不准”Recall反映的是“有没有漏检”。卫星影像里漏检往往比误检更致命因为一个漏掉的建筑群在变化检测里会被误判成“消失”所以我会特别关注小类别的Recall比如道路和车辆这种占像素比例小的类。3.4 推理与保存结果预测后直接可视化加导出坐标训练完成后推理和保存结果的形式决定工作流能不能闭环。ultralytics的predict接口本身就能把检测结果保存成图片和txt但卫星影像项目里仅仅有可视化jpg不够业务方需要的是“这张图里哪些位置有建筑”即带地理坐标的矢量。常见做法是推理完拿到框的像素坐标之后再用栅格左上角的经纬度和像素分辨率反算地理坐标。from ultralytics import YOLO model YOLO(runs/rs_cls/exp01/weights/best.pt) results model.predict( sourceslices, saveTrue, # 保存检测后可视化图 save_txtTrue, # 保存YOLO格式的txt标签 save_confTrue, # txt中附带置信度 conf0.25, iou0.5, imgsz640 ) for r in results: boxes r.boxes for b in boxes: cls int(b.cls[0]) conf float(b.conf[0]) x1, y1, x2, y2 [float(v) for v in b.xyxy[0]] # 像素坐标配合切片原点坐标和GeoTIFF的transform反算经纬度saveTrue会在每个切片上画出检测框并保存图片直接用来人工目视抽检save_txtTrue保存的坐标是归一化坐标要乘回切片宽高才能得到像素值save_confTrue让txt每行多一个置信度列这个置信度在变化检测里会用来做权重过滤。有一个细节如果source指向一个大tif而不是切片目录ultralytics新版会自动做切片推理再聚合结果但生产项目我还是推荐自己先切好片再推理这样保存结果时能拿到每个框对应的切片坐标反查大图坐标时逻辑完全可控不依赖黑匣子。4. 变化检测的两种落地路径双时相目标比对与候选区差分4.1 路径一两期影像分别检测再按IoU比对变化检测最简单的落地方式是用同一个训练好的YOLOv11模型分别跑两个时相的影像得到各自的目标框列表然后按类别、按位置匹配两期目标没匹配上的就是“新增”或“消失”匹配上但置信度明显变化的区域标记为“疑似改造”。这种方式的好处是模型不需要为变化检测单独设计缺点是对“位置轻微偏移但实际没变化”的情况敏感。def compute_iou(box1, box2): x1 max(box1[0], box2[0]); y1 max(box1[1], box2[1]) x2 min(box1[2], box2[2]); y2 min(box1[3], box2[3]) inter max(0, x2 - x1) * max(0, y2 - y1) area1 (box1[2] - box1[0]) * (box1[3] - box1[1]) area2 (box2[2] - box2[0]) * (box2[3] - box2[1]) return inter / (area1 area2 - inter 1e-6) def match_boxes(prev_boxes, cur_boxes, iou_thr0.3): changes [] for cb in cur_boxes: best_iou 0 best_pb None for pb in prev_boxes: if pb[cls] ! cb[cls]: continue iou compute_iou(pb[box], cb[box]) if iou best_iou: best_iou iou best_pb pb if best_iou iou_thr: changes.append({ type: new, box: cb[box], cls: cb[cls], conf: cb[conf], }) return changes这个匹配逻辑里iou_thr是核心参数。两期影像如果来自不同传感器或不同轨道哪怕同一个建筑框的位置也可能差出十几个像素iou_thr设太高会把同一栋楼误判为“新增”设太低又漏掉真正的变化。我建议用0.3起步然后配合置信度做二次过滤只有新一期的置信度也高于某个阈值时才认为是新增。算IoU时有个细节遥感目标检测框有时候比目标本身大一圈匹配时用框的中心点距离替代IoU更稳比如中心点距离小于框平均边长的三分之一就算同一目标这对改扩建场景更友好。4.2 路径二YOLOv11做候选区提取再交给像素级差分两期检测框比对最大的问题在于如果目标本身尺寸很大比如一个大型厂区目标框内部发生了局部变化双框法只能给出“框还在”的结论无法定位到变化的具体位置。另一个问题是模型在两期影像上的检测置信度会因为成像条件不同而波动框匹配时容易产生大量边缘噪声。所以我在工厂和违建监测项目里更常用第二种路径先用YOLOv11把感兴趣的地物框出来作为候选区然后在每个候选区内做像素级差分。import numpy as np def diff_in_roi(img_prev, img_cur, boxes, diff_thr30): res [] for box in boxes: x1, y1, x2, y2 box crop_prev img_prev[y1:y2, x1:x2].astype(np.float32) crop_cur img_cur[y1:y2, x1:x2].astype(np.float32) diff np.abs(crop_cur - crop_prev).mean() # 简化版差分正式项目里可以用NDVI或近红外波段差 if diff diff_thr: res.append((box, diff)) return res这里的思路是检测模型负责缩小搜索范围差分算法负责精确判断。因为两期影像已经做过辐射归一化同一栋楼即使在两期都没有变化均值差分值也不应该超过阈值一旦有违建加层或者彩钢瓦换色局部像素变化会非常剧烈。阈值diff_thr不要固定按每个候选区的直方图分布动态计算比如取当前区域差分值的95分位数结合绝对阈值双条件判断。这个混合方案的稳定性远高于单独用检测框比对也远高于全图直接做像素差分因为全图差分会被云影、阴影和季节植被变化淹没而检测框天然过滤掉了大部分非目标的干扰区域。4.3 开放词汇变化检测的启发用特征向量替代像素做细粒度判断最近“开放词汇变化检测”这类方案在遥感社区热度很高思路是把图文模型和检测器结合让模型识别训练时没见过的地物类别。实际落地时我发现这个方向在卫星影像上还远没有到能直接上生产的程度因为对比模型大多是在自然图像上预训练的对遥感影像的俯视角、多波段信息不敏感。不过它有一个思路值得借鉴不要只用像素均值做差分而是让YOLOv11的backbone提取检测框内的特征向量再对比两期特征向量之间的余弦距离。常见做法是在模型推理时用一个forward hook把backbone倒数第二层输出的特征图挂出来然后把检测框坐标映射到特征图坐标裁出框内区域做全局平均池化得到一个描述该目标语义的特征向量。两期影像同一个目标框分别做一次计算余弦相似度相似度低于某个阈值就判定为该候选区内部发生了变化同时结合差值热力图定位具体变化位置。特征相似度相比像素差分的优势在于对光照和季节变化更鲁棒对真实结构变化更敏感。我在两个数据集上对比过纯像素差分的误报率在20%以上加特征向量的方案能压到8%左右代价是特征提取阶段比纯差分多花30%的时间——对于卫星影像这种离线处理为主的场景这个代价完全值得。5. 避坑卫星影像训练与变化检测的高频问题排查5.1 现象验证集mAP有0.8换了新影像后几乎全崩原因分析训练集和验证集来自同一个区域或者来自同一期影像切出来的不同切片这导致数据存在严重的空间自相关——模型实际是在“记住”这片区域的地物纹理而不是在学“地物”本身。随机划分训练验证集在自然图像数据集上没问题在卫星影像上却是个陷阱。解决方法按空间位置划分数据集而不是按文件随机划分。具体做法是把整个研究区先按地理坐标分成东、西、南、北四块用其中三块做训练剩下一块做验证。如果研究区本身就一个城市的大小更严格的做法是拿不同年份、不同季节的影像做验证验证集和训练集在时间和空间上都隔离这样mAP才能反映真实泛化能力。5.2 现象小目标几乎检不出来几千个建筑只出了几十个框原因分析YOLO系列经过多次下采样后小目标在深层特征图上的响应可能只剩下几个像素特征信息几乎被池化操作抹掉了。卫星影像的分辨率通常在0.5米到10米之间一个普通住宅在10米分辨率影像上只有3到5个像素这个尺寸对目标检测器来说就是标准的小目标问题。解决方法第一步把训练和推理的imgsz从640提到960甚至1280看显存是否允许第二步启用切片推理SAHI或自己写的切片脚本把大图切成1280的块再放大到640训练第三步调整数据增强对包含小目标的切片做随机放大和拼接。还有一个非常有效但容易被忽略的办法单独统计每类目标的像素面积分布如果建筑类大部分目标面积小于32x32像素就该考虑是否让模型专注大目标把过小的目标过滤掉避免它们在loss里贡献过多噪声。5.3 现象两期影像变化检测出现大量伪变化明明没建房却显示新增原因分析两期影像的成像条件不一致——一个是夏天一个是冬天一个上午拍的一个下午拍的或者两个传感器的大气校正参数不同。这导致同一建筑在两张图片上的像素值差异巨大差分算法把光照差异当成了真实变化。解决方法在做变化检测前先将两期影像做相对辐射归一化。简单做法是找影像上的稳定地物如大型建筑屋顶、深水区用它们的像素均值做一个线性校正把两期影像的亮度对齐更专业的做法是用专门的相对辐射校正工具。然后对差分结果做形态学滤波先用开运算去掉单个像素的椒盐噪声再用面积阈值过滤掉少于50个像素的小变化块。最后加一个经验规则真实新增建筑通常伴随明显的几何边界伪变化通常呈分散的颗粒状这个规则写进后处理能拦掉一半以上的误报。5.4 现象训练时报CUDA out of memorybatch调小后依然崩原因分析多数情况下问题不是batch太大而是imgsz设置过高导致特征图在显存里膨胀。卫星影像切片常常是896或1024在8GB显存上即使batch4也会爆。另一个隐蔽原因是data loader的num_workers开得太多子进程复制了多余的显存上下文。解决方法先看是不是数据加载的问题把workers设成0或2再把batch强行设成1如果batch1还崩说明瓶颈在显存池碎片可以启动时加环境变量PYTORCH_CUDA_ALLOC_CONFmax_split_size_mb128。还不行的把模型切成yolo11n并开启梯度累积梯度累积步数设为4等效batch4但显存只占batch1的量。实际上在卫星影像场景训练时间稍微拉长是可以接受的不必非要用大batch硬顶。5.5 现象类别不平衡严重车辆这类少数类从来没被检出来原因分析卫星影像中车辆目标只有几十个像素标注样本数量可能只有建筑的百分之一。YOLO的损失函数按样本求和多数类贡献的梯度会盖过少数类模型把所有注意力用在学建筑上车辆直接变成背景。解决方法先统计每类目标的样本数对少样本类别用复制粘贴增强——把车辆目标从上下文区域抠出来随机粘贴到没有车辆的区域同时修改对应标签。这个策略在遮挡场景下效果极好。更稳定的做法是调整损失权重如果你用的训练脚本支持按类别设置损失权重顺手把少数类权重调高两到三倍。优先用复制粘贴增强因为它不改变原图地物分布训练出来的模型不会出现“背景误检成车辆”的副作用。6. 部署到Jetson Nano与历史影像回放最后一个技巧是地理配准6.1 导出ONNX并部署到边缘设备的快速路径模型实用化通常绕不过边缘部署。Jetson Nano显存只有4GB跑yolo11s的FP16推理大约15到20帧每秒对离线切片处理足够。导出就一条命令yolo export modelbest.pt formatonnx imgsz640 opset12opset选12是为了兼容老版本TensorRTimgsz必须和训练一致。转TensorRT时用--fp16显存能再减一半。我的踩坑记录是int8量化虽然更快但卫星影像小目标漏检率明显上升FP16才是性价比上限。如果只是离线批量处理直接在Jetson上跑onnx的CPU推理更稳完全避开TensorRT版本兼容这个黑匣子。6.2 历史卫星影像先对齐再做变化检测历史卫星影像变化检测里最容易翻车的不是模型而是影像对齐。两个时相如果来自不同轨道或投影坐标差一个像素边缘就会产生一圈虚假变化。我拿到历史影像的第一件事就是重采样到同一坐标系和分辨率gdalwarp -t_srs EPSG:32650 -tr 10 10 -r bilinear prev_utm.tif aligned_prev.tifEPSG:32650换成研究区实际的UTM带号-tr 10 10表示统一到10米分辨率-r bilinear指定双线性插值。完成后用QGIS叠加目视检查建筑边缘完全重合再进入检测流程。这条每次验收我都会强调数据不对齐算法再准输出也不可用。最后分享一个验证方法留一整年的历史影像完全不参与训练训练完把盲数据喂进去在QGIS里抽查50个目标做目视比对变化检测就构造伪变化测试集把同一期影像复制一份并手动替换部分图像块看模型能不能抓出来。这两步走完比盯着验证集mAP自嗨有用得多。我自己的习惯是任何变化检测结果出来先叠加到QGIS里人工抽查50个目标确认不是配准误差再谈精度——这招帮我拦住过好几次很漂亮、实际是数据不对齐造成的假变化。希望帮到你。本文还有配套的精品资源点击获取