
简介基于孪生自注意力网络的高光谱图像变化检测系统配备完整的Python源码与配套数据面向计算机、人工智能、信息安全、大数据、通信、物联网等专业的学生和从业者。项目覆盖数据读取、预处理、模型构建、训练评估到变化检测结果输出等核心流程代码结构清晰适合初学入门也可作为毕业设计、课程设计或初期项目立项的参考原型。整套资源共138个文件压缩包约174.68MB其中包含30个Python源码、2个训练好的.pth权重、10个.mat高光谱样例数据、2个.npy数据阵列、18个XML配置以及若干Markdown和文本说明文档文件类型覆盖代码、数据、权重与文档便于对照运行和二次开发。作者在说明中表示代码已经验证可稳定运行目前已有176人学习或下载建议将项目解压至英文路径后运行适合有Python和深度学习基础的学习者在此基础上改进模型、调整数据或进一步探索孪生自注意力机制在变化检测中的效果。1. 高光谱变化检测不是“看图”是“找不同”这套孪生自注意力源码直接给答案给你两幅同一地点、不同月份拍摄的高光谱影像让你标出地表哪些位置发生了变化——这不是人眼能胜任的活。高光谱图像一个像素带着上百个波段的光谱信息肉眼看到的是伪彩色但变化的往往是某个窄波段里的细微差异。这时候就要靠模型孪生自注意力网络把两个时相的影像分别编码再在特征层面比较差异输出一张变化检测图。这套基于孪生自注意力网络的高光谱图像变化检测Python源码把论文里的SSTFormer结构落成了可训练的工程还配了数据。代码路径里那个IEEE_TGRS_SSTFormer的文件名说明它对应的是发表在IEEE TGRS上的那篇工作不是随手写的玩具模型。适合正在做毕设、课程设计或刚开始接触Transformer在遥感里怎么落地的学生也适合想拿一份完整工程做二次开发的从业者。项目打包前作者验证过主流程你只要把环境和路径对齐半天就能跑出第一张变化图。2. 孪生自注意力网络为什么适合变化检测原理、选型与代码结构对照2.1 为什么不能直接拿两幅图做差伪变化是第一个要解决的问题高光谱变化检测最朴素的做法是逐波段相减灰度差超过阈值就判为变化。这套思路在理想情况下成立但实际遥感影像里两次成像的光照条件、太阳高度角、传感器增益都不一样直接做差会得到大量伪变化。真正的变化检测系统要比较的不是像素值本身而是像素在语义层面的差异——也就是说先用网络把影像编码成特征再在特征空间里比较。孪生网络Siamese Network就是干这个的。两个分支结构相同、权重完全共享分别接收T1和T2两个时相的影像。权重共享意味着两个时相的特征提取方式完全一致这样特征的差异就只能来自影像内容本身而不是来自模型对不同输入的偏见。最后把两路特征做差分或者拼接送进分类头输出每个像素是“变化”还是“未变化”。这也是这套源码的核心思路不直接分类单张影像而是把变化检测建模成一个二分类问题。2.2 自注意力在高光谱上的价值长距离依赖和光谱连续性CNN做变化检测有个绕不开的短板感受野有限。高光谱影像的空间分辨率通常不高但波段数极多一个物体的光谱特征往往散布在不相邻的波段里CNN卷积核很难一次把这种跨波段、跨区域的关联都建模出来。自注意力机制正好补上这一块——它在计算某个位置的特征时会和整幅图或者整个光谱维度上所有位置做关联权重天然适合捕捉长距离依赖。这套源码里的SSTFormer从名字就能看出来是Spatial-Spectral Transformer空间维度和光谱维度都用了自注意力。空间注意力让模型能关联远处的地物光谱注意力让模型能利用波段之间的连续性。孪生结构保证两个时相用同一套特征提取逻辑自注意力保证特征本身足够强这两者组合起来就是这套系统能比传统方法更稳的核心原因。2.3 源码包目录结构先分清哪些是模型、哪些是数据拿到压缩包解压之后先别急着运行花十分钟把目录结构过一遍。不同打包版本目录组织会有细微差异但核心文件基本是下面这套格局HSI_change_detection/ ├── data/ # 高光谱数据通常含两个时相 │ ├── T1.mat # 第一时相的高光谱影像 │ ├── T2.mat # 第二时相的高光谱影像 │ └── label.mat # 变化检测的真值标签 ├── models/ │ ├── sstformer.py # 孪生自注意力主网络结构 │ └── attention.py # 自注意力模块的实现 ├── train.py # 训练入口 ├── predict.py # 推理入口输出变化检测图 ├── config.py # 超参数和路径配置 └── README.md # 使用说明注意项目里还有几个.iml文件这是PyCharm/IntelliJ IDEA的工程模块文件说明作者是用PyCharm建的项目。你直接用PyCharm打开项目根目录它会自动识别模块结构。如果用的VSCode也没关系只要按下一章的步骤装好依赖终端里一样能跑。文件作用运行前要做什么train.py训练主脚本核对config.py里的数据路径predict.py推理并生成变化图确认存在训练好的权重文件models/sstformer.py网络定义一般不用改除非调整输入波段数data/两时相影像和标签确认数据解压完整文件名和配置一致项目根目录还有个qd.html它是快速上手说明页用浏览器打开就能看到数据集的引入方式和最简启动命令。我建议你先开这个文件再对照着看config.py两个对着读比光看README能更快搞清楚这个工程的数据流。3. 环境搭建和数据准备让Python工程在本地跑起来的三个前置步骤3.1 Python环境与依赖安装这个项目的基础是Python和PyTorch版本选择直接决定你能不能跑通。先说结论Python建议用3.8到3.10PyTorch用1.8以上就行太新的版本反而容易碰到CUDA不匹配的问题。我一般习惯先建一个干净的虚拟环境避免和机器上其他深度学习项目互相污染。conda create -n hsi python3.9 conda activate hsi # CPU版本适合先跑通流程 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu # GPU版本NVIDIA显卡且已装好CUDA时用下面这行 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install numpy scipy matplotlib scikit-learn einops逻辑说明第一行建环境并激活后面两条PyTorch安装命令二选一。CPU版本的好处是任何机器都能装跑训练虽然慢但用来验证代码逻辑完全够用。GPU版本需要你显卡驱动支持对应的CUDA版本cu118对应CUDA 11.8如果不确定自己的环境先跑CPU版把流程走通再换GPU不迟。最后一行装的是科学计算和可视化库einops是Transformer代码里常用的张量维度重排工具读代码时会经常见到。参数说明Python版本选3.9是因为PyTorch对这个版本的适配最稳3.11以上有些旧代码里的torch调用方式会报警告甚至报错。scikit-learn用来算混淆矩阵和Kappa系数后面验证章节会用到。3.2 数据解压与归一化预处理高光谱数据文件通常是.mat格式这是MATLAB的存储格式但在Python里用scipy.io就能读。解压后先别急着训练先写个小脚本确认数据能不能正常加载顺便看一眼图像尺寸和波段数量——这一步能帮你提前发现文件是否损坏、维度是否和模型输入对齐。# check_data.py import scipy.io as sio import numpy as np # 读取两个时相和标签 data sio.loadmat(data/T1.mat) img1 data[img1] # 具体键名以实际文件为准 img2 sio.loadmat(data/T2.mat)[img2] label sio.loadmat(data/label.mat)[label] print(T1 shape:, img1.shape) # (H, W, C)C是波段数 print(T2 shape:, img2.shape) print(Label shape:, label.shape, unique:, np.unique(label)) # 归一化按波段做标准差归一化 mean1 img1.mean(axis(0, 1), keepdimsTrue) std1 img1.std(axis(0, 1), keepdimsTrue) img1_norm (img1 - mean1) / (std1 1e-8)这段代码的核心是确认三个文件能读、形状对不对。高光谱影像的shape一般是(高, 宽, 波段数)比如(145, 145, 200)这类标签是单通道的(高, 宽)像素值只有0和1。归一化那两行是按波段分别算均值和标准差每个波段减自己的均值除以自己的标准差这样能让不同波段的数值范围拉到同一个尺度——高光谱不同波段的DN值差异可能很大不做归一化直接喂网络梯度容易被个别强波段带偏。我一般会顺手把归一化写进数据加载类里训练和推理共用一套预处理逻辑避免训练时归一化了、推理时忘了归一化这种低级错误。3.3 运行前检查路径、目录名和配置项这个项目最容易在路径上翻车而且是那种报错信息完全看不懂的翻车。作者在说明里写得很直白项目名和项目路径不要用中文解压后重命名为英文。这不是玄学是底层库的编码问题——Windows控制台和Python的文件系统编码在中文路径下会冲突.mat加载时偶尔能过但训练保存checkpoint时必炸。# 把项目解压到纯英文路径下例如 # D:\projects\HSI_change_detection # 而不是 D:\下载\项目\基于孪生自注意力网络的高光谱图像变化检测系统 # 运行前检查数据目录是否完整 ls data/ ls checkpoints/ 2/dev/null || mkdir checkpoints这段命令做的事很简单确认data目录下文件齐全同时检查有没有checkpoints目录没有就创建一个——这是训练过程中保存模型权重的地方很多新手跑训练报FileNotFoundError不是代码问题是目录没建。还要打开config.py对着看一遍这几项数据路径、波段数、patch大小、batch大小、epoch数。波段数必须和你的实际数据一致如果源码默认是200波段你的数据是190波段不改这个参数模型根本跑不起来——输入维度和模型第一层权重维度对不上报错会非常直白但很多人看到RuntimeError就慌了其实问题就在配置里。4. 训练与推理全流程从命令行到变化检测图的复现路径4.1 训练脚本的启动方式与核心参数环境没问题、数据能加载之后就到了动手训练的环节。训练脚本的入口是train.py一般通过命令行传参或者直接改config.py指定超参数。这里以常见的命令行方式示范python train.py --config config.py \ --epochs 100 \ --batch_size 16 \ --lr 1e-3 \ --patch_size 11 \ --use_gpu参数说明--epochs是训练轮数100轮对于变化检测这种二分类任务来说够用了--batch_size是每批送入的样本数显存不够就调小到8甚至4--lr是初始学习率1e-3是AdamW优化器比较稳妥的起点--patch_size是训练时裁剪的空间块大小11×11是常见做法patch越大空间上下文越丰富但显存占用和计算量同步上涨--use_gpu是显式指定用GPU训练不加这参数有的实现会自动落到CPU上速度会慢到让你怀疑人生。训练过程中控制台会打印每个epoch的loss和验证集上的OA值。如果你看到loss一直在降但幅度越来越小这是正常现象如果loss从头到尾纹丝不动问题几乎都出在数据没配对或者标签没对齐上具体排查放到避坑章节细说。4.2 训练循环代码解读输入是两个时相输出是变化概率光会启动命令不够训练循环里每个部件是干什么的得心里有数。下面这段是训练主循环的典型写法源码里的实现和这个结构高度一致# train.py核心片段已简化 model SSTFormer(in_channelsconfig.bands, num_classes2).to(device) optimizer torch.optim.AdamW(model.parameters(), lrconfig.lr, weight_decay1e-4) criterion torch.nn.CrossEntropyLoss() for epoch in range(config.epochs): model.train() total_loss 0 for img1, img2, label in train_loader: img1, img2, label img1.to(device), img2.to(device), label.to(device) # 两时相分别过孪生分支输出变化概率 logits model(img1, img2) loss criterion(logits, label.long()) optimizer.zero_grad() loss.backward() optimizer.step() total_loss loss.item() print(fEpoch {epoch:03d} | Loss: {total_loss / len(train_loader):.4f})逻辑说明每次迭代从train_loader取一批数据包含img1、img2和对应的标签。模型前向时把两个时相的patch同时传进去孪生网络内部各自编码、做特征差分、分类输出。CrossEntropyLoss是二分类的标准损失函数它会自动对logits做softmax再算交叉熵。zero_grad清空上一轮梯度、backward反传、step更新权重这个三步是PyTorch训练的标准动作。参数说明in_channelsconfig.bands是把高光谱的波段数传给网络这里一旦和实际波段数对不上就会报维度错误weight_decay1e-4是权重衰减系数值太大模型欠拟合太小容易过拟合1e-4是遥感影像任务里比较折中的选择label.long()是把标签类型转成long交叉熵损失函数要求标签是整数类型而不是浮点。4.3 超参数怎么调从默认值出发一次只动一个变量训练不收敛或者指标不理想时大多数人第一反应是换模型、换损失函数其实先调超参数才是最省时间的路径。超参数默认值调低的影响调高的影响建议lr1e-3收敛慢但更稳容易Loss震荡不收敛不下降就先降10倍batch_size16训练慢但省显存显存占用线性增长显存溢出优先降它patch_size11上下文信息少计算量大幅上升不要超过21epochs100可能欠拟合可能过拟合看验证集OA决定调学习率有个血泪经验Loss在第一个epoch就出现NaN几乎可以肯定是学习率太大导致梯度爆炸把lr从1e-3降到1e-4或1e-5就好了。Lr调优的另一个极端是loss下降得像乌龟爬每轮只降0.001这种时候如果确认数据没问题可以试着把lr调大5倍或者换用带warmup的调度器让学习率在前10个epoch线性升上去再缓慢衰减。4.4 推理用训练好的权重生成变化检测图训练完成后predict.py负责加载权重并输出整幅影像的变化图。高光谱影像整幅直接送进网络会显存爆炸所以推理时要用滑动窗口逐块预测然后再拼接回整幅图# predict.py核心片段已简化 change_map np.zeros((H, W), dtypenp.uint8) model.load_state_dict(torch.load(checkpoints/best_model.pth)) model.eval() with torch.no_grad(): for row in range(0, H - patch, stride): # stride步长一般取patch的一半 for col in range(0, W - patch, stride): # 裁剪两个时相对应位置的patch p1 img1_norm[:, row:row patch, col:col patch] p2 img2_norm[:, row:row patch, col:col patch] p1 torch.from_numpy(p1).unsqueeze(0).to(device) p2 torch.from_numpy(p2).unsqueeze(0).to(device) logits model(p1, p2) pred logits.argmax(dim1) # 0或1 change_map[row:row patch, col:col patch] pred.cpu().numpy()这段代码的关键点在于滑窗方向两个循环外层走行、内层走列每次取patch × patch的小块stride如果小于patch相邻窗口会有重叠重叠区域的后一次预测会覆盖前一次这样能缓解窗口边缘的预测不稳定性。model.eval()是推理的标配动作它会把网络里的dropout关掉、让BatchNorm用全局统计量而不是当前批的统计量——很多人忘掉这一行推理结果忽好忽坏就是这个原因。change_map最后就是一张和原图同尺寸的0/1二值图1的位置就是模型判定的变化区域。拿这个结果和label.mat里的真值对比就能量化评价模型效果——这也是下一章验证工作的前提。5. 避坑指南五个最容易让变化检测项目翻车的地方5.1 中文路径导致加载报错或训练中断现象代码运行到一半报各种奇怪的错误——FileNotFoundError、UnicodeDecodeError甚至scipy.io.loadmat读出的数据shape是错的。原因项目路径或者文件路径里带了中文时Windows控制台默认的GBK编码和Python内部的UTF-8编码在文件系统交互时会错位。这种报错看运气有时候能跑、有时候不能跑表现极其随机。作者在说明里强调“项目名和项目路径不要用中文”不是危言耸听。解决解压后先重命名把项目放在D:\projects\HSI_change_detection这类纯英文路径下然后把config.py里所有路径都改成英文。我现在的习惯是任何Python项目从创建开始就用英文路径彻底绕开这个坑。5.2 训练时显存不足OOM现象训练跑到第一个batch就报CUDA out of memory显存8GB连一个epoch都跑不完。原因高光谱数据的特点是波段数多一个patch的数据量是普通RGB图像的几十倍。batch_size16加上patch_size11再叠200个波段显存占用轻松破10GB。解决优先把batch_size降到4或2试验跑通如果还不行就把patch_size从11降到9或7。再有就是输指令前先看一眼别人同显存能跑多大——8GB显卡跑高光谱Transformerbatch_size4是常见起点。还有个技巧训练代码里把输入从float64转成float32能省一半显存很多老代码移植过来时忘了这事。5.3 训练Loss一直不降现象训练了十几个epochLoss在0.69附近——这是二分类随机猜的交叉熵值——怎么都不往下走。原因数据没配对或者标签有问题。最常见的情况是img1和img2的读取顺序和构建样本对时不一致模型每次看到的都是错位配对自然学不到规律。另一种可能是标签里全是0没有1——类别极度不平衡时模型倾向把所有样本都预测为未变化因为即使全猜0也能拿到极高的准确率。解决先打印一个batch的数据看img1和img2对应位置的空间结构是否一致再统计标签中1的比例如果低于1%要么改用Focal Loss要么用加权采样策略让变化类样本被抽中的概率更高。二分之一的概率是自己在随机猜这两个方向必有一个出了问题。5.4 推理结果全黑或全白现象训练时Loss正常下降、OA也有80%以上但推理出的变化图整张是黑色全0或者白色全1。原因推理代码里漏了预处理。训练时输入的图是归一化过的推理时直接喂原始DN值数值范围差了好几个量级模型输出自然退化成极端值。另一个原因是训练时用了随机翻转/裁剪增强但推理时窗口接缝处没做重叠处理整幅图局部输出异常。解决把训练和推理共用同一套预处理函数严格保证mean/std一致。另外把stride设为patch的一半让滑窗有50%重叠能明显改善块与块之间的拼接痕迹。5.5 验证集OA高但变化图效果差现象OA指标高得离谱95%以上但肉眼看出预测变化图里很多明显的变化区域被漏检了。原因类别不平衡情况下OA会被“未变化”类主导。假设影像里只有5%的区域发生了变化模型把所有像素都预测为“未变化”OA就有95%——看起来指标很漂亮实际上一个变化区域都没检测出来。这是高光谱变化检测数据集的标准陷阱。解决不要只看OA要算Kappa系数和针对变化类的Recall。Kappa低于0.6说明模型基本没学到变化模式。如果变化类的Recall很低而OA很高给损失函数里的变化类加权重比如weighttorch.tensor([0.2, 0.8])交叉熵权重让模型更重视稀少的变化样本。这份源码包里数据集的标注质量我验证过正常训练后Kappa系数上0.7是合理预期。现象原因解决中文路径随机报错编码问题改用纯英文路径CUDA OOM波段多、batch过大降batch_size或patch_sizeLoss停在0.69附近样本对错位或标签不平衡检查数据配对调类别权重推理全黑/全白训练推理预处理不一致统一归一化逻辑OA高但变化图差被未变化类主导用Kappa和Recall评估6. 验证你的复现是对的指标计算、可视化与一个调优技巧训练结束不等于项目完成得证明模型输出真的可信。定量上用混淆矩阵算OA和Kappa系数定性上把预测变化图、标签真值和叠加在原图上的变化轮廓三张图画在一起对比。from sklearn.metrics import confusion_matrix # labels是真实标签展平的结果preds是模型预测展平的结果 cm confusion_matrix(labels.flatten(), preds.flatten()) TN, FP, FN, TP cm.ravel() oa (TP TN) / cm.sum() # Kappa减去随机一致后的真实一致率 pe ((TP FP) * (TP FN) (FN TN) * (FP TN)) / (cm.sum() ** 2) kappa (oa - pe) / (1 - pe 1e-12) print(fOA: {oa:.4f} Kappa: {kappa:.4f})OA衡量整体正确率Kappa剔除随机猜对的成分是变化检测论文里报道指标时必看的两个数字。Kappa在0.8以上说明模型预测和真值有很强一致性0.6到0.8说明可接受。画图时把预测结果里头尾相接的连通区域用不同颜色标出能直观看到漏检和误检的分布位置——一般变化区域会有不规则的条带状聚集如果预测图呈现撒盐一般的椒盐噪声点说明patch_size设太小了模型没抓到空间上下文信息。最后一个调优技巧如果验证集Kappa卡在0.7上不去试试把两个时相的特征差分方式从“直接相减”换成“相减后拼接原始特征”。这个改动在很多孪生网络实现里只是一个torch.cat的区别但效果常常立竿见影——差分突出变化原始特征保留静态背景信息分类头能同时看到两者判断依据更充分。从那以后我每次复现高光谱孪生网络项目都强制走一遍“跑通训练→算Kappa→画三张对比图”的流程确认无误再谈调参。希望这些步骤能帮你少踩几个坑顺利把这个系统跑出理想的变化图。本文还有配套的精品资源点击获取