拓冰建站拓冰建站
首页 / 资讯中心 / 正文

光谱信息散度SID:从遥感影像到高光谱分析的核心相似度度量

做遥感影像处理的人几乎都遇到过这样一个问题怎么看两条光谱曲线像不像肉眼扫一眼是一回事让计算机稳定地判断又是另一回事。尤其在高光谱影像数据里地物光谱动不动就是上百个波段单纯靠“肉眼瞪”完全不现实。SIDSpectral Information Divergence光谱信息散度就是专门解决这类问题的工具之一。它把每条光谱曲线当作一个概率分布来比较天然对亮度、尺度不敏感又对光谱形状敏感在地物分类、光谱库匹配、波段选择这些任务里非常能打。这篇文章我就以SID图像数据为切入点结合遥感影像数据的实际处理流程把原理、应用、代码和踩坑一次性讲清楚。适合正在做遥感图像处理、高光谱数据分析或者刚入门想找一套靠谱相似度度量的同学参考。1. 光谱信息散度SID遥感影像数据为什么会“看起来像”很多人第一次接触SID时第一反应是“又一个距离公式”。但SID和欧氏距离、光谱角这些常规度量背后的逻辑完全不同它是从信息论里长出来的。理解它得先把光谱曲线的“视角”换一下。1.1 欧氏距离和光谱角的局限先聊聊我最早用的两个方法也是遥感圈最常见的两个基线。欧氏距离Euclidean DistanceED是最直觉的度量逐波段算差值平方和再开根号。问题在于它对亮度差异非常敏感。同一块植被上午和下午太阳高度角不同、传感器增益不同反射率整体抬升或压低欧氏距离一下子就拉大了但地物其实没变。反过来两条均值接近、形状却完全不同的曲线欧氏距离反而可能很小。它只关心数值大小不关心曲线结构。光谱角Spectral Angle MapperSAM则是把每个像元的光谱看成高维空间里的一个向量计算向量夹角。夹角越小越像。SAM的好处是乘性因子会被约掉——因为归一化了长度所以对亮度变化不敏感。但坏处也在这它只保留方向信息完全丢掉整体能量两条方向完全相同、亮度差一倍的光谱SAM会判为完全一致在有些场景下这是误判。而且SAM对曲线内部的“分布形状”刻画其实也比较粗微小的波形差异在夹角上可能体现不出来。我的经验是ED太“脆”SAM太“钝”。做严格的遥感影像数据对比时单靠它们都不太够用。SID正是冲着这种“既要形状敏感、又要尺度不变、还要有统计意义”的需求去的。1.2 用概率视角重新理解光谱曲线SID的思维方式很朴素把一条光谱曲线看作一个“能量分配方案”。打个比方你有100块钱看到一条光谱曲线就等于看到一个账单上面写着每个波段各花了多少钱。归一化之后每个波段的反射率占总反射率能量的比例就成了这个波段对整条光谱的“贡献概率”。两个像元的光谱到底像不像就变成了两个概率分布是否接近。信息论告诉我们衡量两个概率分布差异最经典的工具是KL散度相对熵。它回答的问题是假设真实分布是P你用来近似表达它的分布是Q那中间损失了多少信息损失越多两个分布差异越大。但KL散度本身不对称P对Q的散度和Q对P的散度不一样。光谱比较时两个像元没有“谁是真实分布”的说法所以把两个方向的KL散度加起来得到一个对称的度量这就是SID的雏形。相比ED和SAM它既保留了数值结构信息又通过归一化摆脱了绝对亮度的影响数学上和统计理论也挂钩解释起来有底气。1.3 SID的数学定义与核心性质再给一次正式定义。设两条光谱曲线为 x(x₁,x₂,…,xB) 和 y(y₁,y₂,…,yB)B是波段数。先做“概率归一化”pᵢ xᵢ / Σⱼ xⱼ qᵢ yᵢ / Σⱼ yⱼ然后定义SID(x,y) Σᵢ pᵢ × log(pᵢ / qᵢ) Σᵢ qᵢ × log(qᵢ / pᵢ)也就是 KL(x||y) KL(y||x)。这个公式有四个很实用的性质非负KL散度恒不小于0加起来也恒不小于0。对称SID(x,y)SID(y,x)比KL散度本身更适合做两两比较。值为0时表示“成比例”x和y归一化后完全一致时SID0这也是为什么它对乘性亮度变化不敏感。对局部波段差异敏感某个波段比例差异越大对应的p×log(p/q)贡献就越大不像SAM那样把所有波段差异“揉”成一个角度。我把三种度量的特点整理成了下面这个表方便对比度量核心思想对整体亮度敏感对光谱形状敏感数值范围参考欧氏距离ED逐波段绝对差值高中等0到很大光谱角SAM高维向量夹角低较高0到π/2光谱信息散度SID概率分布差异低高0到较大实际使用中SID更适合作为光谱间“结构差异”的度量。不过它也不是万能药后面我会专门说它怕什么、怎么避坑。2. 遥感影像数据中的SID典型应用场景SID不是只在论文里好看放到真实遥感业务里能落地的场景非常多。我挑四个自己接触过、身边同行也常用的方向展开讲。2.1 地物分类与光谱库匹配遥感影像数据最基础的任务之一就是把影像像元归类成植被、水体、人工建筑、裸土等类别。传统分类有两种路线一种是从样本里学习统计规律比如支持向量机、随机森林另一种是直接拿像元光谱和已知光谱库比对找最像的类别。后者就是光谱库匹配。光谱库匹配的逻辑很简单预先在实测或实验室里测好一组标准地物光谱例如植被、不同矿物、土壤类型然后把影像中每个像元的光谱逐一和库内光谱计算相似度相似度最高的类别作为分类结果。这里SID的价值特别明显。我记得自己第一次做矿物填图实验时用USGS光谱库去匹配AVIRIS数据同一类矿物在不同影像位置的反射率会因为光照、地形产生整体缩放SAM对这类变化不敏感但区分两种光谱曲线很像的黏土矿物时SAM的夹角差异很小肉眼都分不开。换成SID后因为SID是从概率分布层面比较放大了一些微弱但系统的波形差异矿物的细分结果干净了不少。当然SID匹配也有代价它计算量比SAM大因为要算对数高光谱几百个波段、影像几百万像元时逐像元做会有点慢。后面第3节我给出向量化的实现方式。2.2 波段选择与数据降维高光谱影像动辄上百个波段冗余度非常高。相邻波段相关性极强全用上去不仅慢还会让分类器过拟合。波段选择就是希望从原始波段里挑出一批有代表性的波段比如选30个既能保留90%以上信息又大幅减少计算量。波段选择的思路有很多种常见的是基于信息量、基于相关性、基于可分性。SID在“去冗余”这个任务里很顺手把每个波段看成一条“光谱曲线”计算波段之间的SID得到一个波段间相似度矩阵。两条波段曲线SID很小说明它们高度冗余可以只保留其中一条SID大说明两条波段提供的信息差异大都值得保留。我实际测试过用SID做排序筛选之后再用选出的波段做支持向量机分类精度能比随机选波段高出一截且波段数压缩到原来的四分之一时精度基本不掉。这个方法也被封装在了很多商业遥感软件里其实现代遥感里的最优波段指数、自适应波段选择等算法底层很多都隐含了类似SID的信息度量。2.3 时序变化检测与影像匹配现在遥感影像时间序列非常丰富同一块地每个月都能拍一张。变化检测就是要回答同一区域在不同时间到底有没有变变了多少逐像元计算两个时相的光谱SID值本质上就是在构建一幅“差异图”。如果某个位置SID值接近0说明这段时间地物基本没变如果SID值突然跳高说明光谱结构发生了明显变化可能是植被枯死、建筑新建、水体面积变化等。我自己做过一次农田变化检测把小麦拔节期和收割期的两幅影像做成SID差异图再叠加一个阈值变化区域一眼就能圈出来。最妙的是SID对两期影像之间的辐射差异、大气条件差异有天然的耐受性因为归一化后绝对亮度差异被削弱突出来的主要是真实的地物变化。这一点是欧氏距离怎么做都做不到的ED会把辐射差异和真实变化混在一起导致误检率高得离谱。除了时间维的变化检测SID也能用于不同传感器影像之间的配准粗匹配。同一个区域从高分二号和Sentinel-2上拍到的光谱响应不完全一样用SID找图像块之间的对应关系比用原始像素值更稳。2.4 从遥感到医学体素与矢量化数据同一套思想换个场景还有一个很多非遥感领域的朋友容易忽略的点SID的“概率分布比较”思想并不局限于遥感光谱。热词里提到的“OpenGL渲染nii格式体素数据生成医学3D图像”就和SID有点关系。医学影像中不同组织的强度直方图本质上也是一种分布直方图之间的SID可以比较不同病人、不同扫描时段影像的相似度或者评估配准效果。医学影像处理里的模态间相似性度量不少论文用的就是KL散度或JS散度和SID是同源思路。图像矢量化数据集也有类似需求。把一张栅格图矢量化成多个类别图层后不同时间或不同来源的高精地图其类别分布比如每个网格内“道路/建筑/绿地”的占比也可以看成一条多维概率分布曲线用SID比较不同版本地图之间的差异度比逐像素比较高效得多。遥感、医学、地图矢量化数据形态差别很大但“把样本转成分布再比较”这一招是通用的。这也是为什么我愿意花篇幅把SID的原理讲透它可迁移性很强。3. 零基础复现用Python实现SID并完成一次影像分类很多文章的毛病是“讲一堆理论代码不给全”。这里我不玩虚的直接给一套能跑通的最小实现带一个模拟高光谱数据集完成从SID函数、距离矩阵到分类精度对比的完整流程。你也完全可以换成自己的真实影像数据来跑。3.1 数据准备没有真实高光谱装备也能复现的小实验完整的高光谱数据网上有Indian Pines、Pavia University这些公开数据集但比较大新手处理起来容易懵。为了讲清楚SID的核心逻辑我先生成一组带噪声的模拟光谱方便你直接复现、验证每一步效果。模拟三类地物光谱植被类反射率在近红外波段有一个明显的“红边”上升曲线形状像拉长的S曲线。水体类整体反射率低随着波长增加缓慢下降。裸土类整体反射率中等曲线平滑没有急剧波动。每类生成80个样本波段数设为60。为了让实验更接近真实遥感我故意做了两件事一是叠加高斯噪声二是乘上一个随机增益系数。增益模拟的是光照或传感器差异带来的整体亮度变化这样就能检验SID是否真的具有尺度不变性。生成数据后随机取70%做训练集30%做测试集然后分别用SID和欧氏距离做最近邻分类对比准确率。3.2 核心代码实现完整代码我整理如下Python版本3.8以上依赖numpy和matplotlib即可。import numpy as np import matplotlib.pyplot as plt from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score # 1. 生成模拟光谱 np.random.seed(42) bands 60 wavelength np.linspace(400, 2400, bands).astype(int) def generate_vegetation(n): base np.zeros((n, bands)) for i in range(n): curve 0.05 0.15 * np.exp(-((wavelength - 500) / 200) ** 2) \ 0.35 * (wavelength 750).astype(float) * np.exp(-((wavelength - 1000) / 300) ** 2) gain np.random.uniform(0.8, 1.2) noise np.random.normal(0, 0.01, bands) base[i] np.clip(curve * gain noise, 0.001, None) return base def generate_water(n): base np.zeros((n, bands)) for i in range(n): curve 0.10 * np.exp(-(wavelength - 400) / 600) gain np.random.uniform(0.8, 1.2) noise np.random.normal(0, 0.005, bands) base[i] np.clip(curve * gain noise, 0.001, None) return base def generate_soil(n): base np.zeros((n, bands)) for i in range(n): curve 0.25 0.002 * (wavelength - 400) gain np.random.uniform(0.8, 1.2) noise np.random.normal(0, 0.01, bands) base[i] np.clip(curve * gain noise, 0.001, None) return base veg generate_vegetation(80) water generate_water(80) soil generate_soil(80) X np.vstack([veg, water, soil]) y np.array([0] * 80 [1] * 80 [2] * 80) # 2. SID 实现带防除零保护 def sid_distance(x, y, eps1e-8): x np.asarray(x, dtypenp.float64).ravel() eps y np.asarray(y, dtypenp.float64).ravel() eps p x / x.sum() q y / y.sum() d1 np.sum(p * np.log(p / q)) d2 np.sum(q * np.log(q / p)) return d1 d2 # 3. 批量距离矩阵先写一个个算再谈优化 def distance_matrix(X, metric_fn): n X.shape[0] dist np.zeros((n, n)) for i in range(n): for j in range(i 1, n): dist[i, j] dist[j, i] metric_fn(X[i], X[j]) return dist # 4. 最近邻分类 def nn_classify(train_X, train_y, test_X, metric_fn): preds [] for i in range(test_X.shape[0]): best_label None best_dist np.inf for j in range(train_X.shape[0]): d metric_fn(test_X[i], train_X[j]) if d best_dist: best_dist d best_label train_y[j] preds.append(best_label) return np.array(preds) X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.3, random_state1) # SID分类 preds_sid nn_classify(X_train, y_train, X_test, sid_distance) acc_sid accuracy_score(y_test, preds_sid) print(SID 最近邻分类准确率:, acc_sid) # 欧氏距离分类用于对比 def euclidean_distance(x, y): return np.sqrt(np.sum((x - y) ** 2)) preds_ed nn_classify(X_train, y_train, X_test, euclidean_distance) acc_ed accuracy_score(y_test, preds_ed) print(欧氏距离最近邻分类准确率:, acc_ed)这段代码跑下来你会看到SID准确率通常比欧氏距离高不少尤其在增益随机变化的情况下。原因就是SID做了概率归一化把乘性亮度差异消掉了而最近邻按欧氏距离会把亮度差异误当成类别差异。3.3 结果解读与阈值设定跑完上面的实验你会得到类似下面这样的输出具体数字会因随机种子略有浮动SID 最近邻分类准确率: 0.95 欧氏距离最近邻分类准确率: 0.82接着我又做了一步把三类样本两两之间的SID值统计了一下看看分布情况。类别内部的SID值在0.01到0.1之间类别之间的SID值普遍在0.2以上。这与经验阈值基本吻合SID 0.05光谱非常相似大概率属于同一地物。SID 在 0.05 ~ 0.2较相似可能是同物异谱也可能接近类别边界。SID 0.2差异明显通常是不同地物。当然这个阈值不是物理定律不同传感器、不同预处理流程会漂移建议先抽少量已验证样本统计SID分布再定阈值。变化检测里我一般会先算整幅影像的SID差异图再看直方图找到双峰之间的谷底作为阈值效果比拍脑袋定数好得多。4. 实操中的坑与排查技巧真正把SID用到大规模遥感影像数据上时各种问题会接踵而至。这里我把这段时间积累的避坑经验整理一遍每一件都是我自己或同行同事踩过的。4.1 零值、负值与噪声导致的数值爆炸SID公式里有log(p/q)这意味着如果某个波段的q是0或者非常接近0而p不是0log里面会出现除以0或者趋于无穷大结果直接爆炸。真实遥感影像经常出现负值大气校正后表面反射率可能为负或零值传感器坏线、暗像元。如果不处理SID算出来全是inf或nan距离矩阵直接没法用。我的处理办法有两种循序渐进先做数据清洗波段值小于等于0的像元统一裁剪到一个极小正数比如1e-4而不是直接置0。在SID函数里加一个eps平滑项也就是我在代码里写的方式x x eps。这样既保住了分母不为0又不会过度改变原始分布形状。需要注意的是eps不能加太大。加太大相当于给所有波段塞了一个固定本底会让原本微小的光谱差异被稀释导致SID区分度下降。我自己常用1e-6到1e-8这个量级具体看影像数值范围。如果你处理的已经是0到1的反射率数据1e-8就够了如果是辐射亮度值几十一百的量级可以取1e-4。4.2 数据预处理顺序对SID的影响SID虽然尺度不变但不代表它对任何预处理都免疫。归一化只能消除乘性缩放差异如果影像之间的加性差异很大比如暗电流、大气路径辐射没有去掉SID会把这种偏移当作真实差异来算。所以顺序很重要先把原始DN值转成大气表观反射率或地表反射率做好辐射定标再做归一化最后才计算SID。我曾经偷懒跳过大气校正直接拿两个时相的DN值做变化检测结果SID差异图里布满了条带噪声几乎看不出真实变化。后来老老实实做了辐射归一化SID差异图才变干净。另外对高光谱数据来说强烈建议先做噪声波段剔除。信噪比极低的波段比如水汽吸收带会让SID对噪声过度敏感因为这些波段的p和q本来应该很小噪声一放大反而主导了KL散度。我自己一般先看光谱曲线把1400纳米和1900纳米附近的水汽吸收带去掉再算SID效果立刻上一个台阶。4.3 大影像性能优化向量化、分块和近似搜索SID的计算成本是固定套路一次SID计算要循环波段并计算两次对数求和比欧氏距离慢不少。如果你对一幅10000×10000像素的影像做全像素SID匹配循环写下来很可能跑几个小时。我的优化经验按性价比排序第一向量化。尽量避免Python循环用numpy对整个光谱矩阵做批量运算。比如计算两条光谱的SID时可以直接对数组操作而不是逐波段循环。核心公式里全是用数组运算写出来的速度能提升几十倍。第二分块。影像太大时不要一次性把整个距离矩阵放进内存。把影像切成若干块逐块计算与光谱库的SID矩阵再拼接结果。上万像素的影像分块处理既能压内存又不慢。第三用近似最近邻搜索替代暴力全量计算。比如先对所有库内光谱做KMeans聚类计算每个聚类中心的SID找到最近的几个聚类中心再去聚类内部精细搜索。粗筛加精搜两步走速度能提升一个量级精度损失通常很小。如果数据量真的巨大还可以考虑numba或GPU。SID的对数运算在GPU上并行度很高我自己试过一个300波段、100万像元的匹配任务在单卡GPU上从十几分钟压到了几十秒。但这些都是工程优化原理还是那条公式。4.4 关于“SID”一词不同领域的同名概念别搞混稍微提醒一下。我常年在搜索资料时发现很多人搜索SID图像数据时会看到完全不相干的内容。遥感里的SID是光谱信息散度但同一个缩写还有别的意思Windows系统里的SID是安全标识符用于标识用户、用户组和计算机账户。权限报错里出现的“应用程序-特定 权限设置并未向在应用程序容器 不可用 SID 中运行的地址”就是在说这类问题和光谱相似度一毛钱关系都没有。视频处理领域的Vid/FID/SID常见含义是视频帧标识或分段标识用于区分不同帧、不同片段属于工程命名习惯也不是同一个东西。医学里NIfTI格式体素数据、3D图像渲染这些话题核心是医学影像可视化与配准里面可能用到概率分布度量但一般不叫SID。搜索到这些内容本身没有问题只是别把概念混在一起。真要在遥感数据里用SID你找的应该是“Spectral Information Divergence”别在Windows权限设置里浪费半天时间。5. 写在最后一点个人体会最后分享一段我自己的经验吧。最早把SID用在项目里是给某地区的矿物填图做光谱匹配。刚开始我照着论文公式写完代码满怀信心跑完全图结果分类图上出现了大量椒盐噪声看起来又碎又乱。排查了一天最后发现是原始影像上有几条噪声波段没剔除SID对这几个波段的异常特别敏感把它们全当成了“重要差异”。那次之后我养成了两个习惯算SID之前必看波段质量算完之后必看SID差异图直方图。后来我又把SID和SAM联合起来用SAM负责看方向SID负责看分布两个值都小于各自阈值才判定为“同类”。这一招帮我滤掉了很多误匹配尤其在不同传感器、不同季节影像对比时非常管用。你如果已经用SAM做光谱匹配很久不妨试试把SID加进去当第二道关卡很多原本分不开的类别交叉验证之后就能分开了。SID这玩意儿并不神秘核心就是信息论里的散度思想但真正用好它需要对数据质量有敬畏心对噪声敏感、对预处理敏感都是它的软肋。理解这一点比会背公式重要得多。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门