☰
基于Streamlit的深度学习水下图像增强系统实战解析
2026/10/7 18:17:31 网站建设 项目流程

简介:基于Python与Streamlit构建的深度学习水下图像增强系统完整源码包,面向计算机相关专业学生、教师及企业开发者,尤其适合毕业设计、课程设计与项目初期演示。系统融合UWCNN与WaterNet两条增强通路,提供模型定义、训练与测试脚本,并借助Streamlit实现可交互的前端界面,方便直观对比水下图像增强前后的效果差异。压缩包共44个文件,以10个Python脚本为核心,覆盖界面入口、模型结构、工具函数与网络逻辑,同时包含PNG效果对比图、模型索引/检查点、依赖清单及README文档,整体仅6.04MB,结构清晰,便于快速定位所需模块,目前已有115人学习下载。该项目曾获导师指导认可,评审答辩分达95分;资料内置应用入口、核心网络代码与说明文档,配合系统界面对比截图,可完整走通从加载预训练权重到输出增强图像的全流程。读者可在此基础上替换水下数据集、调整网络层或拓展到其他视觉任务,也可作为深度学习与图像增强方向的进阶练习。

1. streamlit 封装的水下图像增强:先搞清楚它到底是什么再做毕设

做水下图像增强的毕业设计,最怕的不是模型效果差,而是搭了一堆环境、跑了一堆训练代码,最后连个能演示的界面都交不出来。这套“基于 python 的 streamlit 搭建的深度学习水下图像增强系统”解决的就是这个尴尬:UWCNN 和 WaterNet 两个深度学习模型都是现成的,checkpoint 权重也都在,又用 streamlit 把整个流程包成了一个浏览器里点点就能出效果对比图的界面。它本质是一个“模型权重 + 推理脚本 + streamlit 交互层 + 文档”的全套工程,适合用来做毕设演示、课程设计答辩,或者只是想快速跑通水下图像增强的小白。

这里多说一句,这个系统不是“训练平台”,而是把训练好的模型捞出来做前向推理。天体物理有句黑话叫“观测即理论”,放到这里就是“演示即成果”,答辩评委看到的是对比图、参数调节、实时出结果,这就占了很大的便宜。我拆过这类项目不止一次,下面直接讲它里面真正值钱的几个环节:项目结构、环境配置、两个模型各自的推理逻辑,以及 streamlit 的交互是怎么把模型串起来的。

2. 拆开 UIE-DL-master:结构、文件清单、两个深度模型的选型逻辑

拿到压缩包,不要急着跑app.py,先花十分钟把目录摊开看一遍。这个项目的根目录叫UIE-DL-master,里面同时存在两套模型子目录,分别对应两条技术路线。

2.1 文件清单与职责边界

先给一张文件与职责对照表,方便你对照自己的包:

路径职责说明
app.pystreamlit 主入口页面上传图、选模型、展示对比
requirements.txt依赖清单torch、streamlit、opencv、numpy 等
UWCNN/水下图像卷积神经网络含model.py、ops.py、UWCNN_test.py
WaterNet/水下图像增强网络含model.py、ops.py、vgg.py、WaterNet_test.py
test1.png~test4.png样例输入图不同水下色偏与雾化场景
效果对比.png预期输出参考提交报告时直接用
系统界面.pngstreamlit 截屏文档 / 答辩 PPT 素材
README.md/ 授权码说明文件项目说明与授权

两个模型放在两个独立目录里,而不是互相嵌套,这个做法的好处是:model.py各自定义网络结构,互不干扰,你在app.py里引用时路径隔离,改一个不会崩另一个。

2.2 UWCNN 与 WaterNet:两条不同的增强路线

UWCNN 走的是一类基于物理退化模型的思路。它的核心做法是估计场景光的透射率,再把图像从退化域映射回清晰域。在ops.py里你会看到大量张量维度变换和卷积操作,UWCNN_test.py则负责加载 checkpoint 并执行前向推理。

而 WaterNet(子目录里带vgg.py)走的是另一条路线:利用 VGG 感知损失来约束增强结果。这一般不只是简单端到端训练,而是在训练阶段用感知特征的距离来让输出更贴近人眼观感。推理阶段虽然不会显式调用 vgg 的前向损失计算,但权重里已经带了感知优化的痕迹,所以 WaterNet 输出的色调通常比 UWCNN 更自然一些。

提示:如果你在答辩时被问到“为什么选了这两个模型”,最稳的回答是:UWCNN 负责结构恢复,WaterNet 负责感知质量,两者对比能让增强效果在定量指标和人眼观感两个维度上都立得住。

2.3 requirements.txt:这里有两个版本陷阱

streamlit>=1.12.0 torch>=1.8.0 numpy>=1.21.0 opencv-python>=4.5.0 Pillow>=8.3.0

这段依赖清单是这个类项目的常见配置。逻辑上,torch>=1.8.0是一个门槛,新版 PyTorch 基本向下兼容旧模型权重,除非 checkpoint 是用老版本保存的,才会偶发UnpicklingError。opencv-python和Pillow同时存在,因为 streamlit 的file_uploader读出来的是 PIL 对象,而模型预处理通常用的是 cv2 的 BGR 通道顺序,这里就是后面前置坑的伏笔。

3. 跑通系统:环境安装、streamlit 启动、界面与日志排查

很多人在这一步就翻车了。不是模型跑不动,而是环境配置对不上模型代码里隐含的 PyTorch 版本行为差异。下面是按我自己复现经验整理的完整流程。

3.1 安装依赖的推荐顺序

第一次安装依赖,强烈建议你不要直接pip install -r requirements.txt一把梭,因为常见做法是先装 PyTorch 再装其他库,顺序错了容易把 torch 的 CUDA 版本覆盖掉。我的习惯是分三步:

conda create -n uie python=3.8 -y conda activate uie pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu pip install -r requirements.txt

第一步创建独立环境,不要污染你现有的 python;第二步激活环境;第三步先装 CPU 版 torch,保证能跑通流程;最后再装剩下的依赖。等你确定要上 GPU 加速了,再把 CPU 版 torch 卸掉换 CUDA 版。

参数解释:python=3.8不是随便选的,很多 streamlit + torch 组合在 python 3.10 以上会遇到torch和numpy的兼容性警告,3.8 是这类项目的最稳区间。--index-url后面的地址是 PyTorch 官方 whl 托管地址,不是第三方源,CPU 版能绕开 CUDA 环境配置的额外负担。

3.2 启动 app.py 前先做一次模型加载测试

不要直接streamlit run app.py,而是先用 python 直接跑一次UWCNN_test.py,把推理链路单独验证掉。

cd UIE-DL-master python UWCNN/UWCNN_test.py --input test1.png --output out.png

UWCNN_test.py内部通常包含这样几个动作:加载model.py里定义的网络结构,从checkpoint目录读取权重,对输入图做归一化和维度变换,再执行model(img)前向推理。我拆这个文件时见过最常见的写法是:torch.load(checkpoint_path, map_location='cpu'),然后在model.load_state_dict(state_dict)前打印一次 key 长度,用来确认权重没有加载错位。

如果这一步出现size mismatch for ...,说明模型结构和 checkpoint 不是同一套定义,常见原因是你改了model.py里的某个卷积层通道数,又回去用了旧权重。解决方法是把model.py恢复原样,或者重新下载对应权重的 checkpoint。

注意:这个项目里checkpoint目录是独立放在UWCNN子目录下的,别把它挪到根目录后改UWCNN_test.py里的相对路径,否则会报FileNotFoundError。

3.3 streamlit 界面的启动与四个关键交互

streamlit run app.py

启动后浏览器自动打开http://localhost:8501。app.py里一般会用到这么几个 streamlit API:

import streamlit as st from PIL import Image uploaded_file = st.file_uploader("上传水下图像", type=["png", "jpg", "jpeg"]) model_choice = st.selectbox("选择增强模型", ["UWCNN", "WaterNet"]) if uploaded_file is not None: img = Image.open(uploaded_file) st.image(img, caption="原始图像", use_column_width=True) if st.button("开始增强"): st.write("模型推理中……")

file_uploader返回的是一个UploadedFile对象,别把它当成文件路径直接丢给 cv2。selectbox是模型选择的交互入口,button触发推理事件。use_column_width是老版本 streamlit 的参数,如果你装了 1.30 以上版本,它已经被width参数取代,代码里会有黄色警告,不影响运行但建议改成st.image(img, caption="原始图像", width=600)。

app.py里真正调模型的地方,一般是先把 PIL 图片转成 numpy 数组,再切通道顺序给模型的预处理函数。如果你看到st.image显示出来的增强图偏蓝偏红,十有八九是通道顺序问题而不是模型问题。

3.4 日志与报错排查看哪里

streamlit 的报错会直接打在网页上,但完整堆栈只在终端里显示。跑推理时报错,先看终端最后 20 行,绝大多数是下面三类:ModuleNotFoundError(某个依赖没装,回到 3.1 分步装);KeyError(model.py里的权重 key 和 checkpoint 不一致,回 3.2 打印 key 对比);FileNotFoundError(路径不对,确认你当前 shell 的工作目录是不是项目根目录)。

4. streamlit 交互逻辑与前端参数调节:从静图到可操作系统

这一章是区分“交代码”和“交付系统”的分水岭。很多毕设里模型是好的,但演示时只能跑预设图片,评委一要求换图就卡壳。streamlit 的价值在于它把上传、调参、推理、展示串成了一条交互链路。

4.1 参数调节设计:给模型留几颗“旋钮”

这个系统里可以增加的旋钮至少有这么几个:增强强度(对输出做线性融合)、去雾权重、色彩补偿因子。这些参数不一定要进模型内部,常见做法是在模型输出后做后处理融合。

import cv2 import numpy as np def blend_result(original, enhanced, alpha=0.7): """ alpha = 0 时完全用原图,alpha = 1 时完全用模型输出。 水下增强经常会出现过度饱和,留这个旋钮做人工干预。 """ result = cv2.addWeighted(original, 1 - alpha, enhanced, alpha, 0) return result

addWeighted是 OpenCV 里的经典线性融合函数。前两个参数是输入图和权重,第三个参数是第二张图及其权重,最后一个 0 是伽马值,这里不处理非线性亮度偏移就填 0。实际操作里我一般会把alpha默认值设为 0.7,让模型输出占主,但保留 30% 的原图细节,防止纹理被过度平滑。

在app.py里用st.slider("增强强度", 0.0, 1.0, 0.7)把这个函数暴露出来,用户拖动滑块就触发一次blend_result重算,并不需要重新跑模型。这样做的优势很明显:调参不涉及模型前向推理,响应速度极快,答辩演示时可以展示“参数敏感性分析”这一页。

4.2 模型选择的优雅封装

两个模型的前处理和后处理并不是完全一致的,所以在app.py里不能只把 model_choice 拿去当 if 判断,而要把处理管线也一起封装进字典或函数映射里。

def run_enhancement(image_array, model_name): if model_name == "UWCNN": processed = uwcnn_preprocess(image_array) output = uwcnn_infer(processed) return uwcnn_postprocess(output) elif model_name == "WaterNet": processed = waternet_preprocess(image_array) output = waternet_infer(processed) return waternet_postprocess(output)

这个写法把两个模型的差异隔离在各自的preprocess和postprocess函数里,而不是写成一坨 if-else 处理逻辑。后续你想再加一个模型(比如 UColor、FUnIE-GAN),只需要新增一组函数,再在selectbox的选项列表里追加名字即可。参数说明:image_array是 RGB 的 numpy 数组,model_name是 streamlit 里selectbox返回的字符串,两个都知道对方的格式约定,是在模型包里就定好的。

4.3 效果对比页面的排版:左右对比与指标同屏

答辩评委注意力时间有限,页面上不要只放一张图。推荐用st.columns(2)做左右双栏对比,左边原图右边输出图。

col1, col2 = st.columns(2) with col1: st.image(original, caption="退化图像 (输入)") with col2: st.image(enhanced, caption="增强结果 (输出)")

这段代码不负责任何算法逻辑,它的作用是把两幅图在视觉上放在同一高度、同一尺度(use_column_width或width参数一致),让对比更直观。还可以顺手在底部打印两个非参考评价指标,典型做法是利用skimage.metrics.psnr计算 PSNR,虽然这个指标在无参考场景下意义有限,但报告里多一个数字总比没有强。

提示:如果项目包的requirements.txt里没有scikit-image,截图时可以先不打印指标,用图片快速展示效果,等答辩前再补指标排版页。

5. 避坑记录:水下图像增强项目最容易翻车的五个位置

这类项目的逻辑本身不难,难的是各种环境、路径、通道顺序上的细碎问题。按我拆过的类似项目经验,下面这些坑是命中率最高的。

5.1 现象:跑UWCNN_test.py报mat1 and mat2 shapes cannot be multiplied

这个报错几乎每个做过 CNN 前向推理的人都会遇到一次。原因很直接:输入图片的通道数或尺寸和model.py里第一层卷积的期望输入不一致。这个项目把 checkpoint 固定了输入分辨率,比如预处理阶段内部把图片 resize 到固定大小,但你在测试脚本里直接喂了原始test4.png,尺寸可能是 600×800,而模型期望的是 256×256。两个数一乘不匹配,矩阵乘法必然崩。

解决方法是去UWCNN_test.py里找到预处理函数,把cv2.resize()的目标尺寸显式改成模型期望值,或者直接看一眼model.py里卷积层的输入通道定义。我一般是打印一次img.shape,再对比model.py里第一个卷积的 in_channels,两个数字对不上就回源头改。

5.2 现象:上传自己的测试图后,增强图颜色诡异(偏紫或偏绿)

这是典型的三通道顺序问题。file_uploader经 PIL 打开是 RGB,而cv2.imread读取是 BGR,模型训练的预处理大概率沿用了 OpenCV 的 BGR 通道。如果你在app.py里用 PIL 读图后直接丢给模型,前向推理出来的结果就是通道错位,视觉效果约等于滤镜翻车。

解决方法是显式转换:np.array(img)先拿 PIL 的 RGB numpy 数组,再cv2.cvtColor(rgb_img, cv2.COLOR_RGB2BGR)转成模型输入,推理输出后再用cv2.COLOR_BGR2RGB转回来给 streamlit 展示。

5.3 现象:load_state_dict 报Unexpected key(s) in state_dict: module.conv1.weight

权重文件里多了一层module.前缀,几乎可以断定 checkpoint 是从 DataParallel 或多 GPU 训练下保存的。模型结构里定义的 key 是conv1.weight,而保存的是module.conv1.weight,前者从结构里生成,后者从权重文件读取,前缀不对齐就认不出来。

解决方法是加载时剥掉这层前缀,常见做法是加载后遍历 key 重新建一个去掉前缀的字典:

state_dict = torch.load(ckpt_path, map_location='cpu') from collections import OrderedDict new_state_dict = OrderedDict() for k, v in state_dict.items(): name = k[7:] if k.startswith('module.') else k new_state_dict[name] = v model.load_state_dict(new_state_dict)

这段代码先加载权重到 CPU 内存,再把module.前缀切掉,最后重新装载。map_location='cpu'是强迫权重到 CPU,防止你本机没 GPU 时报 CUDA 不可用。

5.4 现象:streamlit 页面报RuntimeError: Cannot run the event loop while another loop is running

这个问题常出现在你把asyncio相关代码或 Jupyter 内核混进了 streamlit 脚本里。streamlit 自身有自己的事件循环,你在脚本里再主动创建 loop 就有冲突。这个项目本身不太可能用到 asyncio,出现这个问题通常是你自己加了额外的异步调用。

解决办法是不要在一个 streamlit 脚本里手动调asyncio.run(),或者把异步逻辑抽成独立模块,用st.cache_resource缓存结果,避免重复执行。

5.5 现象:第一次启动streamlit run app.py后主页能打开,但点“开始增强”按钮无反应

这一类无反应多半不是代码崩溃,而是按钮触发后处理逻辑抛了异常,但异常被 streamlit 的异常桶接住了,界面还在,只在终端里打了堆栈。优先看终端末尾。常见引发点是 torch 在 CPU 环境下的推理速度慢,或者某一步把UploadedFile当路径传给了cv2.imread。cv2.imread读不出对象,返回None,下一步torch.from_numpy(None)直接崩。上策是在cv2.imread之前先把文件写成临时缓存,或者干脆全程用 PIL + numpy 路径。

uploaded_file = st.file_uploader("上传图像") if uploaded_file is not None: file_bytes = np.asarray(bytearray(uploaded_file.read()), dtype=np.uint8) img_bgr = cv2.imdecode(file_bytes, cv2.IMREAD_COLOR)

这段是用np.frombuffer的变体做内存解码,避免文件落地。cv2.imdecode接收字节数组和读取标志位,cv2.IMREAD_COLOR表示读成三通道彩色图,相当于把上传对象直接转成 cv2 能用的图像数据。

6. 进阶验证:用你自己的图片做盲测,并给模型加一个可解释性的后门

到这里系统已经能跑通了。但真正让它从“能跑”变成“能答辩”的,是你有没有做额外的验证。我建议你做三件事:第一,拿 20 张不同水质下的水下图片分别跑两个模型,记录处理时间和增强效果;第二,把结果整理成一张 2×2 对比网格,横轴是模型,纵轴是样本难度;第三,也是最有价值的——在 streamlit 里加一个“暗通道分析”的辅助页。

暗通道先验在水下增强里的作用相当于体检报告。它本身不增强,但能告诉你输入图像的哪些区域雾气浓、哪些区域色彩偏得厉害。你可以直接在app.py里增加一个函数:

def dark_channel(img, patch_size=15): """ 计算暗通道图:取每个 patch 内 RGB 三通道的最小值, 再对最小值做最小值滤波。patch_size 越大暗通道越粗糙。 """ img_min = np.min(img, axis=2) kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (patch_size, patch_size)) dark = cv2.erode(img_min, kernel) return dark

np.min(img, axis=2)是逐个像素取 RGB 三通道最小值,这一步把彩色信息压缩成灰度。cv2.getStructuringElement生成结构化元素,MORPH_RECT表示矩形窗口,patch_size决定窗口宽度,cv2.erode做最小值滤波得到暗通道。暗通道越亮说明那个区域雾越浓。答辩时你可以现场解释:增强前暗通道的平均亮度是 0.53,增强后降到 0.31,这说明模型确实把浓雾区域的可见性提上去了。

这一页加完之后,这个项目的完整度就远远超过“能跑通”的及格线了。从那以后我每次做这类毕设辅助工程,都强制走一遍“先拆目录、再测推理、后加演示、最后补验证图表”的顺序,而不急着先启动界面。先证明模型能单独工作,再让 streamlit 去调用它,排查范围会小很多。希望这份拆解能帮你在答辩或调试路上少走几段弯路。

如果你想快速获取这份带完整文档和系统界面的资源,直接去下载UIE-DL-master解压,按第三章的顺序操作就能在半小时内跑出第一张对比图。祝顺利。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询