简介:面向计算机视觉与Web开发方向的毕业设计,这份Python-Django图像去雾算法研究系统源码包,提供了一套包含前后端与数据库的完整可运行项目。系统基于Django框架与HTML技术构建,支持用户上传含雾图片并实时展示去雾结果,算法层面涵盖暗通道先验、Retinex理论以及物理模型等经典去雾方法。压缩包共250个文件、约48.39MB,主要包含37个Python后端源码文件、38个pyc编译文件、35个JavaScript脚本、14个CSS样式表、9个HTML页面,以及SQL数据库脚本、PPT演示文稿、Word/Markdown说明文档,并附带大量GIF/PNG/JPG图像素材用于界面展示与效果对比,前端还集成了Bootstrap、Layui等常用框架。目前已有71人学习下载。项目同时提供详细说明文档与答辩PPT,覆盖开发背景、设计思路、算法数学原理、代码关键实现及使用方法,适合作为毕业设计参考;目录结构清晰,便于直接运行体验或基于源码进行二次开发与算法扩展。
1. 毕设图像去雾系统:这类源码拿到手,先别急着装环境
图像去雾算法研究系统是毕业设计里很成熟的一类题目:输入一张雾图,后台跑去雾算法,前端页面展示原图和处理图,再用 MySQL 把算法参数、处理记录和图片路径持久化。你看到的这份 Python-Django-html 源码,本质是把「暗通道先验等去雾算法」从单脚本包装成 Web 系统,补齐前后端和数据库后,能直接演示、截图、跑对比实验。这类系统适合两类人:一是想快速交付的毕设党,二是打算往图像处理或 Web 开发方向做课程设计的人。它技术栈不冷门,复现成本主要在环境(Django + MySQL)不在算法。
2. 去雾算法怎么选:先用暗通道先验在本地跑出清晰图
2.1 为什么先定算法,再决定页面怎么设计
拿到这种系统源码,第一步不该是打开页面截图,而是先确认算法核心能不能跑通。去雾算法常见的路线有三条:直方图均衡化、Retinex 系列、暗通道先验。直方图均衡化最简单,但对浓雾图容易颜色发灰;Retinex 也能去雾,调参麻烦,做完之后很难跟答辩老师讲清楚“我到底改了什么”。暗通道先验是 2009 年何恺明那篇经典论文的思路,原理有明确公式,效果直观,且推导只要三步:暗通道、大气光、透射率,非常适合写进毕设论文的算法章节。
另外很多毕设题目会要求“多种算法对比”,这时候不要一开始就写三套算法。正确顺序是:先把暗通道先验做成一个独立的dehaze.py,在命令行里验证几张图,再接入 Django 视图层。这样后面换 Retinex 或者直方图均衡时,只需要在视图里替换一个函数,前端和数据库完全不用动。先跑通单函数,再谈系统架构,能省下大量调试时间。
2.2 暗通道先验的三步推导:暗通道、大气光、透射率
暗通道先验的核心观察是:户外无雾清晰图的局部区域里,至少有一个颜色通道的亮度很低,接近 0。雾天图像因为大气散射,暗通道不再“暗”,所以我们可以把这个偏差反推回去。估算大气光 A 时,通常先求暗通道图,再取暗通道中最亮的前 0.1% 像素,去原图上找这些位置对应的 RGB 最大值,作为全局大气光。
透射率 t 表示光线没有被散射衰减的比例,公式是 t = 1 - ω * 暗通道(I / A) ,其中 ω 通常取 0.95,目的是保留一点点雾,避免恢复图太“假”。最后恢复公式是 J = (I - A) / max(t, t0) + A,t0 设为 0.1 防止分母为 0。整个过程不涉及训练、不依赖数据集,纯数值计算,所以答辩时能现场讲清楚每一步在干什么,这是它成为毕设首选算法的根本原因。
2.3 最小可运行 dehaze.py:三十行代码跑通第一张图
装依赖只需要 OpenCV 和 NumPy,在项目根目录执行:
pip install opencv-python numpy然后写dehaze.py,这部分可以直接放进后续 Django 应用里复用。
import cv2 import numpy as np def dark_channel(img, size=15): # img 是 0~1 的浮点图,(H, W, 3) b, g, r = cv2.split(img) min_ch = cv2.min(cv2.min(r, g), b) kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (size, size)) return cv2.erode(min_ch, kernel) def estimate_atmosphere(img, dark, percent=0.001): h, w = img.shape[:2] num = max(int(h * w * percent), 1) flat_dark = dark.ravel() flat_img = img.reshape(-1, 3) index = np.argsort(flat_dark)[::-1][:num] return np.max(flat_img[index], axis=0) def dehaze(img_bgr, omega=0.95, t0=0.1, size=15): norm = img_bgr.astype(np.float64) / 255.0 A = estimate_atmosphere(norm, dark_channel(norm, size)) norm_A = np.clip(A, 1e-3, 1.0) # 注意:暗通道要在 I/A 上计算,不是直接对 I 算 t = 1.0 - omega * dark_channel(norm / norm_A, size) t = np.clip(t, t0, 1.0) J = np.empty_like(norm) for i in range(3): J[:, :, i] = (norm[:, :, i] - A[i]) / t + A[i] return np.clip(J * 255, 0, 255).astype(np.uint8) def dehaze_file(src_path, dst_path, omega=0.95, t0=0.1): img = cv2.imread(src_path) if img is None: raise ValueError(f"图片读不出来: {src_path}") result = dehaze(img, omega=omega, t0=t0) cv2.imwrite(dst_path, result)逻辑说明:dark_channel里先用cv2.split拆三通道,逐个取最小值,再用cv2.erode做最小值滤波,效果等价于在局部窗口里找暗像素。estimate_atmosphere取暗通道最亮像素在原图中的 RGB 最大值,np.argsort(...)[::-1]是从大到小排序。dehaze里最容易被忽略的是norm / norm_A,这一行保证透射率估算在不同颜色通道上分别做归一化,否则恢复图会偏灰。
参数说明:size=15是暗通道窗口大小,窗口越大透射率越平滑,但也容易出现光晕伪影;omega=0.95控制保留雾的程度,想更通透调到 1.0,但天空区域容易过饱和;t0=0.1是透射率下限,防止原始代码在t接近 0 的地方出现噪声放大。测试时先用一张带白色天空的雾图,如果天空部分出现色块,说明size太大或omega太高。
2.4 用 PSNR/SSIM 提前验收算法,而不是等答辩翻车
很多同学只看视觉效果,凭“看起来清晰了”就写进论文,结果答辩时老师一问“你的去雾效果怎么量化”,回答不上来。常见的量化指标是 PSNR 和 SSIM,需要一张参考清晰图,通常从网上的标准雾图数据集拿,例如经典的“纽约雾图集”。在项目里按下面思路写一个快速校验脚本:
import cv2 from skimage.metrics import peak_signal_noise_ratio, structural_similarity gt = cv2.imread('gt/clear.png') out = cv2.imread('output/result.png') psnr = peak_signal_noise_ratio(gt, out) ssim = structural_similarity(gt, out, channel_axis=2) print(f"PSNR={psnr:.2f} dB, SSIM={ssim:.4f}")如果没有真实参考图,至少截图保存处理前后的局部细节,放大后放进论文作主观对比。流程上建议先跑通dehaze_file生成一张结果图,再用这个脚本算指标,最后才进 Django。因为网页里上传图片、保存记录、更换路径涉及的变量比命令行多,算法问题混在里面会非常难排查。
3. Django 搭建 Web 闭环:上传、去雾、展示、入库
3.1 解压源码后先确认骨架:manage.py、app、模板与 static
Django 项目实战新手最容易犯的错,是把整个 zip 解压后不管三七二十一就运行python manage.py runserver,然后被一堆依赖报错打懵。正确的接手步骤是先看骨架:根目录有没有manage.py,有没有requirements.txt,有没有一个或多个 app 目录,再确认模板和静态文件放哪。先做一次冷启动:
cd D:\dehaze_project python -m venv venv venv\Scripts\activate pip install django opencv-python numpy pillow pymysql python manage.py migrate python manage.py runserver 8000如果压缩包里自带requirements.txt,先执行pip install -r requirements.txt,没有就按上面的包列表补。注意一定要用虚拟环境,不能用全局 Python,否则以后装别的项目依赖会互相污染。启动后浏览器打开http://127.0.0.1:8000,如果能看到页面或 API 返回,说明基础环境没问题。
接下来确认 app。如果源码里已经建好了业务 app,就不要执行python manage.py startapp,Django 创建 app 的命令很简单,但把新 app 注册进settings.py反而容易改出问题。如果确实要新建,命令是:
python manage.py startapp imageapp然后把imageapp加到INSTALLED_APPS。新手建议只看已有 app 的views.py、models.py、urls.py三个文件,先理解现有逻辑,再决定改动范围。你不需要一开始看懂所有代码,只需要找到处理上传的那条路由,从它开始往下追。
3.2 定义记录表:原图、结果图、算法名、参数与创建时间
你要在 MySQL 里存的数据,通常是每次去雾操作的原图路径、结果图路径、算法类型、关键参数 omega 和 t0,以及处理时间。用 Django ORM 定义模型,比直接写 SQL 语句方便得多,而且后台 admin 可以直接录入和查询。常见的模型写法如下:
from django.db import models class DehazeRecord(models.Model): origin_image = models.ImageField(upload_to='origin/%Y%m%d/') result_image = models.ImageField(upload_to='result/%Y%m%d/', blank=True, null=True) algorithm = models.CharField(max_length=50, default='dark_channel_prior') omega = models.FloatField(default=0.95) t0 = models.FloatField(default=0.1) created_at = models.DateTimeField(auto_now_add=True) class Meta: ordering = ['-created_at'] db_table = 'dehaze_record' def __str__(self): return f"{self.algorithm} {self.created_at:%Y-%m-%d %H:%M}"逻辑说明:ImageField需要 Pillow 库,它会自动把上传文件写入MEDIA_ROOT对应的目录,并在数据库里存相对路径。upload_to='origin/%Y%m%d/'意思是按日期分子目录。blank=True, null=True是因为结果图要等算法跑完才写入,先创建记录再更新。ordering = ['-created_at']让 Django admin 默认按时间倒序展示,这个排序规则在后面的数据管理里非常实用。
模型建好后迁移写进数据库:
python manage.py makemigrations python manage.py migrate参数说明:db_table是可选的,用来指定 MySQL 表名,不写的话 Django 会根据 app 名和模型名自动生成。auto_now_add=True表示第一次创建时自动写当前时间,不适合做“更新时间”字段。
3.3 视图里接住上传文件,调用 dehaze 并保存结果
视图层是前后端交接的地方。前端上传一张雾图,后端要接收文件、保存原图、调用算法、保存结果图、返回展示页面。下面是一段能直接跑通的视图代码:
import os from django.conf import settings from django.shortcuts import render from .models import DehazeRecord from .dehaze import dehaze_file def upload_and_dehaze(request): if request.method != 'POST': return render(request, 'index.html') image = request.FILES.get('image') if not image: return render(request, 'index.html', {'error': '请选择图片'}) omega = float(request.POST.get('omega', 0.95)) t0 = float(request.POST.get('t0', 0.1)) # 先存原图,得到数据库里对应的文件路径 record = DehazeRecord.objects.create( origin_image=image, algorithm='dark_channel_prior', omega=omega, t0=t0, ) src_path = record.origin_image.path out_dir = os.path.join(settings.MEDIA_ROOT, 'results') os.makedirs(out_dir, exist_ok=True) # 结果文件名沿用原图文件名,有重名风险,见第 5.5 节处理 out_name = os.path.basename(src_path) out_path = os.path.join(out_dir, out_name) dehaze_file(src_path, out_path, omega=omega, t0=t0) record.result_image = f'results/{out_name}' record.save(update_fields=['result_image']) return render(request, 'result.html', {'record': record})逻辑说明:request.FILES是 Django 对上传文件的封装,靠request.FILES.get('image')拿到文件对象,文件名、内容都从这里来。DehazeRecord.objects.create(...)会自动把上传图片保存到MEDIA_ROOT/origin/日期/下,同时生成一条数据库记录。record.origin_image.path是文件的绝对路径,算法处理要读这个路径。处理完成后手动把相对路径写入result_image字段。
参数说明:settings.MEDIA_ROOT必须提前配好,否则文件会跑到默认空路径。update_fields=['result_image']表示只更新这个字段,能减少一次完整保存的冲突风险。omega和t0从前端表单读,HTML 里写两个输入框,名字要和这里一致。模板里result.html只要输出两条图片 URL 即可:
{% if record %} <h3>原图</h3> <img src="{{ record.origin_image.url }}" alt="原图"> <h3>去雾结果</h3> <img src="{{ record.result_image.url }}" alt="去雾结果"> {% endif %}这里用url而不是手动拼/media/,因为图片路径依赖MEDIA_URL配置,直接拼硬编码很容易在改目录结构后失效。
3.4 模板里把图片路径渲染正确:MEDIA_URL 和 {% static %} 的边界
HTML 网页制作里最常翻车的是图片 404。Django 对两类文件处理方式不同:自己写的index.html、CSS、JS 一般放在static/,用{% load static %}和{% static 'css/style.css' %}引用;用户上传的图片放在media/,用MEDIA_URL映射。很多初学者把上传图放进static,然后改后端代码,越改越乱。
正确配置如下:
# settings.py MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media')然后在项目根路由urls.py末尾加上:
from django.conf import settings from django.conf.urls.static import static urlpatterns = [ # 已有路由 ] if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)逻辑说明:static(settings.MEDIA_URL, ...)只在DEBUG=True时生效,这是开发环境的便利写法。当项目部署到服务器并关闭 DEBUG 后,静态文件交集要给 Nginx 或云存储,Django 自己不处理上传文件的读取。毕业设计现场演示时让 DEBUG 保持 True 没问题,但你要在论文里写清楚开发环境与生产环境的差异,这是答辩老师喜欢追问的点。
参数说明:MEDIA_URL的值必须前后都有斜杠,/media/。如果改成/files/,模板里的record.origin_image.url会自动变成/files/...,不需要改模板。上传文件的根目录MEDIA_ROOT建议用绝对路径或基于BASE_DIR拼接,不要写死C:\xxx,否则换个电脑又得改。
4. MySQL 配置与数据操作:从建库到 Django 查询删除对象
4.1 先在 Windows 把 MySQL 装好,字符集选 utf8mb4
Windows 10 上安装 MySQL,常见做法是用 MySQL Installer 装社区版,组件选 MySQL Server,版本选 8.0 以上。安装过程注意端口默认 3306、root 密码要记牢,服务名通常是 MySQL80。安装完成后打开命令行,先确认服务在跑:
net start MySQL80 mysql -uroot -p进入 MySQL 后,先别急着创建业务表,把数据库和专用账号准备好:
CREATE DATABASE dehaze_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'django_user'@'localhost' IDENTIFIED BY 'your_password'; GRANT ALL PRIVILEGES ON dehaze_db.* TO 'django_user'@'localhost'; FLUSH PRIVILEGES;字符集选 utf8mb4 是为了让数据库能存中文算法名、中文路径和说明文档内容,不能用默认的 latin1,否则插入中文会报Incorrect string value。另建django_user而不是直接用 root,是为了避免后面代码里一旦出现 SQL 注入或误操作,直接把整个 MySQL 数据目录搞坏。毕设项目不用分太多权限,给dehaze_db库全权限就够。
4.2 settings.py 连接 MySQL:mysqlclient 还是 pymysql
Django 官方默认的 MySQL 连接驱动是mysqlclient,但在 Windows 上安装它经常卡在django.core.exceptions.ImproperlyConfigured或编译报错,因为需要 Visual C++ 14.0 的构建工具。另一个方案是pymysql,纯 Python 实现,兼容 MySQLdb 接口,拿来跑毕设没有问题。推荐组合是:pip install pymysql,然后在项目同名包的__init__.py里做一次替换。
比如项目目录是dehaze_project/dehaze_project/__init__.py,加入:
import pymysql pymysql.install_as_MySQLdb()然后settings.py里把数据库配置换成:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'dehaze_db', 'USER': 'django_user', 'PASSWORD': 'your_password', 'HOST': '127.0.0.1', 'PORT': '3306', 'OPTIONS': { 'charset': 'utf8mb4', 'autocommit': True, }, } }逻辑说明:pymysql.install_as_MySQLdb()会骗过 Django 的数据库后端检查,让它以为MySQLdb已经存在。这样ENGINE保持django.db.backends.mysql就可以。HOST写127.0.0.1比localhost更稳,因为 Windows 上 localhost 有时被解析到 IPv6 的::1,MySQL 服务若只监听 IPv4 就会连接失败。
参数说明:CHARSET和OPTIONS里的charset要同时保证一致,避免 Django 连接层和 MySQL 表字符集不一致。autocommit=True保证每次写入立即生效,避免事务没提交导致数据查不到。如果你在自己电脑上装的是 MySQL 5.7,同样可以用这套配置,但 Django 4.2 对 MySQL 版本有最低要求,建议直接用 MySQL 8.0。
4.3 用 admin 和 ORM 管理去雾记录:排序、删除、清空测试数据
数据库建好后,Django admin 是最快的调试入口。在admin.py注册模型,就能在后台看到所有去雾历史记录,并手动删除错误数据。
from django.contrib import admin from .models import DehazeRecord @admin.register(DehazeRecord) class DehazeRecordAdmin(admin.ModelAdmin): list_display = ['id', 'algorithm', 'origin_image', 'result_image', 'created_at'] list_filter = ['algorithm'] ordering = ['-created_at']创建超级管理员后登录/admin/,就能直接看图删记录:
python manage.py createsuperuser python manage.py runserver 8000如果要在代码里清理数据,Django 执行查询-删除对象的标准写法是先用查询集筛出对象,再调用 delete:
# 删除单条 DehazeRecord.objects.get(id=3).delete() # 删除所有没有人脸或处理结果为空的记录 DehazeRecord.objects.filter(result_image__isnull=True).delete() # 清空所有演示数据 DehazeRecord.objects.all().delete() # 按时间倒序取最近 10 条 records = DehazeRecord.objects.order_by('-created_at')[:10]逻辑说明:filter(...).delete()会返回(受影响行数, 明细字典),可以打印出来确认删了多少条。注意 ORM 删除只影响数据库记录和 ImageField 的字段值,不会自动删除media/目录里对应的物理图片文件。如果你要彻底清空磁盘上的图片,需要手动删掉media/origin和media/results下的文件,否则下次迁移到别的机器会把一堆旧图带过去。
5. 避坑:图像去雾系统从能跑到不翻车的 5 个检查点
5.1 恢复图发灰偏色:透射率求错位置
现象:暗通道先验处理出的结果图整体发灰,远景尤其明显,像是蒙了一层雾没去掉。原因:暗通道先验的透射率公式里,必须对I/A求暗通道,而不是对原始图求暗通道。写错之后,t 的估算在高亮区域严重偏大,导致恢复不足。解决:检查代码里是否写了dark_channel(norm / norm_A, size),确保 A 先被广播到每个通道再做最小值滤波。同时确认 A 是从原图上对应的最亮像素区域取值,而不是直接取暗通道矩阵里像素的颜色值。
5.2 页面图片裂:MEDIA_URL 配了但模板没拼对
现象:上传后原图和结果图都能在后台看到记录,但页面上的<img>显示 404。原因:很多人在模板里写<img src="media/{{ record.result_image }}">,少了{{ ... }}自动生成的完整 URL,或者根本没配MEDIA_URL。解决:模板里一律用{{ record.origin_image.url }}和{{ record.result_image.url }},不要在 HTML 里手动拼/media/前缀。检查项目根urls.py是否在最外层路由末尾追加了static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT),少这一行 Django 不会自动提供媒体文件访问。
5.3 migrate 连不上 MySQL:PyMySQL 没装或没启用
现象:执行python manage.py migrate时提示ModuleNotFoundError: No module named 'MySQLdb',或者报Can't connect to MySQL server on '127.0.0.1'。原因:前者是没装pymysql也没调用install_as_MySQLdb();后者是 MySQL 服务没启动、密码错误、端口不对,或者 MySQL 8.0 默认认证插件与旧客户端不兼容。解决:先确认服务在跑,命令行执行net start MySQL80;再确认settings.py的PASSWORD和USER能直接登录;最后检查项目__init__.py里有没有pymysql.install_as_MySQLdb()。如果是认证插件问题,在 MySQL 里执行ALTER USER 'django_user'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_password';再刷权限。
5.4 大图处理卡死:没有先下采样
现象:小图测试正常,一传手机原图,浏览器转圈几十秒,甚至直接报内存不足。原因:暗通道的cv2.erode、np.argsort对超大图非常不友好,一张 4000 像素乘 3000 像素的图,光暗通道矩阵就有千万级元素,Python 循环再一叠加,速度会慢到不可接受。解决:在dehaze_file入口先判断图片长边是否超过 1280,超过就缩到 1280 处理,处理完再放大回原始尺寸保存;或者在视图层限制上传文件大小,例如request.FILES['image'].size > 5 * 1024 * 1024时直接返回提示。去雾算法按区域处理,缩图不会让效果差太多,速度却能提升十倍以上。
5.5 中文文件名导致上传记录错乱:给图片加 UUID
现象:用户上传雾天风景.jpg这类中文文件名,数据库里记录创建成功,但结果页图片打不开,或结果图和原图互相覆盖。原因:浏览器和 Windows 控制台对中文编码处理不一致,ImageField 保存文件名时可能被转成%xx,而os.path.basename拿到的文件名又和MEDIA_URL里的编码对不上。解决:在创建DehazeRecord之前重写文件名,使用 UUID 和原扩展名:
import os from uuid import uuid4 ext = os.path.splitext(image.name)[1].lower() image.name = f"{uuid4().hex}{ext}" record = DehazeRecord.objects.create(origin_image=image)这样数据库和磁盘上的文件名都是纯 ASCII,不会因为中文编码出问题,也避免同名覆盖。处理结果的文件名也可以直接取这个 UUID,不再用os.path.basename(src_path)。
6. 答辩前必做的三件事:批量跑图、指标对比、清空现场
最后阶段不建议再改算法,而是把现有的东西稳定复现给老师看。我会做三件事:先写一个批量跑图脚本,把测试目录里的雾图全部处理后输出到test_out,避免现场演示时一张一张手动上传浪费时间。
import glob import os import cv2 from dehaze import dehaze os.makedirs('test_out', exist_ok=True) for path in glob.glob('test_imgs/*.jpg'): img = cv2.imread(path) result = dehaze(img, omega=0.95, t0=0.1) out_path = os.path.join('test_out', os.path.basename(path)) cv2.imwrite(out_path, result) print(f"done: {path} -> {out_path}")第二步是跑指标。有多张带参考清晰图的样本时,计算平均 PSNR 和 SSIM 并做成表格,写进 LW 或论文的实验章节。主观对比放三组:浓雾、薄雾、夜晚/背光场景,每组放原图、结果图、放大细节图。答辩时老师看完图再看到表格,就会认为你做了完整验证。说明文档里把环境要求、启动步骤、账号密码、测试样例四件事列清楚,PPT 按“问题背景、算法原理、系统设计、结果展示、总结”五页走,十分钟讲完绰绰有余。
第三件事是清空现场。我会把所有测试记录在 admin 里删除,media目录下杂乱结果清掉,再用冷启动命令重新跑一次:runserver、登录 admin、上传雾图、确认展示页三个动作全部录屏。带过那么多毕设,翻车最多的不是算法写不出来,而是现场演示时 MySQL 没启动、图片路径不对、只跑通一张图。把这些变成固定检查清单,比临时抱佛脚有用得多。希望帮到你。
本文还有配套的精品资源,点击获取