智能校园监控实战:YOLOv5+OpenCV+Flask异常行为识别系统
简介这份资源面向计算机视觉与深度学习方向的毕业设计学生及校园安防系统开发者提供一套智能校园安全监控与异常行为识别的完整项目源码。系统以YOLOv5目标检测算法为核心结合OpenCV完成图像滤波、边缘检测与特征提取等预处理后端基于PyTorch构建并训练模型通过Flask搭建RESTful API供前后端数据交互检测日志、行为记录与用户数据则由MySQL统一存储并融入人脸识别实现身份验证可对打架、侵入、异常停留等行为实时告警。压缩包共20个文件约112KB包含7个txt日志与说明、5个Python脚本、4个Shell运行脚本、3张结果图及1份README覆盖模型训练、实验对比与结果绘制等环节。目前已有64人学习适合作为课程设计、毕业设计参考或二次开发起点帮助读者快速理解多组件协同的智能监控体系搭建思路。1. 智能校园监控落地YOLOv5 OpenCV Flask 这套组合拳到底能不能打校园安防这件事做过的人都知道最怕的不是没摄像头而是摄像头背后没人盯。一个几千人的学校出入口、走廊、操场、食堂加起来上百路视频流靠保安盯着电视墙漏报是必然的。我去年帮一所职校做了一版异常行为识别系统核心思路就是用 YOLOv5 做目标检测OpenCV 做视频流预处理和图像增强PyTorch 做训练和推理后端Flask 暴露 API 给前端调用MySQL 存报警记录和人员轨迹。这套方案不是什么学术前沿但胜在工程上跑得通、成本可控、维护门槛低。这篇文章面向的是想把这套系统真正跑起来的人——不管你是做计算机视觉大作业的学生还是接了小项目需要交付的工程师。我会从模型选型、数据标注、训练调参、推理部署到后端 API 串联把每一步的参数和踩坑点讲清楚。你不需要先看完 CS231n 的 PPT 才能动手但需要有一台带 NVIDIA 显卡的机器或者至少能在 WSL 里搭起 PyTorch 环境。读完你应该能判断这套方案在你的场景里值不值得做以及怎么做才能不翻车。2. 从视频流到检测框YOLOv5 与 OpenCV 的流水线怎么搭2.1 为什么选 YOLOv5 而不是 Faster R-CNN 或 YOLOv8先说选型理由。校园监控的场景有几个硬约束第一实时性要求高走廊和出入口的摄像头至少需要 15 FPS 以上的处理速度第二异常行为翻越围墙、摔倒、聚集往往需要结合时序判断但检测层必须先把人和关键物体框出来第三部署环境通常是边缘设备或者中等配置的服务器没有 A100 这种卡。YOLOv5 在这个场景下的优势很明显单阶段检测推理速度快YOLOv5s 在 RTX 3060 上跑 640×640 输入能到 60 FPS 以上模型体积小YOLOv5s 的权重文件只有 14MB 左右方便部署到树莓派 5 或者 Jetson 这类边缘设备社区生态成熟训练脚本和导出工具链完整遇到问题搜得到答案。Faster R-CNN 精度可能略高但两阶段检测的速度在实时场景下很难接受。YOLOv8 虽然更新但如果你用的是较老的 PyTorch 版本或者需要兼容已有的 YOLOv5 代码库迁移成本不低。我一般会建议如果是新项目且环境可控直接上 YOLOv8如果是要复现已有代码或者跑在资源受限的设备上YOLOv5 仍然是稳妥选择。这篇文章以 YOLOv5 为主线因为它的训练和部署流程更成熟踩坑资料也更多。2.2 环境搭建PyTorch、OpenCV 和 YOLOv5 的版本对齐环境配置是第一个容易翻车的地方。PyTorch 和 Python 版本有对应关系OpenCV 的安装方式又分 pip 和 conda 两条路YOLOv5 对 torch 版本也有要求。我推荐用 conda 建虚拟环境避免污染系统 Python。# 创建虚拟环境Python 版本选 3.9 或 3.10 conda create -n campus_security python3.10 -y conda activate campus_security # 安装 PyTorch以 CUDA 11.8 为例 # 如果你用的是 WSL先确认 nvidia-smi 能看到显卡 pip install torch2.0.1 torchvision0.15.2 --index-url https://download.pytorch.org/whl/cu118 # 安装 OpenCV pip install opencv-python4.8.1.78 opencv-contrib-python4.8.1.78 # 克隆 YOLOv5 仓库并安装依赖 git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txt这里有几个参数需要说明。torch2.0.1是我在多个项目里验证过比较稳定的版本和 YOLOv5 的兼容性好。OpenCV 装opencv-python和opencv-contrib-python两个包后者包含一些额外的图像处理模块比如背景减除和光流法后面做异常行为判断会用到。如果你在 WSL 里跑需要先装好 NVIDIA 驱动和 CUDA toolkitnvidia-smi能正常输出才算环境通了。注意不要同时用 pip 和 conda 安装 OpenCV容易出现ModuleNotFoundError: No module named cv2或者版本冲突。如果已经混装了先pip uninstall opencv-python opencv-contrib-python再重新用 pip 装。2.3 视频流接入与 OpenCV 预处理从 RTSP 到检测输入校园监控的摄像头一般是 RTSP 流或者 RTMP 流。OpenCV 的VideoCapture可以直接读 RTSP 地址但实际用的时候有几个坑网络抖动会导致帧丢失直接read()可能返回空帧多路视频流同时读的时候单线程会阻塞。我的做法是用一个独立的线程池去拉流每个摄像头一个线程读到的帧放进队列检测线程从队列里取最新帧。这样即使某一路流卡了不会影响其他路。import cv2 import threading import queue import time class VideoStream: def __init__(self, rtsp_url, queue_size2): self.cap cv2.VideoCapture(rtsp_url) # 设置缓冲区大小减少延迟 self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) self.queue queue.Queue(maxsizequeue_size) self.running True self.thread threading.Thread(targetself._update, daemonTrue) self.thread.start() def _update(self): while self.running: ret, frame self.cap.read() if not ret: # 断流重连 time.sleep(1) self.cap cv2.VideoCapture(self.cap.get(cv2.CAP_PROP_BACKEND)) continue # 如果队列满了丢掉旧帧保证取到的是最新画面 if self.queue.full(): try: self.queue.get_nowait() except queue.Empty: pass self.queue.put(frame) def read(self): try: return self.queue.get(timeout1) except queue.Empty: return None def release(self): self.running False self.cap.release()这段代码的关键参数是CAP_PROP_BUFFERSIZE设成 1 可以大幅降低延迟但有些摄像头不支持这个属性设了也没用。队列大小设成 2 是为了在检测速度跟不上时丢帧而不是积压保证实时性。断流重连的逻辑是必须的校园网络环境不一定稳定没有重连机制的话一路摄像头断了整个系统就少一块。预处理部分YOLOv5 的输入需要是 640×640 的 RGB 图像并且归一化到 0-1。YOLOv5 的推理代码里已经包含了 letterbox 缩放你只需要把 OpenCV 读到的 BGR 帧转成 RGB 就行。如果要做图像增强比如夜间场景提亮可以在送入模型前用 OpenCV 做 CLAHE 或者 gamma 校正。def preprocess(frame): # BGR 转 RGB img cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # 如果光线暗做自适应直方图均衡 if img.mean() 60: clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8, 8)) lab cv2.cvtColor(img, cv2.COLOR_RGB2LAB) lab[:, :, 0] clahe.apply(lab[:, :, 0]) img cv2.cvtColor(lab, cv2.COLOR_LAB2RGB) return imgclipLimit2.0是 CLAHE 的对比度限制参数设太高会放大噪声设太低效果不明显2.0 是我在校园夜间监控里试出来比较平衡的值。tileGridSize(8,8)表示把图像分成 8×8 的块分别做均衡块太小会过度增强局部噪声。3. 训练自己的校园异常行为数据集标注、配置与调参3.1 数据标注用 LabelImg 标出翻越、摔倒和聚集校园异常行为检测公开数据集几乎没有现成的。你需要自己采集和标注。我一般会从摄像头截取视频片段用 FFmpeg 抽帧然后用 LabelImg 标注。标注的类别不用太多初期建议聚焦三类person人、climb翻越、fall摔倒。聚集行为可以通过person的数量和距离来判断不需要单独标一类。# 用 FFmpeg 抽帧每秒抽 2 帧 ffmpeg -i campus_video.mp4 -vf fps2 frames/%06d.jpg # 安装 LabelImg pip install labelImg labelImg frames/ classes.txtclasses.txt里每行一个类别名顺序要和训练配置里的names一致。标注的时候注意翻越行为的框要包含人和围墙的接触区域摔倒的框要包含整个人体不要只框头部。标注质量直接决定模型上限标错了后面调参也救不回来。3.2 数据集划分与 YOLOv5 格式转换YOLOv5 要求的数据格式是每张图对应一个.txt文件每行是class_id x_center y_center width height坐标都归一化到 0-1。LabelImg 可以直接导出 YOLO 格式但如果你用的是其他标注工具需要自己写脚本转。import os import random import shutil def split_dataset(img_dir, label_dir, output_dir, train_ratio0.8): images [f for f in os.listdir(img_dir) if f.endswith(.jpg)] random.shuffle(images) split_idx int(len(images) * train_ratio) train_imgs images[:split_idx] val_imgs images[split_idx:] for subset, img_list in [(train, train_imgs), (val, val_imgs)]: img_out os.path.join(output_dir, images, subset) lbl_out os.path.join(output_dir, labels, subset) os.makedirs(img_out, exist_okTrue) os.makedirs(lbl_out, exist_okTrue) for img_name in img_list: shutil.copy(os.path.join(img_dir, img_name), os.path.join(img_out, img_name)) lbl_name img_name.replace(.jpg, .txt) lbl_src os.path.join(label_dir, lbl_name) if os.path.exists(lbl_src): shutil.copy(lbl_src, os.path.join(lbl_out, lbl_name)) split_dataset(frames/, labels/, campus_dataset/, train_ratio0.8)train_ratio0.8是常见的划分比例但如果你的异常行为样本很少比如摔倒只有几十张建议把比例调到 0.9让模型多学一点。验证集的作用是看模型有没有过拟合如果验证集 loss 一直降不下去说明训练集和验证集分布差异太大需要检查标注或者增加数据。3.3 YOLOv5 训练配置超参数怎么设才不玄学YOLOv5 的训练入口是train.py核心参数在data/campus.yaml和models/yolov5s.yaml里。campus.yaml指定数据集路径和类别数path: ./campus_dataset train: images/train val: images/val nc: 3 names: [person, climb, fall]nc: 3是类别数必须和names的长度一致。models/yolov5s.yaml一般不用改除非你要调整网络结构。训练命令python train.py \ --data data/campus.yaml \ --weights yolov5s.pt \ --epochs 100 \ --batch-size 16 \ --img-size 640 \ --lr0 0.01 \ --lrf 0.01 \ --patience 20 \ --device 0--weights yolov5s.pt表示从预训练权重开始微调这比从头训练收敛快得多。--epochs 100是上限实际训练中如果验证集指标 20 轮不提升--patience 20会触发早停。--lr0 0.01是初始学习率YOLOv5 默认用 SGD 优化器这个值在 batch size 16 的情况下比较合适如果你把 batch size 调到 32学习率可以相应提到 0.02。--lrf 0.01是最终学习率因子控制余弦退火的终点。注意如果你在 WSL 里训练--device 0指定第一块 GPU。如果报CUDA out of memory先把 batch size 降到 8 或者 4再不行就减小--img-size到 416。训练过程中重点看三个指标mAP0.5、precision和recall。校园异常行为检测里recall 比 precision 更重要——漏报一个翻越行为可能出大事误报多一点可以靠人工复核过滤。如果 recall 偏低可以调低置信度阈值或者在数据增强里增加马赛克和混合增强的比例。4. Flask 后端与 MySQL把检测结果变成可查询的报警记录4.1 Flask API 设计接收视频帧、返回检测结果Flask 在这里的角色是中间层前端或者摄像头管理平台把视频帧或者 RTSP 地址发给 FlaskFlask 调用 YOLOv5 推理把检测结果返回同时把异常行为写入 MySQL。API 设计不用太复杂两个接口就够一个/detect接收单帧图像返回检测框一个/stream接收 RTSP 地址做持续检测。from flask import Flask, request, jsonify import torch import cv2 import numpy as np import base64 import pymysql app Flask(__name__) model torch.hub.load(ultralytics/yolov5, custom, pathbest.pt) model.conf 0.4 # 置信度阈值 model.iou 0.45 # NMS IoU 阈值 def get_db(): return pymysql.connect(hostlocalhost, userroot, passwordyour_password, databasecampus_security, charsetutf8mb4) app.route(/detect, methods[POST]) def detect(): data request.json img_b64 data[image] img_bytes base64.b64decode(img_b64) img_array np.frombuffer(img_bytes, np.uint8) frame cv2.imdecode(img_array, cv2.IMREAD_COLOR) frame_rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results model(frame_rgb) detections results.pandas().xyxy[0].to_dict(orientrecords) # 如果检测到异常行为写入数据库 for det in detections: if det[name] in [climb, fall] and det[confidence] 0.5: conn get_db() cursor conn.cursor() cursor.execute( INSERT INTO alerts (behavior, confidence, timestamp) VALUES (%s, %s, NOW()), (det[name], float(det[confidence])) ) conn.commit() cursor.close() conn.close() return jsonify({detections: detections}) if __name__ __main__: app.run(host0.0.0.0, port5000, threadedTrue)model.conf 0.4是置信度阈值低于这个值的检测框会被过滤掉。校园场景下我一般设 0.4 到 0.5太低会误报太高会漏报。model.iou 0.45是 NMS 的 IoU 阈值控制重叠框的合并程度。threadedTrue让 Flask 支持多线程否则多个摄像头同时请求会阻塞。4.2 MySQL 表结构报警记录和人员轨迹怎么存数据库表不用设计得太复杂两张表就够alerts存异常行为报警tracks存人员轨迹。alerts表结构CREATE TABLE alerts ( id INT AUTO_INCREMENT PRIMARY KEY, behavior VARCHAR(32) NOT NULL, confidence FLOAT NOT NULL, camera_id VARCHAR(64), timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, image_path VARCHAR(256), INDEX idx_timestamp (timestamp), INDEX idx_behavior (behavior) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;behavior字段存climb或fallcamera_id标记是哪路摄像头image_path存报警截图路径方便事后查证。索引加在timestamp和behavior上因为查询最多的是「最近一周的翻越报警」这类条件。tracks表用来存人员轨迹如果你需要做轨迹追踪的话CREATE TABLE tracks ( id INT AUTO_INCREMENT PRIMARY KEY, track_id INT NOT NULL, camera_id VARCHAR(64), bbox_x1 FLOAT, bbox_y1 FLOAT, bbox_x2 FLOAT, bbox_y2 FLOAT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_track (track_id, timestamp) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;轨迹追踪需要配合 DeepSORT 或者 ByteTrack 这类算法YOLOv5 只负责检测追踪是另一层逻辑。如果只是做异常行为报警tracks表可以先不建。4.3 推理性能优化从 30 FPS 到 60 FPS 的几个手段YOLOv5 在 RTX 3060 上跑 640×640 输入PyTorch 原生推理大概 30 FPS 左右。要提到 60 FPS有几个手段第一用 TensorRT 导出引擎速度能翻倍但导出过程有坑后面避坑章节会讲第二用半精度 FP16 推理model.half()一行代码速度提升 30% 左右精度损失很小第三降低输入分辨率到 416×416速度提升明显但小目标检测会变差。# FP16 推理 model.half() frame_rgb frame_rgb.astype(np.float16) / 255.0FP16 推理需要显卡支持RTX 系列都支持。如果你用的是 GTX 10 系FP16 性能反而可能下降因为那些卡的 FP16 是模拟的。WSL 环境下 FP16 支持也取决于驱动版本如果报错就退回 FP32。5. 避坑与排查校园监控项目里最容易翻车的 5 个地方5.1 RTSP 流读不到帧或者延迟越来越高现象cv2.VideoCapture(rtsp_url)返回retFalse或者跑了几分钟后画面延迟从 1 秒涨到十几秒。原因RTSP 流默认走 TCP 传输网络抖动时 OpenCV 的缓冲区会积压导致延迟累积。另外有些摄像头不支持CAP_PROP_BUFFERSIZE设了也没用。解决在 RTSP 地址后面加?tcp强制 TCP或者用ffmpeg拉流转发。更彻底的做法是用PyAV或者GStreamer替代 OpenCV 的 VideoCapture它们对缓冲区的控制更精细。如果延迟已经涨上来了只能重启拉流线程。5.2 训练 loss 不下降或者 mAP 一直是 0现象训练了几十个 epochbox_loss和obj_loss都不降mAP 接近 0。原因最常见的是标注格式不对。YOLOv5 要求坐标归一化到 0-1如果你直接用了像素坐标模型学不到东西。另一个原因是data/campus.yaml里的nc和names长度不一致或者图片路径写错了模型加载不到数据。解决先用python utils/autoanchor.py检查 anchor 是否匹配再用python train.py --data data/campus.yaml --weights --epochs 1跑一个 epoch看输出里的train和val样本数是不是 0。如果是 0检查路径。5.3 Flask 多线程下模型推理报 CUDA error现象单线程跑没问题一开多线程就报CUDA error: out of memory或者RuntimeError: CUDA error: device-side assert triggered。原因PyTorch 的 CUDA 上下文不是线程安全的多个线程同时调用同一个模型对象会冲突。解决给每个线程创建独立的模型实例或者用锁串行化推理。更好的做法是用torch.multiprocessing或者把推理服务单独部署成 gRPC 服务Flask 只做请求转发。5.4 MySQL 写入报警记录时中文乱码现象behavior字段存进去是????或者乱码。原因数据库连接没有指定charsetutf8mb4或者表创建时用了latin1。解决连接时加charsetutf8mb4建表时指定DEFAULT CHARSETutf8mb4。如果表已经建了用ALTER TABLE alerts CONVERT TO CHARACTER SET utf8mb4;改。5.5 TensorRT 导出后推理结果和 PyTorch 不一致现象PyTorch 下检测框正常导出 TensorRT 引擎后框的位置偏移或者置信度变了。原因TensorRT 对输入尺寸和归一化方式敏感YOLOv5 导出时如果--img-size和训练时不一致或者--half选项和推理时的精度不匹配都会导致结果偏差。解决导出时用和训练相同的--img-size推理时确保输入预处理和导出时一致。如果偏差不大几个像素可以接受如果偏差很大检查--dynamic选项动态 batch 有时会导致 anchor 计算错误。6. 进阶技巧用 OpenCV 光流法做摔倒行为的二次验证YOLOv5 检测摔倒有个天然缺陷单帧图像里一个人蹲下和摔倒的姿势很像模型容易混淆。我试过在 YOLOv5 检测到fall之后用 OpenCV 的光流法做二次验证——摔倒时人体区域的运动矢量会有一个快速向下的分量而蹲下时运动矢量更平缓。import cv2 import numpy as np def verify_fall(prev_frame, curr_frame, bbox): x1, y1, x2, y2 map(int, bbox) prev_roi cv2.cvtColor(prev_frame[y1:y2, x1:x2], cv2.COLOR_BGR2GRAY) curr_roi cv2.cvtColor(curr_frame[y1:y2, x1:x2], cv2.COLOR_BGR2GRAY) # 计算稠密光流 flow cv2.calcOpticalFlowFarneback(prev_roi, curr_roi, None, pyr_scale0.5, levels3, winsize15, iterations3, poly_n5, poly_sigma1.2, flags0) # 垂直方向的平均运动 mean_vy np.mean(flow[..., 1]) # 如果垂直向下运动超过阈值确认摔倒 return mean_vy 2.0pyr_scale0.5是金字塔缩放比例levels3是金字塔层数winsize15是窗口大小。这些参数是 OpenCV 光流法的经典配置在 640×640 的 ROI 上跑一次大概 5ms不会明显拖慢整体速度。mean_vy 2.0这个阈值需要根据你的摄像头角度和帧率调俯拍角度大的摄像头阈值可以设低一点。这个二次验证能把摔倒的误报率降低大概一半但代价是增加了计算量。如果你的场景对实时性要求极高可以只在 YOLOv5 置信度在 0.4 到 0.6 之间的模糊样本上跑光流验证高置信度的直接报警。提示光流法对光照变化敏感夜间红外模式下效果会打折扣。如果校园监控有夜间场景建议在光流验证前先做一次直方图均衡或者直接用 YOLOv5 的时序版本比如在 LSTM 上做后处理。这套系统我前后调了三个月最大的教训是不要一上来就追求高精度模型先把数据管线和推理链路跑通再慢慢调模型。校园场景的异常行为样本少数据增强和难例挖掘比换模型更有效。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →