简介:一套基于YOLOv5视觉检测的图书馆占座行为实时监测与治理效果分析系统源码,面向高校图书馆、公共自习空间管理人员,以及智慧校园和计算机视觉方向开发者,解决占座行为难发现、治理效果难量化、座位资源分配不透明等问题。压缩包共106个文件,约15.36MB,包含33个Python源码、40个yaml与11个yml配置、5个Shell脚本,同时提供Dockerfile、Git配置、教程ipynb、模型权重pt和测试图片,完整覆盖模型配置、核心逻辑、容器部署、版本控制与演示验证。已有357人学习下载。系统利用YOLOv5对座位占用进行实时检测与行为分析,可输出不同时段占座趋势、识别长时间占座行为,并通过治理前后数据对比评估管理措施是否有效;源码结构清晰,学习者既能直接复现图书馆智能监测流程,也可借鉴其工程化设计,掌握目标检测训练与推理、Python项目组织、Shell自动化运维等技能。这份代码适合作为高校课设、毕业设计或智慧园区视觉项目的完整参考,具有较强的实用与学习价值。
1. 为什么图书馆占座监测要用YOLOv5视觉检测
图书馆占座问题在很多高校都存在。管理员靠肉眼巡检,效率低且无法覆盖所有时段;纯预约系统又感知不到座位上的真实行为——人离开但书还在,人坐在那里玩手机也算“使用”。基于YOLOv5视觉检测的图书馆占座行为实时监测,就是把摄像头画面转成结构化数据:检测人、物品和座位区域,判断“空闲”“使用中”“占座”三种状态,并将这些状态填入治理效果分析报表。这套系统适合毕业设计、图书馆信息化改造,以及任何想用YOLOv5走通采集、训练、部署、分析完整链路的人。难点不在于把模型mAP刷高,而在于从检测框到业务逻辑的映射是否可靠。
2. YOLOv5视觉检测模型选型与训练数据集准备
在YOLOv5环境配置完成后,首先需要确定用哪个尺寸的模型,以及训练数据要标到什么程度。YOLOv5的学习曲线比Faster R-CNN平滑,社区资料多,遇到anchor或loss异常时能快速搜到解决方案。要理解YOLOv5网络结构中的几个关键点,才能知道后面的训练参数怎么改。
2.1 YOLOv5网络结构里真正影响占座检测的组件
2.1.1 Backbone与Neck的分工
YOLOv5的Backbone(CSPDarknet)负责在多尺度上提取特征,Neck(PANet)负责把高层语义和底层细节融合。很多教程只强调Backbone的残差结构,但在占座监测里,书、水杯这类小目标更依赖Neck。默认的yolov5s.yaml中depth_multiple=0.33,width_multiple=0.50,如果数据集里“book”和“bottle”的标注框普遍小于32x32像素,建议把depth_multiple提到0.50,让Neck的卷积层更宽,小目标特征传导更多。此时模型从s变为m,单张推理时间在RTX 3060上约为8ms到12ms,仍然适合实时轮询。
2.1.2 Anchor自适应与默认值的差异
YOLOv5在训练前会重新计算anchor,但默认的通用anchor偏大,适合COCO的80类目标。图书馆里人和书的尺度差异很大,第一次运行训练时会打印“Analyzing anchors...”,这时可以手动检查输出。如果发现最大的anchor比我们要检测的书本大很多,可在data/hyps里设置anchor_t=4.0,扩大正样本匹配范围,缓解小目标匹配不到的问题。不要直接改模型yaml里的anchor列表,训练器的自适应逻辑会覆盖它。
不同backbone宽度对占座检测的影响,可以直观地看下表:
| 模型 | depth_multiple | width_multiple | 参数量 | 640x640推理时间(RTX 3060) |
|---|---|---|---|---|
| yolov5s | 0.33 | 0.50 | 7.3M | 8ms |
| yolov5m | 0.50 | 0.75 | 21.2M | 12ms |
如果摄像头同时监控超过8个座位,建议用yolov5s,因为目标多且彼此重叠少;如果只监控单张书桌,用yolov5m精度收益更明显。
2.2 用自己的数据集训练YOLOv5的最小命令
2.2.1 标注类别和目录组织
建议用labelimg这类工具,类别只设四个:person、bag、book、bottle。不要加“seat”,因为座位状态由后端坐标区域计算,不是检测目标。数据集目录如下:
dataset/ ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/每个图片对应同名txt文件,每行格式为class x_center y_center width height,坐标归一化到0到1。图书馆摄像头多为俯视角度,人只标上半身或头肩区域,否则桌子和电脑的遮挡会造成大量无效标注。标注时还有一个容易忽略的规则:当一个包被放在椅子上且露出部分很少时,仍然要框出完整可见区域,不要因为遮挡而跳过。
2.2.2 训练命令行与关键参数
python train.py \ --data data/seat.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 32 \ --epochs 150 \ --device 0 \ --hyp data/hyps/hyp.scratch-low.yamlseat.yaml中nc: 4,names列表和标注类别顺序一致。hyp.scratch-low.yaml的增强幅度比较保守,适合室内固定场景。如果显卡显存小于8GB,把batch降到16,--img可降到512,但训练和推理的--img必须一致,否则检测框坐标会偏移。训练完成后用python detect.py --source test.jpg --weights runs/train/exp/weights/best.pt --conf 0.35快速看效果,确认数据标签是否有错位。
3. 实时监测系统的前后端架构与模型集成
系统设计源码通常采用基于Django + Vue的Web架构,摄像头RTSP流推给推理服务,推理服务把状态写进数据库和WebSocket通道。下面说明如何把YOLOv5检测结果变成图书馆可用的业务状态。
3.1 Django后端如何接管YOLOv5推理结果
3.1.1 避免在视图里跑detect.py
不要直接把detect.py的main函数塞进Django视图,命令行参数和日志输出会拖垮请求线程。常见的做法是将yolov5作为一个库导入,或者在项目里复制models/common.py中的DetectMultiBackend。最省事的版本是使用torch.hub加载自定义权重:
import torch model = torch.hub.load('/path/to/yolov5', 'custom', path='weights/best.pt', source='local') model.conf = 0.35 model.iou = 0.45 def analyze_frame(frame): results = model(frame, size=640) rows = results.pandas().xyxy[0] return rows[['class', 'name', 'xmin', 'ymin', 'xmax', 'ymax', 'confidence']].to_dict('records')torch.hub.load支持本地源,不需要每启动都联网。模型应在Django的AppConfig.ready()中加载一次,否则每个请求都会初始化一次权重,显存占用飙升。model.conf是全局置信度阈值,model.iou是NMS的IoU阈值,图书馆场景下两者分别设为0.35和0.45比较均衡。
3.1.2 抽帧频率与RTSP断流重连
import cv2 def capture_loop(camera_url): cap = cv2.VideoCapture(camera_url) cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) frame_id = 0 while True: ok, frame = cap.read() if not ok: cap.open(camera_url) continue if frame_id % 9 == 0: yield frame frame_id += 1占座状态一般几秒内不会变化,所以每3秒抽一帧(配合25fps就是每75帧)足够。CAP_PROP_BUFFERSIZE设为1可以减少推流延迟,避免读到几秒前的旧帧。如果摄像头断流,cap.read()会返回False,这里直接用cap.open()重新连接,比重新创建VideoCapture对象更快。
3.2 数据库表结构与座位状态流转
3.2.1 两张核心表的设计
实时状态表存当前识别结果,历史表存状态变化记录:
| 字段 | 类型 | 说明 |
|---|---|---|
| seat_id | CharField | 座位编号 |
| status | CharField | idle/occupied/left_items |
| person_conf | FloatField | 人员检测置信度 |
| items_json | JSONField | 检测到的物品类型和坐标 |
| update_time | DateTimeField | 最后更新时间 |
历史表增加start_time、end_time、duration,每次状态从occupied切到left_items时写一条完整记录,方便后续算平均占座时长。items_json里存的是检测到的bag/book/bottle的归一化坐标,不存人物框,因为人物的坐标只对当次判断有用。
3.2.2 用状态机避免单帧抖动
state_machine = {} # seat_id -> (status, since_time) def update_seat(seat_id, person_boxes, item_boxes, now): status, since = state_machine.get(seat_id, ('idle', now)) new_status = 'occupied' if person_boxes else ('left_items' if item_boxes else 'idle') if new_status != status: state_machine[seat_id] = (new_status, now) save_history(seat_id, status, new_status, now)注意触发条件:如果座位上有包,但人只是暂时离开接水,立刻变成left_items会导致误报。常见做法是设置一个宽限时间,比如人离开超过3分钟才进入left_items,这需要在状态机里加一个pending状态,而不是直接跳转。
3.3 Vue前端实时展示检测结果
3.3.1 用WebSocket推送而非轮询
Django Channels实现ASGI后,前端订阅座位状态频道:
{"seat_id": "A-12", "status": "left_items", "duration": 1860}前端拿到数据后更新座位图。检测框的可视化只在后台大屏上做,用户端的平面图不需要显示bounding box,避免视觉噪音。前端用Vue的<canvas>绘制座位网格,每个格子颜色随status切换。WebSocket推送频率建议控制在每3秒一次,即使座位状态没变也可以推送心跳帧,方便前端判断连接是否存活。
4. YOLOv5训练自己的数据集:参数调整与占座特征优化
模型训练出的“人”和“包”只是中间结果,要在图书馆这种复杂背景里稳定识别,需要针对场景调训练参数和推理逻辑。
4.1 图书馆场景下的数据处理和增强参数
4.1.1 YOLOv5超参数里最值得改的三个值
默认的hyp.scratch-low.yaml对自然场景效果好,但图书馆天花板射灯和窗边逆光让模型容易过拟合光线。实际调整时,优先改这三个:
| 参数 | 默认值 | 建议值 | 原因 |
|---|---|---|---|
| hsv_h | 0.015 | 0.01 | 降低色相偏移幅度 |
| hsv_v | 0.4 | 0.3 | 亮度扰动减小,避免灯光反光干扰 |
| degrees | 0.0 | 5.0 | 允许轻微旋转,模拟摄像头安装误差 |
修改后将yaml通过--hyp hyp.library.yaml传入。另外--cache ram会把图片缓存到内存,数据集不足10GB时可以明显缩短epoch间的时间。如果数据量超过5000张,不要用ram缓存,否则会占满内存导致进程被杀。
4.1.2 用“难例挖掘”补足数据缺口
训练后把所有val图片的检测结果保存,人工挑出FP(假正例)和漏检图,再加进训练集。这个过程重复两轮,比直接增加随机图片有效。不要依赖自动标注工具做全部标注,占座场景里书和包经常互相遮挡,自动标注往往把两个目标合并成一个框,导致后续后处理拿不到准确的目标数。
4.2 后处理逻辑:检测框如何决定“占座”
4.2.1 用座位多边形过滤检测框
模型输出的是全图坐标,但系统只关心落在座位区域内的检测。在搭建系统时预先用鼠标标出每个座位的多边形区域,然后判断目标中心点是否落在多边形内:
def is_inside(bbox, seat_polygon): cx = (bbox[0] + bbox[2]) / 2 cy = (bbox[1] + bbox[3]) / 2 return cv2.pointPolygonTest(seat_polygon, (cx, cy), False) >= 0pointPolygonTest返回正数表示点在内部,负数表示外部。物品检测框的中心可能位于座位区域边缘,所以对bag和book的判断可以用“中心点往八个方向偏移几个像素,任意一个在多边形内即算命中”。这一步能显著减少隔壁座位的干扰,也是从“目标检测”走向“业务判断”的关键一步。
4.2.2 区分“人离开但物品还在”的时长条件
当状态变为left_items后,再通过定时器累计持续时间。常见做法是每5分钟重新检测一次,如果超过15分钟仍未变化,就推送给管理员。这里的15分钟是业务配置值,不要硬编码在代码里,应放到Django的settings或数据库配置表中,方便图书馆管理员自助调整。
4.3 模型在真实图书馆环境下的排错路径
4.3.1 用混淆矩阵定位类别问题
运行python val.py --data data/seat.yaml --weights weights/best.pt --task val,得到混淆矩阵。如果book经常被识别成bottle,说明两者在俯视视角下形状相近,需要补充不同角度和摆放状态的数据。如果person和背景混淆,优先检查训练集里是否有大量远距离的小目标正样本。混淆矩阵里对角线上的数字接近1不代表绝对可靠,还要看每个类别的精确率和召回率。
4.3.2 置信度阈值调整的验证方法
python detect.py --source ./val_imgs --weights best.pt --conf-thres 0.30 --save-txt --project runs/conf_test跑完不同阈值后,统计每个图片的预测数量和人工标签数量,选择F1最高的conf。不要直接调到0.15,那样会把书脊纹理误检成人。如果某个类别始终误检,可以在后处理里对类别单独设置置信度,比如person的conf必须大于0.5,book大于0.3。这种按类别的阈值策略比全局阈值更符合图书馆场景。
4.3.3 常见漏检的根源
除了模型参数,还要检查推理时的输入尺寸。训练时用640,推理时却用了416,小目标会丢失。OpenCV读取的BGR帧和YOLOv5预处理的RGB切换错误也会导致结果异常。调试时可以先保存一帧预处理后的图片,看它和训练集风格是否一致。如果检测结果一直很好,只有某个摄像头不行,优先排查那个摄像头的分辨率和焦距,而不是重新训练模型。
5. 治理效果分析指标与实时监测的验证技巧
治理效果分析不能只靠“感觉占座少了”,需要定义可计算指标,并用系统记录落库。
5.1 占座率与平均占座时长的计算口径
占座率=在统计时段内处于left_items状态的座位数/总座位数。平均占座时长仅计算触发治理动作(如关闭座位或放置提示牌)后释放的座位,避免把夜间无人清场的超长记录拉高平均值。SQL聚合示例:
SELECT DATE(start_time) AS day, COUNT(*) AS cnt, AVG(TIMESTAMPDIFF(MINUTE, start_time, end_time)) AS avg_minutes FROM seat_history WHERE status='left_items' AND action_taken IS NOT NULL GROUP BY DATE(start_time);这里的action_taken字段记录治理动作编码,例如1表示提示单,2表示工作人员介入。只有在治理动作执行后再结束的left_items记录才计入统计,这样得到的是治理措施的响应时间。
5.2 用A/B测试验证治理措施的有效性
在一层贴提示单,另一层保持原样,用系统数据对比两周内占座时长变化。Django管理命令可以生成对比表:
python manage.py governance_analysis --group A --group B --start 2025-04-01 --end 2025-04-14重点看平均占座时长和高峰时段占座率的差异。如果两者差异低于5%,说明治理动作未达到统计显著效果,需要改变执行方式。系统设计时要把分组标签写入座位表,这样治理效果分析可以直接按座位所属组聚合。
5.3 验证监测系统本身的检出正确率
治理效果分析可信的前提是系统判断没有被“质疑”。安排学生执行三种动作:正常就座、放下物品离开、离开后叫管理员清理,系统根据日志生成混淆矩阵,计算Kappa系数。验证时要注意,系统判断的“left_items”和人工记录的“占座”之间有时间偏差,需要把系统状态切换时间点与人工时间点对齐后再计算。这个验证脚本应作为项目源码的一部分交付,方便答辩或立项时展示。检测模型更新后也要重跑验证流程,不能只看loss曲线。
本文还有配套的精品资源,点击获取