简介:面向毕设及课设论文写作的深度学习人流量检测方法参考资料,以MobileNet-SSD轻量级模型为核心,系统介绍了视频监控下行人检测与计数的六个实施步骤:从图像文件列表获取、数据集制作,到模型训练、行人检测、质心追踪与结果输出,并给出网络各层结构参数、Caffe环境配置和实验验证结论。细致说明了数据预处理、跳帧处理、置信度过滤、质心关联等关键细节,还提及dlib跟踪器与SSD检测器协同工作的方式。文档对比了传统CNN,突出深度可分离卷积减少计算量和模型大小的优势,适合移动端或嵌入式部署,并列举公园、文化广场等特殊场景以及疫情期间人流管控的应用价值。资源为1个docx文档,大小224KB,内容专业严谨,已有82人学习。论文结构清晰、用语规范,既可作为毕业设计、课程设计的写作范本,也能为类似视频分析项目的实现提供参考。
1. 人流量检测不等于数人头:先选对路线再写代码
商场、地铁站、校园门口,人流量检测是毕设和课设里被选得最多的场景之一。大多数人的第一反应是拿 YOLO 去框人,然后数框的数量。这个做法在稀疏场景确实成立,可一旦人群在闸机口或通道里挤成一团,检测框互相遮挡,NMS 把相邻人头一并抑制,人数立刻被低估。更麻烦的是,答辩时评委不会只问“你的模型多少 mAP”,他们会追问指标怎么定义、数据从哪来、训练细节是什么。这里把基于深度学习的人流量检测方法从方案选型到训练落地拆开讲:两条技术路线怎么选、数据怎么组织、YOLOv8 最小流程怎么跑通、哪些坑会让结果翻车,结尾给出论文可用的进阶做法。
2. 人流量检测的两条技术路线:目标检测和密度图怎么选
2.1 检测框路线:YOLO 适合什么场景,SSD 和 Faster R-CNN 的边界
基于深度学习的人流量检测,大部分实现本质上是目标检测的变体。检测框路线的输出是“矩形框 + 置信度”,一个人一个框,人数等于框的数量。只要场景里的人比较稀疏、互相遮挡不严重,这个逻辑就成立。校园主干道、办公室走廊、商场入口、图书馆门口,都属于这类场景,这也是课设最常用的选择。
框架选型上,我一般优先推荐 YOLOv8,而不是 SSD 或 Faster R-CNN。原因很实际:ultralytics 把数据增强、训练、验证、推理封装成一条命令行,对新手友好;预训练权重可以直接从 COCO 上迁移;显存占用比 Faster R-CNN 低一个量级;论文里也好展示,画框、标序号、算精度都很直观。SSD 的优势是速度快、旧资料多,但它在小目标密集场景的召回率明显不如 YOLO。Faster R-CNN 虽然有“两阶段精度高”的说法,可训练慢,anchor、RPN 超参数一堆,课设周期根本折腾不起。
这条路线有明确的边界:人群密度升高以后,检测框之间 IoU 迅速变大,NMS 会把大量重叠的正确框当成重复框删掉。地铁车厢、闸机口、大型活动入口这类场景,检测框路线会系统性低估人数。另外,当行人在画面里只占十几个像素时,小目标漏检也会让检测路线失效。所以检测框路线适合“稀疏到中等密度”,具体判断标准是:画面里目标区域的行人重叠率超过 30%,就该考虑密度图方案。
2.2 密度图路线:CSRNet 的输出是什么,什么情况下该换
密度图路线不做检测框,而是对每个像素位置回归一个“局部人群密度值”,把整张密度图求和,就是画面里的总人数。这类方法的代表是 CSRNet,骨干网络用 VGG16,后面接空洞卷积,在不降低特征图分辨率的前提下扩大感受野,对密集场景比较有效。
训练密度图模型时,需要的是点级标注,也就是在每个人头位置点一个点作为头中心。训练前把这些点按高斯核扩散成密度图,多个头重叠的位置做求和。这个环节里高斯核的 sigma 很敏感,核太小密度图稀疏,核太大相邻人头糊成一片,算出来的 MAE 会有明显波动。这里头参数的调整确实有点玄学,一般按照人头在图像里的平均像素直径来定,而不是随便设一个固定值。
密度图路线也有代价:点标注比框标注更费眼力,一张图几百个人头,标完眼睛就花了;模型输出的是热力图,论文里不如检测框直观;而且它天然不保留“谁是谁”的信息,如果后续要做人员跟踪,密度图路线就不合适。选型建议是:课设周期短、场景稀疏,走检测框路线;毕设想做“人群密度估计”这个明确方向、手头有俯瞰视角的密集数据,才上 CSRNet。答辩时评委一旦问“为什么不用密度图”,你需要正面回答标注成本、跟踪需求和场景密度。
| 对比项 | 检测框路线 | 密度图路线 |
|---|---|---|
| 输出形式 | 框 + 置信度 | 密度热力图 |
| 标注成本 | 中等,框一个人 | 高,点一个人头 |
| 密集场景抗性 | 差,NMS 丢框 | 好,天然支持重叠 |
| 论文展示性 | 好,直观可画框 | 中,热力图需要解释 |
| 轨迹跟踪支持 | 支持 | 不支持 |
| 实现难度 | 低 | 中 |
评价指标也要分开看。检测框路线看 mAP50、mAP50-95;密度图路线看 MAE 和 MSE。人流量检测本质关心的是“人数准不准”,所以哪怕走了检测框路线,也要在论文里补上报 MAE 和 MSE,否则评委一句话就能问住:你 mAP 是 0.9,实际数人偏了多少?
3. 人流量检测训练数据从哪来:公开数据集、自建标注与目录组织
3.1 公开数据集能直接当训练集吗:CrowdHuman、VisDrone 与场景偏差
数据是人流量检测里最容易被低估的一环。很多同学直接从网上下载一个“深度学习实战项目案例”配套数据集,训练一轮就换上自己的监控视频去测,结果惨不忍睹。原因不是模型不行,而是训练数据和实测场景根本不在一个分布里。
公开数据集里,CrowdHuman 适合做人体检测的预训练,标注质量高,但它是街景视角;VisDrone 是无人机俯视视角,人和车都很小,适合高位监控场景;Mall Dataset 是老牌人群密度数据集,分辨率低,现在一般只用来做密度图路线的 baseline 对比。用这些数据之前要看清楚标注格式:CrowdHuman 是类似 VOC 的 XML 标注,VisDrone 自带专用格式,需要转换才能喂给 YOLO。
还有一个答辩时容易翻车的操作:有人把 CrowdHuman 的验证集拿来做自己模型的测试集,然后报告 mAP。这不是严格意义上的“你的人流量检测方法”的评测,因为测试集来自公开源。正确的做法是公开数据只用于预训练或做消融对比,最终效果评估要用目标场景自采数据,并写清楚数据来源、标注数量、划分比例。
3.2 自建小样本:用 LabelImg 给头肩打框,而不是全身框
如果手里只有监控视频,最常见的做法是抽帧标注。每秒抽 1 帧,抽出的图像按 8:1:1 分成训练、验证、测试集。标注工具用 LabelImg 这类开源工具就行,不需要额外装复杂平台。关键在标注对象:我建议标头肩,而不是全身。
原因有两个。第一,密集人群里身体遮挡严重,全身框的完整性很差,模型学到的是各种“半个人”特征,反而干扰收敛。第二,头肩在画面里姿态差异小,不管是正面、背面还是低头看手机,头肩轮廓相对统一,类内方差低。约定一点:只要头部可见,即使身体被挡住一半也标头肩;远处小于 16 像素的行人直接跳过。标注一致性比数量更重要,300 张标注一致的图,效果可能好过 1000 张随意框的图。
3.3 从 VOC 到 YOLO 格式:转换脚本与四个边界坑
标注完以后是 XML 格式,YOLO 需要的是每张图对应一个同名 txt,每行是“类别 id 中心点 x 中心点 y 宽 高”,全部归一化到 0 到 1。转换脚本可以复用下面这段:
import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, out_dir, classes): tree = ET.parse(xml_path) root = tree.getroot() # 图像宽高从 <size> 节点读取,缺失时会 KeyError img_w = int(root.find("size/width").text) img_h = int(root.find("size/height").text) lines = [] for obj in root.iter("object"): cls = obj.find("name").text if cls not in classes: continue box = obj.find("bndbox") # 坐标可能越界,先夹紧到图像范围 x1 = max(0, int(box.find("xmin").text)) y1 = max(0, int(box.find("ymin").text)) x2 = min(img_w, int(box.find("xmax").text)) y2 = min(img_h, int(box.find("ymax").text)) x_c = (x1 + x2) / 2 / img_w y_c = (y1 + y2) / 2 / img_h w = (x2 - x1) / img_w h = (y2 - y1) / img_h lines.append(f"{classes.index(cls)} {x_c:.6f} {y_c:.6f} {w:.6f} {h:.6f}") if lines: name = os.path.splitext(os.path.basename(xml_path))[0] + ".txt" with open(os.path.join(out_dir, name), "w", encoding="utf-8") as f: f.write("\n".join(lines))脚本逻辑不复杂:读 XML 里的 bndbox,转成归一化的中心点格式。classes 参数是一个固定的类别列表,例如["person"],它的顺序直接决定 YOLO 类别 id,顺序一旦变了,训练就全乱。注意这里做了越界坐标 clip,否则一张图里某个框超出右边界,x2 大于图像宽度,归一化结果会大于 1,YOLO 会直接报警或隐性丢弃。
四个边界坑要记牢。第一,classes 顺序必须和后面的 data.yaml 保持一致,classes.index(cls)返回的是列表下标,改顺序等于改标签。第二,XML 里<size>有时缺失,最好在脚本里增加一张从原图读宽高的回退逻辑,否则跑到一半才报错。第三,空标注文件要保留为空 txt,不要删除,因为它是背景负样本。第四,目录结构必须严格对齐,YOLO 要求 images 和 labels 同级,且 train、val 各自对应。
转换完之后,数据集目录长这样。冒号这种写法如果太多会乱,所以这里我用代码块展示目录结构:
crowd_data/ images/ train/ val/ labels/ train/ val/ data.yaml4. 用 YOLOv8 跑通人流量检测最小流程:环境配置、训练命令与推理参数
4.1 深度学习环境配置:GPU 版 PyTorch 与 ultralytics 的安装顺序
环境配置是毕设里第一个硬卡点,也是我见过翻车最多的一步。常见问题是把 torch 装成了 CPU 版,训练慢到怀疑人生;或直接pip install torch装最新版,和本机 CUDA 版本对不上,torch.cuda.is_available()永远返回 False。
正确的顺序是:先用 conda 建独立环境,避免和系统 Python 做伴;再确认显卡驱动支持的 CUDA 版本,然后装对应版本的 PyTorch。最后用pip install ultralytics装 YOLO 训练框架。示例命令如下:
conda create -n flow python=3.9 -y conda activate flow pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics第二行根据你的驱动环境改,cu118 只是常见版本之一;如果本机是 CUDA 12 以上,就换对应的 cu12 系列。装完后先跑一句验证:python -c "import torch; print(torch.cuda.is_available())",返回 True 再继续。如果返回 False,多半是 wheel 装成了 CPU 版,或者 conda 环境里缓存了旧包,先pip uninstall torch torchvision再重装。这一步是血泪经验,不要在还没验证 GPU 可用的情况下直接开训,后续所有等待都会变成白费。
4.2 data.yaml 的 path 字段:绝对路径比相对路径省心
YOLOv8 的数据配置存放在 data.yaml 里。它不负责图像预处理,只负责告诉训练器“训练图和标签分别在哪”。标准写法如下:
# 数据集根目录,建议写绝对路径 path: /home/user/crowd_data train: images/train val: images/val # 类别定义:1 个类别,person 的 id 为 0 nc: 1 names: ['person']path 字段是解析其他相对路径的根。我第一次用的时候写的是path: crowd_data/,在项目根目录下运行没问题,换到服务器上立刻找不到图片。后来一律改成绝对路径,谁拿到这份配置都能直接跑,不会因为当前工作目录不同而出错。注意 names 列表顺序要和数据集转换脚本里的 classes 顺序一致,这里如果写['head'],相应的 3.3 节脚本里 classes 也要是['head'],否则类别 id 错位,训练出的模型等于在学错误标签。
4.3 训练命令与四个必调参数:epochs、imgsz、batch、patience
数据就位后,训练命令很短:
yolo detect train \ model=yolov8n.pt \ data=data.yaml \ epochs=100 \ imgsz=640 \ batch=16 \ patience=20model 指定预训练权重,yolov8n.pt是 YOLOv8 里最小的一版,课设场景足够;第一次运行会自动下载权重,网络受限的机器需要提前把权重文件放到运行目录下。epochs 不是越大越好,人流量检测这类单类别任务通常 100 到 200 轮就收敛,多跑只增加过拟合风险。patience=20 是早停参数,验证损失连续 20 轮不下降就停止。imgsz 是推理分辨率,默认 640;如果你的监控画面里人头偏小,把 imgsz 提到 960 能明显提升小目标召回,但显存占用大概翻倍。batch 按显存来,8G 显存跑 yolov8n 用默认 batch=16 一般没问题,如果爆显存,先降到 8 或 4,别硬扛。
| 参数 | 建议值 | 踩坑点 |
|---|---|---|
| model | yolov8n.pt | 没有预训练权重要先搞定下载 |
| epochs | 100~200 | 只看轮数不看收敛曲线会浪费时间 |
| imgsz | 640 或 960 | 显存不够先降这个 |
| batch | 16 → 8 → 4 | OOM 时按这个顺序降 |
| patience | 20 | 设太小会提前截断训练 |
训练完成后不要急着开香槟,先看 runs/detect/train 目录下的 results.png 和 val_batch*.jpg。results.png 里有损失曲线和验证指标曲线,val_batch 图是跑完验证集后模型自己画出来的检测结果,这张图最直观地反映模型到底学没学会。如果 val_batch 里密集区域都是漏检框,说明当前参数不够用,而不是训练没收敛。
4.4 推理参数:conf、max_det 关系人数统计偏差
训练完的模型放在 runs/detect/train/weights/best.pt。对单段视频做检测的命令如下:
yolo detect predict \ model=runs/detect/train/weights/best.pt \ source=video.mp4 \ conf=0.35 \ imgsz=640 \ save=Trueconf 参数决定置信度阈值,默认 0.25。在人流量检测里这个值很敏感:设太低,背景里的广告牌、柱子、书包都会被当成人,误检刷屏;设太高,远处小人头全部漏掉。常规做法是先从 0.25 开始,看几帧预测结果,再往 0.35 或 0.4 调。还有一个容易忽略的参数 max_det,默认值是 300。人流密集的画面上千人头时,检测器最多只输出前 300 个框,后面全被丢掉,人数被硬生生截断。如果画面里人确实很多,把它调到 600 或 1000。
这里有个常见的认知误区:predict 输出的视频只是给人看的,不是人流量统计结果。你需要的不是“哪一帧里有框”,而是“整个时间段里有多少不同的人经过”,这就涉及到下一章的进阶处理,先别急着用检测框数去写论文结论。
5. 人流量检测避坑清单:指标错位、NMS 抑制、显存翻车与场景迁移
5.1 mAP 高不代表人数准:评价指标错位
现象:训练结束 mAP50 显示 0.95,自己拿一段测试视频数人头,误差超过 25%。原因:mAP 评估的是“框得准不准”,按预测框和标注框的 IoU 计算;而人流量检测最终关心的是计数误差。训练验证集里人群稀疏,mAP 很高,一到密集场景,NMS 抑制大量重叠框,mAP 和人数误差之间没有直接对应关系。解决:在论文实验部分单独加一个计数误差评估,选取 10 到 20 帧密集场景,手动数真实人数,计算 MAE 和 MSE。答辩时主动展示 mAP 和 MAE 两张表,比只挂一张 mAP 图更有说服力。
5.2 密集区域的检测框被 NMS 吞掉
现象:模型在人群稀疏区域检测正常,一到闸机口、通道转角这类地方,输出框明显变少,画面上明明有几十个人,模型只给了十几个框。原因:密集人群的目标框互相重叠,IoU 超过了 NMS 的抑制阈值,检测器认为那是同一个目标。解决:推理时把 NMS 的 IoU 阈值从默认 0.7 调到 0.85,允许更多重叠框保留;同时把 max_det 从上文提到的 300 提到 600。如果这样处理后误检变多,就同步把 conf 从 0.25 提高到 0.35。注意这条只适用于“识别到了但被抑制”的情况,如果模型压根没检测到人头,调 NMS 没有意义,得回到数据和 imgsz 上找问题。
5.3 GPU 显存不足和 batch 调整
现象:训练跑到一半,终端刷出 CUDA out of memory,或者启动训练后立刻报错退出。原因:imgsz、batch、模型大小三个变量共同决定显存占用,新手往往一次全拉满。解决:按“batch 降到 8 或 4,imgsz 降到 480,模型换成 yolov8n”的顺序逐级降。这里有个隐藏问题:batch 降到 2 或 1 时,Batch Normalization 的统计量会剧烈波动,模型难以收敛。数据量本来就不大时,与其硬降 batch,不如用预训练权重做迁移学习,而不是从头改骨干网络。装错 torch 版本导致的“显存不足”也有,但那种情况会在环境验证阶段暴露,所以 4.1 节那句torch.cuda.is_available()检查一定不要跳过。
5.4 训练集是街景、实测是低机位监控:场景迁移翻车
现象:用网上下载的行人检测数据集训练,验证集效果不错,换上教室或校园门口的监控视频,漏检率暴增。原因:人流量检测对拍摄视角极其敏感。俯视监控里人是“头肩+头顶”,平视街景里人是“全身+正背面”,两者的特征分布差异很大。用公开数据直接做实测,本质上就是跨域推理。解决:在目标场景自采至少 300 帧,按第 3 章方法标注头肩框,然后用预训练权重继续微调 50 到 100 轮。这一步的收益比加大公开数据量高得多。顺带一提,同一个摄像头下,课堂状态检测和商场人流检测是两个完全不同的任务,模型不通用,别指望一个权重打天下。
6. 进阶技巧:从检测框到人流量曲线,用热力图支撑论文结论
6.1 帧间 IoU 匹配去重,把检测框变成人数曲线
逐帧统计检测框数量没有意义,一个人站在原地不动会被连续计成几十个人。最简单的做法是帧间 IoU 匹配:把上一帧的检测框记为 tracks,当前帧每个框与它们算 IoU,匹配上就沿用原 ID,匹配不上就新建一个 ID。这样能近似得到“画面里同时存在多少人”,以及随时间变化的人数曲线。代码逻辑类似:
def update_tracks(tracks, boxes, iou_thr=0.3): new_tracks = [] for box in boxes: best_t = None best_iou = 0.0 for t in tracks: iou = box_iou(box, t.box) if iou > best_iou: best_iou = iou best_t = t if best_t is not None and best_iou >= iou_thr: new_tracks.append(Track(best_t.id, box)) else: new_tracks.append(Track(new_id(), box)) return new_tracks这个方案只解决“去重”,不解决“进出方向”。如果要统计“某一时刻进来了多少人、出去了多少人”,需要真正的多目标跟踪,比如 DeepSORT,工程量会翻倍。课设和大部分毕设做到曲线这一层已经够用:横轴是时间,纵轴是同时在场人数,能清楚看到早高峰、午间低谷和晚高峰三个时段。把这条曲线放进论文,比单张检测效果图更有说服力。
6.2 用检测框中心点画密度热力图:既好看又能对应密度图路线
另一个论文加分项是热力图。具体做法是:把每帧检测框的中心点提取出来,画到一张全黑底图上,再用高斯核做一次二维扩散,最后套上伪彩色映射,叠加到原图半透明输出。这张图可以直接对应到第 2 章的密度图路线,说明你理解两种方案的联系。实验部分可以对比稀疏时段的检测结果和高峰时段的热力图分布,结论自然落到“人群集中区域在何处、何时最拥挤”。
部署方面,如果毕设要求演示系统而不是纯论文,把 best.pt 导出成 ONNX 后用 OpenCV 的 DNN 模块做推理,速度会比直接跑 PyTorch 快不少,还不用在演示机上配完整深度学习环境。模型部署的取舍是:导出 ONNX 会牺牲一部分可调试性,但换来的是现场演示不卡顿。我自己的习惯是先保留 best.pt 做实验,演示前再导出一版 ONNX,两者分开管理,避免论文实验和演示版本混用。
最后说一个每次带毕设都会强调的教训:不要为了炫技在主干网上堆注意力模块、换各种 Backbone,人流量检测的核心是先保证数据分布对、计数指标对、场景不翻车。把这三件事做好,论文已经能支撑起来。希望帮到你。
本文还有配套的精品资源,点击获取