简介:这份文档面向GIS从业者与遥感数据分析学习者,系统讲解DMSP/OLS夜间灯光数据在ArcGIS Desktop中的校正操作流程,帮助解决传感器辐射性能差异、年际数据不连续、F18突变及DN值0-63天花板效应等实际问题。资源包内含1个docx文件,约592KB,以图文步骤形式呈现,便于对照软件界面逐步操作。内容涵盖中国区域亮值像元影像提取、兰伯特方位角等面积投影转换、NEAREST重采样,以及基于伪不变区域与最小二乘回归的传感器依次校正方法,并说明无重合年份时如何借助相邻年份数据建立校正方程。目前已有5973人学习下载,适合需要提升夜间灯光数据连续性与可比性、开展城市扩展监测或区域经济差异评估的研究人员参考,可据此掌握从数据预处理到传感器相互校正的完整技术路线。
1. 夜间灯光数据校正在ArcGIS里的真实门槛:为什么你导出的DN值总对不上
夜间灯光遥感数据(比如常见的NTL产品)拿到手,很多人第一反应是直接拖进ArcGIS出图,结果发现两个区域亮度差了三倍,或者同一景影像在不同月份拼接后出现明显色阶断层。这不是软件的问题,而是原始DN值本身带着传感器增益、大气散射、月相周期和饱和像元四层干扰。不做校正就直接做统计分析,出来的GDP相关性、城市扩张指数基本是玄学。
这篇笔记面向的是已经会用ArcGIS做基础裁剪和投影转换、但一碰到夜间灯光校正就卡住的从业者。我会把整个流程拆成可复现的步骤:从DN值转辐射亮度、去饱和、年内多期合成,到最终在ArcGIS里用栅格计算器落地。中间会给出具体参数、Python脚本和踩坑记录。如果你手头有DMSP-OLS或NPP-VIIRS类数据,这套流程可以直接套用。ArcGIS 10.8和ArcGIS Pro 3.x在栅格计算器语法上略有差异,我会分别标注。
2. 校正前的数据准备:投影、重采样与无效值处理
2.1 为什么第一步不是打开栅格计算器
夜间灯光原始数据通常以地理坐标系(WGS84)分发,像元大小是30弧秒或15弧秒。直接做栅格计算,ArcGIS会按地理坐标的度数计算,导致高纬度区域像元面积严重变形。常见做法是先投影到适合研究区的等面积投影,比如Albers或Lambert。我一般会先确认三件事:数据是否已经过几何校正、无效值(NoData)填充的是什么、以及是否需要重采样到统一分辨率。
以某跨平台系统的夜间灯光处理Demo为例,原始数据是GeoTIFF,NoData值为-9999。如果直接参与计算,-9999会被当成真实DN值,结果全错。所以第一步是用“设为空函数”或栅格计算器把无效值剔除。
# ArcGIS Pro Python窗口:将NoData值替换为真正的NoData import arcpy from arcpy.sa import * arcpy.env.workspace = r"C:\NTL_Project\raw" arcpy.env.overwriteOutput = True # 输入原始栅格 in_raster = "NTL_2020.tif" # 用Con函数把-9999设为NoData,其余保留原值 out_raster = Con(Raster(in_raster) == -9999, "", Raster(in_raster)) out_raster.save(r"C:\NTL_Project\processed\NTL_2020_clean.tif")这段代码的逻辑是:Con函数逐像元判断,如果值等于-9999就输出空,否则输出原值。参数上注意""表示NoData,不要写成0,否则后续统计会把0当成有效暗背景。ArcGIS 10.8里对应的是Spatial Analyst工具箱下的“条件函数”,操作路径是:Spatial Analyst > 条件分析 > 条件函数,表达式写"NTL_2020.tif" == -9999,真值为空,假值为原栅格。
2.2 投影转换与重采样的参数怎么设
投影转换用“投影栅格”工具,输出坐标系选Albers,中央经线按研究区定。重采样方法选“双线性”还是“最近邻”?夜间灯光是连续型栅格,双线性更平滑,但会改变DN值分布;最近邻保留原始值但可能产生锯齿。我的经验是:如果后续要做辐射定标,用最近邻;如果只是做可视化或趋势面分析,双线性可以接受。
重采样分辨率建议统一到500米或1公里。DMSP-OLS原始分辨率约1公里,NPP-VIIRS约500米。如果混用两种数据,必须重采样到同一网格。这里有个细节:重采样时输出像元大小要写投影后的单位(米),不要写度数。
# 投影并重采样到1公里Albers arcpy.ProjectRaster_management( in_raster=r"C:\NTL_Project\processed\NTL_2020_clean.tif", out_raster=r"C:\NTL_Project\processed\NTL_2020_albers.tif", out_coor_system=arcpy.SpatialReference(102025), # Albers for China resampling_type="NEAREST", cell_size="1000 1000" )102025是某区域Albers投影的WKID,实际用时换成你研究区对应的。cell_size写成"1000 1000"表示X和Y方向都是1000米。如果只写一个1000,ArcGIS会自动应用为正方形像元。
2.3 裁剪与掩膜:别让背景值污染统计
裁剪用“按掩膜提取”,掩膜可以是研究区矢量边界。注意裁剪后边缘像元可能被重采样,建议先裁剪再投影,或者投影后裁剪时勾选“保持裁剪范围”。如果研究区跨多景影像,先做镶嵌再裁剪,镶嵌时重叠区域选“最大值”还是“平均值”?夜间灯光重叠区通常取最大值,因为灯光不会因为多景平均而变暗。
# 按研究区边界裁剪 out_extract = ExtractByMask( in_raster=r"C:\NTL_Project\processed\NTL_2020_albers.tif", in_mask=r"C:\NTL_Project\boundary.shp" ) out_extract.save(r"C:\NTL_Project\processed\NTL_2020_clip.tif")到这里,数据已经干净、投影统一、无效值处理完毕。接下来才是真正的校正环节。
3. DN值转辐射亮度:公式、参数与ArcGIS栅格计算器写法
3.1 为什么不能直接用DN值做跨年比较
DMSP-OLS的DN值是0-63的整数,NPP-VIIRS是0-255左右的浮点。不同传感器、不同年份的增益设置不同,直接比较DN值等于拿不同尺子的刻度对比。校正的第一步是转成物理量:辐射亮度(radiance)或亮度温度。DMSP-OLS常用公式是L = DN^(3/2) * 10^-6(某版本定标公式),NPP-VIIRS则用L = DN * 10^-9(具体系数看元数据)。
这里要强调:不要背公式,去查你下载数据时附带的元数据文件。元数据里会有radiance_calibration或gain字段。我见过有人把DMSP的公式套到VIIRS上,结果整幅图亮度差了三个数量级。
3.2 栅格计算器里的幂运算与浮点精度
ArcGIS栅格计算器的幂运算用**,不是^。^在Python里是异或,在栅格计算器里可能被解释为其他含义。写公式时注意浮点精度:DN是整数,先转浮点再运算,否则DN^(3/2)在整数运算下会截断。
# ArcGIS Pro栅格计算器:DMSP-OLS DN转辐射亮度 # 假设DN范围0-63,公式 L = DN^(3/2) * 10^-6 out_radiance = (Float("NTL_2020_clip.tif") ** 1.5) * 0.000001 out_radiance.save(r"C:\NTL_Project\processed\NTL_2020_radiance.tif")Float()函数把栅格转为浮点型,避免整数幂运算截断。** 1.5就是DN的3/2次方。* 0.000001是乘以10的负6次方。如果你在ArcGIS 10.8里操作,栅格计算器界面直接输入Float("NTL_2020_clip.tif") ** 1.5 * 0.000001,注意文件名要带引号。
对于NPP-VIIRS,公式通常是L = DN * 10^-9,但有些产品已经提供了辐射亮度波段,不需要再转。判断方法:看元数据里units字段,如果是nW/cm2/sr,说明已经是辐射亮度;如果是DN,才需要转。
3.3 去饱和:DMSP-OLS的63阈值怎么破
DMSP-OLS最头疼的是饱和:城市中心DN值卡在63,导致亮度被低估。常见做法是用NPP-VIIRS数据做参考,对DMSP饱和像元进行替换或拟合。ArcGIS里可以用“栅格计算器”配合“Con”函数实现:如果DMSP DN等于63,就用VIIRS对应像元的辐射亮度按比例替换。
# 去饱和:用VIIRS辐射亮度替换DMSP饱和像元 # 先重采样VIIRS到与DMSP同一网格 viirs_resampled = "VIIRS_2020_radiance_resampled.tif" dmsp_radiance = "NTL_2020_radiance.tif" # 计算替换值:VIIRS辐射亮度乘以一个经验系数(需根据研究区拟合) # 这里假设系数为1.2,实际应用时用回归分析确定 out_desaturated = Con(Raster(dmsp_radiance) >= 63 * 0.000001, Raster(viirs_resampled) * 1.2, Raster(dmsp_radiance)) out_desaturated.save(r"C:\NTL_Project\processed\NTL_2020_desaturated.tif")Con函数的第一个参数是条件:DMSP辐射亮度是否达到饱和阈值(63对应的辐射亮度值)。第二个参数是真值:用VIIRS辐射亮度乘以系数。第三个参数是假值:保留原DMSP辐射亮度。系数1.2不是固定的,需要用你研究区内未饱和像元做回归,得到DMSP和VIIRS的线性关系,再取斜率。
注意:去饱和只对城市中心有效,如果研究区没有VIIRS数据,可以用“饱和像元邻域均值”替代,但效果差很多。
4. 年内多期合成与跨年校正:把12个月压成一张可比较的图
4.1 月度数据合成的三种策略
夜间灯光月度数据受月相、云层、气溶胶影响,单月影像噪声大。常见合成策略有三种:最大值合成(MVC)、平均值合成、中值合成。MVC保留最亮像元,适合城市范围提取;平均值合成平滑噪声,适合趋势分析;中值合成抗异常值,适合长时间序列。
我一般用MVC做城市扩张,用中值合成做GDP相关性。ArcGIS里用“像元统计”工具,统计类型选MAXIMUM或MEDIAN,输入12个月栅格。
# 月度数据最大值合成 arcpy.gp.CellStatistics_sa( in_rasters=["NTL_2020_01.tif", "NTL_2020_02.tif", ..., "NTL_2020_12.tif"], out_raster=r"C:\NTL_Project\processed\NTL_2020_MVC.tif", statistics_type="MAXIMUM", ignore_nodata="DATA" )ignore_nodata="DATA"表示如果某月是NoData,其他月参与统计。如果选"NODATA",则任一月为NoData则输出NoData,会丢失大量像元。
4.2 跨年校正:用不变目标区域做相对辐射归一化
不同年份的传感器增益不同,即使都转了辐射亮度,跨年比较仍有系统偏差。常用方法是选取“不变目标区域”(如稳定城市中心或沙漠暗背景),建立年份间的线性回归模型,然后对整幅影像做归一化。
步骤:先在ArcGIS里用“创建随机点”在不变区域生成样本点,再用“提取多值至点”获取各年份辐射亮度,导出到Excel做回归,得到斜率和截距,最后用栅格计算器应用。
# 假设回归得到2020年相对于2015年的校正:L_2015 = a * L_2020 + b # a=0.85, b=0.02(示例值,实际用回归结果) out_corrected = Raster("NTL_2020_radiance.tif") * 0.85 + 0.02 out_corrected.save(r"C:\NTL_Project\processed\NTL_2020_corrected.tif")这里a和b必须来自你的回归分析,不要用示例值。回归时注意剔除饱和像元和NoData,否则斜率会被拉偏。
4.3 用ArcGIS动态表格模块做校正质量检查
校正后怎么验证?我习惯用“动态表格模块”或“波段集统计”对比校正前后均值、标准差和直方图。如果校正后均值偏移超过10%,说明回归模型有问题。另一个技巧:在不变区域上计算校正前后的差值,理想情况下差值应接近0。
# 计算校正前后在不变区域的差值 diff = Raster("NTL_2020_corrected.tif") - Raster("NTL_2020_radiance.tif") diff.save(r"C:\NTL_Project\processed\diff_check.tif") # 然后用分区统计获取不变区域的均值 arcpy.gp.ZonalStatisticsAsTable_sa( in_zone_data="invariant_region.shp", zone_field="ID", in_value_raster=r"C:\NTL_Project\processed\diff_check.tif", out_table=r"C:\NTL_Project\processed\diff_stats.dbf", statistics_type="MEAN" )如果MEAN绝对值大于0.05(辐射亮度单位),说明校正系数需要重新拟合。
5. 避坑与排查:夜间灯光校正里最容易翻车的5个地方
5.1 现象:栅格计算器报错“无法打开栅格”
原因:文件路径含中文或空格,或者栅格被其他程序占用。ArcGIS对中文路径支持不稳定,尤其是10.8版本。解决:把所有数据放在纯英文路径下,关闭其他打开该栅格的窗口。如果还是报错,用“复制栅格”工具先转成Esri Grid格式再计算。
5.2 现象:校正后影像出现大面积0值
原因:NoData被当成0参与运算,或者Con函数的假值写成了0。解决:检查原始NoData值,用SetNull或Con显式处理。另外,栅格计算器里Float()转换时,如果原栅格有NoData,转换后仍是NoData,不会变0。
5.3 现象:跨年校正后城市中心反而变暗
原因:回归斜率a小于1,且截距b太小,导致高值被压缩。解决:检查回归样本是否包含饱和像元。如果包含,剔除后重新拟合。另外,如果研究区城市扩张明显,不变区域选取要避开新城区。
5.4 现象:MVC合成后边缘出现条带
原因:不同月份影像的覆盖范围不一致,边缘像元只有部分月份有值。解决:在合成前用“镶嵌至新栅格”统一范围,或者合成时选ignore_nodata="DATA",但边缘仍可能因月份数不足而偏低。建议至少保留6个月以上的有效像元。
5.5 现象:ArcGIS Pro 3.x里栅格计算器语法不兼容
原因:Pro 3.x默认使用Python 3,Float()函数名没变,但Con函数的参数顺序有调整。解决:在Pro里用arcpy.sa.Con,参数顺序是Con(in_conditional_raster, in_true_raster_or_constant, in_false_raster_or_constant)。如果从10.8迁移,把旧表达式里的Con(条件, 真值, 假值)直接搬过来通常没问题,但注意Raster()对象要显式声明。
6. 进阶技巧:用分区统计和动态表格模块做校正后验证
校正做完不是终点,验证才是。我一般会做两件事:一是用“分区统计”计算校正前后各行政区的灯光总量,看排名是否合理;二是用“动态表格模块”生成时间序列曲线,检查年际变化是否平滑。
分区统计的代码前面已经给过,这里补充一个技巧:统计时用ALL会输出均值、标准差、最大值、最小值等,但夜间灯光更关注SUM和MEAN。如果某区域SUM校正后反而下降,说明该区域有大量饱和像元被错误替换。
动态表格模块在ArcGIS Pro里叫“图表”功能,可以绑定栅格的时间序列。操作路径:右键栅格图层 > 创建图表 > 时间序列。如果数据没有时间字段,先用“添加时间字段”工具把文件名里的年份提取出来。
# 为多期栅格添加时间字段(以文件名年份为例) arcpy.AddField_management("NTL_2020_corrected.tif", "Year", "LONG") arcpy.CalculateField_management("NTL_2020_corrected.tif", "Year", "2020", "PYTHON3")最后说一个我自己的习惯:每次校正完,先不做任何分析,把校正前后影像并排打开,用“卷帘”工具来回拉。如果肉眼能看到明显的亮度突变或色阶断层,说明校正参数有问题。这个土办法比任何统计指标都直观。夜间灯光校正没有一劳永逸的公式,每个研究区的大气条件、传感器状态都不同,回归系数必须自己拟合。希望帮到你。
本文还有配套的精品资源,点击获取