ARTICLE DETAIL

资讯详情

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

基于Qgis二次开发的遥感影像云检测与地物恢复系统实现

基于Qgis二次开发的遥感影像云检测与地物恢复系统实现 简介本资源是一个基于QGIS二次开发的遥感图像云检测与地物恢复桌面应用系统面向遥感、GIS及计算机视觉领域的开发者与科研人员解决遥感影像中云层干扰导致的地物信息缺失问题。系统集成传统阈值/光谱算法与CNN等深度学习模型支持云检测、大气校正、波段合成、地物重建等全流程处理并通过PyQt构建可视化界面降低专业工具使用门槛。压缩包共76个文件51.24MB含21个核心Python源码如main.py、算法线程模块、UI逻辑类、5个.ui界面文件、7个图标与图片资源、5个XML配置及国际化文件以及requirements.txt、README.md和说明文档等结构清晰、模块解耦便于二次开发与算法替换。目前已有70人学习下载提供完整可运行工程涵盖QGIS插件开发、遥感预处理逻辑、多线程图像处理实现及深度学习模型轻量化集成实践是GIS与AI融合落地的典型参考案例。 做遥感影像处理的朋友多多少少都遇到过这样一个痛点辛辛苦苦下载一景影像打开一看大片云层把关键地物遮得严严实实。如果只做目视解译还能靠经验脑补但要是做定量反演、地物分类或者给业务系统供数据云遮挡就直接毁掉整条处理链路。这个项目要解决的就是这个问题。我基于Qgis二次开发做了一套桌面端的遥感影像云检测及地物恢复系统。名字听着长拆开看就三件事自动把云和云影识别出来然后对被遮挡的区域做地物信息恢复最后把这些能力以插件形式集成进Qgis让遥感处理人员在熟悉的GIS环境里一键完成“云检测去云修复”流程。整套系统集成了传统阈值算法、Fmask思想以及深度学习语义分割模型属于典型的“多算法融合平台”适合有遥感影像处理需求、想了解Qgis插件开发或者准备把深度学习模型部署到桌面GIS里的开发者参考。1. 内容整体设计与思路拆解1.1 为什么选Qgis作为二次开发基底先聊选型。市面上能做遥感影像处理的GIS平台不少ArcGIS、ENVI、ERDAS都是老牌选择但我最终还是选了Qgis核心原因有三个第一Qgis是开源项目基于GPL协议二次开发几乎没有授权成本。ArcGIS的ArcObjects开发要买许可ENVI的IDL开发环境也价格不菲而Qgis的插件机制非常成熟PyQgisPython绑定 PyQt的架构让开发者可以快速用Python写逻辑、用Qt做界面开发效率远超原生C方案。第二Qgis原生支持海量栅格格式GDALGeospatial Data Abstraction Library作为底层栅格读写引擎GeoTIFF、ENVI格式、HDF、NetCDF都能直接读取省去了自己写格式解析的麻烦。对于做遥感的人来说这能省掉一多半的数据预处理工作量。第三Qgis的插件框架设计得相当优雅。每个插件就是一个Python包通过metadata.txt声明元信息在Qgis启动时加载用户可以在插件管理器里一键安装、卸载。这种机制非常适合做算法工具集成——用户不需要理解你的算法细节只需要在菜单栏点一下就能把云检测模型跑起来。当然Qgis二次开发也有它的“坑”。比如Qgis自带的Python环境是内嵌的版本更新时依赖库可能不兼容再比如插件调试时Qgis崩溃一次就得重启整个应用开发体验比Web开发差不少。这些我在后面“常见问题”章节会展开讲。1.2 系统解决的核心需求和功能边界这套系统要回答的核心问题是拿到一景含云影像后如何自动化地判断“哪里有云”并尽量还原“云下面到底是什么”。这里要明确一个概念云检测和地物恢复是两件不同难度的事。云检测是“找出问题区域”它是后续所有处理的前提。地物恢复是“解决问题区域”则需要利用空间邻域信息、时间序列信息或多光谱信息来推测被遮挡区域的地物特征。需求拆解下来系统需要具备以下能力支持多光谱遥感影像的读取和显示能叠加矢量图层便于验证精度集成至少两种云检测算法包含传统方法与深度学习方法以应对不同数据源和不同云况提供云影检测功能云层投射产生的阴影区域常被误判为水体或暗地物云检测结果可视化以矢量面或掩膜栅格形式输出对云遮挡区域执行地物恢复支持基于多时相影像的替换修复和基于单景影像的内部插值修复提供处理报表统计云量占比、恢复面积等参数。这样一套功能如果做成独立程序至少需要从零搭建GIS数据模型、符号化渲染、坐标变换等工作半年起步。但基于Qgis二次开发数据管理和可视化这些脏活累活Qgis全帮你干了你只需要聚焦算法实现和流程编排。1.3 多算法融合平台的架构思路“多算法融合平台”这个定位决定了系统不能只绑定某一种云检测方法。实际遥感场景太复杂了厚云、薄云、碎云、云影、雪地、亮裸岩不同地物在不同波段上呈现的光谱特征千差万别没有任何单一算法能“通吃”所有情况。我的架构思路是“三层分离 两阶段融合”三层分离指的是数据层、算法层、应用层彼此解耦。数据层统一通过GDAL读取为numpy数组算法层仅接收numpy数组和元数据应用层只负责参数传递和结果展示。这样后续新增算法只需要按接口标准写一个算法类注册到算法工厂里即可。两阶段融合指的是云检测阶段和地物恢复阶段各自做融合决策。云检测阶段传统阈值算法和深度学习模型分别输出云概率图通过置信度加权融合得到最终的云掩膜——传统算法负责“兜底”深度学习负责“精修”。地物恢复阶段优先使用多时相替换如果有可用同期无云影像找不到合适影像时再降级到单景插值修复。这个思路在有真实数据的时候非常管用。我之前用Landsat 8影像做过测试单用阈值算法在多云区域精度只有78%左右单用UNet模型能达到89%但遇到训练数据里没见过的传感器时模型的误检率明显上升。融合之后整体IoUIntersection over Union交并比稳定在了90%以上。2. 核心细节解析与实操要点2.1 云检测算法选型与原理解读系统里集成的云检测算法我按实现难度和原理差异分成了三个梯队。第一梯队是阈值法也是所有方法的基石。原理很简单云在可见光波段反射率极高在热红外波段亮温极低。对Landsat系列影像经典的OTSU自动阈值法可以直接在蓝色波段Band 2上找分割点对高分影像则常用归一化云指数NDCINormalized Difference Cloud Index来做增强公式是绿波段 - 红波段/绿波段 红波段云区在这个指数上会形成明显的高值聚集。第二梯队是Fmask思路的扩展。FmaskFunction of mask是经典的综合阈值云检测算法它的核心思想是分层次判定先用热红外波段识别厚云再用可见光反射率识别薄云然后结合空间变差函数去除雪和亮地表干扰最后用几何关系推算云影位置。我不可能完全复刻Fmask但借鉴了它的“多特征联合判定”思路把亮度、温度、光谱变差三个特征融合做一个轻量级版本。第三梯队才是深度学习模型用的是UNet变体。为什么UNet因为云检测本质上是逐像素语义分割任务输入多光谱影像输出每个像素属于云/非云的概率。UNet的编码器-解码器结构天然适合这类任务而且训练数据需求量相对较小。我用的是L8 Biome数据集和Sentinel-2云掩膜数据集裁切成了256×256的patch用于训练。三个梯队的关系是阈值法跑得快但精度有限Fmask思路稳健但依赖热红外波段深度学习精度高但数据要求高。实际使用时系统默认使用“阈值法初筛 深度学习精修”的组合模式最大程度兼顾速度和精度。2.2 地物恢复技术路线单景修复与多时相替换地物恢复比云检测难一个量级因为这是“信息补全”问题而不是“分类”问题。系统里我实现了两条恢复路线。一条是单景影像修复路线适用于没有同区域其他时相数据的场景。技术基础是图像修复inpainting分两类基于偏微分方程的方法如OpenCV的Telea算法、Navier-Stokes算法和基于深度学习的方法如PatchMatch结构或者GAN类的生成修复。基于偏微分方程的算法速度快适合纹理简单的区域比如大范围云遮挡下的均匀地表深度学习方法对复杂纹理恢复效果更好但容易产生“幻觉”——生成一些不存在的细节。另一条是多时相替换路线这是工程上最靠谱的方案也是我强烈推荐的。做法是先对影像做配准处理保证不同时相同一地理位置像素对齐然后用云掩膜定位遮挡区域直接从无云时相里“挖”出对应像素填进去。这样得到的恢复结果100%是真实观测值不掺任何猜测成分。实际项目里怎么选择我的经验是有可用无云时相就优先用替换法没有就退而求其次用插值修复。宁可恢复后的区域光谱稍显平滑也不要让深度学习模型“自由发挥”编造地物细节——这在做定量遥感分析时是要出问题的。2.3 数据预处理辐射定标、大气校正与几何配准这里要特别提醒一个很多人忽视的点云检测和地物恢复的精度很大程度上取决于数据预处理做得到不到位。直接拿原始DN值影像跑算法大概率翻车。云检测之前先要确认数据是否做了辐射定标和大气校正。辐射定标是把DN值转换为传感器入瞳处的辐亮度这一步对跨传感器阈值设定尤为重要——不同传感器同一地物的DN值范围可能完全不同但辐亮度值具有一定可比性。大气校正则是消除大气散射和吸收的影响这会直接影响薄云的检测效果。薄云和霾本质上就是大气散射的增强版没做大气校正的影像薄云区域的反射率可能和正常区域在一个水平线上。几何配准则是多时相替换的前置条件。不同时相影像即使都带了地理坐标像素级对齐精度也可能存在偏移尤其是LandsatL1级别产品。所以系统里加了一个基于相位相关的自动配准模块用户选择参考影像和待替换影像后系统自动计算偏移量并重采样对齐。实测下来这个自动配准能把亚像素级的偏移校正到0.3个像素以内完全满足替换要求。预处理环节的总体验是花20分钟做辐射定标和大气校正能给后续算法省下2小时的调参时间。千万不要图省事跳过。3. 实操过程与核心环节实现3.1 开发环境搭建与Qgis插件工程初始化先说环境我用的是Qgis 3.28 LTR版本长期支持版稳定优先它自带Python 3.9环境和PyQt5绑定。开发环境建议用Qgis内置的Python运行插件不搞外部IDE联调——外部IDE配置QT_QPA_PLATFORM和PYTHONPATH的麻烦程度足够劝退一个入门选手。插件工程的结构很固定cloud_removal_plugin/ ├── __init__.py ├── metadata.txt ├── cloud_removal_plugin.py ├── cloud_removal_plugin_dialog.py ├── resources.qrc ├── algorithms/ │ ├── __init__.py │ ├── base_algorithm.py │ ├── threshold.py │ ├── fmask_like.py │ └── deep_model.py └── ui/ └── cloud_removal_dialog.uimetadata.txt里声明插件基本信息关键字段包括nameCloudRemovalPlugin qgisMinimumVersion3.22 descriptionRemote sensing cloud detection and restoration version1.0.0 authorYour Name emailyouremail.com aboutCloud detection and restoration plugin for QGIS tracker repository在__init__.py里通过classFactory()入口函数返回插件类实例这是Qgis加载插件的标准方式。插件类里需要实现initGui()方法创建菜单和工具栏按钮和unload()方法清理菜单和信号连接。有一点要注意Qgis插件运行在Qgis的Python进程里你的算法代码是不能随便调用sys.exit()的也不能直接使用独立Python环境里安装的第三方包。依赖安装的问题后面专门讲。3.2 插件界面设计与参数交互流程插件界面我用Qt Designer设计核心交互分为三步第一步是选择输入影像。用一个QgsMapLayerComboBox控件自动列出当前工程中所有栅格图层用户直接下拉选择不用写文件路径。同时提供一个“浏览文件”按钮支持从磁盘加载GeoTIFF。第二步是设置算法参数。左侧做一个参数面板用户可以选择云检测算法模式快速模式/精确模式/全流程模式深度学习推理的模型文件路径默认指向插件目录下的models/unet_cloud.onnx以及是否启用多时相恢复。第三步是显示结果。系统处理完成后自动把云掩膜以矢量面图层添加到Qgis画布用红色标识云区蓝色标识云影区恢复后的影像以栅格图层叠加显示。处理日志输出到面板下方的QTextEdit控件里。界面设计的核心原则是“减少认知负担”。遥感用户不是算法工程师他们不想理解阈值是什么意思所以界面上尽量用“快速/精细”“云量阈值高/中/低”这种描述性参数代替具体的数字和公式。3.3 云检测模块实现阈值分割与UNet推理阈值分割部分核心代码如下简化为关键逻辑import numpy as np from osgeo import gdal def cloud_detection_threshold(blue_band, green_band, red_band, nir_band, brightness_tempNone): 基于多波段阈值进行云检测. 思路 1. 蓝光波段高反射率cloud_blue blue otsu_threshold 2. NDCI指数增强薄云 3. 若提供亮温数据加入亮温约束 # OTSU自动阈值分割 from skimage.filters import threshold_otsu thr_blue threshold_otsu(blue_band[blue_band 0].flatten()) # 归一化云指数增强 ndci (green_band - red_band) / (green_band red_band 1e-6) # 组合判定蓝光高反射 或 (NDCI高且近红外也不低的情况) cloud_mask (blue_band thr_blue * 0.9) | (ndci 0.25) # 亮温约束若亮温小于阈值通常为厚云增加置信度 if brightness_temp is not None: cloud_mask cloud_mask (brightness_temp 290) return cloud_mask深度学习部分为了不依赖TensorFlow/PyTorch运行时我选择把训练好的模型导出为ONNX格式用onnxruntime做推理。这样Qgis插件环境里只需要安装onnxruntime这一个依赖部署体积小得多。import onnxruntime as ort import numpy as np class DeepCloudDetector: def __init__(self, model_path): self.session ort.InferenceSession(model_path, providers[CPUExecutionProvider]) self.input_name self.session.get_inputs()[0].name self.input_shape self.session.get_inputs()[0].shape def predict(self, img_patch): # img_patch: [C, H, W] float32 input_tensor np.expand_dims(img_patch, axis0).astype(np.float32) outputs self.session.run(None, {self.input_name: input_tensor}) prob np.squeeze(outputs[0]) return prob推理前的预处理有两个关键点一是要做标准差归一化和训练时的数据处理方式保持一致二是要把影像裁剪成模型输入尺寸的patch分批推理后拼回原尺寸。裁剪时的重叠区域建议设置16像素避免拼接缝带来的边缘效应。3.4 掩膜后处理矢量化、平滑与云影剔除模型输出的二值掩膜直接用效果往往很粗糙——单像素噪点多云边界锯齿状且云影常被误检为云。所以后处理环节很关键。我的处理流程是先做形态学开闭运算先腐蚀后膨胀去除小面积噪点、填平空洞然后做连通域分析过滤掉面积小于50个像素的碎块多为误检最后把栅格掩膜矢量化用Qgis的QgsVectorLayer输出为多边形shp文件。云影剔除这一步容易被忽视。云影在光学影像上呈现暗色区域和山体阴影、水体很难区分。我的方案是借助“太阳方位角-云高度”的几何关系来推算云影大致位置云影在云层下风方向与太阳方位角相对偏移量取决于云高和太阳高度角。这个推算精度有限但可以作为弱约束条件将明显不符合几何关系的暗色误检区域滤除。cloud_shadow_probable_region shift(cloud_region, directionopposite_sun_azimuth, distancecloud_height * tan(sun_elevation))3.5 地物恢复实现多时相替换与插值修复多时相替换的代码相对直接核心就是“掩膜索引 像素替换”def replace_cloud_with_clean(cloudy_img, clean_img, cloud_mask, feather_pixels15): 用无云影像替换有云影像的云遮挡区域. # 二值掩膜膨胀做羽化过渡 from scipy.ndimage import binary_dilation mask_dilated binary_dilation(cloud_mask, iterationsfeather_pixels) # 生成权重图掩膜边界过渡区域权重渐变避免接缝突兀 from scipy.ndimage import distance_transform_edt dist_inside distance_transform_edt(cloud_mask) # 云区内部距离 dist_outside distance_transform_edt(~cloud_mask) # 非云区距离 weight dist_inside / (dist_inside dist_outside 1e-6) weight np.clip(weight, 0, 1) # 在云区用干净影像填充过渡带用加权融合 result cloudy_img.copy() fill_region cloud_mask | mask_dilated result[fill_region] clean_img[fill_region].astype(np.float32) result[mask_dilated] (cloudy_img * weight clean_img * (1 - weight))[mask_dilated] return result羽化处理很关键。如果不做过渡直接硬拼接云区边界会出现明显的“补丁效应”在后续分类时会被误识别为异质地物。单景插值修复则直接应用OpenCV的cv2.inpaint函数基于Telea算法。使用时建议把影像转到浮点型并归一化到0-255逐波段修复后再转回浮点型避免uint8截断带来的数据精度损失。3.6 性能优化大影像分块处理与进度反馈遥感影像动辄上万×上万像素直接整幅加载进内存做模型推理32G内存都不够用。所以系统必须做分块处理。分块的核心思路是把全图划分成512×512的瓦片tile每个瓦片重叠32像素overlap逐块处理最后拼回。这样内存占用只取决于瓦片大小和整张影像大小无关。def process_raster_in_tiles(raster_path, tile_size512, overlap32, process_funcNone): ds gdal.Open(raster_path) width, height ds.RasterXSize, ds.RasterYSize # 计算瓦片网格 tiles_x int(np.ceil(width / (tile_size - overlap))) tiles_y int(np.ceil(height / (tile_size - overlap))) result np.zeros((ds.RasterCount, height, width), dtypenp.float32) for ty in range(tiles_y): for tx in range(tiles_x): # 计算当前瓦片的边界 x0 max(0, tx * (tile_size - overlap)) y0 max(0, ty * (tile_size - overlap)) x1 min(width, x0 tile_size) y1 min(height, y0 tile_size) # 读取瓦片数据 tile_data ds.ReadAsArray(x0, y0, x1 - x0, y1 - y0) # 执行算法处理 result[:, y0:y1, x0:x1] process_func(tile_data) return result进度反馈方面Qgis的QProgressDialog标准控件就能满足需求。要注意的是算法处理过程必须放在独立线程里跑否则UI线程被阻塞Qgis会直接“无响应”用户会以为程序死掉了。Qgis插件常用的做法是用QThread或者Python的concurrent.futures.ThreadPoolExecutor。4. 常见问题与排查技巧实录4.1 Qgis插件加载失败的无响应排查Qgis开发中最常见的问题就是插件加载失败。表现是你点了确定安装插件结果Qgis崩溃、闪退或者插件管理器里一直显示“无法加载”。排查路径如下第一步看Replies日志。Qgis的日志系统对Python插件支持很友好在“视图-面板-消息”里打开消息面板切换日志级别为Debug重新加载插件看有没有明显的PythonTraceback。第二步检查metadata.txt的编码和字段格式。这个文件必须是UTF-8编码字段不能有多余空格版本号不能写错——写qgisMinimumVersion3.28还是3.28.0有讲究格式错误直接加载失败。第三步确认插件目录结构正确。Qgis插件目录通常在~/.local/share/QGIS/Qgis3/profiles/default/python/plugins/Linux或%APPDATA%\QGIS\Qgis3\profiles\default\python\plugins\Windows插件必须放在这个目录下的独立文件夹里且文件夹名和插件类名不能冲突。我最常踩的坑是在插件代码里用到了Qgis Python环境没有的第三方库没有提前安装结果加载时import直接报错。这时Qgis会粗暴地禁用插件但不会给你明显的提示只能从日志里看。4.2 深度学习依赖与Qgis内置Python环境的冲突这是第二个大坑。Qgis内置的Python环境是“锁死”的你不能直接pip install往里面装包——它会提示“externally managed-environment”。我的解决办法有两个。办法一用Qgis的OSGeo4W Shell装包Windows。Qgis自带一个OSGeo4W Shell终端在里面运行pip install onnxruntime就会装到Qgis的Python环境里。Linux下则要先找到Qgis的python路径用/usr/bin/python3 -m pip install --user onnxruntime装到用户site-packages。办法二把模型推理封装成独立进程服务。用Flask或者FastAPI搭建一个本地推理服务Qgis插件通过HTTP请求去调用。这样做的好处是彻底隔离依赖环境Qgis插件只装requests就够了缺点是处理延迟略高且需要多维护一个服务进程。我的系统早期用过这个方案后来换成ONNX Runtime后延迟在可以接受的范围内就不再维护独立服务了。如果你要用GPU推理注意Qgis的Python环境里默认装的是CPU版ONNX Runtime。要装GPU版需要自己下载对应的wheel文件通过OSGeo4W Shell的pip指定路径安装——这是个冷门操作但确实可行。4.3 大影像处理内存溢出与卡顿处理用户拿到的影像动辄几个GB处理时内存爆掉是家常便饭。除了前面说的分块处理之外还有几个细节要注意。Qgis的QgsRasterLayer自带金字塔重采样显示机制但这个机制是面向显示的——它会自动降低分辨率来保证缩放流畅。如果你直接基于QgsRasterLayer读像素值做算法处理读到的数据可能不是全分辨率数据导致结果精度受损。正确做法是界面显示层用QgsRasterLayer算法处理层用gdal.Open()直接打开原文件路径两者独立。另一个遇到烦的问题是Qgis图层刷新太慢。当处理结果图层加入画布时Qgis默认会立即全分辨率渲染这在结果图层很大时会导致界面卡死好几秒。解决办法是在添加图层的操作中先设置层为不可见等渲染完毕用户手动勾选打开或者用layer.triggerRepaint()按需刷新。4.4 云检测结果精度不达标时的调优策略如果用户反馈检测结果不准——云漏检了或者裸地被误检了——要从三个方向排查。第一看“检测模式”是否匹配数据源。快速模式基于蓝光阈值适合中低分辨率影像Landsat、Sentinel-2如果用户输入的是高分辨率影像GF-2、WorldView要切换到深度学习模式因为高分辨率影像的地物细节丰富纯光谱阈值法容易把屋顶、雪地、云都混在一起。第二检查数据预处理级别。用户如果上传的是原始DN值影像没有做辐射定标和大气校正那阈值检测结果基本不可用。要在系统里增加一个数据检查功能自动判断影像的元数据是否包含辐射定标信息没有的话提示用户先处理。第三看训练模型的过拟合风险。深度学习模型的训练数据如果主要来自Landsat那迁移到Sentinel-2上效果会打折扣。解决办法是每次推理前让用户选择传感器类型系统根据传感器类型自动加载对应权重的模型。我在系统里为Landsat、Sentinel-2和高分系列分别准备了三套模型权重实测精度普遍提升了3%到5%。5. 项目后续扩展方向这套系统做完之后我自己在实际使用中最大的感受是云检测和地物恢复不是单点技术问题而是一整套工程体系的组合。算法本身再先进没有扎实的数据预处理、没有合理的多算法融合策略、没有顺手易用的交互界面业务落地时还是会卡壳。后续扩展空间其实很明确第一是引入更多传感器支持。目前系统对Landsat和Sentinel-2的支持最完善但国产高分系列影像GF-1、GF-2、GF-6的PMS传感器没有热红外波段使亮度温度约束失效需要专项调整算法流程。第二是时间序列恢复的优化。现在多时相替换只支持“找一景同期无云影像替换”但实际上同一个地点往往有跨季度的多景影像可用。把时间维度扩展进来做一个基于时间加权平均的合成方案能进一步提高恢复结果的可靠性。第三是把模型推理升级为GPU加速。ONNX Runtime支持CUDA后端只要Qgis跑在带NVIDIA显卡的机器上推理速度能提升5到10倍——对批量处理大量瓦片来说这是实打实的效率提升。当然GPU版本的ONNX Runtime安装配置又是一套新坑需要单独写文档说明。最后分享一个小技巧做Qgis插件开发时一定要给你的算法模块写单元测试并且在开发环境里配置自动化测试脚本每次改动代码后跑一遍测试再手动验证。因为Qgis的交互式调试体验真的不算友好有时候你辛辛苦苦改了一个小bug结果一加载插件发现更早的旧代码把新功能覆盖了又得从头排查。有了自动化测试至少能保证算法结果的可回归性能让你把精力花在真正的问题上。本文还有配套的精品资源点击获取
返回列表