简介面向机器学习、遥感图像处理以及计算机相关专业学生非常适合课程设计、期末大作业与毕业设计阶段快速搭建遥感分类实验。代码经过严格调试下载后安装依赖即可运行整体流程覆盖数据准备、特征提取、SVM模型训练、模型保存和分类结果可视化能帮助学习者快速理解完整的算法落地链路。压缩包共13个文件其中6个Python脚本为核心模块另有2个pyc编译文件、2个JSON配置、1个pickle模型文件以及工作区配置和README说明整体约14KB轻量精简便于阅读和二次修改。目前已有305人浏览学习。资源不但包含训练和预测主体还提供多组绘图脚本可直观对比不同特征或参数下的分类效果对已有一定Python与机器学习基础的读者可以按README指引替换自己的遥感数据集梳理数据流与模型调优思路是一份实用的入门到进阶参考。1. 遥感图像分类找 SVM这份源码解决的是「最后一公里」机器学习的遥感图像分类听起来是个高大上的课题但真上手做课程设计或期末大作业时卡住的往往不是理论而是「拿到一张遥感影像后怎么把它变成模型能吃的特征矩阵训练完怎么把结果画出来」。这份基于机器学习的遥感图像分类模型源码.zip就是冲着这个痛点来的压缩包里是一个完整可跑的 SVM 分类项目训练好的模型RS_SVM-1.pickle、数据处理脚本rs_mod_1.py、可视化脚本rs_plot_2.py等和结果文件result.json都齐了。如果你是计科、人工智能、大数据、数学、电子信息方向的学生正在做遥感分类相关课程设计、期末大作业或者毕设这套代码能直接照着改不用从零搭流水线。2. 跑通这套 SVM 遥感分类流水线文件结构与 rs_mod_1 的数据管线拿到 zip 第一步不是看代码而是先搞清楚这些文件谁是谁。项目里rs_mod系列是核心处理脚本rs_plot系列是可视化脚本RS_SVM-1.pickle是已经训练好的 SVM 模型data_plot.py和result.json是结果输出。顺序上一般先跑rs_mod_1.py做数据准备和特征提取再跑rs_mod_2.py训练模型最后用rs_plot系列出图。2.1 先看文件布局rs_mod、rs_plot 和 pickle 各自管什么打开 zip 后第一眼看到的是rs_mod_1.py、rs_mod_2.py、rs_mod_3.py三个脚本。rs_mod_1.py通常负责最脏最累的活读取原始影像、抽取像素特征、组装成训练用的特征矩阵。rs_mod_2.py负责训练 SVM 并把模型存成 pickle。rs_mod_3.py是在前两个基础上做扩展的版本。rs_plot_2.py和rs_plot_3.py是配套的可视化脚本把分类结果、混淆矩阵、样本分布画出来这类脚本在结题报告和答辩 PPT 里基本是刚需。workspace.code-workspace和.vscode/settings.json是 VS Code 的工程配置说明作者是在一个统一工作区里做的调试这对我这种喜欢用 IDE 跑 Python 的人是加分项。处理遥感影像不是读一张图那么简单。常见做法是先做波段拆分再按像素或超像素提取特征最后拼成(样本数, 特征维度)的矩阵。SVM 训练本身不关心你的特征是光谱值还是纹理特征它只认矩阵形状和标签。2.2 特征矩阵怎么构建波段采样与 label 对齐遥感影像和普通照片最大的差别是多波段。一张多光谱影像可能有 4 个、8 个甚至更多波段每个波段代表一个光谱通道。提取特征时如果不做波段选择直接把所有波段铺平特征维度会爆炸SVM 训练会变得很慢而且容易过拟合。rs_mod_1.py里常见的数据管线是这样的先把影像每个波段读成二维数组然后对每个像素取出它在各个波段上的值组合成一个特征向量。如果影像尺寸是 512x512、有 4 个波段那每个像素就是一个 4 维向量整幅影像就是 262144 个样本。标签那边要单独处理。分类模型的标签不是自动从影像里来的而是需要一份标注好的样本。常见做法是准备一个标签栅格或标注文件里面每个位置对应影像里相应像素的类别编号。# 典型的数据组装逻辑rs_mod_1.py 的思路还原 import numpy as np from osgeo import gdal def load_image_and_labels(image_path, label_path): # 读取多光谱影像 img gdal.Open(image_path) bands img.RasterCount rows img.RasterYSize cols img.RasterXSize # 按波段读出数据堆叠成 (rows, cols, bands) img_data np.zeros((rows, cols, bands), dtypenp.float32) for b in range(bands): band img.GetRasterBand(b 1) img_data[:, :, b] band.ReadAsArray() # 读取标签图通常每个像素一个类别编号 lbl gdal.Open(label_path) label_data lbl.GetRasterBand(1).ReadAsArray().astype(np.int32) # 只取有标签的像素避免用背景类污染训练集 valid_mask label_data 0 X img_data[valid_mask].reshape(-1, bands) y label_data[valid_mask].reshape(-1) return X, y这个片段的重点是valid_mask的过滤。很多刚接触遥感分类的同学会把背景像素标签为 0 或 255直接丢进训练集导致模型学出一堆「背景模式」真正的地物类别反而分不开。这里的逻辑是把没有有效标签的像素全部剔除只让有真实地物标签的样本进入训练。另一个关键点是np.float32。遥感影像原始数据经常是 uint16 甚至更高位深如果不转 float 直接算后面做标准化和 SVM 计算时容易出精度问题。SVM 对特征尺度敏感这一步属于让数据「吃起来正常」的预处理。2.3 样本拆分的顺序先洗牌再分集特征矩阵准备好后下一步是把样本拆成训练集和测试集。这里有个容易踩的顺序问题先洗牌再切分。如果影像里同类地物在空间上聚集不洗牌直接按顺序切会导致训练集和测试集分布完全不同模型的分数虚高或虚低。# 训练/测试集划分注意 shuffle 和 stratify 的作用 from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, # 三成样本做测试 random_state42, # 固定随机种子保证可复现 stratifyy # 按类别比例分层采样 ) print(f训练样本: {X_train.shape[0]}, 测试样本: {X_test.shape[0]}) print(f类别分布: {np.bincount(y_train)})stratifyy这个参数值得单独说。遥感影像里地物分布天然不均衡——比如一幅影像里农田占了 60%水体占 5%如果不管三七二十一随机切分测试集里可能一个水体样本都没有汇报的时候却说准确率 95%这是典型的自欺欺人。stratify会让训练集和测试集里的类别比例保持一致模型评估才有说服力。random_state42作用不是迷信是保证你在调参过程中每次跑出来的实验可比。没有固定随机种子一次跑出 91%下一次跑出 83%你根本分不清是参数改好了还是运气改好了。2.4 运行时的目录约定与 README 的用法压缩包里带了README.md别跳过它。我拆过不少项目发现 README 里通常写着最关键的运行前提Python 版本、依赖库、数据文件的相对路径、先运行哪个脚本。另外注意到项目里有__pycache__和.pyc文件说明这套代码在 Python 3.10 环境下跑过。如果你本地是 3.8 或 3.11建议先建一个虚拟环境把依赖装齐了再运行。requirements.txt如果没有就需要根据 import 语句手动装。# 常规依赖清单按 import 语句反推 pip install numpy scikit-learn matplotlib gdal scipy注意gdal的安装经常让人头疼Windows 下的pip install gdal容易报错。常见做法是用 conda 装conda install gdal或者用rasterio代替。如果rs_mod_1.py里用的是rasterio那就直接pip install rasterio省事得多。我是建议先看rs_mod_1.py最上面几个 import 语句再决定装什么不要无脑复制命令。3. 训练与调参RS_SVM-1.pickle 的诞生过程RS_SVM-1.pickle是项目里最有价值的实物资产之一。它说明作者已经跑通过一次完整的 SVM 训练并且把模型序列化保存下来了。这份 pickle 的意义不只是省去重新训练的时间更重要的是它给了你一个「参考答案」——你可以直接加载它来预测新影像也可以把它当基准线对比自己重新训练出来的模型效果。3.1 为什么是 SVM小样本、高维特征下的稳妥选择遥感图像分类的经典算法里SVM 的地位很特殊。深度学习模型需要大量标注样本才能训得好而遥感影像的标注恰恰是最贵的——你要请懂遥感的人逐像素画地物类型。一个课程设计项目的标注量可能只有几千到几万个像素这个量级下SVM 比 CNN 更稳。SVM 的核函数让它具备了处理非线性边界的能力。遥感地物在光谱空间里往往不是线性可分的比如植被和水体在近红外波段可能有交叉。RBF 核可以把原始特征映射到更高维空间在这组源码里大概率用的就是 RBF 核。实际使用中SVM 需要的重点参数就两个C误分类惩罚系数和gammaRBF 核的宽度。很多同学在这两个参数上全靠猜结果要么过拟合要么欠拟合。往下看具体怎么处理。3.2 训练脚本的骨架从特征到 picklers_mod_2.py的核心逻辑可以还原成下面这个结构读入rs_mod_1.py生成的训练数据创建 SVM 分类器训练评估然后把模型存成 pickle 文件。# rs_mod_2.py 的训练逻辑训练 保存模型 import pickle from sklearn.svm import SVC from sklearn.metrics import accuracy_score, classification_report # 假设 X_train, X_test, y_train, y_test 已由 rs_mod_1 生成 clf SVC( kernelrbf, # 径向基核函数 C10.0, # 误分类惩罚系数 gammascale, # 自动按 1/(n_features * X.var()) 计算 class_weightbalanced, # 类别权重自动调整 probabilityTrue # 开启概率输出可视化时有用 ) clf.fit(X_train, y_train) y_pred clf.predict(X_test) print(fAccuracy: {accuracy_score(y_test, y_pred):.4f}) print(classification_report(y_test, y_pred)) # 保存模型等待后续 rs_plot 脚本加载 with open(RS_SVM-1.pickle, wb) as f: pickle.dump(clf, f)C10.0和gammascale是两个关键设定。C越大模型对训练样本的误分类惩罚越重决策边界会尽量把所有训练点都分对代价是过拟合C越小边界越平滑但可能欠拟合。gammascale是 sklearn 的默认策略它根据特征数量自动计算 gamma 值避免因为特征维度差异导致「手设 gamma 直接崩」。class_weightbalanced是遥感分类里的保命设置。前面说过遥感地物分布不均衡如果不做任何处理SVM 会偏向样本多的类别。设置成balanced后sklearn 会自动给少数类更高的权重模型会更关注水体这种占比小但重要的类别。probabilityTrue这个参数容易忽略但开了之后才能用predict_proba()输出每个像素属于各个类别的概率后续画置信度图、做阈值过滤都要靠它。不开的话你只能拿到硬分类结果不能判断模型的确定性。3.3 参数怎么调才不玄学网格搜索的正确姿势很多同学调参是「改一下 C跑一下看分数再改」。这种方式在小数据集上还能碰运气但遥感图像特征维度高、样本量大一次训练可能就要几分钟到几十分钟靠手试根本不现实。更稳的做法是网格搜索搭配交叉验证。GridSearchCV会自动遍历参数组合每一组都用交叉验证评估最后给出最优参数。考虑到训练成本网格不用铺太密C 和 gamma 各给几个数量级候选就够。# 用网格搜索找 C 和 gamma五折交叉验证 from sklearn.model_selection import GridSearchCV from sklearn.svm import SVC import numpy as np param_grid { C: [0.1, 1.0, 10.0, 100.0], gamma: [scale, 0.01, 0.1, 1.0], kernel: [rbf] } # 样本量大时建议把一部分数据抽出来做调参避免全量跑太慢 idx np.random.choice(len(X_train), sizemin(5000, len(X_train)), replaceFalse) X_sub X_train[idx] y_sub y_train[idx] grid GridSearchCV( SVC(class_weightbalanced), param_grid, cv5, # 五折交叉验证 scoringf1_weighted, # 用加权 F1 而不是 accuracy n_jobs-1, # 多核并行 verbose1 ) grid.fit(X_sub, y_sub) print(f最优参数: {grid.best_params_}) print(f最优分数: {grid.best_score_:.4f})注意这里对训练数据做了子采样。全量数据可能几十万个样本网格搜索要跑 16 组参数乘以 5 折那就是 80 次训练在线性核上都够呛更别说 RBF。我一般会先抽 5000 个样本做粗调锁定一个参数区间再用全量数据在最优参数附近做一次确认训练。scoringf1_weighted而不是accuracy这个选择是故意的。类别不平衡时accuracy 会虚高——比如水体只占 5%就算全部预测错也只损失 5 个点。F1 加权会把每个类别的精确率和召回率都算进去评价更全面。3.4 模型持久化的坑pickle 和 joblib 选哪个项目里用的是RS_SVM-1.pickle说明作者用的pickle.dump。这没问题但要提醒一句sklearn 官方推荐用joblib来保存模型因为 joblib 对大数组的序列化效率更高。SVM 的support_vectors_属性在训练后会保留支持向量的原始特征这些是多维数组pickle 处理它们会比较笨重。# 两种保存方式对比 import pickle # pickle 方式项目现有 with open(RS_SVM-1.pickle, wb) as f: pickle.dump(clf, f) # joblib 方式更推荐 from joblib import dump, load dump(clf, RS_SVM-1.joblib) loaded load(RS_SVM-1.joblib)pickle 和 joblib 最大的差别不在代码上而在文件体积和加载速度上。joblib 对大 numpy 数组做了专门优化文件更小、加载更快。但如果你要跟别人共享模型pickle 的兼容性更好一些不用额外装 joblib。这里有个很容易踩的坑跨环境加载 pickle 模型时sklearn 版本不一致会导致AttributeError或加载出来的模型行为异常。后面避坑章节会细说。4. 结果验证与可视化rs_plot、data_plot 与 result.json训练完模型只走了一半路。课程设计和毕设答辩时老师不会只看一个准确率数字他要看分类结果图、看混淆矩阵、看每一类的精确率和召回率。rs_plot_2.py、rs_plot_3.py和data_plot.py就是干这个的。result.json是评估结果的集中输出用 JSON 格式存着方便后续做图表和汇报。4.1 result.json 里该有什么accuracy、分类报告与混淆矩阵打开result.json一个合格的遥感分类项目的评估结果应该包含这样几件事整体准确率、每个类别的精确率和召回率、F1 分数、类别对应的实际名称。JSON 的好处是结构化可以拿来做对比、画图甚至直接喂给其他脚本。{ model: RS_SVM-1, accuracy: 0.9185, classes: [农田, 森林, 水体, 建设用地], classification_report: { 农田: {precision: 0.92, recall: 0.88, f1-score: 0.90], 森林: {precision: 0.95, recall: 0.97, f1-score: 0.96} } }看result.json时建议先看每个类别的召回率而不是整体准确率。整体准确率 0.92 看着不错但如果水体这一类的召回率只有 0.53说明一半以上的水体被漏分了。这在遥感应用里可能是大问题——漏检水体对防洪评估、湿地监测来说是不可接受的。4.2 绘图脚本在画什么分类结果图与混淆矩阵rs_plot_2.py这类脚本做的事情一般有两件一是加载训练好的模型对整幅影像逐像素预测输出一张分类结果彩色图二是绘制测试集上的混淆矩阵热力图。逐像素预测的流程需要把整幅影像的所有像素都过一遍模型。注意这里要做跟训练时一样的预处理——波段选择要一致、数据尺度要一致否则模型会「看不懂」新数据。# rs_plot_2.py 的思路加载模型并生成全图分类结果 import pickle import numpy as np import matplotlib.pyplot as plt # 加载训练好的模型 with open(RS_SVM-1.pickle, rb) as f: clf pickle.load(f) # 读取原始影像 from osgeo import gdal img gdal.Open(remote_sensing_image.tif) rows, cols, bands img.RasterYSize, img.RasterXSize, img.RasterCount img_data np.zeros((rows, cols, bands), dtypenp.float32) for b in range(bands): img_data[:, :, b] img.GetRasterBand(b 1).ReadAsArray() # 展平成像素 x 波段 的矩阵 flat_pixels img_data.reshape(-1, bands) # 模型预测 pred clf.predict(flat_pixels) pred_img pred.reshape(rows, cols) # 用指定颜色映射画出分类图 color_map {1: green, 2: blue, 3: gray, 4: red} plt.imshow(pred_img, cmaptab20) plt.colorbar() plt.title(SVM Classification Result) plt.axis(off) plt.savefig(classification_result.png, dpi300)这段代码有一个容易忽略的隐患训练时喂给模型的特征是经过筛选的波段而这里直接用了全部波段。如果训练时波段数和这里的bands不一致predict会直接报维度错误。我拆过好几个翻车案例都是在可视化脚本里忘了对齐训练时的特征维度。plt.imshow(pred_img)用整数值直接画颜色是可行的但最好确认类别编号和颜色映射的对应关系。遥感分类图里常见的配色习惯是植被用绿色、水体用蓝色、建设用地用红色/灰色这样导师一眼就能看出分类是否合理。4.3 可视化验证的侧重点不看准确率看边界分类结果图出来后评审老师最关注的不一定是整体准确率而是边界质量和斑块连续性。SVM 逐像素分类的典型问题是「椒盐效应」——单个像素被独立分类结果图上出现大量孤立噪点。这是逐像素分类的通病不是因为模型坏了而是没有考虑空间上下文信息。data_plot.py如果做的是样本分布图或特征散点图重点应该看不同类别在特征空间有没有重叠。画一个前两个主成分或两个波段的散点图可以直观判断 SVM 用线性边界够不够还是必须上 RBF 核。这类图在答辩里很加分因为它直接呼应了「为什么选 RBF-SVM」这个必问题。5. 避坑记录遥感图像分类的四个常见坑这一章是血泪经验的集中汇总。以下四个问题我拆项目时反复遇到每个都是「现象 → 原因 → 解决」的结构建议对照着排查。5.1 坑一pickle 模型跨机器加载直接报错现象在自己电脑上训练好模型、保存 pickle 后换一台电脑或换一个 Python 环境加载直接报AttributeError: Cant get attribute SVC或ModuleNotFoundError: No module named sklearn。原因pickle 文件里存的是对象的序列化数据加载时依赖 Python 能找到对应的类定义。如果加载环境里 skearn 版本不同类的内部结构可能变了pickle 找不到匹配的类如果环境里压根没装 sklearn那直接就废了。解决加载模型前先确认环境一致性至少保证 sklearn 主版本号一致。更稳的做法是保存模型时额外记录一份环境信息# 保存模型时附带环境信息避免以后“翻车” import pickle, sklearn, sys with open(RS_SVM-1.pickle, wb) as f: pickle.dump({ model: clf, sklearn_version: sklearn.__version__, python_version: sys.version }, f)这样加载时可以校验版本如果版本不匹配至少知道是环境问题而不是代码问题。另外我建议同步用joblib.dump存一份双保险。对毕设来说交代码的时候附一个 requirements.txt 比什么都管用。5.2 坑二不标准化特征SVM 被高数值波段带偏现象训练出来的模型准确率看着还行但查看分类报告发现某一类几乎永远预测不对或者换了数据后模型完全失效。原因遥感影像的不同波段数值范围差异很大。比如可见光波段可能是 0 到 255热红外波段可能是几千到几万。SVM 的 RBF 核计算距离时数值范围大的波段会主导距离计算导致模型实质上只看了这一两个波段其他波段成了摆设。解决训练前做特征标准化让每个波段均值为 0、方差为 1。from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test)注意fit_transform和transform的区别只能用训练集的统计量去标准化训练集和测试集不能对测试集单独做fit。严格来说StandardScaler保存下来后预测新影像时也要用同一个 scaler 转换否则训练和预测的数据分布不一致。5.3 坑三样本不平衡时看 accuracy 是自我欺骗现象训练时打印的 accuracy 有 0.95看着很漂亮但classification_report里有一两个类别的 recall 是 0 或很低。画分类结果图时发现面积小的地物类别基本没被分出来。原因某个类的样本占比过高SVM 为了最小化整体错误率直接把少数类全部牺牲掉。比如一幅影像里农田占 85%模型把所有像素都预测成农田accuracy 都已经有 85% 了看起来还没有很差。解决训练时加class_weightbalanced评估时用f1_weighted或直接看classification_report的每一行。同时检查np.bincount(y_train)的类别分布如果某一类占比小于 5%要么想办法补样本要么明确告诉老师这个类别的评估结果不具备统计意义。5.4 坑四训练时删了某个波段预测时忘了改维度现象rs_mod_1.py里做波段选择做了特征降维训练模型一切正常但到rs_plot_2.py画全图分类结果时predict直接报ValueError: X has 8 features, but SVC is expecting 6 features。原因训练代码和预测代码用了不同的特征提取逻辑。训练时可能删了相关性高的波段或者做了 PCA 降维但可视化脚本里没有应用相同的变换。解决把特征提取和变换逻辑封装成同一个函数训练和预测共用。不要在两份代码里各写一遍特征处理那样几乎必然出现「训练一套、预测另一套」的问题。def extract_features(img_data, bands_used): 统一的特征提取函数训练和预测都用它 return img_data[:, bands_used] # 训练时 X extract_features(img_data_train, [0, 1, 2, 4]) # 预测时 X_new extract_features(img_data_new, [0, 1, 2, 4])这个问题的根子是代码结构问题不是算法问题。一份干净的遥感分类代码里特征处理逻辑应该只出现一次要么写在模块里被复用要么用同一个脚本跑完整个流程。6. 进阶从 SVM 走出去把交叉验证变成默认动作如果你已经复现了这份源码接下来一件值得做的事是换个分类器跑同一份特征数据看看差异。遥感分类里随机森林是 SVM 最常见的对标方案它对特征尺度不敏感、不需要调太多参数、训练速度还快。把rs_mod_2.py里的SVC换成RandomForestClassifier其他代码几乎不用动就能做一次公平对比。from sklearn.ensemble import RandomForestClassifier # 替换掉 SVC其他流程不动 rfc RandomForestClassifier( n_estimators200, max_depth10, min_samples_leaf3, class_weightbalanced, random_state42 ) rfc.fit(X_train_scaled, y_train)一个实际操作建议是跑三折交叉验证而不是一次性切分。单次切分受随机性影响大可能这次 0.91、下次 0.86。交叉验证会跑多次取平均稳定性好得多而且不需要额外写循环代码——cross_val_score一行就能搞定。当初我拿到第一份遥感分类项目的时候就是照着源码先跑通再对比 SVM 和随机森林的结果。跟导师汇报时被问到的第一个问题就是「为什么用 SVM 而不是随机森林」因为做了对比实验能直接回答「在小样本下 RBF-SVM 的边界更干净随机森林在样本量上去后 F1 高约 2 个百分点」——这种对比才是有说服力的。从那以后我每次跑机器学习分类项目都会强制走一遍「先看类别分布、再做标准化、最后交叉验证」这三步不管数据集多大多小。这些动作在源码里可能只是一行参数但少了它们结果就是另一种味道了。希望这份拆解帮你在遥感分类项目上少踩几个坑把时间花在真正有意义的模型分析上。本文还有配套的精品资源点击获取