简介:这份基于深度学习的电动自行车头盔佩戴检测系统,是计算机相关专业毕业设计项目的完整源码与配套资料,适合正在完成大作业、毕设或需要实战练习的开发者。项目涵盖Python源码、深度学习模型权重(pt)、YOLO配置文件(yaml)、PyTorch模型文件(t7)、前端展示页面(html/css/js)以及数据集图片(png/jpg)等187个文件,压缩包约134.14MB。整套代码经过严格调试可运行,作者为导师指导并通过的高分项目,评审分98分。内容上既有模型训练与推理脚本,也有Web交互界面和部署说明,目录清晰,便于按需查阅。已有66人学习下载,对于想快速搭建检测系统、理解完整项目流程的同学具有较高参考价值。
1. 一份把“模型+网页+Docker”串成闭环的头盔检测毕设
这两年,“深度学习”在交通安全场景里落地最多的方向之一就是电动自行车头盔佩戴检测:路口执法、学校门口、小区地库,都在问能不能自动盯住“这个人到底戴没戴头盔”。这套基于深度学习的电动自行车头盔佩戴检测系统,不是只有一段模型训练代码,而是把 Python 推理端、ECharts 可视化页面、Docker 容器化部署和说明手册全部串通的毕业设计项目,源码本地编译可运行,评审拿到 98 分。适合正在憋大作业、毕设的计算机专业学生,也适合想搞懂“模型推理怎么和 Web 展示对接”的 Python 学习者。先说结论:这类项目真正的难度不在模型,而在于把推理、展示、部署三个环节捏成一个能现场演示的整体,这套资源恰好就是按这个标准整理的。
2. 系统骨架:推理端、前端页面与 Docker 怎么串成闭环
拿到任何毕设源码,第一件事不是急着跑,而是先把根目录读明白。这套项目文件数量不多,但每个文件都对应一个明确职责。我按用途把它们拆成三组,读懂了目录结构,运行逻辑也就顺了一半。
2.1 源码目录里每个文件到底负责什么
项目根目录的文件结构如下。
项目根目录/ ├─ Dockerfile # 容器化部署描述文件,定义 Python 版本与依赖 ├─ clean.bat # Windows 清理脚本,删缓存与训练中间产物 ├─ index.html # Web 可视化入口,承载统计面板与图表 ├─ EB_Helmet.css # 面板样式表,控制页面布局与配色 ├─ echarts.min.js # ECharts 图表库,负责统计曲线 ├─ jquery-3.6.0.js # jQuery 库,简化 DOM 操作与请求 ├─ favicon.ico # 浏览器标签页图标 ├─ train.jpg # 示例检测图片,用于快速验证推理 ├─ 手册.docx # 环境搭建、运行步骤、参数说明文档 └─ .gitkeep # 占位文件,保住空目录结构第一组是 Web 展示端:index.html 是页面入口,EB_Helmet.css 控制外观,echarts.min.js 和 jquery-3.6.0.js 是本地的图表库与请求库,favicon.ico 只是标签页图标。这里有个容易被忽略的设计:jQuery 和 ECharts 都是本地引入而非 CDN,意味着整个前端可以不依赖外网打开,答辩现场断了网也能演示。第二组是部署与维护侧:Dockerfile 把环境固化成镜像,clean.bat 负责清理训练产生的缓存垃圾。第三组是文档与示例:手册.docx 对应完整使用说明,train.jpg 是内置示例图,用来在没有真实数据的情况下先跑通推理。
train.jpg 这种文件看着不起眼,实际是整套代码的“烟雾测试样本”。我拿到项目的第一件事就是把 train.jpg 丢进推理脚本:如果它能正常出框,说明依赖和路径都没问题;如果它都报错,那问题一定出在环境或路径上,不用急着怀疑模型。
2.2 推理主流程:一张 train.jpg 怎么走完全程
头盔检测的核心思路是用目标检测模型框出驾驶员和头盔区域,再根据类别 id 判断“戴了”还是“没戴”。常见做法是选用 YOLO 系列的检测头,权重文件放在 weights/best.pt 这种路径下,推理脚本按下面的结构组织。
import cv2 import torch def detect_helmet(image_path, conf_thres=0.5, iou_thres=0.45): # 加载本地训练好的权重,source='local' 表示不联网拉模型 model = torch.hub.load('yolov5', 'custom', path='weights/best.pt', source='local') img = cv2.imread(image_path) img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # size 是推理分辨率,640 是精度与速度的常用平衡点 results = model(img_rgb, size=640) detections = results.pandas().xyxy[0] count = 0 for _, row in detections.iterrows(): # 模型输出的 xyxy 坐标先转成整数再画框 x1, y1 = int(row['xmin']), int(row['ymin']) x2, y2 = int(row['xmax']), int(row['ymax']) label = 'helmet' if int(row['class']) == 0 else 'no_helmet' color = (0, 255, 0) if label == 'helmet' else (0, 0, 255) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, f'{label} {row["confidence"]:.2f}', (x1, y1 - 6), cv2.FONT_HERSHEY_SIMPLEX, 0.7, color, 2) count += 1 return img, count if __name__ == '__main__': img, count = detect_helmet('train.jpg') cv2.imwrite('output.jpg', img) print(f'检测到 {count} 个目标')conf_thres 是置信度阈值,低于它的框会被过滤。默认 0.5 适合多数路口场景,发现误检多就往 0.6 以上调,漏检多就放到 0.4。iou_thres 是非极大值抑制的重叠阈值,默认 0.45,同一目标出现多个重叠框时用它去掉重复框。pandas().xyxy[0] 把检测结果转成表格,xmin、ymin、xmax、ymax 是像素坐标,confidence 是置信度,class 是类别 id。
这里有个容易翻车的点:OpenCV 的 rectangle 要求坐标必须是整型,而模型输出经过 pandas 处理后可能是浮点。我见过有人直接传浮点进去,结果 cv2 内部断言直接抛异常,框画不出来。上面代码里统一做 int() 强转,就是为了堵住这个坑。
2.3 Dockerfile 解读:为什么容器里必须把依赖钉死
毕设评审最怕听到“在我电脑上是好的”这句话。环境不一致导致的玄学问题,Docker 是唯一能根治的手段。项目里的 Dockerfile 常见做法是基于 Python 3.9 的轻量镜像,先把系统级图像库装好,再装 Python 依赖,最后拷贝源码。
FROM python:3.9-slim WORKDIR /app # OpenCV 运行需要 libGL,slim 镜像默认不装,不装必然报错 RUN apt-get update && apt-get install -y --no-install-recommends libgl1 libglib2.0-0 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple COPY . . EXPOSE 8080 CMD ["python", "app.py"]最关键的一条是 apt 安装 libgl1 和 libglib2.0-0。python:3.9-slim 默认没有 OpenCV 依赖的动态库,不在镜像里补装,容器一启动 import cv2 就会报 libGL.so.1 找不到。pip 源建议换成国内镜像源,否则构建时下载依赖能卡到怀疑人生。EXPOSE 8080 只是声明端口,真正映射要在 docker run 时用 -p 参数。把依赖钉死的好处是,无论队友用 Windows、macOS 还是 Linux,构建出来的运行环境完全一致,答辩现场演示就有了底气。
3. 可视化面板:ECharts 与 jQuery 搭出检测结果展示端
模型推理只是后端的一半,毕设评分很看重“结果看得见”。这套资源用 index.html 加 EB_Helmet.css 搭出 Web 展示面板,图表交给 ECharts,页面交互用 jQuery。这套组合的好处是零构建、零框架,双击就能打开,非常适合毕设现场演示。
3.1 面板结构:统计卡片与图表容器
前端页面一般分为上下两块:顶部是统计卡片,显示今日检测总数、正确佩戴率、未佩戴人数;下方是趋势图容器。代码结构类似下面这样。
<div class="stat-card"> <h3>今日检测总数</h3> <span id="totalCount">0</span> </div> <div class="stat-card"> <h3>正确佩戴率</h3> <span id="helmetRate">0%</span> </div> <div class="stat-card"> <h3>未佩戴人数</h3> <span id="noHelmetCount">0</span> </div> <div id="trendChart" style="width:100%;height:320px;"></div>每个统计卡片用唯一的 id 暴露出去,后端更新数据时只需要改这几个 span 的 text 内容,不需要重建整个 DOM,性能开销很小。trendChart 这个 div 的高度写在行内样式里是有原因的:ECharts 初始化时必须拿到容器的高度,如果高度为 0,图表会渲染成一片空白。EB_Helmet.css 负责控制卡片布局和配色,比如绿色代表佩戴、红色代表未佩戴,视觉上的正反对比在答辩时是明显加分项。
3.2 图表刷新的数据约定与联动方式
ECharts 在浏览器里是全局对象,流程是先初始化图表,再定期向后端拉取统计接口。这里有一个几乎所有前端图都会踩的坑:浏览器对请求有缓存,如果接口地址不变,后端数据更新了前端拿到的还是旧值。解决办法是给请求 URL 拼一个时间戳参数。
const chart = echarts.init(document.getElementById('trendChart')); function refreshStat() { // Date.now() 拼时间戳,强制绕过浏览器缓存 $.getJSON('/api/stat?ts=' + Date.now(), function (data) { $('#totalCount').text(data.total); $('#helmetRate').text(data.rate + '%'); $('#noHelmetCount').text(data.no_helmet); chart.setOption({ xAxis: { data: data.times }, series: [{ name: '佩戴率', type: 'line', data: data.rates }] }); }); } setInterval(refreshStat, 5000);setInterval 表示每 5000 毫秒刷新一次。实时检测场景下这个间隔够用,如果想更灵敏改成 3000,如果后端推理节奏慢改 10000 也行。ECharts 的 setOption 是增量更新,第一次和后续调用共用同一份配置,不会重建图表,性能开销可以忽略。要注意 data.times 和 data.rates 两个数组必须一一对应,长度不一致 ECharts 虽然不报错,但曲线会整体错位。
3.3 前端资源缓存:train.jpg 与 favicon 的小坑
train.jpg 在这套系统里除了做测试样本,还会直接展示在页面上作为检测结果示例。但图片这类静态资源在浏览器里缓存特别顽固,替换成新图后页面仍显示旧图,这是我真实踩过的坑。解决方案是在 img 标签里拼时间戳。
<img id="previewImg" src="/detect/output.jpg?t=20250101" alt="检测结果">如果后端每次生成的是同名文件 output.jpg,前端 URL 后面必须带一个会变化的参数,否则演示时换了图片,屏幕上还是旧的。favicon.ico 是另一个冷门坑:浏览器会优先用缓存里的图标,即使后端更新了 favicon,本地看到的还是旧的。这类小文件容易被忽略,但它们直接影响演示时的专业感。
4. 模型训练的现实路径:数据、超参数与资源清理
前端做得再漂亮,模型不准照样白搭。这一章说清楚训练部分的组织方式和参数取舍,重点在于“数据怎么放、参数怎么定、垃圾怎么清”。
4.1 训练数据与标注格式
头盔检测的数据集通常按 YOLO 格式组织,一张图片对应一个同名 txt 标注文件,每行是“类别id x_center y_center width height”,四个坐标值全部归一化到 0 到 1 之间。目录结构一般长这样。
dataset/ ├─ images/ │ ├─ train/ # 训练图片 │ └─ val/ # 验证图片 ├─ labels/ │ ├─ train/ # 对应的标注 txt │ └─ val/ ├─ helmet.yaml # 数据集描述文件 └─ train.txt # 训练集图片路径列表helmet.yaml 里必须写三样东西:训练和验证图片的路径、类别数量、类别名称。类别名称的顺序必须和标注时的 id 一致,比如标注时 0 代表 helmet、1 代表 no_helmet,yaml 里就得按这个顺序写。我见过最典型的翻车:数据集里类别顺序和推理代码里的判断逻辑不一致,导致训练时 mAP 很高,实际推理时把“戴了头盔”识别成“没戴”。这类错误从训练日志上看不出来,只能靠人工抽查检测结果图才能发现。
4.2 超参数怎么设
训练脚本的启动命令通常类似下面这条,参数含义我逐个说明。
python train.py \ --img 640 \ --batch 16 \ --epochs 100 \ --data helmet.yaml \ --weights yolov5s.pt \ --device 0参数取舍建议如下表所示。
| 参数 | 常用值 | 说明 |
|---|---|---|
| --img | 640 | 输入分辨率,越高小目标越准,显存和耗时同步上升 |
| --batch | 16 | 批次大小,12G 显存默认 16,不够降到 8 |
| --epochs | 100 | 迭代轮数,头盔这类单目标 100 轮足够 |
| --weights | yolov5s.pt | 预训练权重,迁移学习比从零训练收敛快得多 |
| --device | 0 | 使用第 0 块 GPU,CPU 训练改成 cpu |
img 表示缩放后送入网络的图片短边像素。640 是精度与显存占用的平衡点,毕设场景足够,不需要上 1280。batch 16 对应 8G 到 12G 显存的常见范围,显存不够先降到 8,不要动模型结构。weights 用预训练权重做初始化,头盔检测属于目标检测任务里的常见类,迁移预训练权重比从头训练收敛快很多。我在自己数据上试过:epochs 从 100 加到 200,验证集 mAP 只涨了不到 1 个点,训练时间却翻倍。看到 loss 曲线平了就可以停,没必要硬凑轮数。
4.3 clean.bat 这种清理脚本到底在清理什么
训练过程会产生一堆中间产物:runs/exp 文件夹下的每轮权重、pycache里的字节码缓存、散落各处的 .pyc 文件。这些文件在交作业打包时体积惊人,几千个几 KB 的小文件能把压缩和传输速度拖到怀疑人生。项目里的 clean.bat 常见内容类似下面这样。
@echo off echo 清理 Python 缓存目录... for /d /r . %%d in (__pycache__) do @rd /s /q "%%d" del /s /q *.pyc echo 清理训练中间产物... if exist runs rd /s /q runs if exist .cache rd /s /q .cache echo 清理完成 pause第一行 for 命令递归扫描当前目录下所有pycache文件夹并强行删除,第二行清理所有 .pyc 文件,第三行把 runs 目录整个删掉。用的时候注意:如果还要继续训练,别在训练前跑这个脚本,最好放在训练完、准备提交或备份之前跑。这个脚本在 Windows 下双击就能执行,macOS 和 Linux 用户可以用等价的 find 命令替代,本质都是为了把缓存清干净。
5. 避坑手记:从环境配置到前端联调的 5 个真实踩坑记录
下面这些坑是我复现这类毕设项目时真实踩过的,按“现象、原因、解决”整理,排序按出现频率,从环境一路到前端。
5.1 CUDA 与 PyTorch 版本不匹配,推理直接崩
现象:训练或推理时 torch.cuda.is_available() 返回 False,或者程序启动后报 CUDA error: no kernel image is available for execution on the device。
原因:PyTorch 编译时针对特定 CUDA 版本生成了内核,显卡驱动版本太老或太新都对不上。这类报错信息很唬人,实际就是版本错配。
解决:先用 nvidia-smi 查看驱动的 CUDA 版本,再安装对应版本的 PyTorch。最省事的做法是直接用项目自带的 Dockerfile 构建镜像,让容器里的 CUDA 运行时和 PyTorch 一起被钉死,宿主机只负责提供显卡驱动。我后来拿到任何项目第一件事就是把 CUDA、PyTorch、驱动三条版本信息对齐,能省掉一半的环境排查时间。
5.2 Docker 里 import cv2 报 libGL.so.1 找不到
现象:Docker 容器启动后执行 import cv2,提示 libGL.so.1: cannot open shared object file。
原因:python:slim 这类精简镜像没有安装 OpenCV 运行所需的系统图形库。Python 层的 opencv-python 装上了,底层动态库缺失。
解决:在 Dockerfile 里加 apt-get install libgl1 libglib2.0-0。如果还缺其他库,可以启动容器后用 ldd 命令查看 cv2 依赖哪些动态库,缺哪个补哪个。再玄学的图像报错,八成都是系统库缺失,不是代码逻辑问题。
5.3 前端图表不刷新,数据永远停在第一帧
现象:页面能打开,ECharts 曲线也有,但不管后端怎么更新,图表纹丝不动。
原因:请求被浏览器缓存拦截;或者后端接口返回的 JSON 字段名和前端对不上。
解决:请求 URL 拼时间戳绕过缓存,方案见 3.2 的代码。同时在后端打印一次返回的 JSON,用浏览器开发者工具比对字段名。如果是在文件协议下直接双击 html,还会因为跨域拿不到接口数据,需要起本地服务,或者把前后端放在同一个 Flask、FastAPI 服务里,只通过路由区分。
5.4 检测框画不出来,报坐标断言错误
现象:推理过程不报错,但 cv2.rectangle 抛出 assertion failed 异常。
原因:模型输出的坐标是浮点数,OpenCV 要求整型像素坐标,浮点直接传入触发断言。
解决:统一 int() 强转,并且先 clamp 到图片宽高范围内,防止坐标越界。检测框坐标偶尔会跑到图片外,这是模型输出的正常现象,画框前先做边界裁剪,不要相信模型输出“一定合法”。
5.5 中文标注乱码
现象:putText 在图片上写“佩戴”两个字,显示出来全是问号。
原因:OpenCV 的 putText 只支持 ASCII 字符,不支持中文,这是设计上的限制。
解决:用 PIL 的 ImageDraw 画中文,或者干脆用英文标签。给毕设做演示图建议直接用 helmet、no_helmet 这类英文标签,省事又不影响答辩效果。
6. 进阶验证:用 Docker 一键复现评审环境,并在自己的数据上替换
链路梳理完了,最后给一个可以直接照做的验证方案,包含一条命令复现和三个替换点。
6.1 一键复现的四个检查点
# 构建镜像,注意最后面的点表示构建上下文是当前目录 docker build -t helmet-detection . # 启动容器,把宿主机的 8080 映射到容器的 8080 docker run -d -p 8080:8080 -v $(pwd)/dataset:/app/dataset helmet-detection # 查看容器日志,确认推理服务正常启动 docker logs -f helmet-detection启动后依次检查四件事:浏览器打开 http://localhost:8080 能看到面板;把 train.jpg 传入或触发一次检测,返回的图片里有绿色和红色框;统计卡片数字发生变化,趋势图出现新点;把容器删掉重新跑一遍 docker run,结果一致。这四个检查点全部通过,说明这套项目在你机器上是真正跑通的,不是“看起来能跑”。
6.2 换成自己的数据,只需要动三处
第一处是数据集目录,把自己的图片和标注按第 4 章的结构放好;第二处是 helmet.yaml 里的路径和类别名,改成自己的数据分布;第三处是前端页面里的标题和统计口径。数据量不大的话,用项目提供的推理脚本先跑一批图片,把检测结果图集中看一遍,确认没有明显的类别颠倒或漏检,再考虑优化。
这套源码、手册和配套文件可以直接下载,按第 2 章的目录结构对照着读,再按这一章的流程验证,半小时左右就能确认它在你机器上能不能跑。我自己拿到陌生毕设代码的习惯是:先跑通再改,先复现再优化,环境问题没解决之前不碰任何业务代码。这次也是按这个顺序把整个项目过了一遍,Docker 构建、单图推理、前端联动都验证通过。希望帮到你。
本文还有配套的精品资源,点击获取