
Python与卫星遥感技术结合实现地表变化检测与分析:从数据获取到变化图斑的实战指南
大家好,作为一名长期混迹在GIS和遥感圈的技术博主,我经常被问到:“如何用Python处理卫星影像,自动找出哪里新建了楼房,或者哪片森林消失了?” 今天,我就带大家走一遍完整的实战流程,用Python结合GEE和本地库,实现一个简单但有效的地表变化检测与分析。你会发现,这门技术并不像想象中那么高深莫测,核心思路清晰,工具链成熟,剩下的就是我们的代码和耐心了。
第一步:搭建环境与明确技术栈
工欲善其事,必先利其器。我们这次的技术栈组合非常经典:Google Earth Engine (GEE) 用于云端获取和处理海量卫星数据,本地Python环境 用于精细分析和可视化。GEE解决了我们个人电脑无法存储和处理TB级影像的痛点。
首先,你需要一个GEE账号并启用API。接着,在本地安装必要的库:
pip install earthengine-api geemap scikit-image rasterio matplotlib numpy geopandas
踩坑提示:安装 `earthengine-api` 后,务必在命令行运行 `earthengine authenticate`,按照提示完成浏览器授权。这是连接云端的第一步,很多新手会卡在这里。
第二步:获取前后时相卫星影像
变化检测的核心是比较两个不同时间的影像。我们以监督分类后比较为例,选取某城市郊区两年间的Sentinel-2影像(它提供免费且质量不错的多光谱数据)。
以下代码演示如何在GEE中筛选、去云并导出影像到Google云盘(你需要有自己的云盘和项目):
import ee
import geemap
ee.Initialize()
# 定义感兴趣区域(这里用杭州萧山区的大致范围)
roi = ee.Geometry.Rectangle([120.1, 30.0, 120.5, 30.3])
# 定义时间范围
date_pre = ['2020-05-01', '2020-09-30'] # 变化前
date_post = ['2022-05-01', '2022-09-30'] # 变化后
# 定义去云函数(针对Sentinel-2)
def maskS2clouds(image):
qa = image.select('QA60')
cloudBitMask = 1 << 10
cirrusBitMask = 1 << 11
mask = qa.bitwiseAnd(cloudBitMask).eq(0).And(qa.bitwiseAnd(cirrusBitMask).eq(0))
return image.updateMask(mask).divide(10000)
# 获取影像集,并选择中值合成以进一步减少噪声
collection_pre = ee.ImageCollection('COPERNICUS/S2_SR')
.filterBounds(roi)
.filterDate(date_pre[0], date_pre[1])
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 20))
.map(maskS2clouds)
collection_post = ee.ImageCollection('COPERNICUS/S2_SR')
.filterBounds(roi)
.filterDate(date_post[0], date_post[1])
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 20))
.map(maskS2clouds)
# 合成中值影像
image_pre = collection_pre.median().clip(roi)
image_post = collection_post.median().clip(roi)
# 选择用于分类的波段(这里用了红、绿、蓝、近红外)
bands = ['B4', 'B3', 'B2', 'B8']
# 启动交互式地图选择训练样本(这是关键步骤!)
Map = geemap.Map(center=[30.15, 120.3], zoom=11)
Map.addLayer(image_pre, {'bands': ['B4', 'B3', 'B2'], 'min': 0, 'max': 0.3}, 'Pre-Image')
Map
# 运行后,在地图上用绘图工具手动勾选样本点/区域,例如“植被”、“水体”、“建筑”、“裸地”
实战感分享:样本质量直接决定分类成败。我的经验是,每个类别至少采集30个以上的样本点,并且要均匀分布在整个区域。对于“建筑”类,要区分新房顶(亮)和老房顶(暗),样本都要涵盖。
第三步:训练分类器与执行分类
拿到样本后,我们使用随机森林算法进行分类。这里假设我们已经通过交互地图创建了名为 `training` 的样本集合。
# 将样本点与影像值关联,生成训练数据集
training_data = image_pre.select(bands).sampleRegions(
collection=training, # 这是你之前绘制的样本FeatureCollection
properties=['landcover'],
scale=10
)
# 训练随机森林分类器
classifier = ee.Classifier.smileRandomForest(50).train(
features=training_data,
classProperty='landcover',
inputProperties=bands
)
# 对前后两期影像进行分类
classified_pre = image_pre.select(bands).classify(classifier)
classified_post = image_post.select(bands).classify(classifier)
# 导出结果到云盘(后续从云盘下载到本地)
task_pre = ee.batch.Export.image.toDrive(
image=classified_pre,
description='classified_pre_export',
scale=10,
region=roi,
fileFormat='GeoTIFF'
)
task_pre.start()
# 对 classified_post 执行同样的导出操作
踩坑提示:GEE的导出任务是异步的,`task.start()` 只是提交任务。你需要去GEE的“Tasks”面板查看进度,并在完成后从Google云盘下载TIFF文件到本地工作目录。这个过程可能需要几分钟到半小时。
第四步:本地计算变化检测与结果分析
现在,我们有了两期分类图。变化检测最简单直接的方法就是“后减前”,找出类别发生变化的像素。
import rasterio
import numpy as np
import matplotlib.pyplot as plt
from skimage import morphology
# 1. 读取下载好的分类结果
with rasterio.open('classified_pre.tif') as src:
class_pre = src.read(1)
profile = src.profile
with rasterio.open('classified_post.tif') as src:
class_post = src.read(1)
# 2. 计算变化矩阵(简单相减,非变化区域为0)
change_raw = class_post - class_pre
# 注意:这里变化值不为0的像素就是发生了变化,但我们需要关注特定变化,如裸地->建筑(假设编码 3->4)
change_mask = np.zeros_like(change_raw)
change_mask[(class_pre == 3) & (class_post == 4)] = 1 # 3->4 代表裸地转为建筑
# 3. 后处理:去除小斑块(噪声)
change_cleaned = morphology.remove_small_objects(change_mask.astype(bool), min_size=50) # 最小50个像素
change_cleaned = morphology.remove_small_holes(change_cleaned, area_threshold=25)
# 4. 将变化图保存为新的GeoTIFF
profile.update(dtype=rasterio.uint8, count=1)
with rasterio.open('change_builtup.tif', 'w', **profile) as dst:
dst.write(change_cleaned.astype(rasterio.uint8), 1)
# 5. 简单统计与可视化
change_pixels = np.sum(change_cleaned)
total_pixels = change_cleaned.size
change_area_km2 = change_pixels * 10 * 10 / 1000000 # 假设10米分辨率,换算成平方公里
print(f"检测到建筑扩张像元数:{change_pixels}")
print(f"估算变化面积:{change_area_km2:.2f} 平方公里")
fig, axes = plt.subplots(1, 3, figsize=(15,5))
axes[0].imshow(class_pre, cmap='tab20c')
axes[0].set_title('2020年土地覆盖')
axes[1].imshow(class_post, cmap='tab20c')
axes[1].set_title('2022年土地覆盖')
axes[2].imshow(change_cleaned, cmap='Reds')
axes[2].set_title('裸地->建筑变化图斑')
plt.show()
实战感分享:直接相减得到的变化图往往“椒盐噪声”严重(散落的单像素点)。remove_small_objects 这一步至关重要,它能将破碎的变化斑块过滤掉,让结果更接近现实中“成片开发”的图景。阈值(`min_size`)需要根据你的研究区域尺度和分辨率反复调整,这是我的经验之谈。
第五步:总结与进阶思考
至此,我们已经完成了一个完整的“数据获取-分类-变化检测-输出”的闭环。这个流程是许多实际项目(如违章建筑监测、森林砍伐预警)的简化核心。
当然,这只是起点。要提升精度和实用性,还有很长的路要走:
- 分类优化:尝试更复杂的分类器(如SVM、CNN),加入纹理特征、指数(如NDVI、NDBI)作为输入。
- 直接变化检测法:跳过分类步骤,直接对两期影像的光谱差异(如变化向量分析CVA)或通过深度学习模型(如Siamese网络)进行端到端的变化检测,有时能获得更好效果。
- 时序分析:利用GEE强大的时序数据能力,分析多年连续变化趋势,而不仅仅是两个时间点。
- 与GIS集成:将生成的 `change_builtup.tif` 导入QGIS或ArcGIS,与行政区划、道路等矢量数据叠加,进行更深入的空间分析。
希望这篇教程能帮你推开Python遥感变化检测的大门。整个过程就像侦探破案,卫星数据是现场照片,Python是你的放大镜和推理工具。多动手,多调参,多思考物理意义,你一定能从这些看似枯燥的像素中,解读出大地讲述的生动故事。代码和数据就在那里,现在就打开编辑器,开始你的第一次“地球观察”吧!

评论(0)