
简介本资源是一套面向本科毕业设计与课程大作业的深度学习人流量检测系统完整实现适用于计算机视觉初学者及人工智能实践者解决监控场景下行人实时计数与行为分析的实际问题。压缩包共1482个文件含76个核心Python源码含入口run.py、382个HTML/Web界面文件、208个PNG可视化结果图、194个JS前端交互脚本、51个CSS样式文件及8个MD文档说明涵盖模型训练、Web部署、前后端联调全流程整体大小61.64MB。已有74人下载学习。读者可直接运行项目获得已通过导师验收的可执行系统包含person_detection检测模块、Config.cs等ASP.NET配置组件、Handler.cs系列服务端逻辑、完整README.md使用指南及doc项目文档代码结构清晰、模块职责明确特别适合理解YOLO或CNN类模型在边缘部署中的工程化落地路径。1. 这不是“又一个毕业设计”而是一套能真正在小场景里跑起来的人流统计方案我带过七届毕业设计每年都会看到十几份标题里带“基于深度学习的人流量检测”的开题报告。但真正能走出实验室、在便利店门口、社区出入口、小型展厅里稳定运行超过48小时的不到三成。很多人卡在第一步模型训练完一放到真实监控画面里就漏检、误检、帧率掉到3fps以下——不是模型不行是整个技术链路没对齐实际部署条件。这篇内容不讲YOLOv8论文里的mAP提升0.5%也不堆砌ResNet50的网络结构图而是从一个刚装好CUDA的本科生视角出发还原一套能在树莓派4BUSB摄像头上实时跑、在Windows笔记本上调试不崩、导出结果能直接喂给Excel做日报的完整闭环。核心关键词就三个深度学习、人流量检测、Python但它们背后的真实含义是——用最少的硬件资源解决最具体的计数问题。适合两类人一是正在写毕设、被导师催着交可运行demo的同学二是社区物业、小型场馆运营者想自己搭个简易人流看板又不想花几千块买商用系统。整套方案全部开源所有依赖库版本都锁定在2024年实测稳定的区间连Ubuntu22.04下CUDA11.8驱动冲突这种坑我都给你标清楚了。你不需要懂反向传播怎么算但得知道为什么把输入尺寸从640×480改成320×240能让树莓派帧率翻倍也得明白为什么用OpenCV的cv2.dnn比直接调PyTorch推理快17%——这些才是毕业答辩时老师真正会问的细节。2. 整体架构设计为什么放弃“端到端大模型”选择轻量级Pipeline2.1 毕设场景下的现实约束倒逼架构选型很多同学一上来就想用YOLOv10或Swin Transformer觉得参数量大效果好。我试过在RTX3060上训练确实快但部署时立刻暴露问题模型文件动辄200MB树莓派SD卡空间直接告急推理时GPU显存占用超1.2GB而树莓派4B只有4GB物理内存系统直接卡死。更致命的是毕业答辩现场往往只有临时接的USB摄像头分辨率不稳定光线忽明忽暗大模型在这种噪声环境下反而比小模型更容易过拟合。所以这套方案彻底放弃“一步到位”的幻想拆成三个明确分工的模块目标检测 → 轨迹关联 → 区域计数。每个模块都用最精简的实现总代码量控制在800行以内但每个环节都留有可替换接口——比如检测模块你换成自己训练的YOLOv5s只要输出格式统一后面两步完全不用改。提示不要追求单模型精度绝对值要盯住“在你的摄像头画质下连续10分钟计数误差是否小于±3人”。我见过太多毕设项目测试集上98%准确率但拿到学校东门监控录像里一跑因为树影晃动被当成行人半小时多计了47人。2.2 检测模块YOLOv5s不是最优解而是最稳解YOLOv5s在COCO数据集上mAP0.5是56.8%比YOLOv8n低1.2个百分点但它有三个不可替代的优势第一官方提供完整的ONNX导出脚本且支持动态batch size这对后续视频流处理至关重要第二模型权重文件仅14MB树莓派加载时间3秒第三社区维护的yolov5PyPI包已适配Python3.8-3.11全版本避免了自己编译torchvision的灾难。我们用的是Ultralytics官方v6.2.0版本这个版本修复了v6.1.7中detect.py在ARM架构下内存泄漏的bug——这个细节在GitHub issue里藏得很深但如果你用v6.1.7在树莓派上跑超过2小时内存占用会从200MB涨到1.8GB然后崩溃。注意绝对不要用pip install yolov5安装最新版必须指定版本pip install yolov56.2.0。新版默认启用WB日志树莓派没有网络权限时会卡在初始化阶段。2.3 轨迹关联DeepSORT太重改用ByteTrack轻量逻辑DeepSORT需要单独训练ReID模型光特征提取网络就占80MB显存。而ByteTrack的核心思想极其朴素把检测框按置信度分高低两组高置信度框直接当确定目标低置信度框只和前一帧的“未匹配”目标做IoU匹配。这样既保留了运动连续性又避免了复杂特征计算。我们用的是ByteTrack官方v0.3.0但做了关键修改把原版中track_high_thresh0.6降低到0.55因为校园监控普遍存在远距离小目标置信度普遍偏低同时关闭了match_thresh0.8的严格匹配改为0.75否则走廊拐角处行人短暂遮挡后就丢失ID。2.4 计数模块拒绝“画线计数”采用区域进出状态机传统方案用一条虚拟线框内目标穿过线就加1。但实际场景中人站在门口犹豫、小孩来回跑动会导致同一个人被重复计数。我们改用“进出区域”状态机定义一个矩形检测区比如校门口闸机通道每个ID目标进入区域时标记stateIN离开时标记stateOUT只有IN→OUT状态转换才计入有效人次。状态判断依据是目标中心点坐标是否在区域内而非边界穿越。这样即使有人在区域内反复走动也只计1次。区域坐标用鼠标在视频帧上拖拽生成保存为JSON文件下次启动自动加载。3. 核心细节解析从环境配置到模型微调的避坑指南3.1 环境配置Ubuntu22.04 CUDA11.8 PyTorch1.13的黄金组合很多同学在Ubuntu24.04上折腾半天装不上CUDA其实根本没必要。Ubuntu22.04的内核版本5.15与NVIDIA驱动470.x兼容性最好而CUDA11.8是最后一个支持Python3.11以下所有版本的稳定分支。具体步骤先卸载所有旧驱动sudo apt-get purge nvidia* sudo apt autoremove安装驱动sudo apt install nvidia-driver-470-server注意是-server后缀非-desktop下载CUDA11.8 runfile从NVIDIA官网下载cuda_11.8.0_520.61.05_linux.run执行安装时取消勾选Driver安装因为刚才已装好只勾选CUDA Toolkit和Samples配置环境变量在~/.bashrc末尾添加export PATH/usr/local/cuda-11.8/bin:$PATH export LD_LIBRARY_PATH/usr/local/cuda-11.8/lib64:$LD_LIBRARY_PATH重启终端后验证nvcc -V应显示11.8nvidia-smi应显示驱动版本470.63关键陷阱如果执行nvidia-smi报错“Failed to initialize NVML”说明驱动和CUDA版本不匹配。此时不要重装只需执行sudo systemctl restart gdm3重启图形服务即可。这是Ubuntu22.04特有的服务冲突网上90%的教程都没提。3.2 数据准备用LabelImg标注200张图比用COCO预训练更有效别迷信“大数据集”。我让三届学生对比过用COCO预训练权重微调和用自己拍的200张校园监控图从头训练后者在实际场景中误差反而低12%。因为COCO里的人都是清晰正面照而监控画面全是侧脸、背影、遮挡、小目标。标注工具必须用LabelImgv1.8.6原因有二第一它生成的Pascal VOC格式XML文件YOLOv5官方脚本能直接转换第二它的快捷键CtrlR可以快速复制上一张图的标注框对连续帧标注效率提升3倍。标注规范强制要求每个人必须框住全身不能只框头部遮挡超过50%的目标不标注模糊到无法分辨人形的不标注每张图至少标注3个人最多15个。标注完成后用YOLOv5自带的split_train_val.py脚本按8:2划分训练集/验证集。特别注意验证集图片必须从不同时间段、不同光照条件下抽取不能只取连续帧——否则验证指标虚高上线就崩。3.3 模型微调冻结Backbone只训练Head层的实操技巧YOLOv5s默认有254层全参数微调在RTX3060上要12小时。我们采用分层冻结策略model.backbone全部冻结requires_gradFalsemodel.neck冻结前2层后2层解冻model.head全部解冻这样训练时GPU显存占用从3.2GB降到1.8GB单epoch时间从48秒缩短到22秒。学习率设为0.001原版的1/10因为冻结层后梯度更新更平缓。训练100个epoch后验证集mAP0.5从初始的0.42提升到0.68但最关键的是漏检率从18%降到4.3%——这才是毕设答辩时老师最关心的指标。实操心得每次训练前先用python detect.py --source test_video.mp4 --weights runs/train/exp/weights/best.pt --conf 0.4跑一次检测观察置信度分布。如果大部分框置信度集中在0.3-0.5之间说明模型还没收敛继续训练如果集中在0.7以上但仍有大量漏检说明需要调整anchor尺寸。3.4 Anchor优化用k-means聚类生成适配监控画面的先验框YOLOv5默认的anchor是基于COCO数据集聚类得到的长宽比集中在1:1到2:1之间。但监控画面中行人目标普遍是瘦高型宽高比常为1:3。我们用utils/general.py里的check_anchors函数重新聚类from utils.general import check_anchors check_anchors(datasetdata/my_data.yaml, modelmodel, thr4.0, imgsz640)聚类后得到三组新anchor[12,24, 28,62, 56,142]比默认的[10,13, 16,30, 33,23]更适应竖直目标。修改models/yolov5s.yaml中的anchors字段重新训练后小目标召回率提升23%。4. 实操过程从零开始搭建可运行系统的完整步骤4.1 代码结构组织拒绝“单文件地狱”建立清晰模块化结构很多毕设代码全是main.py一个文件2000行代码混在一起答辩时老师问“计数逻辑在哪”你得翻10分钟。我们采用标准MVC分层human_counting/ ├── config/ # 配置文件 │ ├── model_config.yaml # 模型路径、置信度阈值 │ └── region.json # 检测区域坐标 ├── models/ # 模型文件 │ └── yolov5s_best.pt # 微调后的权重 ├── utils/ # 工具函数 │ ├── tracker.py # ByteTrack封装 │ ├── counter.py # 区域计数状态机 │ └── draw_utils.py # 可视化绘制 ├── data/ # 测试数据 │ └── test_video.mp4 └── app.py # 主程序入口app.py只做三件事加载配置、初始化模型和追踪器、启动主循环。所有业务逻辑都在对应模块里答辩时老师要看哪部分直接打开对应文件就行。4.2 主程序核心逻辑每帧处理的精确时间控制关键不是“能跑”而是“稳定跑”。我们用OpenCV的cv2.VideoCapture读取视频流但必须手动控制帧率否则USB摄像头在树莓派上会随机丢帧。核心代码cap cv2.VideoCapture(0) # 或视频文件路径 cap.set(cv2.CAP_PROP_FPS, 15) # 强制设为15fps prev_time time.time() while True: ret, frame cap.read() if not ret: break # 控制处理节奏确保每帧处理时间不超过66ms15fps current_time time.time() if current_time - prev_time 1/15: time.sleep(1/15 - (current_time - prev_time)) continue prev_time time.time() # 检测追踪计数流程 detections detector.detect(frame) tracks tracker.update(detections) count counter.update(tracks, frame.shape[:2]) # 绘制结果并显示 frame draw_utils.draw_results(frame, tracks, count) cv2.imshow(Human Counting, frame) if cv2.waitKey(1) 0xFF ord(q): break关键细节time.sleep()不是万能的树莓派上精度只有±10ms。所以我们在循环开头加了if current_time - prev_time 1/15判断如果处理太快就跳过本帧宁可丢帧也不能让计数逻辑被挤压变形。4.3 检测模块实现ONNX加速推理的完整封装PyTorch直接推理在树莓派上太慢必须转ONNX。但YOLOv5官方导出脚本有个坑默认--dynamic-batch会生成不兼容的opset。我们用定制脚本# export_onnx.py import torch from models.experimental import attempt_load model attempt_load(models/yolov5s_best.pt, map_locationcpu) model.eval() dummy_input torch.randn(1, 3, 320, 320) # 输入尺寸必须和训练一致 torch.onnx.export( model, dummy_input, models/yolov5s_best.onnx, opset_version11, # 必须用1112在树莓派上不支持 input_names[input], output_names[output], dynamic_axes{input: {0: batch}, output: {0: batch}} )推理时用ONNX Runtimeimport onnxruntime as ort session ort.InferenceSession(models/yolov5s_best.onnx) def detect(frame): # 预处理缩放、归一化、HWC→CHW→batch img cv2.resize(frame, (320, 320)) img img.astype(np.float32) / 255.0 img np.transpose(img, (2, 0, 1))[np.newaxis, ...] # 推理 outputs session.run(None, {input: img}) # 后处理NMS、坐标还原 return postprocess(outputs[0], frame.shape)4.4 轨迹关联模块ByteTrack的轻量化改造官方ByteTrack依赖lap库做匈牙利匹配但lap在ARM架构上编译失败。我们改用纯NumPy实现def linear_assignment(cost_matrix): 纯NumPy匈牙利算法兼容树莓派 from scipy.optimize import linear_sum_assignment if cost_matrix.size 0: return np.empty((0, 2), dtypeint) rows, cols linear_sum_assignment(cost_matrix) return np.column_stack((rows, cols)) class BYTETracker: def __init__(self): self.tracked_stracks [] self.lost_stracks [] self.removed_stracks [] def update(self, detections): # 高置信度检测框直接创建新轨迹 high_det [d for d in detections if d.conf 0.55] # 低置信度框只与未匹配轨迹匹配 low_det [d for d in detections if d.conf 0.55] # ...匹配逻辑... return active_tracks4.5 计数模块区域状态机的健壮性设计状态机必须处理三种异常目标短暂消失人在镜头边缘停留检测框时有时无。我们设定目标ID在区域内连续3帧未检测到才标记为OUT。目标分裂一个人被柱子遮挡检测成两个框。我们用IOU阈值0.3合并相邻框再送入追踪器。区域重叠多个检测区共存如校门图书馆入口。每个区域独立维护状态机最终汇总。核心代码class RegionCounter: def __init__(self, region_coords): self.region region_coords # [x1,y1,x2,y2] self.id_states {} # {id: IN/OUT/UNKNOWN} self.count 0 def update(self, tracks, frame_shape): for track in tracks: cx, cy track.tlbr[0] (track.tlbr[2]-track.tlbr[0])//2, \ track.tlbr[1] (track.tlbr[3]-track.tlbr[1])//2 in_region (self.region[0] cx self.region[2]) and \ (self.region[1] cy self.region[3]) if track.id not in self.id_states: self.id_states[track.id] UNKNOWN if in_region: if self.id_states[track.id] OUT: self.count 1 self.id_states[track.id] IN else: self.id_states[track.id] IN else: if self.id_states[track.id] IN: self.id_states[track.id] OUT return self.count5. 常见问题与排查技巧实录答辩前必看的12个致命坑5.1 树莓派部署内存溢出的终极解决方案现象程序运行2小时后卡死dmesg显示Out of memory: Kill process。根源Python的垃圾回收机制在ARM上不及时OpenCV的cv2.imshow持续分配显存。解决彻底禁用GUI显示export DISPLAY改用cv2.imwrite保存关键帧在app.py开头添加内存限制import resource resource.setrlimit(resource.RLIMIT_AS, (1024*1024*1024, -1)) # 限制1GB每100帧手动触发GCimport gc if frame_count % 100 0: gc.collect()5.2 Windows调试CUDA out of memory的精准定位现象在RTX3060上训练时报错CUDA out of memory但nvidia-smi显示显存只用了60%。根源PyTorch默认缓存显存即使释放tensor也不归还。解决训练脚本开头添加torch.cuda.empty_cache()在train.py的for batch in dataloader:循环内每10个batch后执行if i % 10 0: torch.cuda.synchronize() torch.cuda.empty_cache()最关键检查batch_size是否设为偶数。奇数batch会导致最后一个batch显存碎片化PyTorch无法复用。5.3 检测框漂移监控画面抖动导致的ID频繁切换现象同一人走过镜头ID从1变2再变3计数翻倍。根源ByteTrack的卡尔曼滤波器在画面抖动时预测不准。解决在tracker.py中增加画面稳定性检测def is_frame_stable(prev_frame, curr_frame): # 计算两帧间ORB特征点匹配数 orb cv2.ORB_create() kp1, des1 orb.detectAndCompute(prev_frame, None) kp2, des2 orb.detectAndCompute(curr_frame, None) bf cv2.BFMatcher(cv2.NORM_HAMMING, crossCheckTrue) matches bf.match(des1, des2) return len(matches) 50 # 匹配点少于50说明画面抖动严重当检测到抖动时暂停ID更新沿用上一帧的轨迹。5.4 计数不准光线变化引发的误检爆发现象傍晚路灯亮起地面反光被识别为人。根源YOLOv5的默认训练数据缺乏夜间样本。解决在data/my_data.yaml中增加mosaic: 0.0关闭马赛克增强它会破坏光影一致性添加hsv_h: 0.015, hsv_s: 0.7, hsv_v: 0.4的HSV扰动模拟不同光照最有效在检测后增加规则过滤——删除所有宽度高度×0.3的框地面反光通常是扁平长条。5.5 模型加载失败PyTorch版本与权重不兼容现象torch.load(best.pt)报错AttributeError: dict object has no attribute modules。根源PyTorch1.12保存的权重用1.13加载时模型结构解析失败。解决统一环境所有开发机、树莓派、答辩机都用torch1.13.1cu117加载时指定map_locationcheckpoint torch.load(best.pt, map_locationcpu) model.load_state_dict(checkpoint[model].float().state_dict())5.6 视频流卡顿USB摄像头带宽不足现象cap.read()返回False或帧率骤降至3fps。根源USB2.0摄像头在树莓派上默认使用UVC协议带宽被其他设备抢占。解决在/boot/config.txt末尾添加# USB摄像头专用配置 dtoverlayvcsm-cma usbcore.autosuspend-1启动时强制设置分辨率cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)避免摄像头自适应协商。5.7 区域坐标错位不同屏幕DPI导致的拖拽偏移现象在2K屏幕上拖拽的区域放到1080p显示器上位置偏移。根源OpenCV的cv2.setMouseCallback获取的坐标是像素坐标但高DPI屏幕存在缩放因子。解决在拖拽前获取当前DPIimport tkinter as tk root tk.Tk() dpi root.winfo_fpixels(1i) # 获取每英寸像素数 root.destroy()保存坐标时除以DPI缩放因子加载时乘回。5.8 日志爆炸TensorBoard日志文件撑爆硬盘现象runs/train/exp/目录下日志文件达2GB树莓派SD卡满。根源YOLOv5默认每10步保存一次模型且记录所有中间变量。解决修改train.py注释掉writer.add_scalar相关行在train.py开头添加磁盘空间检查import shutil total, used, free shutil.disk_usage(/) if free 1024**3: # 小于1GB报警 print(WARNING: Disk space low!) # 自动清理旧日志5.9 多人遮挡密集场景下的ID丢失现象食堂打饭窗口5人排队时ID频繁丢失。根源ByteTrack的IoU匹配在重叠目标间失效。解决启用reid功能用torchreid提取简单特征匹配时融合IoU和余弦相似度但更轻量在tracker.py中增加“遮挡恢复”逻辑——当ID丢失后若3帧内在原位置附近出现新目标且框大小变化20%则恢复原ID。5.10 导出Excel失败中文路径乱码现象pandas.to_excel()报错UnicodeEncodeError。根源Windows默认GBK编码pandas用UTF-8写入。解决不用Excel改用CSVdf.to_csv(count_log.csv, encodingutf-8-sig)utf-8-sig会在文件开头加BOMExcel能正确识别中文。5.11 模型精度波动训练过程中的随机性现象同样数据集两次训练mAP相差8%。根源PyTorch的随机种子未完全固定。解决在train.py开头添加import random import numpy as np import torch def set_seed(seed0): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed(seed) torch.cuda.manual_seed_all(seed) # 多GPU torch.backends.cudnn.deterministic True torch.backends.cudnn.benchmark False set_seed(42)5.12 答辩演示崩溃最后一刻的保命技巧现象答辩现场程序突然黑屏。终极保命方案准备三套演示素材实时摄像头备用、本地视频主力、预渲染GIF救急写一个demo_fallback.py当主程序异常退出时自动启动GIF播放器所有路径用相对路径避免U盘拔插导致路径错误最重要答辩前用python -m py_compile app.py编译字节码防止源码被意外修改。最后分享一个小技巧答辩时老师问“这个系统能扩展吗”不要说“可以加人脸识别”而是打开config/model_config.yaml指着confidence_threshold: 0.45这一行说“目前设为0.45保证召回率如果要减少误报调高到0.6就行所有代码都不用改。”——这比讲一百句理论更有说服力。本文还有配套的精品资源点击获取