简介:一套基于Qgis二次开发的遥感图像云检测及地物恢复系统,属于Python方向毕业设计项目,面向地理信息系统、遥感科学与数字图像处理、深度学习等方向的在校生与开发者,旨在构建汇集云检测与去云算法的桌面端应用,涉及大气校正、波段合成等典型遥感预处理流程,可用于课程设计、毕设演示或算法对比研究。压缩包共74个文件,核心代码以Python脚本(.py/.pyc)为主,配合Qt界面文件(.ui)、图标与界面图片(.ico/.png)、配置文件(.xml/.json)及说明文档,整体约51.21MB;源码结构清晰,包含主窗体、功能对话框、遥感算法模块及Qgis工具封装等目录,另提供tif示例数据与qm翻译文件,便于调试和功能复现。已有254人浏览学习,代码经过运行验证,可在此基础上扩展新算法或调整界面,适合快速搭建遥感图像处理桌面工具。
1. 这套基于Qgis二次开发的云检测与地物恢复系统,解决的到底是什么问题
做遥感方向毕设的人,十有八九在拿到第一批Landsat影像时会愣一下:云覆盖百分之三四十,植被指数算出来一片花白。手工在ENVI里勾云掩膜,一景影像少说两小时,而且云边缘到底勾到哪一像素,每个人标准都不一样。基于Qgis二次开发的遥感图像云检测及地物恢复系统,本质上就是把这两件事自动化:用Python写云检测算法,透过PyQGIS把算法做成能点的按钮,检测出云区域后再做地物恢复,把被云盖住的地表像元用无云参考信息补回来。这是一条很适合本科或硕士毕设的完整技术链路,既涉及遥感图像语义分割,也涉及栅格处理、插件开发和结果评估,工作量可控,演示效果好。适合手里有遥感影像但不想只会调软件做基线的同学,也适合想用Python把图像处理和GIS结合起来的开发者。
2. 先定技术路线:PyQGIS插件、独立应用还是混合架构
2.1 PyQGIS能做什么:它和ArcGIS二次开发的差别
“Qgis二次开发”这个说法,往细里分有两种:一种是写QGIS插件,在QGIS界面里加自己的工具面板;另一种是脱离QGIS界面,把qgis.core库当作Python包导入独立脚本,做批处理。这两种做法在毕业设计里都常见,但难度曲线完全不同。PyQGIS是QGIS官方提供的Python绑定,它暴露给开发者的主要模块包括qgis.core、qgis.gui、qgis.analysis,能在Python里创建和操作矢量、栅格图层,做坐标变换、地理处理,也能访问地图画布。和ArcGIS的ArcPy相比,PyQGIS最大优势是授权干净、跨平台,而且它直接构建在GDAL/OGR之上,读取GeoTIFF、MrSID这类遥感格式不需要额外配置。另一个实际优势是,QGIS自带Python运行时,插件在QGIS内部运行,不需要你自己去配一堆系统库的path。很多同学一上来纠结“我该用ArcGIS还是QGIS”,如果你的导师没有明确要求必须在ArcGIS体系内,选QGIS做毕设的阻力要小得多,尤其是在你自己电脑上开发和演示的场景。
2.2 插件、独立程序、混合架构:毕设选型的三个考量
我在给这个系统选型时列过一个对比表,这里直接给你参考:
| 方案 | 用户交互 | 开发量 | 演示效果 | 适合场景 |
|---|---|---|---|---|
| QGIS插件扩展 | 在QGIS菜单/工具栏中启动 | 中 | 好,地图显示即所得 | 面向教师的成果演示、毕业论文系统展示 |
| 独立Python程序 | 命令行或自建GUI窗口 | 大 | 一般,要自己写地图控件 | 批处理、算法实验为主 |
| QGIS应用内嵌入子窗口 | 在QGIS中停靠面板 | 偏大 | 最好 | 有时间做复杂交互,比如框选、多视图对比 |
毕设系统通常建议走插件扩展。理由是QGIS已经把图层管理、坐标投影、地图缩放、属性查看全做好了,你只需要聚焦在云检测和地物恢复算法本身,最后把结果以栅格图层方式推送到画布即可。独立程序的坑在“地图可视化”上,你会花大量时间在渲染而不是遥感算法上,这在时间有限的毕设节奏里很伤。混合架构我一般建议给那些需要把处理逻辑和界面彻底解耦的场景,比如后续打算把算法单独打包成服务,但对答辩而言没必要。
2.3 搭建PyQGIS开发环境:QGIS安装、Python解释器与IDE配置
这一步是新手第一个玄学点:为什么在命令行里能import cv2,QGIS插件里却报ModuleNotFoundError。原因九成是解释器装错位置。QGIS安装完成后,自己会带一个Python环境,以Windows下的QGIS 3.x为例,通常会在安装目录的bin/python.exe。插件运行在这个Python环境里,不是你系统里那个python。所以开发环境搭建的第一原则:所有第三方库,numpy、opencv-python、scikit-image,都要安装到QGIS文件夹内建的Python里。
# 进入QGIS内置Python目录,具体盘符和版本按你的安装位置调整 cd "C:\Program Files\QGIS 3.x\bin" # 用这个 python 安装依赖,注意是 python 不是 python3 python -m pip install --upgrade pip python -m pip install numpy opencv-python-headless scikit-image这里用opencv-python-headless而不是opencv-python,原因是QGIS已经有GUI框架,headless版本不打包Qt相关模块,能减少插件加载时的动态库冲突。开IDE调试时,PyCharm或VSCode都能用,关键是解释器要指向QGIS安装目录下的python.exe。在PyCharm里,Settings -> Project -> Python Interpreter -> Add -> Existing environment,手动浏览到bin/python.exe。配置完成后,在IDE里新建脚本,写一行from qgis.core import QgsApplication,不报错就代表环境已通。注意PyCharm如果提示缺包,先确认包是不是装进了同一个解释器,这是让无数人翻车的环节。
配置好IDE以后,还有一件事要做:注册QGIS运行所需的变量环境。在PyCharm里调试独立脚本时,往往要在Run Configuration的Environment variables里加上PYTHONPATH,指向QGIS的python目录和share/qgis/python目录。否则即使解释器对了,from qgis.core import QgsApplication依然会失败。Windows下常见配置类似:PYTHONPATH=C:\Program Files\QGIS 3.x\apps\qgis\python。Linux下则是源码包路径,不同发行版差异大,需要按实际安装位置改。这块属于开发经验问题,血泪教训是不要依赖系统环境变量,直接在IDE的运行配置里写死最省事。
3. 云检测的分步实现:波段阈值到语义分割模型的落地
3.1 云在光谱上的规律:为什么阈值法总是差一点
云检测要抓住的核心物理特征是:云在可见光波段反射率显著高于普通地表,但和雪、盐碱地、城市亮色屋顶在单波段上混叠严重。多波段组合能提高区分度,比如蓝波段高反射加上热红外亮温低,是过去通用的经验规则。Landsat 8的B2蓝波段和B10热红外波段结合,能筛掉大部分非云目标。常见的做法是计算云检测指数,比如HOT(Haze Optimized Transform)和NDSI(归一化积雪指数),前者利用蓝红波段相关性,后者用来剔除雪。阈值法的问题在于:薄云、半透明云、破碎云在空间频率上具有过渡特性,单一阈值不可能对所有时相、所有地表类型都适用。这也是我在这套系统里最终引入语义分割模型的原因——深度学习模型能学习云的纹理和上下文信息,而不仅仅看单像元光谱值。但阈值法不是一无是处,它最大的价值是能快速生成初始掩膜,作为训练数据的“弱标签”来源。
3.2 第一个跑通的最小脚本:PyQGIS读取波段并输出云掩膜
在写模型之前,先用PyQGIS把遥感影像读进来、算一个规则云检测、导出掩膜。这个脚本是整套系统的地基,它验证了三件事:影像能读、像元值能处理、结果能写回栅格。我用QgsRasterLayer加载GeoTIFF,再用QgsRasterBlock读取指定的波段范围。注意不要对整个大影像调用block.data()一次性读入,内存会直接翻车,先看影像尺寸再决定分块大小。
from qgis.core import QgsRasterLayer, QgsRasterBlock import numpy as np def read_band_block(layer, band_no, x, y, w, h): """分块读取波段数据,返回 numpy float32 数组""" provider = layer.dataProvider() block = QgsRasterBlock(provider, band_no, x, y, w, h) if not block.isValid(): return None # block.data() 返回原始字节,按波段数据类型 reshape 成二维 dtype = np.float32 arr = np.frombuffer(block.data(), dtype=dtype).reshape(h, w) return arr.copy() def simple_cloud_mask(layer, blue_band=2, thermal_band=10, thr_blue=0.15, thr_thermal=280): """以Landsat8为例:蓝波段反射率高且热红外亮温低于云的像元判为云。 thr_blue 是反射率阈值,thr_thermal 是亮温或DN阈值,单位按影像scale而定。 """ width = layer.width() height = layer.height() mask = np.zeros((height, width), dtype=np.uint8) tile_w, tile_h = 512, 512 for y in range(0, height, tile_h): for x in range(0, width, tile_w): w = min(tile_w, width - x) h = min(tile_h, height - y) blue = read_band_block(layer, blue_band, x, y, w, h) thermal = read_band_block(layer, thermal_band, x, y, w, h) if blue is None or thermal is None: continue # 两个条件同时满足:蓝波段偏亮、热红外亮温偏低 cloud = (blue > thr_blue) & (thermal < thr_thermal) mask[y:y + h, x:x + w] = cloud.astype(np.uint8) * 255 return mask这段代码里QgsRasterBlock的构造函数参数比较容易忽略:第一个参数是dataProvider而不是layer,第二是波段号,从1开始计数,不是0。第三个起是读取区域的x、y、宽、高。我把阈值写在参数默认值里,实际使用时要根据你的影像预处理状态调整。若你加载的是表面反射率产品,蓝波段阈值取0.15还算合理;若直接用DN值,就要跟着缩放系数换算。热红外的亮温阈值在不同纬度、不同季节变化不小,这也是阈值法的边界。
3.3 拿Landsat QA波段构建训练数据:语义分割标注的免费方案
到了做遥感图像语义分割这一步,第一个拦路虎是标注。手工用LabelMe勾云,一景大影像勾几百个多边形,手会废掉。在云检测这个任务上,有一条天然的捷径:Landsat的QA波段和Sentinel-2的场景分类层本身携带云掩膜标记。Landsat 8 Collection 2的QA_PIXEL波段里,bit 3表示云,bit 4表示云阴影,按位解析就能得到逐像元标签。Sentinel-2的SCL波段更直接,值3代表云影,8代表中高概率云,9代表高概率云。
def parse_qa_pixel(qa_array): """解析Landsat QA_PIXEL波段,返回云和非云二值标签。""" # bit 3 (值=4) 是云,bit 4 (值=8) 是云影 cloud_bit = (qa_array & 4) > 0 shadow_bit = (qa_array & 8) > 0 label = (cloud_bit | shadow_bit).astype(np.uint8) return label注意QA波段在Landsat头文件里是以带符号整数存储的,读取时务必用原始类型,不要先标准化成float32再做位运算,否则符号位会污染结果。构建训练集时,我会把蓝、绿、红、近红外的反射率波段四通道作为模型输入,QA解析出的label作为监督标签。还有个细节:QA里的云边缘往往带一圈“可能是云”的过渡像素,直接当标签会引入噪声。一般做法是做一次形态学腐蚀,去掉边缘几个像素,保留高置信度云核。这个清洗编码在数据加载逻辑里写清楚,免得被论文审稿人追问标签噪声的处理方式。
3.4 把模型接进QGIS:推理接口与结果写回栅格的代码
当Unet或DeepLabV3+模型在外部Python环境训练好后,下一步是把模型推理嵌入QGIS插件。这里最稳妥的部署方式是用ONNX导出模型,再用onnxruntime做CPU推理,而不是在QGIS里硬加载PyTorch,后者依赖体积太大且在换机器时容易崩。模型输入是固定的四波段影像块,比如256x256,推理前要做归一化,推理后得到逐像元置信度图。
import onnxruntime as ort import numpy as np from osgeo import gdal def run_cloud_model(patch_list, session): """把多波段影像块输入ONNX模型,返回云概率图。 patch_list: list of (h,w,4) float32 数组,已按训练时的通道顺序排好。 """ input_name = session.get_inputs()[0].name probs = [] for patch in patch_list: # 模型要求的NCHW布局,把HWC转成NCHW tensor = np.transpose(patch, (2, 0, 1))[np.newaxis, ...].astype(np.float32) pred = session.run(None, {input_name: tensor})[0] probs.append(pred[0, 1, :, :]) # 取云类别的概率通道 return np.vstack(probs) if len(probs) > 1 else probs[0]推理结果要写回QGIS可显示的栅格,不推荐自己造GeoTIFF写入函数,直接用GDAL在内存里创建数据集,再通过QgsRasterLayer加载即可。用GDAL的MEM驱动创建内存栅格,指定和输入影像相同的地理变换和投影,然后把概率图写入band 1,最后刷新QGIS画布,图层立即显示。另一个关键点是透明化:云检测结果图层里,概率低于阈值的像元应设置为透明,否则整幅图会被概率色带盖住。我这个项目里习惯把阈值设为0.5,再把透明色设为0值,配合QgsSingleBandPseudoColorRenderer渲染,效果最直观。
4. 地物恢复的分步实现:时序替换与插值修复的实操对比
4.1 云遮挡恢复的三个层次:补像素、补纹理、补语义
地物恢复要回答的问题是:云的掩膜已经画出来了,掩膜底下的地表信息怎么补。最朴素的思路是像素插值,用周围无云像元往云区推,OpenCV的inpaint算法就是这个思路,对小面积云斑块效果尚可,但云区一旦成片,插值结果会糊成一片。第二个层次是纹理恢复,通过参考无云时期的影像纹理,把纹理迁移到被遮挡区域,这类方法的代表是泊松融合和基于块的纹理合成,实现复杂且容易被导师质疑“恢复出来的地物是编造的”。第三个层次是语义重建,用GAN或diffusion模型根据上下文生成地物,视觉效果好,但可解释性差,论文里很难讲清物理依据。对毕设而言,最推荐的组合是“时序替换为主、插值修复兜底”,因为时序替换有真实的物理光照信息,能够写清楚数据来源,答辩时站得住脚。
4.2 时序替换:同区域无云影像的叠加与配准
时序替换的基本假设是:同区域不同时期的地表变化不大,可以利用目标日期附近、云量低的另一景影像,把云区对应像素替换过来。这个假设在植被生长季和城区场景下基本成立,但在农田翻耕、水体变化明显的情况下要谨慎,替换前最好做一个逐像元的差异分析。
from osgeo import gdal def temporal_replace(base_path, ref_path, mask_path, out_path): """云区用参考影像替换,无云区保留目标影像。 三幅图必须已经对齐到同一分辨率、同一CRS和同一像元网格。 """ base_ds = gdal.Open(base_path) ref_ds = gdal.Open(ref_path) mask_ds = gdal.Open(mask_path) band = base_ds.GetRasterBand(1) cols, rows = band.XSize, band.YSize driver = gdal.GetDriverByName('GTiff') out_ds = driver.Create(out_path, cols, rows, base_ds.RasterCount, band.DataType) out_ds.SetGeoTransform(base_ds.GetGeoTransform()) out_ds.SetProjection(base_ds.GetProjection()) for i in range(1, base_ds.RasterCount + 1): base_band = base_ds.GetRasterBand(i).ReadAsArray().astype('float32') ref_band = ref_ds.GetRasterBand(i).ReadAsArray().astype('float32') mask = mask_ds.GetRasterBand(1).ReadAsArray().astype('uint8') # 云区像元用参考影像,其余用目标影像 merged = np.where(mask > 0, ref_band, base_band) out_ds.GetRasterBand(i).WriteArray(merged) out_ds.FlushCache()这段代码能跑的前提是两景影像已经做过严格配准。常见做法是先用gdal.Warp把参考影像重采样到目标影像的像元网格,指定目标影像的GeoTransform和Projection。坐标参考系不一致的问题常发生在来自不同传感器、不同L1处理级别的数据上。替换完成后,云区边缘往往有明显接缝,我一般会追加一个3到5像素的羽化处理,把云边缘的权重复合拉平,视觉上的突变会减少很多。
4.3 插值修复实现与参数调节:inpaint不是万能的
时序替换有一个硬前提:必须找得到无云参考影像。如果目标日期前后几十天的影像都被云覆盖,这套方案直接失效。这时候要回到插值方法。OpenCV的cv2.inpaint在OpenCV 3以后支持两种算法,Telea和Navier-Stokes,前者基于邻域像素加权,速度快,后者基于偏微分方程扩散,边缘保持更好。遥感数据处理时有两个细节必须注意。
import cv2 import numpy as np def inpaint_each_band(dst_band, mask, radius=3, method=cv2.INPAINT_TELEA): """对单波段影像做修复,mask取0和255的云掩膜。""" # cv2.inpaint 只支持 uint8,先做线性拉伸到0~255 mn, mx = np.nanmin(dst_band), np.nanmax(dst_band) if mx - mn < 1e-6: return dst_band band_u8 = ((dst_band - mn) / (mx - mn) * 255).astype(np.uint8) out_u8 = cv2.inpaint(band_u8, mask, radius, method) # 反归一化回原始的数值范围 out = out_u8.astype(np.float32) / 255.0 * (mx - mn) + mn return out第一个坑是数据类型:遥感影像的DN值范围很宽,直接传入inpaint会把中间灰阶全丢掉,必须做归一化和反归一化。第二个坑是多光谱影像不能当RGB处理,需要每个波段独立执行上面的逻辑,包括那些不在可见光范围的波段,否则修复结果里云区的近红外值会和其他波段脱节,导致后续计算植被指数时云区数值异常突兀。radius参数控制邻域大小,我调试下来小云块取3到5最稳,大范围云区取7以上会缓解模糊感,但计算时间会明显增加。Telea快、NS边缘锐利,但也有代价:边缘区域有高对比地物时容易产生伪纹理。这类参数调节属于血泪经验,建议在论文里固定一组参数后做敏感性分析,而不是答辩被问慌。
4.4 效果怎么量化:PSNR、SSIM、RMSE与论文图表
地物恢复做完了,不能说“看起来还行”,需要量化指标。最常用的是PSNR、SSIM和RMSE,它们都需要一个参考真值。在实际实验中,我会准备无云的参考影像,然后把云掩膜叠加到参考影像模拟出“带云影像”,运行恢复算法后,指标只统计云掩膜范围内的像元。
from skimage.metrics import peak_signal_noise_ratio as psnr from skimage.metrics import structural_similarity as ssim def eval_metrics(restored, reference, mask): """只在云掩膜区域内评估恢复质量。 restored: 恢复后影像 float32 reference: 无云真值影像 float32 mask: 云掩膜 bool,True表示云区 """ if mask.sum() == 0: return 0, 0 r = restored[mask] ref = reference[mask] mse = np.mean((r - ref) ** 2) rmse = np.sqrt(mse) # PSNR 需要以数据范围作为分母,这里假设影像范围0~1 psnr_val = 10 * np.log10(1.0 / max(mse, 1e-10)) # SSIM 需要二维窗口,只取掩膜外扩的局部区域 ssim_val = ssim(restored, reference, data_range=1.0) return rmse, psnr_val, ssim_val指标计算本身不复杂,真正难的是论文里怎么呈现。标准做法是准备四联图:原始带云影像、云掩膜、恢复结果、无云参考影像,再配一张表格列出多组影像的平均PSNR/SSIM/RMSE。要注意的是,PSNR和SSIM对薄云区域的恢复质量天然有利,因为薄云下方的地表信息本来就部分保留,所以论文里应该把薄云和厚云分开统计。这样导师和评审能看到你在思考任务的难点,而不是只堆指标。
5. 避坑清单:QGIS二次开发里四个高频翻车点和排查方法
5.1 import cv2失败:Python环境指向错位
现象是插件加载时日志抛ModuleNotFoundError: No module named 'cv2',但你在系统命令行里运行python -c "import cv2"明明能通过。原因是QGIS插件运行在它自带的Python运行时里,这个运行时的site-packages和操作系统级的Python完全是两套目录。解决方法是回到第2章环境配置的操作,直接在QGIS安装目录的python执行pip安装。另一个隐蔽原因是装了opencv-python而不是opencv-python-headless,前者在加载时会尝试导入Qt绑定,和QGIS内部的Qt版本不一致会造成段错误或加载静默失败。排查时可以打开QGIS的Python控制台,手动执行import cv2看具体报错的动态链接库名字,这个信息往往指明是Qt冲突还是单纯的模块缺失。
5.2 结果图层和底图对不上:坐标参考系的坑
检测完云掩膜,映射到地图画布上发现图层位置偏移了半条街区,或者压根不显示,多数是CRS不一致。QGIS画布会自动把不同CRS的图层投到同一个显示参考系,但这一步依赖每个图层源文件里写好的投影信息。如果你的输出GeoTIFF在创建时没有继承输入影像的GeoTransform和Projection,GDAL默认把它当成未知CRS,画布就无法正确叠加。解决方法是写入前显式复制参考影像的投影。
from qgis.core import QgsCoordinateReferenceSystem, QgsProject def set_output_crs(layer, epsg_id=4326): crs = QgsCoordinateReferenceSystem.fromEpsgId(epsg_id) layer.setCrs(crs) # 把画布的项目CRS也锁定,让散落的图层统一坐标空间 QgsProject.instance().setCrs(crs)注意遥感原始影像常见UTM投影,EPSG代码是32650之类的分带号,你输出栅格时应该继承源文件而不是强行转成4326,否则像元面积会被重采样,在后续统计云覆盖率时引入误差。我遇到这类情况的第一反应是先右键图层看属性里的CRS,和底图对比之后再怀疑算法。
5.3 大影像一读内存就爆:分块读取的写法
读一景Landsat场景全分辨率约1GB,用QgsRasterLayer的dataProvider().block()直接读整个栅格,32GB内存的机器也会卡住。QGIS提供QgsRasterIterator做分块遍历,允许你设置每个瓦片的宽高,在处理完当前块后自动跳到下一块。
from qgis.core import QgsRasterIterator def iterate_raster_band(provider, band_no): """分块遍历整个波段,避免一次性加载全图。""" iterator = QgsRasterIterator(provider) # 限制每块最大1024x1024,防止单块内存占用过高 iterator.setMaximumTileWidth(1024) iterator.setMaximumTileHeight(1024) if not iterator.startRasterRead(band_no, provider.xSize(), provider.ySize(), provider.extent()): return while True: ret, col, row, block = iterator.readNextRasterPart( provider.xSize(), provider.ySize()) if not ret: break # 这里的 block 是那一块的 QgsRasterBlock,直接转成numpy再算 arr = np.frombuffer(block.data(), dtype=np.float32) # 处理当前分块...用QgsRasterIterator时,startRasterRead传入的画布范围如果比实际栅格范围大,遍历的块数会增加但不会出错;我这里直接传了provider的范围规避这个问题。内存优化是必须提前做的,不要在答辩现场才意识到读一幅高分辨率影像要等两分钟。
5.4 换台机器插件就跑不起来:依赖与部署
代码在你自己电脑上跑得好好的,换到导师的电脑或答辩教室的机器上,最常见的翻车现场是:插件目录能加载,但一运行就报缺了某个库,或者模型权重路径找不到。原因基本是写了绝对路径,比如D:/project/model.onnx,换台机器路径自然失效。正确写法是使用QGIS插件自带的目录定位——在插件主类的__init__里通过os.path.dirname(__file__)拿到插件包所在目录,所有模型的相对路径都基于此拼接。依赖库方面,QGIS插件不会帮你自动装第三方库,我习惯在插件里加一个环境自检函数,启动时检查cv2、onnxruntime、numpy是否可导入,缺失时用消息条提示用户去执行安装命令,而不是直接崩溃。打包答辩机环境时,优先装和开发机相同版本的QGIS长期支持版,避免因QGIS小版本差异导致API变化。
6. 进阶验证:把系统从“能跑”做到“能答辩”
系统跑通是第一步,能应对老师的追问是第二步。我通常会写一个batch_evaluate.py,自动遍历一个测试集文件夹里的所有带云影像,逐景执行云检测和地物恢复,算完指标后把结果追加进CSV,并输出一个汇总四联图。这样做的好处是,答辩时老师随口问“你这个方法在大范围云和薄云上的精度差异有多大”,你直接打开CSV就能给出一个分云况统计的数值。自动化验证脚本的代码量不大,但它是整个毕设成果“可复现性”的最好证明。
演示顺序上,不要在答辩现场跑训练,只演示推理链路:打开QGIS、启动插件、选择影像、点运行、图层叠加显示。所有模型权重和测试数据的路径都预先配置好,并且要在插件的配置面板里允许用户重新选择路径,避免拷到演示机器上就跑不动。我个人的教训是,第一次在学院机房演示时,插件加载成功但模型推理卡了十几秒又没有进度条,台下以为是死机,极其尴尬。后来给算法加了一个简单的if handler:进度回调,让QGIS状态栏实时显示百分比,这个问题才算解决。
这套系统的价值不在算法多前沿,而在把遥感图像处理链路用QGIS二次开发的方式组装完整:数据读取、云检测、训练集构造、模型推理、地物恢复、指标评估,每个环节都有独立的产出物可以写进论文。按照第5章的避坑经验提前把环境问题排掉,剩下的核心时间应该投入到云检测精度对比实验,那才是毕业答辩真正的加分项。希望这些踩坑记录能让你少走几段弯路,后面开发顺利。
本文还有配套的精品资源,点击获取