ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

ICESat-1/2卫星测高数据Python处理全流程:去噪与可视化实战

ICESat-1/2卫星测高数据Python处理全流程:去噪与可视化实战 简介针对ICESAT-1/ICESAT-2卫星激光测高数据的处理需求这份Python程序包面向从事冰川变化监测、气候研究的科研人员及地理信息专业学生。程序覆盖HDF5数据加载、光子与波形数据去噪、可视化交互界面等关键环节可有效降低原始数据中噪声干扰帮助使用者快速完成数据预览和初步分析。程序包整体约717.59MB共35个文件包含7个Python源文件、编译生成的pyc文件、可直接运行的exe主程序、示例HDF5数据文件、依赖清单及GUI图标与说明文档兼顾源码学习与免环境配置的快捷使用。由于主程序已打包为exe不具备Python环境的用户也能直接体验去噪和可视化流程。项目内置光子去噪与波形去噪两套模块并配套交互式绘图画布、数据导出及csv结果文件便于对照验证。资源已有1145人学习适合需要快速上手ICESAT系列数据或扩展二次开发的科研入门者。 从上个月开始一直泡在ICSat-1和ICESat-2的卫星测高数据里从NSIDC下载数据、h5py解析、去噪处理一路做到可视化出图前前后后把程序重写了三版。今天把这套Python处理流程完整整理出来内容既可以当工具代码直接抄也把我在GLAS与ATLAS数据上踩过的几个大坑一并说清楚。这套程序解决什么问题ICESat-1在2003到2009年服役搭载GLAS全波形激光测高仪ICESat-2从2018年接棒搭载的是ATLAS光子计数激光测高仪。两者都是极地冰盖、海冰、陆地高程和森林冠层测量的核心数据源。但GLAS输出的是连续波形ATLAS输出的却是一堆离散光子事件点云数据结构和噪声特性完全不同。所以去噪和可视化不能一套方案走天下必须拆成两条链路做独立处理。这次的项目正好就是把两套数据链路封装到同一个Python程序框架里既能处理ICESat-1的GLAH06高程数据也能处理ICESat-2的ATL03全球定位光子数据可视化部分覆盖轨迹图、高程剖面图、去噪前后对比图。搞极地、水文、森林碳汇、海洋测绘的朋友应该都能直接用上。1. 项目背景与整体设计思路1.1 两类传感器数据为何要分开处理很多第一次接触ICESat数据的同学会问ICESat-1和ICESat-2不都是激光测高吗为什么不能写一套代码统一处理表面上确实都是激光雷达但工作机制差异决定了数据处理方式不一样。ICESat-1的GLAS发射40Hz激光脉冲接收端记录返回光的完整波形一个脉冲对应一个几十纳秒的波形信号每个波形里隐藏着地表高程、粗糙度和多层次结构信息。ICESat-2的ATLAS则是微脉冲光子计数系统发射的激光被分成6束每秒钟向地面打出一万个脉冲接收端记录的是一个个离散光子的到达时刻和位置。对ATLAS来说一次沿轨扫描得到的是海量光子点云其中真正来自地表的信号光子可能只占很小比例大量光子是太阳背景辐射噪声白天尤其严重。所以在程序设计上我把两条数据链路的读取、去噪、可视化模块完全分开只在上层保留统一的调用接口。ICESat-1走的是“波形/高程序列 小波阈值去噪”路线ICESat-2走的是“光子点云 密度聚类去噪”路线两条路线的输出统一成DataFrame格式后面绘图和分析不用重复写代码。1.2 程序功能拆解从原始文件到成果图整个程序按数据流拆成了四层文件读取层负责把HDF5里的字段解析成numpy数组预处理层做坐标轴转换、单位换算和异常值剔除去噪层根据传感器类型切换到不同算法可视化层把处理结果绘制成轨迹图、剖面图和对比图。这种模块化设计有个明显好处就是替换数据源很方便。比如你想把ICESat-2的ATL03换成ATL06陆地冰高程产品只需要改文件读取层里的几个字段名后面的去噪和可视化完全不用动。我最初就是急着出一个区域的冰盖高程剖面临时在读取层里把ATL03和ATL06的数据都加上了上层逻辑没有任何改动就出了两张对比图。2. 环境配置与依赖库选型解析2.1 一套能跑三年的Python环境我强烈建议用conda建一个独立环境不要直接装在系统Python里。ICESat数据处理的依赖库涉及编译型包cartopy、shapely、scikit-learn系统Python环境一旦装乱了排查起来非常费事。conda create -n icesat python3.10 -y conda activate icesat conda install -c conda-forge h5py numpy pandas scipy scikit-learn matplotlib cartopy pywavelets -y这条命令把该装的都装齐了。Python版本我自己用的是3.10实测3.8到3.11都没有问题。注意cartopy一定要用conda装不要用pippip装cartopy经常在GEOS和PROJ依赖上卡住最后还得回来用conda。2.2 依赖库清单与各自职责库名职责实测注意点h5py读取ICESat数据的HDF5文件只读取需要的字段不要整层读入numpy/pandas数组运算和结构化输出百万级光子数据建议用numpypandas只在统计阶段用scipy波形滤波、MAD噪声估计signal.savgol_filter非常好用pywtICESat-1波形和高程序列的小波阈值去噪高频细节层数不要选太高容易把地表信号削弱scikit-learnDBSCAN光子点云密度聚类调参核心是eps和min_samples数据量过大时用HDBSCAN替代matplotlib cartopy轨迹图、剖面图、地图叠加百万级散点图务必开rasterizedTrue你可能会问为什么不直接选更容易的替代库。比如有人喜欢用pandas.read_hdf实测读ATL03并不好用因为HDF5内部层级太深read_hdf对这类文件的支持不稳定h5py直接按路径取字段反而更清晰。去噪部分我对比过几种方案滑动窗口和中值滤波对小噪声有效但对光子点云的密集噪声处理不干净DBSCAN是实际测试下来最稳的方法。3. 数据读取与预处理细节3.1 ICESat-1 GLAS高程数据的读取ICESat-1的GLAH06产品是40Hz的高程产品里面包含经纬度、大地水准面高、冰面/陆面高程等字段。程序里读取核心字段的代码如下import h5py import numpy as np file_path GLAH06_633_2102_001_0071_0_01_0001.H5 with h5py.File(file_path, r) as f: lat f[/Data_40HZ/Geolocation/d_lat][:] # 纬度单位度 lon f[/Data_40HZ/Geolocation/d_lon][:] # 经度单位度 elev f[/Data_40HZ/Elevation_Surfaces/d_elev][:] # 高程单位米 valid (~np.isnan(elev)) (elev -10_000) (np.abs(lat) 90) lat, lon, elev lat[valid], lon[valid], elev[valid]这里最关键的是筛除无效值。GLAH06里有些轨道段没有测到有效回波高程字段会填充空值或异常大值不处理后面画图全是一堆飞点。我加了一个高程下限的判断同时用np.isnan过滤掉空值这两步做完数据质量肉眼可见地提升。3.2 ICESat-2 ATL03光子数据的读取ATL03是ICESat-2的全局定位光子产品程序读取的核心逻辑如下以第一组波束gt1l为例import h5py import numpy as np file_path ATL03_20181019095851_029501102_000_01.h5 with h5py.File(file_path, r) as f: gt /gt1l lat_ph f[f{gt}/heights/lat_ph][:] lon_ph f[f{gt}/heights/lon_ph][:] h_ph f[f{gt}/heights/h_ph][:] # 光子椭球高单位米 dist_ph f[f{gt}/heights/dist_ph_along][:] # 沿轨距离单位米 quality_ph f[f{gt}/heights/quality_ph][:] # 质量标签ATL03一个文件的光子数量在百万量级读取时我建议不要一次性读全部字段按需读取最省内存。比如只画高程剖面就不需要读ph_index_beg等索引字段。光子数据的quality标签是0-4的整数0到4分别对应不同的处理状态程序里至少要把quality小于0的无效光子剔掉。3.3 单位、参考面和坐标对齐的三座大山读ICESat数据最容易被坑的就是单位换算。ICESat-2的h_ph单位是米但有些产品如ATL06里高程字段因为压缩存储会用缩放因子读取时需要乘上scale_factor加上offset。我建议在读取层里统一查一遍字段属性养成看field.attrs的习惯。另一座大山是参考面。ATL03和GLAH06的高程都是相对于WGS84椭球面的椭球高而不是我们通常理解的海拔高。如果你想跟水准数据或高程模型对比需要减去大地水准面差距geoid undulation这个可以通过pygeodesy或读取GLAH06里的d_gpdHt字段来补。第三个坑是坐标轴对齐。ATL03沿轨距离dist_ph_along的起始参考点跟纬度、经度字段的参考点可能不是同一个如果你的程序里需要把光子高度和地理位置精确对应最好用segment_ph_index等索引字段做一次坐标对齐。我最初偷懒直接画图结果发现剖面图横坐标和轨迹图对不上来回查了两天才发现是沿轨距离参考点不一致。4. 去噪算法实现与参数选择4.1 小波阈值去噪ICESat-1数据的主力滤波方案ICESat-1的GLAS波形和40Hz高程序列同时存在系统噪声和脉冲噪声传统滑动平均虽然能滤掉高频但会把地表细节一起磨平。小波阈值去噪在实测中表现更均衡它能把信号和噪声在不同频带上分开再对细节系数做阈值收缩。我用的核心代码是import pywt import numpy as np def wavelet_denoise(signal, waveletdb4, level4, modesoft): coeffs pywt.wavedec(signal, wavelet, levellevel) # 使用第一层细节系数的MAD估计噪声标准差 sigma np.median(np.abs(coeffs[-1])) / 0.6745 threshold sigma * np.sqrt(2 * np.log(len(signal))) coeffs_th [coeffs[0]] for c in coeffs[1:]: coeffs_th.append(pywt.threshold(c, threshold, modemode)) return pywt.waverec(coeffs_th, wavelet)这里的关键有两点。第一噪声标准差用第一层细节系数的中位绝对偏差MAD来估计除以0.6745是为了把MAD换算成高斯分布下的标准差这是Donoho和Johnstone提出的经典阈值去噪方法比直接算标准差更抗异常值。第二阈值的选择公式是sigma * sqrt(2 * log(N))N是信号长度这个阈值会随信号长度增加而变大对长序列更保守避免过度滤波。在GLAH06的高程序列上实测db4小波4层分解得到的剖面既保留了冰盖表面的短波起伏又有效去掉了孤立噪声点。如果你的数据本身比较平滑可以把level降到3细节损失会更小。4.2 基于DBSCAN的光子点云去噪ICESat-2的ATL03光子云如果用传统滤波结果会很糟糕。原因是光子计数雷达的噪声是随机离散的跟信号光子在空间上混叠只有从“密度”角度才能把两者区分开。我的方案是把每个光子映射到二维平面上一个维度是沿轨距离另一个维度是高程然后在这个平面上跑DBSCAN密度聚类。from sklearn.cluster import DBSCAN from sklearn.preprocessing import StandardScaler import numpy as np dist dist_ph # 沿轨距离米 elev h_ph # 高程米 # 构造二维特征矩阵标准化消除量纲影响 X np.column_stack([dist, elev]) X_scaled StandardScaler().fit_transform(X) clustering DBSCAN(eps0.2, min_samples10).fit(X_scaled) labels clustering.labels_ signal_mask labels ! -1为什么要先做标准化因为沿轨距离量级是几万米高程量级只有几百米如果不缩放DBSCAN的邻域半径eps会完全被距离维度主导高程方向上几米的变化根本不会被识别为邻域聚类结果几乎等于只按距离切段。用StandardScaler把两个维度拉到同一量级后eps和min_samples的物理含义就变成了“标准尺度下单位邻域内至少有几个点才算信号”。在实测中eps在0.15到0.3之间、min_samples在8到15之间对大多数陆地冰和裸地场景效果比较稳定。夜间数据由于背景噪声很低min_samples可以取小一些5到8就够白天数据噪声光子显著增多min_samples建议提到12以上。4.3 去噪参数的实战调优心得这里补充几个调参经验。第一不要一上来就全区域跑DBSCAN可以先取一小段轨道数据比如100公里范围内的光子把去噪效果调到满意再批量跑。第二观察高程直方图如果信号光子分布有明显的高斯峰可以先用高程区间截断去噪把明显偏离地表的噪声光子直接删掉再用DBSCAN精处理。这个两步法在白天强噪声情况下能节约大量时间。第三DBSCAN的输出标签为-1的是噪声在做可视化时单独用一个灰暗颜色映射千万不要直接丢弃后画图否则你没法判断去噪是否合理。5. 可视化方案与出图实现5.1 地面轨迹图叠加地图底图ICSat卫星数据可视化最基础的一张图是地面轨迹图展示卫星飞过的路线和你关心的研究区域。配合cartopy可以快速叠加海岸线核心代码如下import matplotlib.pyplot as plt import cartopy.crs as ccrs import cartopy.feature as cfeature fig plt.figure(figsize(10, 6)) ax plt.axes(projectionccrs.PlateCarree()) ax.add_feature(cfeature.COASTLINE, linewidth0.5) ax.set_extent([lon.min() - 0.5, lon.max() 0.5, lat.min() - 0.5, lat.max() 0.5]) ax.scatter(lon, lat, s0.5, cred, transformccrs.PlateCarree(), rasterizedTrue) plt.savefig(track_map.png, dpi300)rasterizedTrue是我反复强调的当你画几十万个地理坐标时向量格式点会拖垮PDF和后续编辑软件的内存光栅化之后图片质量不降文件体积却会小很多。轨迹图里还可以叠加研究区的边界shapefile用matplotlib的ax.add_geometries就可以实现。5.2 高程剖面与去噪前后对比图高程剖面图是分析测高数据最直观的工具。ATL03数据在去噪前是一团密密麻麻的光子点里面既有地表轮廓也有大量噪声去噪后地表形态会非常清晰。下面的代码把去噪前后的结果放在上下两个子图里fig, (ax1, ax2) plt.subplots(2, 1, figsize(12, 7), sharexTrue) # 原始光子云 ax1.scatter(dist_ph, h_ph, s0.1, clightgray, labelraw photons) ax1.set_ylabel(Elevation (m)) ax1.set_title(Before denoising) # 去噪后的光子云信号光子红点噪声光子灰点 ax2.scatter(dist_ph[~signal_mask], h_ph[~signal_mask], s0.1, clightgray, labelnoise) ax2.scatter(dist_ph[signal_mask], h_ph[signal_mask], s0.1, cred, labelsignal) ax2.set_xlabel(Along-track distance (m)) ax2.set_ylabel(Elevation (m)) ax2.set_title(After denoising) plt.tight_layout() plt.savefig(profile_denoise.png, dpi300)如果你对高程剖面的细节感兴趣可以先对信号光子做高程分段统计中值得到一条光滑的地表高程曲线。这个曲线可以直接导出成CSV配合后续流程计算高程变化率或冰盖厚度变化。5.3 海量数据的出图性能优化ATL03一个文件上百万光子直接画matplotlib会卡到怀疑人生。除了前面说的rasterized还有三个技巧非常实用。第一个是抽稀显示。先用numpy随机抽取一部分点做预览比如idx np.random.choice(len(dist), size200000, replaceFalse)预览时看到整体趋势后再全量出图。第二个是限制范围只绘制研究区内的光子用经纬度或沿轨距离做布尔索引即可。第三个是使用散点图时调整s参数光子点云尽量用s0.1或更小太大画面会变成一整块墨团。我自己在画阿拉斯加某区域32个ATL03文件时如果不抽稀光保存一张图就要一分多钟加了抽稀和rasterized之后整张图秒出肉眼几乎看不出差别。6. 常见问题与故障排查实录在实际运行这套程序的过程中我总结了几个高频问题这里按出现频率排序列成表格问题现象根因排查与解决方案h5py读取报错找不到字段路径ATL03波束名称不对强弱光束编号各不相同先打印f.keys()查看顶层结构再按gt1l/gt1r/gt2l等路径逐层确认高程剖面全是纵向条纹光子云未做去噪或min_samples过小加大min_samples或先做高程直方图截断再看剖面是否收敛轨迹图在地图外偏移很厉害经度或纬度单位填错比如把毫度当成度检查attrs中的单位说明必要时除以比例因子白天数据去噪后地表轮廓断裂DBSCAN的eps太小信号光子之间密度不均匀适当增加eps到0.3以上或改用HDBSCAN自适应邻域程序内存占用超过16G一次读入全部字段或全部波束改为按gt分组处理只保留需要的字段用完立即释放变量出图时中文字体乱码matplotlib默认字体不支持中文设置plt.rcParams[font.sans-serif] [SimHei]取消unicode负号这里重点说下第一项。ATL03文件的波束命名规则是gt1l、gt1r、gt2l、gt2r、gt3l、gt3r其中l是弱光束r是强光束三者强弱不同信噪比也完全不同。如果你后续要做同一区域的多波束对比一定不要混用光束类型。我最初对比时间序列时没注意把gt1l和gt2r的剖面叠在一起还以为是数据异常排查半天才发现是光线强度差异造成的正常现象。再说一个ATSAT-2特有的细节ICEsat-2每一条轨道的起止时间都不同而光子的沿轨距离是相对参考点累计算的不同轨道之间无法直接按沿轨距离做时间序列对比。需要先用轨道号和时间字段对齐再把同一地理区域的沿轨距离重新投影到公共坐标上这步做不好后续任何时间序列变化分析都会得出错误结论。程序里我还加了几个辅助函数比如自动读取轨道号和时间、根据经纬度框选研究区、把去噪结果合并到GeoDataFrame导出成GeoJSON。如果你的应用场景偏向绘图和快速探索这些辅助函数能省不少事。最后想说的是处理ICESat数据真正的瓶颈不在代码而在对传感器物理特性的理解。同样的DBSCAN白天数据自动放宽eps夜间数据收窄min_samples效果会发生质的改变。建议拿到新区域的数据时先跑一小段剖面把参数定下来再批量处理效率比反复试错高得多。这套程序我也会持续维护欢迎一起交流思路和遇到的个性化问题。本文还有配套的精品资源点击获取
返回列表