
简介面向计算机相关专业毕业设计与人流量检测实战的完整项目包以深度学习算法为主线包含可运行的人流量检测系统源码、数据/模型处理环节及配套项目说明适合正在准备毕设的学生和需要课程设计、期末大作业参考的实战学习者。压缩包共1235个文件大小61.78MB主体由76个Python源码文件、Jupyter Notebook说明文档和项目说明PDF组成另附380余个HTML页面以及大量JS/CSS文件构成的展示界面并有图片、GIF动图、Java与Web辅助文件等便于按模块阅读和复用。内容经过导师指导与认可项目已严格调试并可运行能作为高分毕业设计底稿或动手复现深度学习检测流程的练手项目。已有435人学习/下载适合希望快速搭建人流量检测系统、理解完整工程结构的读者。1. 深度学习人流量检测毕业设计选题里最容易被低估的一类项目商场门厅、地铁站台、校门口不同时段的人流量差别非常大。基于深度学习的人流量检测系统就是把摄像头画面输入检测模型实时识别画面里的行人并输出人数统计。这类题目看起来简单实际做起来要同时碰目标检测、数据集处理、跟踪去重和计数逻辑四块内容恰好是深度学习入门阶段最能体现完整工程能力的组合。这套Python实现的方案从数据集准备、模型训练到计数逻辑一条线走通适合正在选毕业设计题目、或者已经选了人流量检测但还没理清实现路线的同学。源码包和项目说明放在文末先讲清楚这套东西怎么落地。2. 从选型到环境YOLO系检测器、行人数据集与Python环境的一次性配齐2.1 为什么毕业设计场景优先选YOLO系检测器人流量检测的核心是行人检测而行人检测本质上是目标检测的一个子任务。目前能做这个事的主流方案大概分三类两阶段检测器Faster R-CNN 系列、单阶段检测器YOLO 系列、SSD、以及基于 Transformer 的 DETR 系。毕业设计这个场景下我一般会优先推荐 YOLO 系原因很直接训练成本可控、推理速度快、生态成熟遇到问题搜得到答案。两阶段检测器精度上限高但训练和推理都慢做实时人流量统计时帧率很难拉上去。DETR 系在新颖性上有优势但调参门槛高对显存要求也更大本科毕设的时间预算通常撑不住。YOLO 系列在精度和速度之间取得了一个很实用的平衡点而且从 YOLOv5 到 YOLOv8官方都提供了预训练权重基于 COCO 权重做微调行人这类类别收敛非常快。人群密集场景下YOLO 系配合适当的数据增强也能有不错的表现。具体选哪个版本我的建议是如果你的机器只有 6G 以下显存用 YOLOv5s 或者 YOLOv8n 这类轻量模型起步先把流程跑通再谈精度如果显存在 8G 以上可以直接上 YOLOv8m。这套源码包默认按 YOLOv8 的接口组织但数据格式是通用的换版本不需要改数据集。2.2 数据集选型VisDrone、CrowdHuman 与自拍数据的取舍行人检测可用的公开数据集不少但各有各的脾气。这里我列一张对比表方便你按自己的场景选。数据集标注格式特点适合场景VOC 2007/2012XML包含 person 类标注规范小目标少入门验证流程COCOJSON类别全person 类数据量大但图片场景杂通用微调VisDronetxt类似 YOLO 格式无人机视角小目标密集人群多密集人群计数CrowdHumantxt / odgt行人密集遮挡严重框标注完整遮挡场景训练MOT Challengegt.txt视频序列带轨迹标注跟踪计数验证实际做的时候我习惯拿 COCO 的 person 类做预训练微调再用 VisDrone 或者 CrowdHuman 的小目标数据补一轮。有一个容易翻车的点VisDrone 的标注是任意四边形不是标准矩形框直接拿来训练会报错或者产生大量无效框需要先转成 axis-aligned 的矩形框格式。自拍数据这块如果毕设要求必须用自己采集的数据建议用手机固定机位拍一段 5 到 10 分钟的视频抽出帧后用半自动标注工具比如 X-AnyLabeling框人。自拍数据的优势是场景贴近真实应用但不要只靠自拍数据训练数量太少会过拟合正确做法是公开数据集预训练加自拍数据微调。2.3 Python 环境与依赖安装的完整顺序环境配置是第一个大坑。我见过太多人一上来就pip install ultralytics然后遇到 CUDA 版本不匹配、opencv 编译失败一个下午就没了。这里给一套我实际用过很多次的安装顺序。# 1. 创建独立虚拟环境避免污染系统 Python conda create -n crowd_det python3.10 -y conda activate crowd_det # 2. 先装 PyTorch注意根据显卡情况选择 CUDA 版本 # 有 N 卡就用 CUDA 11.8 对应的版本只有 CPU 就装 cpu 版 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 3. 再装 ultralytics它会自动拉取 opencv 和 numpy 依赖 pip install ultralytics # 4. 项目还用到视频处理和简单可视化 pip install opencv-python tqdm装完先跑一条命令验证环境是通的python -c from ultralytics import YOLO; m YOLO(yolov8n.pt); print(model loaded)能打印出model loaded说明环境基本没问题。这里有几个细节值得说明先装 PyTorch 再装 ultralytics是为了让 PyTorch 的 CUDA 版本优先被 pip 识别避免 ultralytics 自动装一个 CPU 版。Python 版本我固定用 3.10因为 3.11 以上在部分 Windows 机器上会遇到 numpy 编译兼容问题。如果你只有 CPU训练会慢很多但流程能走通别在环境阶段就卡住。3. 训练到收敛标注格式、超参与损失曲线的对应关系3.1 数据集目录组织与标签格式转换YOLO 系列统一使用 txt 格式的标签每一行代表一个目标框格式是class x_center y_center width height坐标全部归一化到 0 到 1。这个格式和 VOC 的 XML、COCO 的 JSON 都不一样所以拿到公开数据集第一件事就是转换。目录结构按 YOLO 的约定来组织dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yamldata.yaml是训练入口内容如下path: dataset train: images/train val: images/val test: images/test names: 0: person如果你的原始数据是 VOC 的 XML 格式需要写一个转换脚本。这里给一个已经验证过的转换片段import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_path, out_dir, img_w, img_h): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.iter(object): cls obj.find(name).text if cls ! person: continue # 人流量检测只保留 person 类 bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) # 归一化中心点坐标和宽高都除以图片尺寸 x_center (xmin xmax) / 2 / img_w y_center (ymin ymax) / 2 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f0 {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) out_path os.path.join(out_dir, os.path.splitext(os.path.basename(xml_path))[0] .txt) with open(out_path, w) as f: f.write(\n.join(lines))转换脚本里两个关键点一是只保留 person 类因为人流量检测不需要 car、dog 这些类别留着会让模型在推理时多出无意义的输出二是坐标必须归一化YOLO 训练时会把输入图片 resize 成固定尺寸如果标签还是绝对像素坐标loss 直接爆炸。img_w 和 img_h 从哪来从对应的 XML 里读 size 字段或者用 PIL 打开原图拿宽高别写死不同图片尺寸不一样。3.2 训练脚本与超参速查表数据准备好之后训练本身反而简单。YOLOv8 的 CLI 接口已经把大部分细节封装好了yolo train datadataset/data.yaml modelyolov8n.pt epochs50 imgsz640 batch16 device0参数说明modelyolov8n.pt表示用预训练权重热启动比随机初始化收敛快得多epochs50对行人检测来说基本够用再多容易过拟合imgsz640是默认输入尺寸如果画面里行人特别小可以调到 960 但训练时间会明显变长batch16取决于显存显存不够就减到 8 或 4device0指定用第一块 GPUCPU 训练可以改成devicecpu。训练过程中最重要的参考指标是 loss 曲线但很多人只看 mAP忽略 loss。我的习惯是每 5 个 epoch 看一眼终端输出的box_loss和cls_loss如果两个 loss 同时稳步下降说明模型在学习如果 box_loss 下降但 cls_loss 震荡大概率是正负样本不平衡可以检查一下数据里是不是背景框远多于行人框。这里有一张我常用的超参速查表不同机器可以直接抄参数低配CPU/小显存中配8G 显存说明imgsz480640越小越快小目标会漏batch416显存不够会 OOMepochs3050看 loss 收敛情况lr00.010.01初始学习率workers48数据加载线程数有一个连着问很多人都会忽略的点lr0这个参数在迁移学习时别开太大。预训练权重已经有很好的特征提取能力学习率一大前面几轮就会把原来的特征破坏掉典型表现是训练 loss 先降后升mAP 卡在很低的水平。3.3 从训练日志判断模型是否真的在收敛训练结束之后项目目录下的runs/detect/train里会生成一堆文件最重要的几个是results.png、weights/best.pt和weights/last.pt。best.pt是验证集表现最好的权重last.pt是最后一轮的权重推理和部署一律用best.pt。判断收敛情况的实战经验看results.png里的曲线图重点关注val/box_loss这条线。理想状态是前 20 个 epoch 快速下降后面进入平缓区不再有明显波动。如果到第 40 个 epoch 还在剧烈震荡说明学习率没降下来或者数据有问题这时候调大cosine参数让学习率按余弦曲线衰减或者手动降低lr0。这里有个很多人会犯的错误只看 train loss 曲线不看 val loss。train loss 在过拟合阶段会持续下降但 val loss 已经反弹了模型学会了记住训练集而不是泛化。判断是否收敛要以 val loss 为主train loss 只做参考。训练完成后跑一次验证命令拿到的 mAP50 和 mAP50-95 就是你毕设论文里的核心指标yolo val modelruns/detect/train/weights/best.pt datadataset/data.yaml行人检测场景下mAP50 达到 0.8 以上算合格0.9 以上算优秀如果在 0.7 以下先别调模型回头检查标签有没有错大部分情况是数据集的问题而不是网络结构的问题。4. 计数逻辑落地检测框过滤、虚拟区域与去重计数的实现4.1 从检测框到人流量数值数据流怎么打通模型训练出来只是第一步系统要输出某时段进入多少人还需要一层业务逻辑。这里的数据流是视频帧逐张送入模型 → 得到检测框和置信度 → 过滤掉低置信度框 → 判断行人在不在预设区域 → 跨帧去重 → 累加计数。每一步都有参数需要调也会踩坑。检测框过滤是最容易忽略的一环。模型会输出所有置信度大于等于默认阈值通常是 0.25的框但实际场景里0.3 置信度的框很多是误检比如把远处的广告牌人形图案当成行人。我一般把conf设置在 0.35 到 0.45 之间具体看你的场景。画面里行人密集、相互遮挡多阈值就调低一些画面空旷、行人少阈值可以调高。虚拟区域不是必须的但强烈建议加。有的场景只需要统计某个闸口两边的人流量不设区域的话画面边缘走过的行人也会被计数数据就失真了。区域定义有两种方式矩形框或者一条虚拟线。矩形框适合统计区域内的实时人数虚拟线适合统计进出人数本文的源码包两种都实现了默认用的是虚拟线方案。4.2 跨帧去重与虚拟线计数实现计数模块的核心代码看起来不长但逻辑细节全在边界条件里。下面这段是去掉重复计数的关键实现基于检测框中心点做最近邻匹配import cv2 import numpy as np from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) LINE_Y 360 # 虚拟线的纵坐标单位是像素 CONF_THRESH 0.4 # 置信度过滤阈值 MATCH_DIST 50 # 跨帧匹配的最大像素距离 cap cv2.VideoCapture(test_video.mp4) next_id 0 prev_centers {} # id - (prev_cx, prev_cy) counted set() # 已经计数过的目标 id total_count 0 while True: ret, frame cap.read() if not ret: break results model(frame, confCONF_THRESH)[0] boxes results.boxes.xyxy.cpu().numpy().astype(int) cur_centers {} for x1, y1, x2, y2 in boxes: cx, cy (x1 x2) // 2, (y1 y2) // 2 # 匹配上一帧的目标找距离最近且小于 MATCH_DIST 的 best_id, best_d None, 1e9 for pid, (px, py) in prev_centers.items(): d (cx - px) ** 2 (cy - py) ** 2 if d best_d and d MATCH_DIST ** 2: best_id, best_d pid, d if best_id is None: best_id next_id next_id 1 cur_centers[best_id] (cx, cy) # 跨线判断上一帧在线上方这一帧在线下方 if best_id in prev_centers: prev_cy prev_centers[best_id][1] if prev_cy LINE_Y and cy LINE_Y and best_id not in counted: total_count 1 counted.add(best_id) prev_centers cur_centers cv2.line(frame, (0, LINE_Y), (frame.shape[1], LINE_Y), (0, 255, 0), 2) cv2.putText(frame, fcount: {total_count}, (30, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) cv2.imshow(frame, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这段代码的逻辑说明每一帧先用模型跑出检测框取每个框的中心点作为目标的代表位置。当前帧的每个中心点去上一帧的所有中心点里找距离最近的如果距离小于MATCH_DIST认为是同一个目标沿用上一帧的 ID否则分配一个新 ID。这就是最简单的跨帧跟踪去重比每帧都独立计数要准确得多。参数调优里最核心的是MATCH_DIST。这个值设大了两个人擦肩而过会被当成同一个人漏计设小了同一人因为检测框抖动被拆成两个 ID重复计。经验值是按画面分辨率来1080p 画面里行人正常行走相邻帧中心点位移在 10 到 30 像素之间取 50 比较安全。帧率越高这个值可以越小帧率低到 10fps 以下就得放大到 80 以上否则目标跟丢是必然的。LINE_Y这个值根据你的摄像头安装角度来想统计从下往上穿过画面的人就选在画面中下方位置反过来统计从上往下穿逻辑不变把prev_cy LINE_Y and cy LINE_Y两个条件对调。还有一点别人做这段通常会漏掉counted集合导致同一个人来回跨线被反复计数。加了它之后一个人一整段视频只计一次这才是进出人数的正确语义。4.3 置信度、IOU 与计数灵敏度的耦合关系跨帧去重代码里我只用了置信度过滤没提 NMS 的 IOU 阈值但实际调参时它俩是耦合的。YOLO 默认的iou0.45意思是重叠度超过 45% 的两个框会被合并成一个。人群密集时两个人挨得近检测框重叠大NMS 可能把两个人的框合并成一个直接少计一个人。遇到这种情况把 IOU 阈值调低到 0.3NMS 更保守允许更多重叠框保留。代价是画面里同一人可能出现两个检测框这个时候跨帧匹配的MATCH_DIST反而要适当调大因为同一人的两个候选框中心点距离很近去重逻辑会倾向于合并它们。我的做法一般是先调 IOU 让检测框数量和人工目测一致再去调MATCH_DIST让跨帧不丢目标两次调整分开做不要同时改。还有一类情况视频画质差、行人与背景颜色接近导致模型同一帧内一会儿输出两个框一会儿输出一个框。这种帧间抖动会让局部计数忽多忽少但不影响 total 的累计值因为counted集合已经锁定了 ID。所以人流量计数的核心是累计值实时人数显示反而对模型稳定性要求更高。5. 常见问题排查五个把人流量检测项目整翻车的坑5.1 训练阶段的四个高频问题第一个高频问题是数据集路径写错导致训练启动就报错。现象是yolo train跑起来几秒钟就退出日志里报AssertionError: train dataset not found。原因是data.yaml里的路径和实际目录结构对不上尤其是 Windows 下用了反斜杠路径。解决办法是把data.yaml里的路径全部改成相对路径并且确认images/train和labels/train两个目录都存在且有内容。我习惯在训练前先写一行检查代码数一下images/train下图片数量和labels/train下标签数量是否一致不一致就是标签转换有遗漏。第二个高频问题是训练时 OOM显存溢出。现象是训练跑到第几个 batch 直接中断报CUDA out of memory。原因是batch设太大或者imgsz太大。解决办法是先把batch降到 4 试试如果还报错就把imgsz从 640 降到 512。还有一种不太容易想到的情况同时开着多个 Jupyter Notebook 或者浏览器里挂着视频都会占显存先把无关程序关掉再训练。第三个高频问题是 mAP 一直很低但 loss 已经收敛。现象是训练了 50 个 epochloss 曲线很漂亮但验证集 mAP50 只有 0.5 左右。原因通常有两个方向一是标签坐标转换错了比如归一化时用了错误的图片尺寸导致框位置偏移二是训练集和验证集来自不同分布比如训练集全是白天场景验证集全是晚上。排查办法是写一个可视化脚本把训练集图片和标签画在一起肉眼检查框和人对不对得上这一步能看出八成的问题。第四个高频问题是检测框大量偏向画面某个角落。现象是推理时所有框集中在左下角停车场的柱子被框成人。原因是训练数据里行人只在某个区域出现模型学会了这个区域容易出人而不是学会人长什么样。解决办法是补一批行人出现在画面不同位置的数据或者做数据增强时把mosaic打开YOLOv8 默认开启 mosaic 增强能明显缓解位置偏见。这个坑最隐蔽因为它不影响 loss 收敛只影响泛化。5.2 计数阶段的判定逻辑错误计数阶段最常见的坑是同一个行人被反复计数。现象是视频里只有三个人来回走动最终计数却变成了三百。原因非常明确没有加counted集合或者counted集合在每帧被重新初始化了。这个问题的排查其实很简单在计数代码里把目标 ID 和跨线状态打印出来盯几帧就能看出来。解决方式是严格按照 4.2 节的结构counted必须在循环开始前初始化且只增不减。还有一个和场景强相关的坑摄像头安装角度不对导致跨线计数完全失效。现象是行人明明从画面下方走到上方但计数一直为 0。原因是虚拟线画在了行人不可能经过的位置比如线太靠上而画面下半部分才是行人活动区域。这个问题的排查方式是先把视频暂停打印出检测框中心点的实际坐标确认LINE_Y落在行人轨迹必经的位置上。另外如果摄像头俯视角度过大看到的行人框特别扁中心点计算没问题但 NMS 后框的位置会跳需要把MATCH_DIST调得比平视场景更大。最后还有一个所有做视频处理的人都会遇到的性能问题离线跑一个 10 分钟的 720p 视频处理时间比视频本身还长。原因是模型推理帧率低而且没有跳帧。解决办法是设置cap.read()之后按frame_skip间隔处理比如每 3 帧处理 1 帧丢掉的 2 帧用上一帧的检测结果代替。帧率要求不高的统计场景完全够用处理速度能翻三倍。6. 验证与进阶从离线视频到实时推流的改造与指标核对模型训练完、计数逻辑写完项目看起来已经完整了但真正能写进毕业论文里作为结果呈现的是验证数据和对比实验。这里分享一套我自己的验证习惯以及把离线处理改成实时推流的具体做法。先说指标核对。计数系统不同于纯检测模型除了 mAP还必须有人流量计数的准确率指标。我的做法是准备一段 3 分钟的测试视频人工逐帧数一遍记录实际进场人数 N_ground_truth跑完程序记录 N_prediction然后用计数准确率 ACC 1 - |N_prediction - N_ground_truth| / N_ground_truth。这个指标算出来放进论文的结果章节比单贴一张检测效果图要有说服力得多。我测过这套系统的典型表现场景空旷、行人不交叉时 ACC 在 95% 以上人流密集、互相遮挡时 ACC 掉到 80% 左右这说明优化方向应该是数据层面的遮挡样本而不是调模型结构。验证阶段还有一件值得做的事把模型在验证集上的误检和漏检图片批量导出人工扫一遍。误检图片往往能帮你发现数据标注的错误比如漏标了某个行人模型反而检出来了说明模型学到了你没标出来的目标这时修正标注数据再训练一轮mAP 会涨不少。实时推流的改造其实核心就两步。第一步把视频源从文件换成摄像头或 RTSP 流第二步是把模型导出成更快的推理格式。RTSP 流的接入方式很简单cv2.VideoCapture(rtsp://192.168.1.100:554/stream1)注意解码延迟通常比本地文件高 100 到 300 毫秒对计数影响不大。模型加速方面先用yolo export modelbest.pt formatengine device0导出 TensorRT 引擎在 NVIDIA 显卡上推理速度能提升两倍以上。如果你的部署机器没有 N 卡导出 ONNX 格式再用onnxruntime跑也比直接用 PyTorch 推理要快。# 导出 TensorRT 引擎注意和训练时的 imgsz 保持一致 yolo export modelruns/detect/train/weights/best.pt formatengine imgsz640 batch1 device0 # 导出 ONNX 格式适合 CPU 部署 yolo export modelruns/detect/train/weights/best.pt formatonnx imgsz640导出之后加载方式略有不同TensorRT 引擎文件后缀是.engine直接传给YOLO()就能被自动识别。这一步如果在 Jetson 这类边缘设备上做记得把batch1写死动态 batch 在部分设备上会触发序列化错误。最后聊一个我踩过的坑模型导出和训练都正常但在一个新的 Python 环境里加载 TensorRT 引擎时报错提示 engine 版本不匹配。原因是你换了机器之后当前机器的 TensorRT 版本和导出时不一致。解决办法是别把.engine文件当成通用产物到处拷贝要么在同一台机器上导出和部署要么直接导出 ONNX 格式兼容性广得多。从那以后我每次跨机器部署模型都强制走一遍 ONNX 导出流程导出的.onnx文件配合 opencv 的 DNN 模块或者 onnxruntime 做推理再也没踩过版本问题的坑。人流量检测这个方向数据、训练、计数、部署四个环节你都过了一遍之后换任何目标检测相关的毕设题目这套方法论都还能复用希望帮到你。本文还有配套的精品资源点击获取