简介:面向计算机相关专业毕业设计与人流量检测实战的完整项目源码包,基于深度学习完成系统设计,内含 Python 源码与项目说明文档,可作为高分毕业设计参考,辅助完成从环境配置、模型搭建到结果展示的完整流程。项目经过严格调试,能够直接运行,同时适用于课程设计、期末大作业。压缩包共 1235 个文件,大小约 61.78MB,以 76 个 Python 源文件为核心,配合 382 个 HTML 页面、194 个 JS 脚本、208 张 PNG 图片以及 CSS、配置与说明文档等,覆盖前端展示、后端接口、模型调用和部署配置模块。目前已有 435 人学习/下载。包内项目说明与工程目录结构清晰,便于快速定位入口文件和核心逻辑;从前端页面到后端处理均有相应实现,能帮助理解人流量检测系统的完整交互流程,也适合在现有代码基础上扩展与二次开发。
1. 人流量检测作为毕设:我能用它干什么、到底要交什么
如果你的毕业设计题目是“基于深度学习的人流量检测系统设计与实现”,那你大概率已经查过一圈资料,发现网上要么是单纯讲 YOLO 原理的教程,要么是连数据集都没有的“半成品”。这个题目真正要交付的东西其实很明确:一个能对图片或视频中的人进行识别、计数并输出结果的系统,配套 Python 源码、训练好的模型和一份能讲清楚“为什么这么做”的说明文档。和做口腔疾病图像识别这类深度学习视觉毕设一样,核心方法论是相通的——先选定模型,再解决数据,最后把检测逻辑封装成可以演示的系统。本文不教你重新发明目标检测,而是按“选型、准备数据、训练、串成系统、避坑”这条路线,把每一步做成你能直接复现的作业。新手按命令走能跑通,熟手直接看参数和边界坑。
2. 方案选型:为什么毕设几乎都落在 YOLO + 计数逻辑上
2.1 检测方案对比:为什么不是 Faster R-CNN,也不是 SSD
人流量检测本质是目标检测加上计数后处理。现阶段能落地且易解释的路线主要是三条:Faster R-CNN、SSD 和 YOLO 系列。Faster R-CNN 准确率高,但两阶段结构在视频推理时帧率低,如果你要做实时演示,画面一卡一卡很减分。SSD 速度尚可,但小目标效果一般,而人流场景里远处的人往往只占几十个像素,正好打在短板上。YOLO 系列是目前毕设里最稳妥的选择,v8 前后的版本在精度和速度之间平衡得最好,官方有预训练权重,数据格式相对友好,而且你答辩时能拿“准确率和实时性如何权衡”这个点展开讲。
我一般建议选 YOLOv8 或 YOLOv5 作为检测基座,前者接口更现代,后者生态文档更多。如果你用的是 PyTorch,两者都原生支持,不用额外装奇怪的依赖。对于人数统计,不要直接在检测框数量上求和,而是按检测框中心点做区域判断,加上简单的帧间去重逻辑。后面第四章会给出可运行代码。
2.2 系统模块拆分:拿到题目先画好这四个部分
拿到题目后不要立刻去抄源码,先把这个系统拆成四个模块,后面写代码和写论文都按这个骨架展开:
- 数据模块:图像或视频输入,支持摄像头更佳,但演示环境未必有 USB 摄像头,所以先保证图片和视频文件能跑通。
- 检测模块:利用 YOLO 模型推理出画面中每个人的边界框、置信度和类别,且只保留 person 类。
- 计数模块:对检测框中心点做区域判定,划分“入口区、出口区、停留区”,统计当前人数和累计人数。
- 可视化与输出模块:在帧上绘制检测框和计数结果,可将结果导出为 CSV 或标注后的视频。
这四块基本对应你要写的源码包里的四个目录或四个文件。结构清晰还有一个好处:论文的“系统设计”章节可以按图说话,不用对着一个巨型脚本硬编故事。环境搭建上用 conda 或 venv 隔离 Python 虚拟环境,避免把系统 Python 搞乱。很多同学在这一步就翻车,直接在全局环境 pip install,然后被各种依赖冲突折腾一整晚。
3. 数据准备与模型训练:从标注格式转换到权重复现
3.1 数据集从哪里来:开源数据集和自建小样本怎么搭配
人流检测的训练数据通常有两个来源。第一是开源数据集,常见做法是使用 CrowdHuman、MOT Challenge 或 SCUT-HEAD 等公开数据集,它们都带人框标注。如果你的题目要求场景限定在校园或商场,那么更合理的做法是用开源数据预训练,再补充你自己拍摄的几十到几百张目标场景图片做微调。不要指望自建几百张就能从零训出一个可用模型,深度学习模型的收敛依赖数据多样性,场景太少会出现严重的过拟合。最简单可行的流程是:下载开源数据,用其中的人流量相关子集做训练,自建数据只做测试和少量微调。
数据标注工具常见做法是用 LabelImg 或 Label Studio,前者轻量,后者支持多人协作。标注时只需画矩形框,类别填 person。注意框要贴合人的轮廓,不要上下各留一大截空白,这会影响 IoU 计算,也容易让模型学会“框得比人还大”的错误惯性。
3.2 把 VOC 或 COCO 格式转换成 YOLO 格式的脚本
开源数据集里 CrowdHuman 常用 VOC 风格标注,或者直接给 JSON。YOLO 系列训练需要的格式是:每张图片对应一个同名 .txt 文本文件,每行是“类别 中心点x归一化 中心点y归一化 框宽归一化 框高归一化”。这个转换不写脚本会很痛苦。下面是一个把 VOC XML 转成 YOLO txt 的最小脚本,按实际路径改一下就能用。
import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_dir, txt_dir, class_names): os.makedirs(txt_dir, exist_ok=True) for xml_file in os.listdir(xml_dir): if not xml_file.endswith('.xml'): continue tree = ET.parse(os.path.join(xml_dir, xml_file)) root = tree.getroot() img_w = int(root.find('size/width').text) img_h = int(root.find('size/height').text) txt_name = os.path.splitext(xml_file)[0] + '.txt' lines = [] for obj in root.findall('object'): name = obj.find('name').text if name not in class_names: continue cls_id = class_names.index(name) box = obj.find('bndbox') xmin = float(box.find('xmin').text) ymin = float(box.find('ymin').text) xmax = float(box.find('xmax').text) ymax = float(box.find('ymax').text) x_center = (xmin + xmax) / 2.0 / img_w y_center = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f'{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}') if lines: with open(os.path.join(txt_dir, txt_name), 'w') as f: f.write('\n'.join(lines)) if __name__ == '__main__': class_names = ['person'] voc_to_yolo('data/VOC/annotations', 'data/VOC/labels', class_names)这段脚本的核心是解析 XML 里的bndbox坐标,然后除以图片宽高做归一化。有一点容易忽略:XML 里<size>下的宽高必须和实际图片一致,否则框会错位。我在实际处理中遇到过图片 PNG 是 RGBA 通道但标注尺寸按 RGB 读的情况,导致的直接后果是训练时 mAP 看着正常,推理时框总是偏左上。所以转换完一定要抽样可视化检查,不要直接开训。
3.3 训练命令与关键参数选择
数据准备好之后,用官方仓库的训练入口即可。以 YOLOv8 为例,安装 ultralytics 包后,命令行直接跑:
yolo train data=dataset.yaml model=yolov8n.pt epochs=50 imgsz=640 batch=16 device=0如果数据集目录结构是按 images 和 labels 组织的,dataset.yaml内容类似:
path: ./datasets/flow train: images/train val: images/val names: 0: person训练时最常见的参数选择是:模型规模用yolov8n或yolov8s,毕设场景不需要追求最大精度,显存有限时 n 版本迭代更快;imgsz用 640,人流场景远距离小目标可以试 960,但显存占用会明显涨;epochs不用盲目上 300,50 到 80 轮足够看清收敛趋势,训练日志里box_loss和cls_loss不再明显下降就说明可以停了。batch大小以显存不爆为原则,6GB 显存跑 n 版本设 16 没问题,跑 s 版本要降到 8。梯度累积不熟不要轻易开,保持默认策略更稳定。训练结束后在runs/detect/train目录下看results.png和confusion_matrix.png,前者能看出 loss 曲线是否正常收敛,后者能看出误检主要集中在哪一类上。
4. 把检测模型串成人流量统计系统:计数逻辑与可视化
4.1 检测封装:置信度过滤和类别过滤为什么不能省
模型训练好之后要做的第一件事不是直接数人,而是写一个检测封装函数,只提取 person 类结果,并过滤掉低置信度的框。这一步是计数精度翻车的主要来源。原始 YOLO 推理输出会包含很多置信度只有 0.3 左右的框,这些框在密集人群中大量存在,不及时过滤会把人数虚高。同时,模型如果同时训练了多个类别,必须显式过滤出 person 类,否则会把包、车、树都算成人流量。
下面给出一个封装 YOLOv8 推理的检测函数,输出每帧中所有人的中心点和边界框,供计数模块调用。
from ultralytics import YOLO def detect_persons(frame, model_path='runs/detect/train/weights/best.pt', conf_thres=0.35, iou_thres=0.5): model = YOLO(model_path) results = model.predict(frame, conf=conf_thres, iou=iou_thres, verbose=False) persons = [] for box in results[0].boxes: cls_id = int(box.cls[0]) label = results[0].names[cls_id] if label != 'person': continue x1, y1, x2, y2 = map(float, box.xyxy[0]) conf = float(box.conf[0]) cx = (x1 + x2) / 2.0 cy = (y1 + y2) / 2.0 persons.append({'bbox': [x1, y1, x2, y2], 'center': (cx, cy), 'conf': conf}) return persons这段代码的输出结构很直接:people 列表里每个元素包含检测框、中心点坐标和置信度。conf_thres是保命参数,在白天正常光线下建议设 0.35 到 0.4,夜间或摄像头角度较偏的场景调低到 0.25。iou_thres控制重叠框的抑制强度,密集人群里人挨着人的情况,阈值太严会合并掉真实目标,建议保持 0.5 左右。注意这个函数每次调用都加载一次模型,实际运行时应把模型初始化移到函数外,只加载一次,否则视频流处理时会非常慢。
4.2 区域计数与帧间去重:只统计中心点是不够的
在单张图片上,直接统计persons的长度就是当前人数。但视频流里同一个路人会在连续几十帧反复出现,直接累加会把人数放大几十倍。常见的做法是“按帧计数”和“按进入事件计数”两种。校园入口场景通常要的是累计通过人数,这时就得做去重或区域判定。如果只做当前画面人数统计,直接取当前帧的检测框数量即可,这个值会随时间波动,正好画成折线图。
下面给一个简单的按区域计数的示例,把画面中线当作分界,统计从下往上穿过中线的目标数。这里用目标中心点做判断,并把连续帧间位移小于阈值的检测视为同一目标,避免重复计数。
class FlowCounter: def __init__(self, line_y=480, min_move=5): self.line_y = line_y self.min_move = min_move self.tracks = [] self.passed = 0 def update(self, persons): for p in persons: cx, cy = p['center'] matched = None for t in self.tracks: dist = abs(t['cx'] - cx) + abs(t['cy'] - cy) if dist < self.min_move: matched = t break if matched is None: self.tracks.append({'cx': cx, 'cy': cy, 'last_y': cy}) else: if matched['last_y'] < self.line_y <= cy: self.passed += 1 matched['last_y'] = cy self.tracks = [t for t in self.tracks if t['cy'] > cy - 2] return self.passed这个类的逻辑很朴素:目标中心点在上边界下方出现,当它的 y 坐标从小于分界线变为大于分界线时,判定为进入一次。min_move调太大会把不同的人误判成同一个目标,调太小同一个人的检测框只要轻微抖动就算成新目标,在 720p 画面下一段 50 米通道内,取 5 到 10 是比较稳的范围。真实场景里这种简易追踪在人群密集、遮挡频繁的时段精度会明显下降,但如果你的毕设定位是“系统设计与实现”,这种简单方法足够支撑你完成演示和答辩。
4.3 把结果输出成可视化界面或保存文件
演示时如果用 Flask 起一个本地网页,在网页里实时显示检测结果和人数折线,通常是毕设答辩中最出效果的做法。我一般建议让模型处理视频帧,并把带框的视频保存为 mp4,再同时统计每帧人数输出 CSV。下面是保存标注视频的代码片段,核心是把检测框画到帧上,并按帧写入人数。
import cv2 def make_video_with_count(source_video, out_video): cap = cv2.VideoCapture(source_video) fps = cap.get(cv2.CAP_PROP_FPS) w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) writer = cv2.VideoWriter(out_video, cv2.VideoWriter_fourcc(*'mp4v'), fps, (w, h)) counter = FlowCounter(line_y=h * 0.6) while True: ok, frame = cap.read() if not ok: break persons = detect_persons(frame, conf_thres=0.35) passed = counter.update(persons) for p in persons: x1, y1, x2, y2 = p['bbox'] cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2) cv2.putText(frame, f'PASS: {passed}', (30, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) writer.write(frame) cap.release() writer.release()这段代码的坑在于detect_persons内部每次加载模型,视频里每一帧都要重复加载一次,整个视频处理会慢到无法接受。正确做法是在make_video_with_count外层加载一次模型,把 model 传进函数,或者在 detect 函数外面用全局缓存。另外,视频编码用mp4v通用性最好,Windows 上也能直接播放,换成avc1在一些播放器里反而不能识别。输出 CSV 很简单,每帧记录时间戳和当前人数即可,后面画图表或者做数据分析会非常省事。
5. 人流量检测避坑指南:4 个最容易让毕设翻车的地方
5.1 训练集里人多但标注框漏标,导致漏检率高
现象:模型训练完,测试时一个画面里五六个人,只检测出两三个。 原因:训练集里有很多人被漏标了,或者标注框只框了上半身。模型学到的模式是“人只有半截”,自然漏检。 解决:用标签质量检查工具把每张图的标注框渲染出来看一遍,漏标多的图片直接删掉,不要留在训练集里。另外要保证标注框覆盖整个人体轮廓,头部被截掉一点没关系,但身体中线不能偏。
5.2 计数逻辑直接统计检测框数量,导致人数剧烈抖动
现象:同一段视频,上一帧统计 12 人,下一帧突然变成 20 人,再过两帧又变回 13 人。 原因:不是检测模型出了问题,而是低置信度的误检框在连续帧之间不稳定出现。在某些交叠严重或光照差的帧里,模型把一个手臂误认为一个人,产生了瞬时高值。 解决:置信度阈值从 0.25 提高到 0.4,并加入“连续 N 帧都被检测到才算一次有效计数”的滑窗确认逻辑。这个方法在运动场景里配合min_move调参可以明显压低抖动。
5.3 白天正常、夜间或逆光场景直接崩
现象:同一个模型,白天测试效果不错,一到楼道或傍晚场景,人数骤减,画面上全是漏检。 原因:模型在训练时见过的大多是白天光照正常的数据,没学过低照度下的成像特征。这不是模型玄学问题,是数据分布问题。 解决:收集目标场景的夜间数据,尽量抽出一两百张做微调;如果实在没有夜间数据,可以在推理前对帧做 CLAHE 对比度增强,然后再送进模型。代价是推理帧率会下降一点。如果摄像头是仰角安装的,还要检查图片里人是否出现严重的上下裁切,这种情况下模型训练时见过的完整人体比例与现场不符,也会导致漏检。
5.4 训练 loss 不降反升,或者前几轮就炸掉
现象:loss 曲线在最开始剧烈波动,然后 transformer 式的断崖上升,最终收敛到无法使用。 原因:最常见的是学习率太高。另一个工地现场遇到的问题是把归一化坐标写错,标注文件里某个坐标值大于 1,模型训练时会被拽到错误方向。还有一个可能:dataset.yaml里路径写错,模型实际上读到了空数据集,训练时 loss 自然不稳定。 解决:先跑一个只含十几张图的极小数据集,如果训练能正常收敛,再放大;把学习率从默认 0.01 降到 0.001 跑一遍对照;检查 labels 目录里所有 txt 文件的坐标值是否都小于等于 1。这个做法在调任何检测模型时都适用,用最小配置排除系统性问题。
6. 帧间去重的进阶技巧:用轨迹平滑提高计数的可信度
最后这章不讲大而全的优化,只讲一个能立竿见影的细节:对检测中心点做时间维度的平滑处理。简易追踪里,目标的中心点在连续帧之间会有小幅度抖动,直接把坐标用于区域判定会产生临界帧反复误判。可以维护一个目标轨迹列表,存储最近 5 帧的中心点坐标,只有当当前帧的坐标与轨迹均值的距离小于 1.5 倍最大抖动幅度时才算同一目标,否则开新轨迹。
def smooth_center(track, new_center, max_jitter=12.0): xs = [p[0] for p in track] + [new_center[0]] ys = [p[1] for p in track] + [new_center[1]] return (sum(xs) / len(xs), sum(ys) / len(ys))这个技巧的核心是:不要直接使用单帧的检测坐标去触发计数事件,而是使用平滑后的轨迹坐标。触发越过门限的判定条件不变,但坐标来源变了之后,边界抖动造成的重复计数明显减少。我现在的习惯是,每次调整检测参数后,会固定同一段测试视频,分别统计平滑前后的误差绝对值,比较人群通过量相对参考值的偏差。这个办法能把很多“看起来合理、实际不稳”的系统在交付前排掉。
希望这段思路能帮你把“基于深度学习的人流量检测系统”这个题目从头到尾串透彻。不管最后提交的源码包结构是什么样,请务必保证里面包含一份能完整复现的 README、训练好的权重文件和你自己录制的一段能跑通的演示视频,这会让你省去大量答辩时的解释成本。
本文还有配套的精品资源,点击获取