简介:这份资源面向计算机视觉与深度学习方向的毕业设计学生及校园安防系统开发者,提供一套智能校园安全监控与异常行为识别的完整项目源码。系统以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 python=3.10 -y conda activate campus_security # 安装 PyTorch,以 CUDA 11.8 为例 # 如果你用的是 WSL,先确认 nvidia-smi 能看到显卡 pip install torch==2.0.1 torchvision==0.15.2 --index-url https://download.pytorch.org/whl/cu118 # 安装 OpenCV pip install opencv-python==4.8.1.78 opencv-contrib-python==4.8.1.78 # 克隆 YOLOv5 仓库并安装依赖 git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txt这里有几个参数需要说明。torch==2.0.1是我在多个项目里验证过比较稳定的版本,和 YOLOv5 的兼容性好。OpenCV 装opencv-python和opencv-contrib-python两个包,后者包含一些额外的图像处理模块,比如背景减除和光流法,后面做异常行为判断会用到。如果你在 WSL 里跑,需要先装好 NVIDIA 驱动和 CUDA toolkit,nvidia-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_size=2): self.cap = cv2.VideoCapture(rtsp_url) # 设置缓冲区大小,减少延迟 self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) self.queue = queue.Queue(maxsize=queue_size) self.running = True self.thread = threading.Thread(target=self._update, daemon=True) 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(timeout=1) 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(clipLimit=2.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 imgclipLimit=2.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 fps=2 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_ratio=0.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_ok=True) os.makedirs(lbl_out, exist_ok=True) 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_ratio=0.8)train_ratio=0.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。
训练过程中重点看三个指标:mAP@0.5、precision和recall。校园异常行为检测里,recall 比 precision 更重要——漏报一个翻越行为可能出大事,误报多一点可以靠人工复核过滤。如果 recall 偏低,可以调低置信度阈值,或者在数据增强里增加马赛克和混合增强的比例。
4. Flask 后端与 MySQL:把检测结果变成可查询的报警记录
4.1 Flask API 设计:接收视频帧、返回检测结果
Flask 在这里的角色是中间层:前端或者摄像头管理平台把视频帧或者 RTSP 地址发给 Flask,Flask 调用 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', path='best.pt') model.conf = 0.4 # 置信度阈值 model.iou = 0.45 # NMS IoU 阈值 def get_db(): return pymysql.connect(host='localhost', user='root', password='your_password', database='campus_security', charset='utf8mb4') @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(orient='records') # 如果检测到异常行为,写入数据库 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(host='0.0.0.0', port=5000, threaded=True)model.conf = 0.4是置信度阈值,低于这个值的检测框会被过滤掉。校园场景下我一般设 0.4 到 0.5,太低会误报,太高会漏报。model.iou = 0.45是 NMS 的 IoU 阈值,控制重叠框的合并程度。threaded=True让 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) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;behavior字段存climb或fall,camera_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) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;轨迹追踪需要配合 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)返回ret=False,或者跑了几分钟后画面延迟从 1 秒涨到十几秒。
原因:RTSP 流默认走 TCP 传输,网络抖动时 OpenCV 的缓冲区会积压,导致延迟累积。另外有些摄像头不支持CAP_PROP_BUFFERSIZE,设了也没用。
解决:在 RTSP 地址后面加?tcp强制 TCP,或者用ffmpeg拉流转发。更彻底的做法是用PyAV或者GStreamer替代 OpenCV 的 VideoCapture,它们对缓冲区的控制更精细。如果延迟已经涨上来了,只能重启拉流线程。
5.2 训练 loss 不下降或者 mAP 一直是 0
现象:训练了几十个 epoch,box_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字段存进去是????或者乱码。
原因:数据库连接没有指定charset='utf8mb4',或者表创建时用了latin1。
解决:连接时加charset='utf8mb4',建表时指定DEFAULT CHARSET=utf8mb4。如果表已经建了,用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_scale=0.5, levels=3, winsize=15, iterations=3, poly_n=5, poly_sigma=1.2, flags=0) # 垂直方向的平均运动 mean_vy = np.mean(flow[..., 1]) # 如果垂直向下运动超过阈值,确认摔倒 return mean_vy > 2.0pyr_scale=0.5是金字塔缩放比例,levels=3是金字塔层数,winsize=15是窗口大小。这些参数是 OpenCV 光流法的经典配置,在 640×640 的 ROI 上跑一次大概 5ms,不会明显拖慢整体速度。mean_vy > 2.0这个阈值需要根据你的摄像头角度和帧率调,俯拍角度大的摄像头阈值可以设低一点。
这个二次验证能把摔倒的误报率降低大概一半,但代价是增加了计算量。如果你的场景对实时性要求极高,可以只在 YOLOv5 置信度在 0.4 到 0.6 之间的模糊样本上跑光流验证,高置信度的直接报警。
提示:光流法对光照变化敏感,夜间红外模式下效果会打折扣。如果校园监控有夜间场景,建议在光流验证前先做一次直方图均衡,或者直接用 YOLOv5 的时序版本(比如在 LSTM 上做后处理)。
这套系统我前后调了三个月,最大的教训是:不要一上来就追求高精度模型,先把数据管线和推理链路跑通,再慢慢调模型。校园场景的异常行为样本少,数据增强和难例挖掘比换模型更有效。希望帮到你。
本文还有配套的精品资源,点击获取