news 2026/10/12 13:10:12

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
卫星云图识别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_size=0.2, stratify=y, random_state=42 ) print("训练集:", X_train.shape, "验证集:", X_val.shape)

stratify=y是必须写的,尤其当某个类别只有一百来张时,不做分层划分很可能验证集里直接缺一个类,后面classification_report会报警告,答辩时也很难看。random_state=42是为了可复现——实验报告里写「准确率 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_estimators=200, max_depth=16, random_state=42, 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_estimators=200对 700 样本足够,n_jobs=-1让所有 CPU 核并行,max_depth=16防止树太深在小数据上完全记住训练集。这里用了StandardScaler,虽然随机森林对尺度不敏感,但如果你同时做了 SVM 对照,scaler 可以复用,一套代码出两组结果。

SVM 也是一样,sklearn.svm.SVC换上 RBF 核就能跑。实践中随机森林在这个量级的数据上往往已经能到 85% 以上的准确率,这就是报告里「传统方法天花板」的参照线。后面 CNN 如果只高出两三个点,也能说明问题。

3.2 路线二:小型 CNN 结构设计,别一上来就搬 ResNet

700 张图训 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_classes=4): super().__init__() self.features = nn.Sequential( nn.Conv2d(3, 16, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2), # 32 -> 16 nn.Conv2d(16, 32, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2), # 16 -> 8 nn.Conv2d(32, 64, 3, padding=1), 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×4,64 * 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_classes=len(le.classes_)) optimizer = optim.Adam(model.parameters(), lr=1e-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:i+32] yb = y_train_t[i:i+32] optimizer.zero_grad() loss = criterion(model(xb), yb) loss.backward() optimizer.step() # 验证 model.eval() with torch.no_grad(): pred = model(X_val_img).argmax(dim=1) 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(f"epoch {epoch+1: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_size=32对 700 样本合适;lr=1e-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(p=0.5), transforms.RandomRotation(degrees=10), transforms.ColorJitter(brightness=0.2, contrast=0.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, mode="min", factor=0.5, patience=5 ) 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(f"early stop at epoch {epoch+1}") break

patience=5的意思是验证集 loss 连续 5 个 epoch 不创新低就降学习率,max_bad=10是早停阈值。注意 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, label="train loss") plt.plot(val_acc_list, label="val acc") plt.xlabel("epoch") plt.ylabel("loss / acc") plt.legend() plt.tight_layout() plt.savefig("result_curve.png", dpi=200)

dpi=200是给论文和 PPT 用的最低标准,默认的 100 会糊。混淆矩阵用 seaborn 画,annot=True把数字直接标在格子里,云图类别之间哪些容易混一眼就能看出——比如卷云和层云经常互相误判,这正是答辩时可以主动讲的故事。

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(seed=42): 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", encoding="utf-8") as f: json.dump(label_dict, f, ensure_ascii=False)

推理脚本里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", dpi=200)

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 图像数据,我都会强制先写一段数据检查再碰模型。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/12 13:07:40

疲劳驾驶多模态数据集解析:对齐、标签与边缘部署实战

简介&#xff1a;本资源是一个面向深度学习初学者的疲劳驾驶监测图像数据集&#xff0c;适用于人工智能、计算机视觉方向的入门实践与小项目开发&#xff0c;特别适合以闭眼、打哈欠等典型疲劳状态为识别目标的二分类或关键点检测任务。压缩包共2000个文件&#xff0c;包含2915…

作者头像 李华
网站建设 2026/10/12 13:07:36

Python多线程队列消费实战:threading与queue构建高效并发模型

1. 为什么单线程处理队列总是差点意思做过数据采集、日志处理或者批量任务调度的朋友大概率都遇到过这样的场景&#xff1a;一个生产者往队列里塞数据&#xff0c;一个消费者从队列里取数据慢慢处理。刚开始数据量小&#xff0c;跑得挺顺畅&#xff0c;等到数据量一上来&#x…

作者头像 李华
网站建设 2026/10/12 13:06:50

Spring JDBC声明式事务管理:核心原理、传播行为与失效场景全解析

1. 为什么声明式事务是Spring JDBC里最值得吃透的一环刚接触Spring JDBC那会儿&#xff0c;我对事务的理解还停留在“手动开、手动提交、出错回滚”的阶段。每次写业务方法&#xff0c;开头先拿Connection&#xff0c;中间一堆try-catch&#xff0c;最后在finally里小心翼翼地c…

作者头像 李华
网站建设 2026/10/12 13:06:44

JMeter 测试 WebService 接口:WSDL 解析与 SOAP 请求实战

每次接到接口测试任务&#xff0c;我第一件事就是问&#xff1a;这个接口是 REST 风格的还是老式 WebService&#xff1f;如果是后者&#xff0c;多半会收到一个 .wsdl 文件&#xff0c;打开满屏 XML&#xff0c;既没有文档说明也没有现成的请求示例。别慌&#xff0c;用 JMete…

作者头像 李华
网站建设 2026/10/12 13:06:07

医学图像分割开题报告怎么写?从问题拆解到预实验跑通

简介&#xff1a;一份基于深度学习的医学图像分割开题报告&#xff0c;以颈部动脉斑块分割为研究对象&#xff0c;面向医学影像分析方向的研究生、本科毕业设计及科研人员。内容从心血管疾病早期诊断切入&#xff0c;系统阐述了动脉粥样硬化斑块分割的临床意义&#xff0c;梳理…

作者头像 李华