基于YOLO的人流量检测系统:从模型训练到Web端完整实现

发布时间:2026/9/24 20:24:36
基于YOLO的人流量检测系统:从模型训练到Web端完整实现 简介基于深度学习的人流量检测系统毕业设计项目采用Python语言开发面向计算机等相关专业学生可用于毕业设计、课程设计及期末大作业。项目已经过导师指导并获高分通过代码完整、依赖齐全下载后配置好环境即可直接运行能够实现视频或图像中的人流检测与统计。压缩包共包含1235个文件整体约61.75MB以76个Python源码文件为核心配合382个HTML页面、194个JavaScript脚本以及CSS、PNG、GIF等资源覆盖前端界面、后端逻辑、模型调用等完整的系统实现。目前已有170人学习下载适合需要快速搭建深度学习应用或参考完整项目工程的同学。通过研读源码可深入理解目标检测模型用于人流统计的思路、Web端交互设计以及项目的部署流程便于在此基础上进行二次开发和论文撰写。1. 人流量检测毕设千篇一律分数差在选择和工程完整度上一到答辩季十个做视觉毕业设计的人里至少有三个是“人流量检测”。听起来是大热门实际上失分点高度重复模型跑通了但只知道调别人的 demo换数据就崩计数逻辑只在视频上“看着对”一问原理就卡壳演示端要么是黑漆漆的终端窗口要么是没法交互的静态截图。这篇笔记不跟你谈空泛的“深度学习入门”直接按一套能拿高分的完整方案拆模型选型、数据集处理、训练命令、计数逻辑、Web 演示端、答辩前必须避开的坑。你读完能把 Python 工程从 data 目录一路搭到前端页面也能回答“为什么用这个模型”“为什么这个参数能提高精度”这类必问题。适合两类人一是选题正好是“基于深度学习的人流量检测系统设计与实现”的毕业生手里有源码包但不知道怎么讲清楚二是想用这个项目练手、准备投算法岗简历的 Python 开发者。下面所有内容围绕最常见的完成方案——YOLO 系列检测器加目标追踪加 Web 展示——来讲不绑定某个所谓“唯一”源码包按这套思路你能独立复现百分之九十的同类项目。2. 人流量检测的模型选型与数据集准备为什么大家最终都回到 YOLO2.1 检测算法选型YOLO 不是最优解但它是毕业设计里最可靠的解人流量检测本质上是目标检测任务把画面里每一个“人”或者“人头”框出来再在框的基础上做统计。可选的技术路线很多Faster R-CNN 系列、SSD、CenterNet、YOLO 系列甚至直接用 ViT 做端到端检测。但毕设场景下我强烈建议你把重心放在 YOLO 上原因不是它精度最高而是工程收益最好。从硬件条件看大部分本科毕设只有一块消费级显卡甚至可能是 6G 显存的笔记本Faster R-CNN 训练一轮的时间成本对调参极不友好。从数据量看YOLO 对几百到几千张图的微调任务收敛得很稳定而自注意力类的检测器在这量级下很容易欠拟合。从答辩角度看YOLO 的社区资料最多无论是 loss 曲线、anchor 原理还是 mAP 计算都有大量可查证的材料老师追问起来你不会被卡住。下面是几条常被拿来对比的路线按我的实际经验打分参考方案训练速度精度上限部署难度毕设推荐度Faster R-CNN慢高中不推荐调参周期太长SSD快中低低一般小目标表现弱YOLO v5/v8快高低推荐自己搭 CNN 检测头快低低不推荐答辩说服力弱“自己用 CNN 搭一个分类网络再滑窗检测”听起来很酷但滑窗在 1080p 图上做一次推理要几秒钟根本撑不起实时展示。我见过有同学用 ResNet 做图像分类然后滑动切片结果密集人流场景下人贴人全糊成一团这种方案在答辩现场翻车概率极高。2.2 数据集路线公开数据集打底还是自己标注现场数据人流量检测的数据集准备有两套常见做法按你的场景选第一套是纯公开数据集打底。用 COCO 数据集里的 person 类别做预训练或直接微调这是最省事的路径覆盖面广各种角度、遮挡、光照都有。COCO 的 person 类对“行人全身”效果不错但如果你要检测“人头”还需要另外补数据。人头检测在密集场景下更实用因为人挤人的时候大部分身体被遮挡了只有头露在外面。常见做法是引入 CrowdHuman 这类密集人群数据集里的人员框标注或 VisDrone 里 person 类的俯视数据。第二套是自己录视频标注。如果你要面向校园教室、商场入口等特定场景性能和说服力都更强。用 LabelImg 或 Labelme 画矩形框注意人流量检测的标注规范和通用目标检测不太一样。我一般要求三个原则一是只标可见区域身体被挡了百分之六十但头完整可见就只标头的框二是边界上的半个人要标不能因为走出画面就跳过不然计数会忽多忽少——这条直接影响最后人流统计的准确性三是海报、广告牌、橱窗里的人像不要标它们是检测器最容易误报的硬负样本后面避坑章节会细说。自己标注的数据量按百级起步密集场景建议每人头至少一百个实例。2.3 微调框架迁移学习比从零训练靠谱得多选定模型和数据后不要从随机权重开始训练。一个成熟的人流量检测系统常见做法是拿 COCO 预训练权重做初始化再做迁移学习微调。理由很直接预训练模型已经学会了边缘、纹理、人体部件这类通用特征你需要它学的是“你们现场场景下的人群形态”和“人头与背景的判别边界”而不是重新教它认识什么是人脸和肩膀。我常用的一套微调参数是输入图像尺寸 640batch size 取显卡显存能承受的最大值初始学习率 0.01训练 100 个 epoch前 10 个 epoch 冻结 backbone 的前几层只训练检测头后面解冻全网络用更低学习率精调。如果你拿到的源码包是基于 YOLO 改的训练入口通常是 train.py核心参数从命令行或者一个 yaml 配置里读。一个典型的项目数据配置长这样# data.yaml 项目数据配置 train: ./datasets/crowd/images/train # 训练集图片目录 val: ./datasets/crowd/images/val # 验证集图片目录 nc: 1 # 类别数人流量检测通常只有 person 一类 names: [person] # 类别名称 # 以下是建议不是硬字段 # 如果你同时检测人头和人体nc 改成 2 # names 改成 [head, person]这套配置的意义在于把数据和模型解耦。你换一批数据只需要改路径和类别数模型代码完全不用动。很多源码包把类别数写死在代码里换数据后训练不报错但 loss 一直不降——这种定位起来很费时间所以拿到任何源码先检查 nc 是不是和你的标注一致。参数上还要留意一个点如果你只检测人头类别名别写成 person训练时没问题但混淆矩阵和答辩展示时老师看到“person 检测头”会下意识觉得你整个任务定义混乱。3. 跑通人流量检测源码最小训练命令与人流计数逻辑3.1 一套合格的项目目录长什么样拿到一个深度学习毕设源码先别急着点运行先花十分钟把目录结构认清楚。一套合格的人流量检测系统目录组织一般是下面这样的松散约定pplcounter/ ├── data/ # 数据集软链接或数据配置文件 │ └── data.yaml ├── models/ # 模型定义与 Backbone 配置 ├── weights/ # 预训练权重和微调结果 ├── train.py # 训练入口 ├── detect.py # 单图/视频推理入口 ├── count.py # 人流计数核心逻辑 ├── utils/ # 通用工具绘图、指标计算、视频读取 ├── webapp/ # 演示端Flask 等 └── requirements.txt # Python 依赖清单注意这个结构不代表你手上的压缩包里一定长这样但绝大多数合理实现都能映射到这个框架。你拿到源码后第一件事是把 train.py、detect.py 和 count.py 这三个文件打开通读一遍确认模型加载路径和数据路径是相对路径还是绝对路径——用绝对路径的源码在别人机器上跑不通是家常便饭。3.2 最小训练命令从数据校验到权重产出训练之前先确认环境。深度学习环境配置是另一个大坑我建议你就用 conda 建一个独立环境Python 版本选 3.8 或 3.9PyTorch 版本跟着你的显卡驱动决定没必要追最新。装依赖用一行命令搞定pip install -r requirements.txtrequirements.txt 里通常包含 torch、torchvision、opencv-python、numpy、pyyaml、tqdm 这些缺什么补什么即可。然后做一次最小训练验证命令如下python train.py --data data/data.yaml --weights yolov5s.pt --img 640 --batch 8 --epochs 1这一条命令的重心不是真的训练而是验证数据加载、标签解析、模型前向全链路没有报错。如果这行能跑完一个 epoch说明你的数据和代码是通的。真正微调用下面这组python train.py --data data/data.yaml --weights yolov5s.pt --img 640 --batch 16 --epochs 100 --cache --patience 15 --project runs/train --name crowd_v1参数含义按毕设答辩必问的标准来讲--img 是输入图片缩放尺寸人流量检测里人群密集区域的小目标多从 640 提升到 768 通常涨点但显存占用和推理耗时都会上升--batch 受显存约束用梯度累积可以变相加大 batch但收敛速度不会同比例提升--cache 把图片提前加载进内存而不是每轮重复读磁盘能提速百分之二十到三十--patience 是早停轮数loss 连续 15 个 epoch 不降就自动保存最优权重退出避免熬夜等一个永远不收敛的训练。训练完成后权重默认存在 runs/train/crowd_v1/weights/ 下best.pt 是验证集上表现最好的权重last.pt 是最后一轮的权重。平时演示用 best.pt中间断点续训才用 last.pt。3.3 推理脚本加载权重并输出检测框训练完之后你需要一个能对单张图片和单段视频做检测的推理脚本。YOLO 系的官方仓库自带 detect.py用法是python detect.py --weights runs/train/crowd_v1/weights/best.pt --source test_video.mp4 --conf 0.35 --iou 0.45 --save-txt--conf 是置信度阈值0.35 起步比较合理。人流检测场景下你宁可有少量误检也别漏检所以 conf 不要拉太高0.6 以上会把很多遮挡的行人丢掉。--save-txt 会把每一帧的目标位置和置信度写进 txt 文件这是你做计数和后处理的输入。如果你要基于这个结果算人流不能只依赖 detect.py 的输出文件更常见的是在 Python 里直接调用模型接口把检测结果拿回内存做逻辑处理。核心代码如下# 用 YOLO 模型对单张图像做推理并拿到结构化检测结果 import cv2 import torch def load_model(weights_path: str, conf_thres: float 0.35): 加载本地权重, 返回推理模型。Device 自动选择 cuda 或 cpu device torch.device(cuda if torch.cuda.is_available() else cpu) model torch.hub.load(ultralytics/yolov5, custom, pathweights_path, force_reloadFalse) model.conf conf_thres # 低于该置信度的框直接丢掉 model.classes [0] # 只保留 person 类别, 过滤其他类 model.to(device) return model def infer_image(model, image_path: str): 输入图片路径, 返回解析后的检测结果。 results.xyxy[0] 的每一行格式是: [x1, y1, x2, y2, confidence, class_id] results model(image_path) # 内部自动做缩放和归一化 dets results.xyxy[0].cpu().numpy() return [(float(x1), float(y1), float(x2), float(y2), float(conf)) for *box, conf, cls in dets]逻辑上需要注意 model.classes [0] 这个参数它把检测范围锁死在你定义的那个类别上。混合检测人头和人体的场景下你得把它去掉在返回结果里再按 class_id 分流。另外一个很多人踩过的坑是 torch.hub.load 默认会去下载模型结构定义离线环境会卡在下载步骤解决办法是把项目里的模型结构代码放到本地改用 import 方式加载。3.4 人流计数的两种常见实现区域计数与跨线计数检测框只是中间产物毕设真正要展示的是“人流量”这个数字。人流量检测系统里最常见的计数实现有两种按你的应用选型。区域计数统计的是画面中瞬时人数适合密度展示。实现上就是判断每个检测框中心点是否落在 ROI 多边形内落在里面就加一。跨线计数统计的是某个时间段内穿过一条虚拟线的总人数适合出入口统计。跨线需要逐帧追踪同一个目标否则同一人会在连续几帧里反复计数。轻量方案是做个简单的中心点跟踪迭代器# 基于中心点距离的简易目标跟踪计数 class LineCounter: 跨线计数: 检测框中心跨过 line_x 即计数一次 def __init__(self, line_x: int): self.line_x line_x self.tracked {} # track_id - 上一帧中心x坐标 self.next_id 0 self.crossed_ids set() self.count 0 def update(self, boxes, frame_width: int): boxes: 当前帧检测框列表 [(x1, y1, x2, y2), ...] 先忽略检测框的宽高, 只取中心点做前后帧匹配。 这是最朴素的方案, 密集场景会串号, 但演示足够。 new_tracked {} for box in boxes: cx (box[0] box[2]) / 2 cy (box[1] box[3]) / 2 # 找到上一帧里离自己最近且距离小于 50 像素的目标 best_id None best_dist 50 for tid, prev_cx in self.tracked.items(): d abs(prev_cx - cx) if d best_dist: best_dist d best_id tid if best_id is None: best_id self.next_id self.next_id 1 new_tracked[best_id] cx # 跨线判定: 上一帧在左, 这一帧在右 if (self.tracked.get(best_id, cx) self.line_x cx and best_id not in self.crossed_ids): self.crossed_ids.add(best_id) self.count 1 self.tracked new_tracked return self.count这个简易实现的问题会在密集场景暴露人被大范围遮挡再出现时中心点匹配会跟到别人身上去计数立刻不准。它适合那些不太挤、人流量稀疏的场景演示。如果你要做真正的密集人流分析后期必须引入 ByteTrack 或 DeepSORT 这类跟踪器我在最后一章展开。4. 给检测系统做演示端Flask Web 界面的最小实现与前后端联调4.1 为什么用 Flask 而不是花哨的前后端分离架构毕设答辩的演示环境很不稳定可能是老师的电脑、教室的投影仪主机甚至可能临时没网。用 Vue 加 Node 那套答辩现场装环境就得半小时风险极大。我最常用的方案是 Flask 配一个原生 HTML 页面数据走 JSON视频流走 MJPEG整个过程不用装数据库不需要 node_modules。Flask 是 Python 生态里最成熟的轻量 Web 框架和人流量检测源码天然在同一个 Python 环境里不需要额外搭进程。演示端要满足的功能就三个上传一段视频或打开摄像头、页面实时显示检测画面、旁边挂一个不断跳动的人流量计数。这三个功能后端两个接口就能搞定。4.2 后端接口设计上传检测与视频推流# webapp/app.py 人流量检测演示端后端 import os import cv2 from flask import Flask, request, jsonify, Response from collections import deque app Flask(__name__) model load_model(./weights/best.pt) counter_history deque(maxlen200) # 保存最近 200 帧的计数结果 app.route(/detect_upload, methods[POST]) def detect_upload(): 接收前端上传的图片, 返回检测框坐标和人流量统计 file request.files[image] img cv2.imdecode(np.frombuffer(file.read(), np.uint8), cv2.IMREAD_COLOR) boxes infer_image(model, img) # 复用上文推理函数 count len(boxes) # 按瞬时人数统计 return jsonify({count: count, boxes: boxes}) app.route(/video_feed) def video_feed(): MJPEG 视频流: 前端用 img 标签请求这个地址即可实时播放 def generate(): cap cv2.VideoCapture(0) # 0 是默认摄像头 while True: ret, frame cap.read() if not ret: break boxes infer_image(model, frame) draw_boxes(frame, boxes) cur_count len(boxes) counter_history.append(cur_count) ret, jpeg cv2.imencode(.jpg, frame) yield (b--frame\r\n bContent-Type: image/jpeg\r\n\r\n jpeg.tobytes() b\r\n) return Response(generate(), mimetypemultipart/x-mixed-replace; boundaryframe) if __name__ __main__: app.run(host0.0.0.0, port5000, threadedTrue)每个参数都要讲到能答辩的程度。host 设 0.0.0.0 是为了让同一局域网下的手机和投影设备能直接访问如果你的电脑开了防火墙记得放行 5000 端口。threadedTrue 是必须的否则视频流推送给一个前端时整个后端会阻塞另一个请求全部卡死。MJPEG 在广域网场景下带宽占用很大但教室局域网完全够用这是它作为毕设方案最大的合理性。4.3 前端最小页面不写一行复杂 JS演示页面不要用现代脚手架一个 HTML 文件就能说完。关键点是用 img 标签直接指向 /video_feed 地址再开一个 setInterval 定时器每 500 毫秒请求一次 /count 接口刷新人数。两张图秒懂!DOCTYPE html html headmeta charsetutf-8title人流量检测演示端/title/head body !-- MJPEG 视频流直接用 img 显示 -- img src/video_feed width960 height540 h2当前人数: span idcount-display0/span/h2 script async function refreshCount() { const resp await fetch(/count); // 再写一个 /count 接口 const data await resp.json(); document.getElementById(count-display).innerText data.count; } setInterval(refreshCount, 500); // 每500毫秒刷新一次人数 /script /body /html这里解释一个前面的伏笔。img 标签显示视频流的方案兼容性极好不需要处理 WebRTC不需要握手也不怕跨域问题。缺点是无法在画面上叠加绘制检测框因为 img 只是被动接收已经画好框的帧。如果老师当场要求关掉检测框只看原画面你的另一个实现是加一个 query 参数如 /video_feed?draw0后端检测到 draw0 就跳过 draw_boxes 直接推原始帧。这种边缘要求不难但提前做了会显得你考虑得很周全。4.4 有没有必要换成 PyQt 桌面端如果你手头的“高分毕业设计源码”是 PyQt5 写界面的那也很正常这类程序在“人流量检测系统”里占比很高。PyQt 的好处是界面更像传统软件文件选择、按钮、日志框都更顺手缺点是跨机器部署时 PyQt 的版本兼容性比 Flask 差不少——把 conda 环境原样拷到另一台机器上经常编译报错。我的建议是源码里如果已经有一段能跑的 PyQt 界面答辩版本就用它如果要从零写优先选 Flask。不要把时间花在追求界面的炫酷程度上你打开往届区优秀毕设的系统截图会发现高分项目在界面上都是克制的。5. 人流量检测系统的 5 个常见坑现象、原因与解决办法5.1 训练刚开始就报 CUDA out of memory现象训练命令跑起来不到三十秒终端直接红色报错提示 RuntimeError: CUDA out of memory。原因你的 batch size 和输入分辨率把显存撑爆了。尤其在人流量检测里图片尺寸调到了 768 甚至 1280batch 还保持在 16爆显存几乎是必然的。解决优先把 batch 减半然后检查 --img 参数。如果 640 分辨率下 batch 8 仍然爆说明你的显卡显存低于 6G考虑三步解决显存低于 6G 时把模型从模型名带 s 的换到更轻量的版本开启梯度累积用 batch 4 累积两步等效 batch 8把训练数据先缩略处理一遍再送进网络。答辩并不要求你用多大数据量一个 2000 张图片、单类别的微调任务batch 4 和 batch 16 的最终 mAP 差距通常不超过 3 个百分点。为了调大 batch 去买新显卡完全没必要。5.2 模型把海报、广告牌上的人像当成真人现象训练完测试时路牌广告、橱窗模特、电视画面里的人被框了出来实时计数虚高得离谱。原因数据集里几乎全是真实人物没有加入“画中人”这种硬负样本检测器学到的是纹理特征分不清真实人物和平面印刷品。解决回到数据环节手动采集一批包含海报、广告牌、大屏幕的照片全部标为不带标签的背景图放进训练集。YOLO 训练时没有标注框的区域会被当作背景模型自然会学习抑制这些误检。这里我提醒你一个细节别把海报上的人用 person 框标出来继续训练那会让模型更加坚定这些是真人。正确做法是整张图不标注任何框作为纯背景参与训练。加上一百来张这种“负样本”误检基本能压到可接受范围。5.3 计数结果忽高忽低同一个人被反复数现象演示时视频里一个人在画面边缘来回走动计数从 5 涨到 15 又跳回 5看起来完全不可信。原因瞬时区域计数的逻辑没有做去重——同一个目标在边缘区域进进出出每一帧都被当成了新的人或者检测的置信度阈值太低某几帧检测器闪烁一会儿框出来一会儿框不出来。解决区域计数里给检测框加一个现实约束目标中心点必须连续两帧以上出现在 ROI 内才算数同时给检测器设两个阈值检测时用 0.25 的低阈值保召回展示计数时用 0.5 再做一次过滤把不确定的框过滤掉。再进一步就是对视频做抽帧计数每秒只取一帧做统计而不是每帧都累计这个技巧能显著让数字稳定下来虽然牺牲了实时性但演示效果反而更自然。5.4 答辩电脑没 GPU推理卡成幻灯片现象你在自己电脑上跑 30 FPS 的检测到答辩现场用老师的老笔记本没 GPU视频走一步停三秒原本想展示的实时性完全崩了。原因模型权重按 GPU 推理优化在 CPU 上跑了完整浮点计算流程检测帧率直接掉一个数量级。解决有两层准备。第一层是推理端优化训练完把 best.pt 导出成 ONNX 并在 CPU 上用 OpenVINO 加速常见源码里都有 export 脚本导出后推理速度通常能快 2 到 4 倍第二层是降低画面负载答辩现场准备一段 720p 的预录视频而不是现场开摄像头视频文件放在 SSD 里流畅度远大于实时解码摄像头。我自己的习惯是提前准备两台设备——主力机跑实时摄像头备用机跑预录视频现场谁出问题用谁。这个习惯在毕业答辩这种不能 NG 的场景里救过我两次希望用不上但准备了一定不亏。5.5 中文路径与文件名引发的诡异报错现象代码在自己电脑跑得好好的拷到另一台电脑后死活加载不了权重报错信息有时候是 file not found有时候是 OpenCV 读取图片返回空。原因Windows 上项目放在了带中文的目录里比如 “C:\Users\张三\毕业设计\基于深度学习的...”YOLO 的很多工具函数对中文路径支持有缺陷或者 Python 的默认编码和你源码文件的编码不一致。解决项目根目录改成纯英文路径里不要有空格、括号、中文把数据集的路径改成相对路径读取。代码文件统一用 UTF-8 编码保存并在 train.py 和 detect.py 开头加两行import sys sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))这两行让 Python 始终以项目根目录为基准解析模块引用不管从哪个目录启动都不会把相对路径搞乱。这是我做了这么多套源码之后总结出来的通用修复不光是中文路径很多“在我电脑上能跑”的问题都是这个根源。6. 冲刺高分的最后一步把检测系统升级成“统计系统”的验证思路毕设答辩时老师最爱问的一句是“你这个‘人流统计’和人‘目标检测’到底有什么区别”大多数同学的答案停在“检测到人之后数数”这个回答撑不起高分。高分答案是让系统有跟踪能力让统计结果能解释、能验证。任务上把检测框串成轨迹比单独做检测多一层功夫。常见做法是引入 ByteTrack它不需要重识别特征只靠检测框的 IoU 和置信度做关联代码量小、速度影响可控对毕业设计来说也比 DeepSORT 容易讲清楚。引入跟踪之后跨线计数的正确率会有质的提升密集场景下人和人也不会串号。实本文还有配套的精品资源点击获取