2026/10/12 13:10:16

卫星云图识别CV大作业:从CSV到CNN全链路源码与避坑指南

卫星云图识别CV大作业:从CSV到CNN全链路源码与避坑指南 简介一份面向计算机视觉课程的大作业解决方案聚焦卫星云层图像的理解与识别。项目以Python为主要实现语言附带实验报告PDF、答辩PPT和README说明适合高校计科、人工智能、自动化等专业的学生用于课程设计、毕业设计或作为入门进阶的项目范本。代码经过测试并成功运行上传前已验证可放心用于学习二次开发。包体共140个文件约62.33MB其中包含70个.py源码、44个.pyc编译文件、6个shell脚本、5个.csv数据文件、若干yaml配置文件等覆盖模型训练、测试、结果展示等环节。目录结构完整结果图片、数据文件和说明文档均已整理好便于读者快速定位关键模块。目前已有239人学习下载资源中还包含详细的实验报告与讲解PPT适合作为项目初期的立项演示和答辩参考也支持在现有代码基础上修改实现其他功能。1. 卫星云层图像的理解与识别一份能直接跑通的计算机视觉大作业源码卫星云层图像的理解与识别听起来像遥感专业才会碰的方向实际上它是计算机视觉大作业里很典型的「小样本图像分类」题目数据量不大、类别明确、要从数据读到模型训练再到实验报告全链路交付。这份资源把整条链路都备齐了——Python 源码、700 张训练样本的 CSV、四个独立测试集、实验报告 PDF、答辩 PPT 和结果图下载解压后就能对照自己的课程设计或者毕设初期演示来复用。我按照自己拆这类项目的习惯把文章分成「看懂 CSV 数据结构 → 双路线建模 → 小样本训练技巧 → 避坑 → 答辩级展示」五段新手能一步步跟着复现熟手可以直接跳到参数和踩坑部分。2. 先把 CSV 看懂700 张云图的字段结构与预处理2.1 先回答最朴素的问题CSV 里装的到底是图像还是特征拿到train_700.ver0.csv这样的文件第一反应往往是懵的说好是图像识别怎么给的是电子表格这其实是这类课程项目的常见组织方式——每行是一个样本前面若干列是图像像素展开或者手工特征最后一列是标签。先用 pandas 打开看结构不要急着训练import pandas as pd df pd.read_csv(train_700.ver0.csv) print(shape:, df.shape) print(列名前10个:, df.columns[:10].tolist()) print(最后两列:, df.iloc[0, -2:].tolist()) print(标签分布:\n, df.iloc[:, -1].value_counts())这段代码回答三个问题样本量是多少、每行有多少列、标签列长什么样。shape 里如果是(700, 3073)说明 700 张图、每行 3072 个像素值加 1 个标签如果列数不能直接开平方大概率是灰度图加通道数或者前面还有若干特征列。打印最后一列的value_counts()能看到标签是stratus、cumulus、cirrus、ocean之类字符串还是已经被编码成 0/1/2/3后面建模时处理方式不一样。接下来还要确认像素值域。云图这种数据有的题目已经把像素归一化到 0~1有的还是相机原始 0~255。判断方法很简单import numpy as np pixel_part df.iloc[:, :-1].values.astype(np.float32) print(像素最小值:, pixel_part.min(), 最大值:, pixel_part.max()) print(单行像素总数:, pixel_part.shape[1])常见做法是根据值域决定要不要除以 255。如果打印出来最大值在 255 附近就除以 255如果已经在 1 以内就别画蛇添足。这里有个很容易踩的坑在后面单列先记住归一化的判断依据是实际值域不是文件名。2.2 像素归一化与训练/验证集划分种子固定比玄学调参重要小数据集最怕的是验证集划分不稳定。700 张样本随便切一次和再切一次验证集上的准确率能差三到五个百分点这不是模型问题是划分问题。所以我会固定随机种子同时用分层划分保证每个类别在训练集和验证集里的比例一致from sklearn.model_selection import train_test_split import numpy as np X df.iloc[:, :-1].values.astype(np.float32) y df.iloc[:, -1].values # 值域判断后做归一化 if X.max() 1.0: X X / 255.0 X_train, X_val, y_train, y_val train_test_split( X, y, test_size0.2, stratifyy, random_state42 ) print(训练集:, X_train.shape, 验证集:, X_val.shape)stratifyy是必须写的尤其当某个类别只有一百来张时不做分层划分很可能验证集里直接缺一个类后面classification_report会报警告答辩时也很难看。random_state42是为了可复现——实验报告里写「准确率 91.5%」的前提是别人按同样代码能重新跑出这个数种子不定复现就是空话。2.3 四个测试集的分工model_test_1 到 4 为什么要分开处理资源里给了model_test_1.csv、model_test_3.csv、model_test_4.csv和test.csv很多初学者习惯把它们 concat 成一个大文件再预测这是一个后面会详细说的坑。这里先把正确读法写出来import pandas as pd test_files [model_test_1.csv, model_test_3.csv, model_test_4.csv, test.csv] for name in test_files: tdf pd.read_csv(name) print(name, shape:, tdf.shape) print( 前两行最后两列:, tdf.iloc[:2, -2:].values.tolist())为什么要逐个看而不是直接拼因为这几个测试集很可能来自不同拍摄条件或不同区域分布不一致。逐个查看能确认每个文件的行数、列数是否一致以及有没有缺失标签列。实际预测时也应该分别输出submission_1.csv、submission_3.csv这类独立文件不要提前合并否则后期发现问题想拆分就得重跑。3. 建模双路线先跑通机器学习基线再用 CNN 提点3.1 路线一随机森林/SVM 做基线十分钟拿到第一个可信分数图像识别大作业如果直接上 CNN报告里缺少对照实验答辩容易被问「你怎么证明 CNN 比传统方法好」。所以我的习惯是先做一个传统机器学习基线哪怕只是为了支撑报告里的对比表格。700 个样本、几千维特征随机森林在笔记本上几分钟就能跑完from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import accuracy_score, classification_report from sklearn.preprocessing import StandardScaler scaler StandardScaler().fit(X_train) X_train_s scaler.transform(X_train) X_val_s scaler.transform(X_val) clf RandomForestClassifier( n_estimators200, max_depth16, random_state42, n_jobs-1 ) clf.fit(X_train_s, y_train) pred clf.predict(X_val_s) acc accuracy_score(y_val, pred) print(RandomForest val acc:, round(acc, 4)) print(classification_report(y_val, pred))参数上n_estimators200对 700 样本足够n_jobs-1让所有 CPU 核并行max_depth16防止树太深在小数据上完全记住训练集。这里用了StandardScaler虽然随机森林对尺度不敏感但如果你同时做了 SVM 对照scaler 可以复用一套代码出两组结果。SVM 也是一样sklearn.svm.SVC换上 RBF 核就能跑。实践中随机森林在这个量级的数据上往往已经能到 85% 以上的准确率这就是报告里「传统方法天花板」的参照线。后面 CNN 如果只高出两三个点也能说明问题。3.2 路线二小型 CNN 结构设计别一上来就搬 ResNet700 张图训 ResNet 大概率过拟合到怀疑人生。课程大作业的正解是设计一个参数量在几十万级别的小网络三层卷积加两层全连接既有深度学习的内容可写又能在普通笔记本上用 CPU 几分钟训完一个 epoch。这里假设数据能还原成 3 通道 32×32 的图像如果实际是灰度图把第一层的nn.Conv2d(3, ...)改成 1 即可import torch import torch.nn as nn class SmallCloudCNN(nn.Module): def __init__(self, num_classes4): super().__init__() self.features nn.Sequential( nn.Conv2d(3, 16, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), # 32 - 16 nn.Conv2d(16, 32, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), # 16 - 8 nn.Conv2d(32, 64, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), # 8 - 4 ) self.classifier nn.Sequential( nn.Flatten(), nn.Linear(64 * 4 * 4, 128), nn.ReLU(), nn.Dropout(0.3), nn.Linear(128, num_classes), ) def forward(self, x): return self.classifier(self.features(x))注释里标注了每一层池化后的特征图尺寸变化32×32 经过三次MaxPool2d(2)变成 4×464 * 4 * 4 1024就是全连接层的输入维度。如果你的图像是 64×64这个数要改成64 * 8 * 8很多人的代码一跑就报错就是没算这一步。Dropout 放 0.3对小网络来说是个比较稳妥的值太大模型学不动太小起不到正则作用。3.3 训练循环与评估交叉熵、Adam、保存最佳模型训练部分用最标准的 PyTorch 写法。关键点是验证集上表现最好的模型要单独保存而不是用最后一个 epoch 的参数。大作业报告里的最终指标应该来自「最佳验证模型」import torch.optim as optim from sklearn.preprocessing import LabelEncoder # 标签转索引 le LabelEncoder() y_train_idx le.fit_transform(y_train) y_val_idx le.transform(y_val) X_train_img torch.tensor(X_train).reshape(-1, 3, 32, 32) X_val_img torch.tensor(X_val).reshape(-1, 3, 32, 32) y_train_t torch.tensor(y_train_idx) y_val_t torch.tensor(y_val_idx) model SmallCloudCNN(num_classeslen(le.classes_)) optimizer optim.Adam(model.parameters(), lr1e-3) criterion nn.CrossEntropyLoss() best_acc 0.0 best_state None for epoch in range(30): model.train() for i in range(0, len(X_train_img), 32): xb X_train_img[i:i32] yb y_train_t[i:i32] optimizer.zero_grad() loss criterion(model(xb), yb) loss.backward() optimizer.step() # 验证 model.eval() with torch.no_grad(): pred model(X_val_img).argmax(dim1) val_acc (pred y_val_t).float().mean().item() if val_acc best_acc: best_acc val_acc best_state {k: v.clone() for k, v in model.state_dict().items()} print(fepoch {epoch1:02d} | val_acc {val_acc:.4f} | best {best_acc:.4f}) model.load_state_dict(best_state) print(best val acc:, round(best_acc, 4))batch_size32对 700 样本合适lr1e-3是 Adam 的默认推荐值如果发现验证集震荡再考虑降到 3e-4。best_state的深拷贝很重要PyTorch 里model.state_dict()返回的是引用直接赋值会在后续优化器 step 时被改掉很多人的最佳模型指标莫名其妙变差就是栽在这里。4. 小样本训练的常用技巧数据增强与学习率调度4.1 数据增强把 700 张变成 1400 张有效样本700 张图对 CNN 来说确实紧张数据增强是性价比最高的正则手段。但云图和猫狗图不一样翻转、旋转是有物理意义的——云的朝向本身没有固定方向所以水平翻转和小幅旋转都是安全的。这里用torchvision.transforms实现注意训练集和验证集的 transform 必须分开from torchvision import transforms train_tf transforms.Compose([ transforms.RandomHorizontalFlip(p0.5), transforms.RandomRotation(degrees10), transforms.ColorJitter(brightness0.2, contrast0.2), transforms.ToTensor(), ]) val_tf transforms.Compose([ transforms.ToTensor(), ])RandomRotation(10)是血泪教训换来的参数——超过 15 度云图纹理会被扭曲模型反而学不到稳定特征。ColorJitter 的 brightness 和 contrast 给 0.2 就好云图是遥感数据颜色偏移太大等同于引入噪声。增强后的数据不需要单独存储PyTorch 的Dataset每次迭代时实时做变换代码上只是在取样本时多套一层 transform。实际操作时我会把验证集准确率作为增强是否有效的唯一标准增强前 88%增强后 91%说明有效如果增强后反而下降先检查旋转角度是不是太大了。4.2 学习率调度与早停对抗小数据上的验证集震荡小数据集训练有个典型现象验证集准确率在某个值附近来回跳不上升也不崩。这时候硬 train 只是浪费机器时间正确做法是学习率调度加早停。我一般用ReduceLROnPlateau它在验证集 loss 连续几个 epoch 不降时自动把学习率减半非常省心import copy scheduler optim.lr_scheduler.ReduceLROnPlateau( optimizer, modemin, factor0.5, patience5 ) best_val_loss float(inf) bad_epochs 0 max_bad 10 for epoch in range(60): train_loss run_train_one_epoch() # 复用上一节的训练循环 val_loss, val_acc validate() scheduler.step(val_loss) if val_loss best_val_loss - 1e-4: best_val_loss val_loss best_state copy.deepcopy(model.state_dict()) bad_epochs 0 else: bad_epochs 1 if bad_epochs max_bad: print(fearly stop at epoch {epoch1}) breakpatience5的意思是验证集 loss 连续 5 个 epoch 不创新低就降学习率max_bad10是早停阈值。注意 loss 的「新低」判断留了1e-4的容差避免浮点噪声触发误判。把这两个参数写进实验报告老师一眼就知道你不是只会model.fit()。4.3 从结果图反推复刻验证集评估图报告直接可用下载包里带了一张结果图如果你要写自己的报告建议按同样的方式生成三类图训练/验证 loss 曲线、验证集准确率曲线、混淆矩阵热力图。这些图是答辩时的主力素材。画图脚本可以复用下面这个框架import matplotlib.pyplot as plt import seaborn as sns from sklearn.metrics import confusion_matrix # 假设已经收集了每个 epoch 的 train_loss_list 和 val_acc_list plt.figure(figsize(8, 5)) plt.plot(train_loss_list, labeltrain loss) plt.plot(val_acc_list, labelval acc) plt.xlabel(epoch) plt.ylabel(loss / acc) plt.legend() plt.tight_layout() plt.savefig(result_curve.png, dpi200)dpi200是给论文和 PPT 用的最低标准默认的 100 会糊。混淆矩阵用 seaborn 画annotTrue把数字直接标在格子里云图类别之间哪些容易混一眼就能看出——比如卷云和层云经常互相误判这正是答辩时可以主动讲的故事。5. 卫星云图识别避坑指南五个真实踩过的坑5.1 reshape 报错最容易在第一步翻车现象X_train.reshape(-1, 3, 32, 32)报cannot reshape array of size 3072 into shape (3,32,32)。原因单行像素总数 3072 可以拆成 3×32×32但你没有确认它到底是 3 通道 32×32还是灰度图 1×64×48或者前面还混着几列特征。解决先打印pixel_part.shape[1]然后做因式分解判断。3072 优先试(3, 32, 32)如果数据本身是灰度就用(1, H, W)后在第一个卷积层把in_channels改成 1。永远不要盲目猜 shape。5.2 验证集指标忽高忽低同一份代码跑两次结果不一样现象第一次跑 val acc 91%第二次跑只有 87%模型结构一点没动。原因train_test_split没固定random_state或者 PyTorch 的 DataLoader 没设torch.manual_seed。700 样本的验证集只有 140 张随机波动被放大到肉眼可见。解决在文件最开头加一段固定种子的代码import torch import numpy as np def set_seed(seed42): torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) np.random.seed(seed) set_seed(42)5.3 标签字典排序错位测试集输出全乱现象训练时用sorted(set(y))构建字符串到索引的映射预测测试集后输出类别感觉「差了一位」准确率暴跌。原因数据预处理脚本和预测脚本各写了一次字典构建逻辑类别顺序在不同版本 Python 或不同环境下排序不稳定或者训练时用了LabelEncoder预测时忘了复用同一个实例。解决训练完成后把标签字典存成文件预测时只加载不重建import json label_dict {label: idx for idx, label in enumerate(le.classes_)} with open(label_dict.json, w, encodingutf-8) as f: json.dump(label_dict, f, ensure_asciiFalse)推理脚本里json.load读回来反查时用字典的 value 找 label一劳永逸。5.4 数据增强一把梭训练集 loss 比验证集还高现象加了旋转 30 度、亮度抖动 0.5、随机裁剪之后训练集 loss 居高不下验证集也跟着崩。原因增强强度过大把云图扭曲成了模型不认识的样子。增强的目的是引入多样性不是制造噪声样本。解决从最轻的组合开始——水平翻转加 Rotate 10 度跑一个 epoch 看训练集 loss 能不能正常下降能下降再加 ColorJitter。每一步都以验证集指标为判断依据不要一次性堆满。5.5 四个测试集合并预测提交后准确率参差不齐现象把model_test_1.csv、model_test_3.csv、model_test_4.csv和test.csv拼成一个表预测提交后某些文件准确率高、某些文件低得离奇。原因这些测试集可能来自不同的卫星、不同时间段或不同地理区域数据分布不一致。合并后模型在分布内的测试集上表现正常分布外的就是靠猜。解决分别读、分别预测、分别保存输出。报告里也分开写每个测试集的结果并讨论差异原因——这反而是答辩加分项说明你做了数据分布分析而不只是跑了个模型。6. 从 CSV 重建云图生成答辩级可视化素材的最后一公里6.1 把一行像素还原成一张能看的云图这是很多人在答辩前才想起的事整个项目跑完了报告里连一张原始输入图都没有。其实从 CSV 还原图像只需要十几行代码import matplotlib.pyplot as plt sample X_train[0].reshape(3, 32, 32).transpose(1, 2, 0) # CHW - HWC plt.imshow(sample) plt.title(str(y_train[0])) plt.axis(off) plt.savefig(sample_cloud.png, dpi200)transpose(1, 2, 0)是因为 PyTorch 用 CHW 顺序matplotlib要的是 HWC不转的话图像颜色通道会错乱。建议从训练集中挑 3~4 张有代表性的图外加 2 张模型预测失败的图做成对比图放进 PPT比任何技术描述都有说服力。答辩时如果被问到「数据长什么样」直接把这张图调出来比空口解释强得多。6.2 PPT 与实验报告的结构对齐实验报告 PDF 和报告 PPT 是这份资源里容易被忽略但非常值钱的部分。我翻这类报告时发现真正得分高的结构就四块按这个顺序写不会乱任务描述与数据说明、方法传统方法对照加 CNN 细节、实验参数、曲线、混淆矩阵、失败分析、结论。表格里每一行都要能对应到一个你跑过的实验报告里的准确率不要写「很高」「效果不错」这种话直接写数字加模型名分数自然就上去了。训练脚本、CSV、四个测试集、实验报告和 PPT 我都整理在下载包里拿到手先按第 2 章的脚本跑一遍数据检查再用第 3 章的 CNN 训练脚本出一版自己的结果。整个过程里我吃过最多的亏就是没固定随机种子和乱猜 reshape 维度现在每次拿到新的 CSV 图像数据我都会强制先写一段数据检查再碰模型。希望帮到你。本文还有配套的精品资源点击获取