☰
基于YOLO的深度学习人流量检测系统设计与Python实现
2026/10/9 2:19:55 网站建设 项目流程

简介:面向计算机相关专业本科生的毕业设计项目,基于深度学习实现人流量检测,内含完整Python源码、项目说明与运行环境配置参考,可支撑毕业设计、课程设计或期末大作业等场景。压缩包共1235个文件、约61.54MB,其中Python脚本(.py)为模型训练与检测核心代码,网页交互文件(html/js/css)提供可视化展示,ipynb和md文档记录调试过程与使用说明,png/jpg/gif为演示素材,代码与资源分类存放。已有263人学习下载。项目经导师指导并严格调试,覆盖数据预处理、模型训练、实时检测和结果可视化完整流程,同时备注了环境搭建、参数调整与常见排错思路,便于读者快速理解算法原理并迁移到自己的数据上,有效降低毕业设计或项目实战的入门门槛。

1. 从YOLO到人流计数:人流量检测系统到底在做什么

商场物业、地铁安保、景区运营,这些场景的管理者真正要的不是「画面里此刻有多少人」,而是「每五分钟从哪个口进了多少人」。这个诉求落到技术上,就是一套基于深度学习的人流量检测系统:先用目标检测模型把画面里的行人或人头框出来,再用跟踪逻辑给每个目标分配一个稳定ID,最后通过虚拟线或区域判定完成计数。标题里的「Python源码+项目说明」,意味着这套链路的每一步都要有可运行的代码和可读的文档支撑,放在毕业设计里就是一个从模型训练到业务计数都闭环的高分项目。适合计算机视觉方向的本科生,或者想快速把检测模型接到实际计数业务的工程师。这套系统最容易在哪一步翻车?不是检测,而是计数——很多人把检测框数量直接当人流量,结果同一帧里一个人被重复算了好几次,最终数字完全不可信。

2. 训练数据怎么来:公开数据集选型与标注转YOLO格式

2.1 人流量检测的数据集选型:人头框还是人体框

做这套系统之前,先想清楚检测目标。常见做法是两种:检测整个人体,或者只检测头部。固定机位俯拍的闸机、安检口场景,人体互相遮挡严重,人体框大量重叠,NMS之后框还在漂;人头框虽然目标小,但遮挡少、边界清晰。我在做这类项目时,一般先拍一段现场视频,统计目标的像素尺寸,小于32像素的就果断走人头检测路线。数据的另一个来源是公开数据集,MOT Challenge、CrowdHuman这类是绕不开的选择,前者带跟踪标注,后者密集人群标注质量高,适合做检测预训练。但要注意,公开数据集通常标注的是人体框,而你的业务场景可能需要人头框,这个不匹配会在迁移时吃大亏,后面会专门讲。

2.2 把labelme标注转换成YOLO训练格式:转换脚本与参数说明

YOLO系模型训练要求每张图片对应一个同名txt文件,每行是「类别id 归一化x_center 归一化y_center 归一化宽 归一化高」。而手工标注工具labelme默认输出JSON,里面是绝对像素坐标。这个格式差是第一个坑。我常用的转换脚本如下:

import json import os def labelme_to_yolo(json_path, out_dir, class_map=None): with open(json_path, 'r', encoding='utf-8') as f: data = json.load(f) img_w = data['imageWidth'] img_h = data['imageHeight'] base = os.path.splitext(os.path.basename(json_path))[0] lines = [] for shape in data['shapes']: label = shape['label'] if class_map is not None: cls_id = class_map[label] else: cls_id = 0 points = shape['points'] xs = [p[0] for p in points] ys = [p[1] for p in points] x_min, x_max = min(xs), max(xs) y_min, y_max = min(ys), max(ys) # 计算中心点与宽高,再归一化到 0~1 区间 x_center = (x_min + x_max) / 2.0 / img_w y_center = (y_min + y_max) / 2.0 / img_h box_w = (x_max - x_min) / img_w box_h = (y_max - y_min) / img_h # 边界裁剪:标注框越界会导致训练时 loss 异常 x_center = max(0.0, min(1.0, x_center)) y_center = max(0.0, min(1.0, y_center)) box_w = max(0.0, min(1.0, box_w)) box_h = max(0.0, min(1.0, box_h)) lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}") out_path = os.path.join(out_dir, base + '.txt') with open(out_path, 'w', encoding='utf-8') as f: f.write('\n'.join(lines))

这段脚本的逻辑分三步:读JSON拿图像宽高和标注多边形,求外接矩形的中心点与宽高并做归一化,最后写成YOLO需要的txt格式。class_map参数是一个字典,例如{"head": 0, "person": 1},用来把字符串标签映射成从0开始的类别id。这里有个参数细节:如果图片尺寸很小但标注很多多边形,归一化后的小数位保留6位就够用,更多位不影响训练;真正影响训练的是标注框越界问题,所以我在代码里强制做了clip,这是标注数据清洗里最常见的修补方式。

2.3 数据划分:训练集验证集不能按视频帧随机切

数据准备好后要划分目录,推荐结构是dataset/images/{train,valid}和dataset/labels/{train,valid},比例用9比1或8比2。很多人图省事,把视频抽帧后的所有图片按比例随机划分,这会导致同一个人的连续帧同时出现在训练集和验证集,评估指标虚高,模型实际泛化能力很差。正确做法是先按视频片段划分,比如前3段视频全部进训练集,第4段视频全部进验证集,保证验证集看到的人脸、衣着、光照与训练集不完全重叠。我在做毕业设计项目时,还会额外留一段完全不同场景的视频作为最终测试,这段完全不参与训练和调参,专门用来模拟交付后的真实效果。

3. 用YOLOv5做检测器:训练命令、参数调节与结果判断

3.1 为什么选YOLOv5而不是Faster RCNN或SSD

人流量检测的实时性是硬指标。Faster RCNN在两阶段检测器里精度不错,但单张1080P图片在GPU上推理要200毫秒以上,做实时视频流很吃力;SSD速度快但小目标召回率差,密集人群里的小人头基本漏一半。YOLOv5是单阶段检测器,平衡了速度和精度,而且生态成熟,从训练到TensorRT部署的教程资料多,成熟度对单人开发的项目是很大的加成,遇到问题能搜到答案比什么都重要。从前面第2章的数据准备可以看出,YOLO格式的标注转换工具链也最完善。为什么总强调生态?因为毕业设计周期有限,你不可能把所有时间花在调一个冷门模型的推理代码上,把精力留在计数逻辑和系统演示更划算。

3.2 训练命令与关键参数:img、batch、epochs怎么定

数据目录和yaml配置就绪之后,训练命令长这样:

python train.py \ --data datacfg/crowd.yaml \ --weights weights/yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 120 \ --device 0

crowd.yaml里至少要写清三样东西:训练集和验证集图片路径、类别数量、类别名称。weights用yolov5s.pt这个预训练权重做迁移学习,而不是从空白权重开始训练,这是COCO预训练模型自带的基础视觉特征,能大幅缩短收敛时间。img参数是训练分辨率,密集人头场景建议直接拉到768,小目标特征在低分辨率下容易被下采样丢掉。batch大小受显存限制,16G显存跑yolov5s可以到32,8G显存就老实16;如果还爆显存,把输入图缩到640比硬降batch更稳妥。epochs对小型数据集100到150之间足够,再多大概率过拟合。

下面是几个最影响结果的参数速查:

参数常见取值对结果的影响
--img640 / 768密集人群用768能明显提升小目标召回
--batch8 / 16 / 32在显存允许下尽量大,太小会震荡
--epochs100 / 150超过150收益递减,易过拟合
--weightsyolov5s.pt从COCO预训练迁移,比随机初始化快很多

3.3 训练结果怎么看:loss降了不代表能交付

训练过程要盯四个指标:train loss、val loss、mAP50和Recall。很多新手只看mAP50,觉得0.9就是好模型。在人流量检测里,Recall比Precision更值得关注——漏掉一个人头,计数结果就永久少一个人;误检一个目标,跟踪去重后可能只多算一次。mAP50-95这个指标可以忽略,它对边界框的精确度要求过高,而人流计数场景框稍微偏一点不影响计数。另一个判断点是训练结束前30个epoch的val loss是否稳定,如果训练loss还在降、val loss开始反弹,说明模型开始记训练集了,这时候应该用倒数第30个epoch的权重,而不是最后一个epoch的权重,这个权重导出后做测试往往效果更好。

4. 从检测框到人流量数字:跟踪+虚拟线计数的落地实现

4.1 为什么纯检测计数一定会翻车

检测模型每帧输出一堆带类别的边界框,如果直接把这一帧的框数量当作人流量,至少会有两个严重错误。第一,同一个行人在连续帧里会被重复计数,每帧都数一遍等于把停留时间换算成了虚假流量;第二,检测模型是逐帧独立的,漏检、误检在时间轴上随机出现,直接求和结果波动极大。这就是为什么人流量检测系统必须引入跟踪:目标跟踪给每个行人分配一个唯一ID,系统知道这一帧的框和上一帧的框是不是同一个人,计数时按ID去重,同一个ID只算一次。跟踪算法方面,DeepSORT是经典选择,ByteTrack近年更常用,后者对低置信度检测框的处理更稳定,密集人群里ID切换次数明显更少。

4.2 虚拟线计数的Python实现思路:按ID去重

计数逻辑本身不复杂,核心是维护一个已经通过的ID集合,防止重复计数。以下是我常用的思路代码:

class CountLine: def __init__(self, line_y, direction='down'): self.line_y = line_y self.direction = direction self.passed = set() self.count = 0 def update(self, track_id, cx, cy, prev_cy): # 这个ID已经跨过线,直接跳过,避免重复计数 if track_id in self.passed: return if prev_cy is None or cx is None: return # 从线上方走向下方:上一帧在线上方,当前帧在下方 if self.direction == 'down' and prev_cy < self.line_y <= cy: self.count += 1 self.passed.add(track_id) # 从下方走向上方 elif self.direction == 'up' and prev_cy > self.line_y >= cy: self.count += 1 self.passed.add(track_id)

这个类不关心跟踪器怎么实现,它只接收track_id和前后两帧的中心点纵坐标。当目标的中心点跨越设定好的直线时,计数值加一并记录ID。关键参数是line_y,它需要结合摄像头安装角度来定,俯拍视角下放在画面中段偏下比较合理,太靠近画面边缘容易被误检干扰。direction决定统计的是进还是出,双向通道就建两个CountLine实例,一个down一个up。这套逻辑有个前提:track_id必须稳定,如果同一个行人每几帧就被跟踪器换个新ID,他过线时就会被重复计数。所以真正要调的是上游跟踪器的参数,不是这个类。

4.3 置信度阈值、帧率:两个直接影响计数质量的参数

检测置信度阈值一般设在0.3到0.5之间。设太低,广告海报上的人形图案会被当成真行人;设太高,小头目标被过滤掉,漏计严重。我一般先取0.4,然后用验证视频跑一遍,看检测出的False Positive和漏检数量再微调。帧率同样关键,测试中发现每3帧检测一次足够,跟踪器在40毫秒内不会跟丢目标;如果为了性能降到每秒只处理5帧,行人快速走动时跟踪框会跳跃,ID切换概率剧增。建议在项目说明里明确写清「检测每N帧执行一次,跟踪每帧执行」的配置,这是实际部署时最容易忽略的细节,也是答辩时对方可能会追问的点。

5. 避坑与排查:人流量检测系统常见的5个坑

5.1 训练loss不降或反复震荡

现象:训练前10个epoch,loss从0.1降到0.07之后就不再变化,或者在0.08附近来回跳。原因排查下来通常是三选一:学习率过高、数据标注有误、类别数量不匹配。解决路径是这样的:先看日志里的learning rate,初始设置0.01对小数据集偏高,降到0.001再试;然后检查data yaml里的类别数量是否和标注一致,比如你只标了"person"一类,但class_map里给了三个类别,模型在训练时就没法收敛;最后随机抽几组标注txt,画在原图上检查,大概率能找到框没包住目标或中心点偏了的情况。

5.2 验证mAP很高,但视频里漏检严重

现象:训练日志里mAP50达到0.92,在自采视频里一跑,很多行人被漏掉。原因几乎都是训练集和测试集的分布差异:公开数据集是户外大街场景,你的摄像头是室内俯拍视角,光照和拍摄角度完全不一样,模型的泛化能力支撑不住这种分布偏移。解决方法是补拍100到200帧现场视频,标注后加入训练集重新微调,通常就能压下来。这条经验值钱在提醒你,验证集应该模拟真实部署场景,而不是只用公开数据集。

5.3 计数结果反复横跳:ID频繁切换

现象:一个人匀速走过虚拟线,计数从1变成3,再变回2。原因是跟踪器把同一个人的轨迹切断了,中间换了一次ID,前面已经记录过的ID在新ID出现时又被当成新人。ByteTrack默认对低置信度框做二次匹配,但参数min_box_area设置过小时,很多噪声框参与匹配反而导致轨迹断裂。解决方法是调大min_box_area过滤小噪声框,或者把跟踪器内部的最大丢失帧数从默认值往上调一点,让目标短暂遮挡后能重新接上原轨迹。这属于调参玄学,但按这个路径试,大概率能稳住。

5.4 广告牌上的人的误检

现象:商场背景里有大幅海报,海报里的人被检测成人流量,计数虚高。原因就是训练集里缺少这类负样本。解决策略有两个:一是收集十几张类似的广告牌图片加入数据集,标注为空,让模型学会判断这是背景;二是在推理阶段加一个ROI过滤区域,把海报所在的图像区域直接排除在检测范围之外。第二种方法在固定摄像头场景下更省事,就是改一行配置的事。

5.5 GPU显存OOM

现象:训练跑到第5个epoch直接报CUDA out of memory进程退出。原因是batch设太大,或者训练分辨率拉到768的同时还开了多尺度训练。解决步骤从易到难:先调小batch到8,再把img降到640,如果还不行,检查是不是其他进程占用了显存,最后可以开梯度累积替代物理batch增大。这个坑本身不难,但容易出现一种情况:系统自动重启之后显存被旧进程占用,OOM一直报,先重启机器再训练往往就解决了。

6. 进阶与验证:人头检测、轨迹热力图与计数精度自评

6.1 俯拍场景优先切到人头检测

如果你的摄像头装在通道正上方俯拍,建议把检测目标从整个人换成头部。人体框在这种视角下高度方向压缩严重,相邻行人的框互相重叠,NMS会随机砍掉其中一个;而头部目标在人脸上方,彼此重叠概率小,漏检率明显低。切换后要同步做两件事:数据集全部换成头部标注,训练分辨率提高到768,因为头部目标像素面积比人体小一个量级,需要更高分辨率兜底。

6.2 用轨迹数据画区域热力图加分

检测加跟踪的副产品是每个行人的完整轨迹点序列。把这些轨迹点按坐标聚合,用OpenCV的addWeighted叠加到背景图上,就能得到一张人流分布热力图。这个可视化在答辩演示时非常加分,它直观展示了系统不只是会数数,还能看出哪个区域最拥挤。实现上不用复杂的密度估计,把轨迹点画到一张黑色画布上做高斯模糊,再与原始帧叠加,效果就足够好。

6.3 计数精度怎么自评才算数

给系统下结论之前,先做一次手工基准测试。找一段3分钟的现场视频,人工逐帧数出实际通过虚拟线的总人数,然后跑系统,对比输出。误差在正负5%以内算合格,超过10%就算不可交付。更严格的做法是用MOT格式的Ground Truth轨迹文件,计算MOTA和IDF1;IDF1低于70说明ID切换太频繁,计数结果就不能全信。这是我从几个项目里总结出来的习惯——评估不看检测mAP,只看最终计数误差,因为业主根本不在乎mAP是多少,他们只想知道今天报上来的数字对不对。希望这篇笔记能帮你把系统的最后一公里走稳,也祝你的项目顺利通过。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询