简介:面向机器学习、遥感图像处理以及计算机相关专业学生,非常适合课程设计、期末大作业与毕业设计阶段快速搭建遥感分类实验。代码经过严格调试,下载后安装依赖即可运行,整体流程覆盖数据准备、特征提取、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), dtype=np.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_size=0.3, # 三成样本做测试 random_state=42, # 固定随机种子,保证可复现 stratify=y # 按类别比例分层采样 ) print(f"训练样本: {X_train.shape[0]}, 测试样本: {X_test.shape[0]}") print(f"类别分布: {np.bincount(y_train)}")stratify=y这个参数值得单独说。遥感影像里地物分布天然不均衡——比如一幅影像里农田占了 60%,水体占 5%,如果不管三七二十一随机切分,测试集里可能一个水体样本都没有,汇报的时候却说准确率 95%,这是典型的自欺欺人。stratify会让训练集和测试集里的类别比例保持一致,模型评估才有说服力。
random_state=42作用不是迷信,是保证你在调参过程中每次跑出来的实验可比。没有固定随机种子,一次跑出 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(误分类惩罚系数)和gamma(RBF 核的宽度)。很多同学在这两个参数上全靠猜,结果要么过拟合要么欠拟合。往下看具体怎么处理。
3.2 训练脚本的骨架:从特征到 pickle
rs_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( kernel='rbf', # 径向基核函数 C=10.0, # 误分类惩罚系数 gamma='scale', # 自动按 1/(n_features * X.var()) 计算 class_weight='balanced', # 类别权重自动调整 probability=True # 开启概率输出,可视化时有用 ) clf.fit(X_train, y_train) y_pred = clf.predict(X_test) print(f"Accuracy: {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)C=10.0和gamma='scale'是两个关键设定。C越大,模型对训练样本的误分类惩罚越重,决策边界会尽量把所有训练点都分对,代价是过拟合;C越小,边界越平滑,但可能欠拟合。gamma='scale'是 sklearn 的默认策略,它根据特征数量自动计算 gamma 值,避免因为特征维度差异导致「手设 gamma 直接崩」。
class_weight='balanced'是遥感分类里的保命设置。前面说过遥感地物分布不均衡,如果不做任何处理,SVM 会偏向样本多的类别。设置成balanced后,sklearn 会自动给少数类更高的权重,模型会更关注水体这种占比小但重要的类别。
probability=True这个参数容易忽略,但开了之后才能用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), size=min(5000, len(X_train)), replace=False) X_sub = X_train[idx] y_sub = y_train[idx] grid = GridSearchCV( SVC(class_weight='balanced'), param_grid, cv=5, # 五折交叉验证 scoring='f1_weighted', # 用加权 F1 而不是 accuracy n_jobs=-1, # 多核并行 verbose=1 ) grid.fit(X_sub, y_sub) print(f"最优参数: {grid.best_params_}") print(f"最优分数: {grid.best_score_:.4f}")注意这里对训练数据做了子采样。全量数据可能几十万个样本,网格搜索要跑 16 组参数乘以 5 折,那就是 80 次训练,在线性核上都够呛,更别说 RBF。我一般会先抽 5000 个样本做粗调,锁定一个参数区间,再用全量数据在最优参数附近做一次确认训练。
scoring='f1_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), dtype=np.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, cmap='tab20') plt.colorbar() plt.title('SVM Classification Result') plt.axis('off') plt.savefig('classification_result.png', dpi=300)这段代码有一个容易忽略的隐患:训练时喂给模型的特征是经过筛选的波段,而这里直接用了全部波段。如果训练时波段数和这里的bands不一致,predict会直接报维度错误。我拆过好几个翻车案例,都是在可视化脚本里忘了对齐训练时的特征维度。
plt.imshow(pred_img)用整数值直接画颜色是可行的,但最好确认类别编号和颜色映射的对应关系。遥感分类图里常见的配色习惯是植被用绿色、水体用蓝色、建设用地用红色/灰色,这样导师一眼就能看出分类是否合理。
4.3 可视化验证的侧重点:不看准确率看边界
分类结果图出来后,评审老师最关注的不一定是整体准确率,而是边界质量和斑块连续性。SVM 逐像素分类的典型问题是「椒盐效应」——单个像素被独立分类,结果图上出现大量孤立噪点。这是逐像素分类的通病,不是因为模型坏了,而是没有考虑空间上下文信息。
data_plot.py如果做的是样本分布图或特征散点图,重点应该看不同类别在特征空间有没有重叠。画一个前两个主成分或两个波段的散点图,可以直观判断 SVM 用线性边界够不够,还是必须上 RBF 核。这类图在答辩里很加分,因为它直接呼应了「为什么选 RBF-SVM」这个必问题。
5. 避坑记录:遥感图像分类的四个常见坑
这一章是血泪经验的集中汇总。以下四个问题我拆项目时反复遇到,每个都是「现象 → 原因 → 解决」的结构,建议对照着排查。
5.1 坑一:pickle 模型跨机器加载直接报错
现象:在自己电脑上训练好模型、保存 pickle 后,换一台电脑或换一个 Python 环境加载,直接报AttributeError: Can't 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_weight='balanced',评估时用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_estimators=200, max_depth=10, min_samples_leaf=3, class_weight='balanced', random_state=42 ) rfc.fit(X_train_scaled, y_train)一个实际操作建议是跑三折交叉验证而不是一次性切分。单次切分受随机性影响大,可能这次 0.91、下次 0.86。交叉验证会跑多次取平均,稳定性好得多,而且不需要额外写循环代码——cross_val_score一行就能搞定。
当初我拿到第一份遥感分类项目的时候,就是照着源码先跑通,再对比 SVM 和随机森林的结果。跟导师汇报时被问到的第一个问题就是「为什么用 SVM 而不是随机森林」,因为做了对比实验,能直接回答「在小样本下 RBF-SVM 的边界更干净,随机森林在样本量上去后 F1 高约 2 个百分点」——这种对比才是有说服力的。
从那以后我每次跑机器学习分类项目,都会强制走一遍「先看类别分布、再做标准化、最后交叉验证」这三步,不管数据集多大多小。这些动作在源码里可能只是一行参数,但少了它们,结果就是另一种味道了。希望这份拆解帮你在遥感分类项目上少踩几个坑,把时间花在真正有意义的模型分析上。
本文还有配套的精品资源,点击获取