简介:用TensorFlow训练的CNN模型识别多云、雨天、晴天、日出等常见天气类型,测试准确率约90%,数据集共1070张标注图像。系统以Django为Web框架,前端支持图片上传与实时识别结果展示,后端使用SQLite存储识别记录和用户操作日志。内置管理员后台(地址http://127.0.0.1:8000/admin/login/,账号super,密码123456),可查看、筛选和删除历史识别记录。项目结构清晰,包含weather_check主应用、静态资源static、用户上传文件存储目录media、测试图库test_image、图像预处理模块image_handle、工具函数utils、技术文档document及依赖清单requirements.txt。开发环境适配PyCharm,数据库可用Navicat 12可视化管理,所有配置已预设,无需额外调试即可运行,适合本科毕业设计、课程大作业或轻量级AI应用快速部署。
1. 这不是“调个API就完事”的天气识别——它是一套能真正跑在你本地、看得见数据流向、改得了每一行逻辑的完整AI工程闭环
我带过六届本科生毕设,每年都有至少三四个学生拿着“基于Python的天气识别系统”当题目来找我。但翻完代码,八成是直接调用百度/阿里云的图像识别API,前端传张图、后端吐个JSON,连模型长什么样都不知道。这种项目答辩时一问训练过程就卡壳,更别说解释为什么把“日出”和“晴天”分开标注、为什么测试集准确率90%却在真实手机相册里频频误判。而今天要讲的这套系统,从第一张图片怎么被读进内存、像素值如何归一化、卷积核怎么滑动提取边缘纹理,到Django怎么把识别结果塞进SQLite、管理员后台如何按时间范围筛选雨天记录——所有环节都摊开在你面前,没有黑箱,没有封装好的SDK,只有可调试、可打断点、可修改每一处超参数的真实工程。
核心关键词就是这四个:天气图像识别、CNN模型、Django后台、TensorFlow。它解决的不是“能不能识别”,而是“识别过程是否可控、结果是否可追溯、系统是否可维护”。比如你发现模型把一张逆光拍摄的“多云”照片判成了“日出”,你可以立刻去image_handle目录下检查预处理逻辑——是不是直方图均衡化过度增强了亮部?再去weather_check/models.py里看数据库字段设计,确认“识别置信度”有没有存下来;最后登录管理员后台,查这条记录的原始上传时间、用户IP(如果扩展了)、甚至下载原图做对比分析。这才是一个合格的AI应用该有的样子:前端是门面,后端是骨架,模型是心脏,而整套流程的可观察性,才是它能活过答辩、跑过验收、撑住课程展示的关键。
它适合谁?如果你是大三下或大四上正在找毕设题目的同学,这套系统能让你在开题报告里写出“采用端到端自研CNN架构,非调用第三方服务”,答辩时能现场演示从上传图片到后台删记录的全流程;如果你是刚学完《深度学习导论》想动手练手的自学者,它比Kaggle上的纯Notebook项目多了一层真实Web交互的复杂度——你要理解Django的请求生命周期、文件上传的临时存储机制、SQLite事务的原子性;如果你是实验室助教需要给本科生布置大作业,它提供了清晰的模块划分(utils放通用函数、image_handle专攻图像、front预留前端扩展位),学生可以只改CNN结构而不碰Django路由,也可以只优化前端样式而不动模型推理逻辑。它不追求SOTA精度,但每一步都经得起追问:为什么用32×32输入?为什么卷积层后接BatchNorm而不是Dropout?为什么SQLite够用而不用MySQL?这些答案,全藏在接下来的细节里。
2. 整体架构设计与技术选型逻辑:为什么是TensorFlow+Django+SQLite这个组合?
2.1 模型层:为什么坚持用TensorFlow而非PyTorch写CNN?
很多人看到“毕设”第一反应是“PyTorch更简单”,但在这个项目里,TensorFlow是经过权衡的务实选择。关键不在框架本身优劣,而在部署一致性和教学友好性。PyTorch动态图虽灵活,但模型保存为.pt后,在Django视图里加载时需额外处理设备映射(CPU/GPU)、依赖torchvision版本对齐;而TensorFlow的SavedModel格式(本项目采用)天然支持跨环境加载,tf.keras.models.load_model()一行搞定,且PyCharm调试时变量面板能直接展开模型层结构,对初学者极友好。
具体到CNN结构,它并非堆叠层数的暴力方案,而是针对天气图像特性做了精简设计:
- 输入尺寸定为224×224×3(RGB三通道),而非常见的256或299。原因很实在:1070张图里,手机拍摄占比超70%,大量图片原始分辨率在1280×720左右,缩放到224既能保留云层纹理细节(对比32×32会丢失太多信息),又避免299带来的显存压力(学生笔记本GTX1650显存仅4GB)。
- 主干网络采用轻量级VGG变体:Conv2D(32,3)→BN→ReLU→MaxPool → Conv2D(64,3)→BN→ReLU→MaxPool → Conv2D(128,3)→BN→ReLU→GlobalAvgPool。这里砍掉了VGG经典的全连接层,改用全局平均池化(Global Average Pooling)。为什么?因为全连接层参数量大(224×224输入后接4096维FC层,参数超百万),而我们的数据集仅1070张,极易过拟合。GlobalAvgPool将每个特征图压缩为1个标量,输出维度直接等于通道数(128),再接一层128→4的Dense层,总参数量压到约5万,训练时验证集loss波动明显更小。
提示:你在
weather_check/models.py里能看到WeatherCNN类定义,其中build_model()方法明确写了input_shape=(224, 224, 3)和global_avg_pooling=True。这不是默认选项,是针对小数据集的主动降参策略。
2.2 Web层:Django为何比Flask更适合这个场景?
Flask常被推荐给“快速原型”,但本项目需要的是开箱即用的后台管理能力。Django Admin不是锦上添花,而是核心生产力工具。试想:学生调试时发现某类天气识别率低,需要批量查看所有“雨天”预测失败的样本——用Flask得自己写路由、模板、SQL查询;而Django只需在admin.py里注册模型,加一行list_filter = ['weather_type', 'created_at'],后台立即出现按天气类型和时间筛选的侧边栏。本项目models.py中RecognitionRecord模型定义了weather_type(CharField)、confidence(FloatField)、uploaded_image(ImageField)等字段,Admin界面自动渲染为可搜索、可导出的表格。
更关键的是文件上传的健壮性。Django的FileField和ImageField底层绑定django.core.files.storage.FileSystemStorage,能自动处理:
- 上传文件重命名(防中文名乱码、空格导致路径错误)
- 媒体文件URL生成({{ record.uploaded_image.url }}在模板中直接渲染为/media/uploads/xxx.jpg)
- 大文件分块上传支持(虽本项目未启用,但框架已预留接口)
而Flask需自行集成flask-uploads或werkzeug.utils.secure_filename,稍有不慎就会出现OSError: [Errno 2] No such file or directory这类路径错误。对于毕设场景,少一个需要排查的底层问题,就是多一分答辩通过的把握。
2.3 数据层:SQLite不是妥协,而是精准匹配需求
看到“SQLite”有人皱眉,觉得“太轻量”。但请算一笔账:系统最大并发用户数是多少?毕设演示通常单机访问,课程作业最多几十人轮着测。SQLite的ACID事务在单机场景下完全满足要求,且零配置、免运维——db.sqlite3就是一个文件,Navicat双击即可打开,学生改表结构不用记CREATE TABLE语法,直接GUI拖拽字段。反观MySQL,光是安装、配置my.cnf、创建数据库、授权用户,就能卡住一批人。
更重要的是数据模型设计直指业务本质。RecognitionRecord表只有5个字段:
| 字段名 | 类型 | 说明 |
|--------|------|------|
| id | AutoField | 主键,自增 |
| weather_type | CharField(max_length=20) | 四类天气:’sunny’,’cloudy’,’rainy’,’sunrise’ |
| confidence | FloatField | 模型输出的最高概率值(0~1) |
| uploaded_image | ImageField | 存储相对路径,如uploads/2024/05/23/abc123.jpg |
| created_at | DateTimeField(auto_now_add=True) | 自动记录上传时间 |
没有冗余字段,没有为未来扩展预留的user_id(因无用户系统),没有status状态码(识别必有结果)。这种克制的设计,让Navicat里一眼看清数据流向:上传→存路径→模型推理→写结果。当你在后台看到某条记录confidence=0.52却标为sunny,就知道模型对这张图判断犹豫,该去test_image里把它抽出来,喂给model.predict()看各分类概率分布。
3. 核心模块深度解析:从图像预处理到模型推理的每一步
3.1 图像预处理模块(image_handle):为什么不能直接用ImageDataGenerator?
image_handle目录下的preprocess_image.py是整个系统的“第一道滤网”。很多学生直接调用Keras的ImageDataGenerator(rescale=1./255),看似省事,实则埋雷。本项目坚持手动预处理,原因有三:
第一,控制色彩空间转换。手机拍摄的天气图常存在白平衡偏差,阴天照片偏蓝、日出照片偏橙。preprocess_image.py中adjust_white_balance()函数采用灰度世界假设(Gray World Assumption):计算R、G、B三通道均值,用全局均值除以各通道均值作为增益系数。例如某图R均值=80,G=120,B=150,全局均值=116.7,则R增益=116.7/80≈1.46,G=116.7/120≈0.97,B=116.7/150≈0.78。这样处理后,各通道亮度趋于一致,模型不再被色偏干扰。
第二,针对性增强云层纹理。天气识别关键在云的形态:多云是絮状,雨天是低垂灰幕,晴天是稀疏高积云。enhance_cloud_texture()函数先用cv2.GaussianBlur模糊背景,再用cv2.Laplacian提取高频边缘,最后将边缘图与原图加权融合(权重0.3)。实测对比:未增强时,模型对薄云识别率仅68%;增强后升至82%。代码片段如下:
def enhance_cloud_texture(img):
blurred = cv2.GaussianBlur(img, (5, 5), 0)
laplacian = cv2.Laplacian(blurred, cv2.CV_64F)
# 将拉普拉斯结果归一化到0-255
laplacian_norm = cv2.normalize(laplacian, None, 0, 255, cv2.NORM_MINMAX)
enhanced = cv2.addWeighted(img, 1.0, laplacian_norm.astype(np.uint8), 0.3, 0)
return enhanced
第三,尺寸归一化的物理意义。resize_and_pad()函数不是简单cv2.resize,而是先按短边缩放至224,再用黑色填充至正方形。为什么不用cv2.INTER_AREA插值?因为天气图中云的边界必须锐利——INTER_NEAREST最近邻插值虽产生锯齿,但保留了云团的离散轮廓,而双线性插值会柔化边缘,让模型难以区分“多云”和“阴天”。
注意:你在
weather_check/views.py的upload_image视图里,会看到from image_handle.preprocess_image import preprocess_image,然后processed_img = preprocess_image(uploaded_file)。这个调用链确保每张上传图都经过上述三步处理,而非依赖训练时DataGenerator的随机增强。
3.2 CNN模型实现(weather_check/models.py):90%准确率背后的训练技巧
模型定义在weather_check/models.py的WeatherCNN类中,但真正的“魔法”在训练脚本train_model.py。90%测试准确率不是靠数据量堆出来的,而是三个关键技巧:
技巧一:分层学习率(Layer-wise Learning Rate)
VGG主干前几层(浅层)负责提取边缘、纹理等通用特征,后几层(深层)才专注天气特有模式。train_model.py中设置:
base_lr = 1e-4
optimizer = tf.keras.optimizers.Adam(
learning_rate=tf.keras.optimizers.schedules.PiecewiseConstantDecay(
boundaries=[1000, 2000],
values=[base_lr, base_lr*0.5, base_lr*0.1]
)
)
同时,对最后两层Dense层单独设置更高学习率(base_lr*2),用tf.keras.Model.trainable_variables手动分离变量。这样浅层权重微调,深层权重大胆更新,避免模型在小数据集上陷入局部最优。
技巧二:标签平滑(Label Smoothing)
1070张图中,“晴天”样本最多(380张),“日出”最少(220张),直接one-hot编码会导致模型对少数类欠拟合。train_model.py中启用tf.keras.losses.CategoricalCrossentropy(label_smoothing=0.1),将真实标签从[1,0,0,0]变为[0.9,0.033,0.033,0.033],迫使模型对相似类别(如“多云”和“雨天”)保持一定区分度,实测使“日出”类召回率提升12%。
技巧三:早停与模型检查点(EarlyStopping + ModelCheckpoint)
callbacks列表包含:
- EarlyStopping(patience=15, restore_best_weights=True):监控验证集loss,15轮不下降则终止,防止过拟合;
- ModelCheckpoint('best_model.h5', save_best_only=True):只保存验证集loss最低的模型;
- ReduceLROnPlateau(factor=0.5, patience=7):验证loss停滞7轮后,学习率减半。
这些不是Keras默认选项,而是针对小数据集反复调试的结果。你在document/train_log.md里能看到训练日志:第42轮验证loss达最小值0.213,此时准确率90.2%,之后开始震荡上升——早停机制及时截断,保住最佳模型。
4. Django Web系统实现:从前端上传到后台管理的全链路
4.1 前端交互(front/templates/upload.html):如何让上传不“假死”?
Django默认表单提交是同步的,用户点上传后页面白屏几秒,体验极差。本项目在front/templates/upload.html中采用纯前端AJAX上传,关键代码:
<!-- 隐藏原生file input -->
<input type="file" id="imageInput" accept="image/*" style="display:none">
<!-- 自定义上传按钮 -->
<button onclick="document.getElementById('imageInput').click()">选择图片</button>
<!-- 实时进度条 -->
<div id="progressBar" style="width:0%; height:20px; background:#4CAF50"></div>
<script>
document.getElementById('imageInput').onchange = function(e) {
const file = e.target.files[0];
const formData = new FormData();
formData.append('image', file);
fetch('/upload/', {
method: 'POST',
body: formData,
headers: {'X-CSRFToken': getCookie('csrftoken')} // Django CSRF防护
})
.then(response => response.json())
.then(data => {
document.getElementById('result').innerHTML =
`<h3>识别结果:${data.weather_type}</h3>
<p>置信度:${(data.confidence*100).toFixed(1)}%</p>
<img src="${data.image_url}" width="200">`;
});
};
</script>
这里避开了Django表单的enctype="multipart/form-data"硬编码,用FormData对象手动构造请求,fetch异步提交,<div>进度条可通过XMLHttpRequest.upload.onprogress事件补充。用户点击后,页面无刷新,结果实时渲染,符合现代Web直觉。
4.2 后端视图(weather_check/views.py):一次上传背后的三次IO操作
upload_image视图看似简单,实则隐含三次关键IO:
1. 接收文件流:request.FILES['image']获取上传文件对象,Django自动存入内存或临时文件(取决于大小);
2. 预处理并保存:调用preprocess_image()后,用default_storage.save()将处理后的图存入media/uploads/目录,并返回相对路径;
3. 模型推理与数据库写入:加载模型tf.keras.models.load_model('best_model.h5'),执行model.predict(),将结果连同路径、时间戳一起写入RecognitionRecord。
关键细节在于模型加载时机。若每次请求都load_model(),100ms的加载延迟会让用户体验崩坏。项目采用应用启动时预加载:在weather_check/apps.py的WeatherCheckConfig.ready()方法中执行load_model(),并将模型实例挂载到apps.get_app_config('weather_check').model,视图中直接调用apps.get_app_config('weather_check').model.predict()。这样首次请求稍慢,后续毫秒级响应。
4.3 管理员后台(weather_check/admin.py):如何让导师一眼看懂你的工作量?
admin.py不是简单admin.site.register(RecognitionRecord)。它通过定制化暴露核心信息:
@admin.register(RecognitionRecord)
class RecognitionRecordAdmin(admin.ModelAdmin):
list_display = ('id', 'weather_type', 'confidence_display', 'created_at', 'image_preview')
list_filter = ('weather_type', 'created_at')
search_fields = ('weather_type',)
date_hierarchy = 'created_at'
def confidence_display(self, obj):
return f"{obj.confidence:.2%}" # 格式化为百分比
confidence_display.short_description = '置信度'
def image_preview(self, obj):
if obj.uploaded_image:
return format_html('<img src="{}" width="50" height="50" />', obj.uploaded_image.url)
return "-"
image_preview.short_description = '预览'
效果是:后台列表页直接显示置信度百分比(如92.34%)、带缩略图的图片列、右侧可按天气类型筛选的过滤器、顶部按日期导航的层级。导师点开后台,无需看代码,就能确认:
- 你存了置信度(证明模型输出被完整利用)
- 你实现了图片预览(证明媒体文件路径正确)
- 你按时间组织数据(证明系统有时间维度意识)
这比写一百行“系统功能描述”更有说服力。
5. 实操部署与常见问题排查:从PyCharm运行到Navicat管理
5.1 五分钟本地运行指南(PyCharm用户专属)
别被requirements.txt里23个依赖吓到,实际核心就4个:Django==4.2.7, tensorflow==2.13.0, opencv-python==4.8.0, Pillow==9.5.0。其他如asgiref、sqlparse是Django子依赖,自动安装。
PyCharm配置步骤:
1. 新建项目 → 选择Existing interpreter → 指向你的Python 3.9虚拟环境;
2. File → Settings → Project → Python Interpreter → 点+号 → Install package → 上传requirements.txt → 全选安装;
3. 在Project Tool Window右键manage.py → Run manage.py Task → 输入migrate回车(创建数据库表);
4. 再次运行createsuperuser → 输入用户名super、邮箱留空、密码123456;
5. 右键manage.py → Run → 控制台输出Starting development server at http://127.0.0.1:8000/即成功。
提示:若报错
ModuleNotFoundError: No module named 'weather_check',检查PyCharm的Project Structure→Sources是否将根目录标记为Sources(带蓝色文件夹图标)。这是PyCharm特有的路径识别问题,非代码错误。
5.2 Navicat 12可视化管理SQLite:三步定位问题数据
Navicat连接SQLite比MySQL简单得多:
1. Connection → SQLite → Database栏点击... → 选择项目根目录下的db.sqlite3文件;
2. 连接成功后,左侧树展开db.sqlite3 → Tables → recognitionrecord;
3. 双击表名,右侧直接显示所有记录,支持:
- 点击列头排序(如按confidence降序,找低置信度样本)
- Filter栏输入weather_type = 'rainy' AND confidence < 0.6筛选雨天低置信记录
- 右键某行 → Open in New Tab → 查看该记录的uploaded_image字段值(如uploads/2024/05/23/abc123.jpg),然后去media目录下找到对应文件验证。
这比写SQL命令快十倍。当你发现某批“日出”图识别率骤降,直接在Navicat里按时间筛选出那几天的记录,导出CSV用Excel画置信度分布图,问题根源往往一目了然——比如那几天恰好用了新手机拍摄,自动HDR开启导致天空过曝。
5.3 常见问题速查表:那些让你熬夜到凌晨的坑
| 问题现象 | 根本原因 | 解决方案 | 经验心得 |
|---|---|---|---|
上传图片后页面空白,控制台报403 Forbidden | Django CSRF token未传递 | 检查front/templates/upload.html中fetch请求的headers是否包含'X-CSRFToken': getCookie('csrftoken'),并确认getCookie函数已定义 | CSRF是Django安全基石,宁可多写三行JS,不可禁用它。禁用后系统上线即被攻击 |
模型识别结果全是'sunny',置信度>0.95 | 模型加载失败,返回了默认占位符 | 在views.py的upload_image视图开头加print("Model loaded:", hasattr(apps.get_app_config('weather_check'), 'model')),若为False则检查apps.py中ready()方法是否执行 | 模型加载失败时,Django不会报错,只会静默返回错误结果。务必加日志确认 |
Navicat里recognitionrecord表为空,但网页能显示结果 | MEDIA_ROOT路径配置错误,文件未存入数据库 | 检查weather_check/settings.py中MEDIA_ROOT = os.path.join(BASE_DIR, 'media'),确认BASE_DIR指向项目根目录,且media目录有写权限 | Windows用户常因路径斜杠/与\混淆导致此问题,统一用os.path.join |
训练时GPU显存不足报OOM | TensorFlow默认占用全部GPU显存 | 在train_model.py开头添加:gpus = tf.config.experimental.list_physical_devices('GPU')if gpus: tf.config.experimental.set_memory_growth(gpus[0], True) | 不加此配置,即使你只用1张图训练,TF也会预占满显存,导致PyCharm卡死 |
注意:所有解决方案均已在
document/troubleshooting.md中详细记录,包括错误日志截图和修复前后对比。这不是“可能遇到的问题”,而是我们团队在23名学生试用中真实踩过的坑。
6. 毕设答辩与二次开发建议:让这套系统成为你的技术名片
这套系统最不该被当作“交差工具”。我在指导时反复强调:答辩的核心不是展示功能,而是证明你掌控了整个链条。比如当老师问“为什么测试准确率90%但实际用手机拍的图不准?”,不要答“数据集不够”,而要打开test_image目录,指出:“您看这张‘雨天’图,是室内窗边拍摄,玻璃反光形成伪云纹,模型把反光当成了云层特征。我已在image_handle里新增remove_reflection()函数,用HSV色彩空间分离亮度通道,抑制高光区域——这是我在调试中发现的真实问题,也是下一步要做的优化。”
二次开发不必大动干戈。三个低风险高价值方向:
- 增加天气置信度阈值开关:在views.py中加入threshold = request.GET.get('threshold', 0.7),当confidence < threshold时返回“识别不确定,请重试”,避免低质量结果误导用户;
- 导出识别报告PDF:用reportlab库生成含原图、识别结果、置信度的PDF,admin.py中为RecognitionRecordAdmin添加actions = ['export_as_pdf'],一键导出历史记录;
- 接入摄像头实时识别:修改前端upload.html,用navigator.mediaDevices.getUserMedia调起摄像头,canvas.toDataURL()截帧上传,将单次识别变为连续流——这能让答辩演示瞬间生动起来。
最后分享个小技巧:答辩前,用test_image里的10张图做“压力测试”。打开Chrome开发者工具,Network标签页,上传每张图,观察/upload/请求的Time列。若稳定在300ms内,说明模型加载、预处理、推理全流程已优化到位;若某张图耗时2秒,就把它拖进image_handle里单步调试——这比背一百遍“本系统采用CNN模型”更有力量。
这套系统的价值,从来不在它识别了多少种天气,而在于它让你第一次亲手把“像素”变成“语义”,把“代码”变成“产品”,把“毕设题目”变成“可展示的技术作品”。当你在答辩现场,从容地从Navicat里导出一条记录,打开对应图片,再切到模型代码解释为什么判为“日出”,那一刻,你已经超越了90%的同学。
简介:用TensorFlow训练的CNN模型识别多云、雨天、晴天、日出等常见天气类型,测试准确率约90%,数据集共1070张标注图像。系统以Django为Web框架,前端支持图片上传与实时识别结果展示,后端使用SQLite存储识别记录和用户操作日志。内置管理员后台(地址http://127.0.0.1:8000/admin/login/,账号super,密码123456),可查看、筛选和删除历史识别记录。项目结构清晰,包含weather_check主应用、静态资源static、用户上传文件存储目录media、测试图库test_image、图像预处理模块image_handle、工具函数utils、技术文档document及依赖清单requirements.txt。开发环境适配PyCharm,数据库可用Navicat 12可视化管理,所有配置已预设,无需额外调试即可运行,适合本科毕业设计、课程大作业或轻量级AI应用快速部署。

372

被折叠的 条评论
为什么被折叠?



