简介:这份资源面向计算机视觉与深度学习方向的毕业设计学生及校园安防系统开发者,提供一套完整的智能校园安全监控与异常行为识别方案。系统以YOLOv5目标检测算法为核心,结合OpenCV完成图像滤波、边缘检测与特征提取等预处理,后端基于PyTorch构建并训练模型,通过Flask搭建RESTful API实现前后端数据交互,同时引入人脸识别进行身份验证,并利用MySQL存储用户数据、检测日志与行为记录,可对打架、侵入、异常停留等威胁行为实时告警。压缩包共20个文件,约112KB,包含7个txt日志与说明、5个Python脚本、4个Shell运行脚本、3张结果图及1份README,覆盖模型训练、实验对比与结果可视化等环节。目前已有64人学习下载,适合作为课程设计或毕业设计的参考实现,帮助读者快速理解多技术组件协同的安防系统架构与落地思路。
1. 从一份校园监控源码包说起:YOLOv5 + OpenCV + Flask 能跑出什么
校园安防这个场景,最尴尬的不是没摄像头,而是摄像头后面没人盯。一个中等规模的校园,出入口、走廊、操场、食堂加起来几十路 RTSP 流,保安室两三个人轮班,真正能持续盯住的画面不超过四路。漏报、误报、事后翻录像找证据,是这类项目的常态。这份「基于深度学习与计算机视觉的智能校园安全监控与异常行为识别系统」源码包,走的就是把「人盯屏」换成「模型盯屏」的路子:YOLOv5 负责逐帧检出人、车、包等目标,OpenCV 负责解码、缩放、画框和推流,PyTorch 作为训练与推理底座,Flask 暴露后端 API,MySQL 落库事件与告警记录。
它解决的不是「识别一切异常」这种大话,而是三件具体的事:第一,把视频流里的目标框出来并打上类别和置信度;第二,按规则判定异常行为(比如人员进入禁区、非工作时间出现移动目标、人群密度超阈值);第三,把判定结果通过接口推给前端或告警模块,并写进数据库留痕。适合谁?做计算机视觉大作业的学生、要交课程设计的中级学习者、以及想拿一套能跑通的端到端 pipeline 做二次开发的人。不适合指望开箱即用、直接上生产环境的团队——这份代码的价值在于流程完整、模块清晰,而不是工程鲁棒性。
我拆这类包的习惯是先看目录结构再决定要不要配环境。常见布局是detect/(YOLOv5 推理)、utils/(OpenCV 图像处理与画框)、app.py(Flask 入口)、models/(权重)、sql/(建表脚本)。先确认权重文件在不在、requirements.txt里 torch 和 opencv 的版本钉没钉死,再动手。下面按「环境怎么搭 → 推理怎么跑 → 异常判定怎么写 → 接口怎么接 → 坑在哪」的顺序拆开讲,每一步都给能直接抄的命令和代码。
2. 环境搭建与依赖对齐:PyTorch、OpenCV、YOLOv5 三件套怎么不打架
2.1 为什么版本对齐比装得快更重要
这类项目翻车,八成不是算法问题,是版本问题。YOLOv5 的requirements.txt对 torch、torchvision、opencv-python 都有隐含版本区间,PyTorch 又和 CUDA 驱动、Python 版本强绑定。你如果先pip install torch装了最新版,再装 YOLOv5 依赖,很可能出现torchvision和torch版本不匹配,报RuntimeError: Detected that PyTorch and torchvision were compiled with different CUDA versions。血泪经验是:先定 Python 版本,再定 CUDA,再定 torch,最后才装 opencv 和 flask。
我一般用 conda 建独立环境,Python 选 3.8 或 3.9,这两个版本对 YOLOv5 各分支兼容性最好。CUDA 版本看显卡驱动,nvidia-smi右上角那个 CUDA Version 是驱动支持的上限,装 torch 时选的 cu 版本不能超过它。没有 N 卡就装 CPU 版,推理慢但流程能跑通,做课程设计够用。
# 建环境,Python 版本别贪新 conda create -n campus_sec python=3.9 -y conda activate campus_sec # 先装 PyTorch,cu118 是示例,按 nvidia-smi 显示的版本改 # 没有 GPU 就用 CPU 版:pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu pip install torch==2.0.1 torchvision==0.15.2 --index-url https://download.pytorch.org/whl/cu118 # 再装 OpenCV 和 Flask,opencv-python 别装 headless 版,除非你不需要 imshow 调试 pip install opencv-python==4.8.1.78 flask==2.3.3 pip install mysql-connector-python==8.1.0 numpy==1.24.3逻辑说明:先装 torch 是因为它体积大、依赖 CUDA 运行时,先落地能避免后面被 opencv 的依赖顶掉。opencv-python和opencv-python-headless的区别在于前者带 GUI 支持,调试时能cv2.imshow看画面,服务器部署再换 headless。numpy钉在 1.24 是因为 1.25 之后部分旧版 torch 会报_ARRAY_API not found。
参数说明:--index-url指向 PyTorch 官方 wheel 源,比默认源快且版本全。cu118对应 CUDA 11.8,如果你驱动只支持到 11.7,就换cu117。装完用下面这段验证,别急着跑项目。
import torch, cv2, flask print("torch:", torch.__version__, "cuda:", torch.cuda.is_available()) print("opencv:", cv2.__version__) print("flask:", flask.__version__) # 期望输出:cuda 为 True(有 N 卡)或 False(CPU),opencv 4.8.x,flask 2.3.x2.2 YOLOv5 源码拉取与权重放置
YOLOv5 不要用 pip 装的那个yolov5包,版本滞后且接口和官方仓库不一致。常见做法是直接 clone 官方仓库到项目根目录,再把项目自带的detect.py或自定义推理脚本放进去调用。权重文件(.pt)一般放在weights/或models/下,源码包里如果没带权重,需要自己下yolov5s.pt(小模型,推理快,适合校园场景的实时性要求)。
# 在项目根目录拉 YOLOv5,注意和项目代码的目录层级 git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txt # 权重放到项目约定的位置,假设项目读的是 ../models/yolov5s.pt mkdir -p ../models # 把下载好的 yolov5s.pt 放进去,或用官方脚本下 python -c "import torch; torch.hub.load('ultralytics/yolov5','yolov5s')"逻辑说明:torch.hub.load会自动下载权重到缓存目录,第一次跑会慢,之后走本地缓存。项目代码里如果写死了权重路径,就把缓存里的.pt拷到对应位置,别改代码去迁就权重,改路径容易漏改。requirements.txt里如果有torch>=1.7这种宽区间,装之前先看你已装的 torch 版本是否满足,不满足就手动跳过 torch 那行,用pip install -r requirements.txt --no-deps再单独补缺的包。
3. 推理主流程:从 RTSP 流到带框画面,OpenCV 和 YOLOv5 怎么接
3.1 视频流读取与帧预处理
校园监控的输入通常是 RTSP 流,也可能是本地视频文件做回放分析。OpenCV 的VideoCapture两者都能吃,但 RTSP 有个经典坑:断流后read()返回 False,不重连就会一直空转。我一般加一个重连计数,连续失败超过阈值就重新open。帧预处理这块,YOLOv5 内部会自己做 letterbox 缩放,你不需要手动 resize,但要注意传入的是 BGR 还是 RGB——OpenCV 读出来是 BGR,YOLOv5 的推理接口一般接受 numpy 数组并内部转 RGB,如果你自己写了预处理,别重复转。
import cv2 import time def open_stream(url, retry=5): cap = cv2.VideoCapture(url) if not cap.isOpened(): raise RuntimeError(f"无法打开视频流: {url}") return cap def read_frames(cap, reconnect_url, max_fail=30): fail = 0 while True: ret, frame = cap.read() if not ret: fail += 1 if fail > max_fail: # 连续失败,重连 cap.release() time.sleep(2) cap = open_stream(reconnect_url) fail = 0 continue fail = 0 yield frame逻辑说明:read_frames做成生成器,主循环里for frame in read_frames(...)就能持续拿帧,断流自动重连。max_fail=30是按 25fps 估算的,约 1.2 秒无帧就重连,太短会频繁重连,太长会丢事件。参数reconnect_url单独传是因为cap重连后是新对象,原 url 得留着。
3.2 YOLOv5 推理封装与结果解析
YOLOv5 的推理结果是一个Results对象,里面xyxy是框坐标,conf是置信度,cls是类别索引,names是索引到类别名的映射。校园场景一般只关心person(人)、car、backpack这几类,可以在推理后按类别过滤,减少后续判定逻辑的干扰。置信度阈值conf_thres默认 0.25,校园监控建议提到 0.4~0.5,宁可漏检也别误报太多,保安被误报烦了就会关掉系统。
import torch # 加载模型,只加载一次,别在循环里 load model = torch.hub.load('ultralytics/yolov5', 'custom', path='models/yolov5s.pt', force_reload=False) model.conf = 0.45 # 置信度阈值 model.iou = 0.5 # NMS 的 IoU 阈值 model.classes = [0] # 只检测 person,0 是 COCO 里 person 的索引 def infer(frame): results = model(frame) df = results.pandas().xyxy[0] # 转成 DataFrame 方便过滤 detections = [] for _, row in df.iterrows(): detections.append({ "x1": int(row["xmin"]), "y1": int(row["ymin"]), "x2": int(row["xmax"]), "y2": int(row["ymax"]), "conf": float(row["confidence"]), "label": row["name"] }) return detections逻辑说明:model.classes = [0]让模型只输出 person 类,省掉后处理过滤。results.pandas()是 YOLOv5 内置的便捷接口,依赖 pandas,如果项目没装就pip install pandas。force_reload=False避免每次调用都重新加载权重。参数iou=0.5控制 NMS 合并重叠框的力度,人群密集时调低到 0.4 能减少框重叠,但可能把挨得近的两个人合成一个,按场景调。
3.3 画框与推流输出
检出结果要画回帧上才能给人看。OpenCV 的rectangle和putText够用,颜色按类别区分,人用绿色,其他用蓝色。如果要做 Web 端展示,Flask 那边通常用 MJPEG 流,把每帧编码成 JPEG 再拼 multipart 响应。这里注意cv2.imencode的 quality 参数,太高带宽扛不住,太低画面糊得看不清框。
def draw(frame, detections): for d in detections: cv2.rectangle(frame, (d["x1"], d["y1"]), (d["x2"], d["y2"]), (0, 255, 0), 2) label = f'{d["label"]} {d["conf"]:.2f}' cv2.putText(frame, label, (d["x1"], d["y1"] - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) return frame def gen_mjpeg(cap, model): for frame in read_frames(cap, "rtsp://your_stream"): dets = infer(frame) frame = draw(frame, dets) ok, buf = cv2.imencode('.jpg', frame, [cv2.IMWRITE_JPEG_QUALITY, 70]) if not ok: continue yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + buf.tobytes() + b'\r\n')逻辑说明:gen_mjpeg是生成器,Flask 路由里用Response(gen_mjpeg(...), mimetype='multipart/x-mixed-replace; boundary=frame')就能推流。IMWRITE_JPEG_QUALITY设 70 是画质和带宽的折中,720p 下约 200KB/帧,25fps 就是 5MB/s,局域网够用,公网要再降。参数boundary=frame必须和生成器里的--frame一致,不一致浏览器不认。
4. 异常行为判定与 Flask 接口:规则引擎怎么写才不误报
4.1 从检测框到行为事件
YOLOv5 只给框,不给行为。异常判定得自己写规则。校园场景常见的三类:禁区入侵(框的中心点落在预设多边形内)、人群聚集(同一帧内 person 框数量超阈值且框间距离近)、非工作时间移动目标(时间窗 + 有框)。规则引擎不要写死在推理循环里,抽成独立函数,方便调参和加规则。
from shapely.geometry import Point, Polygon # 禁区多边形,坐标按实际画面分辨率标定 FORBIDDEN = Polygon([(100, 100), (500, 100), (500, 400), (100, 400)]) def check_intrusion(detections, frame_shape): events = [] for d in detections: cx = (d["x1"] + d["x2"]) / 2 cy = (d["y1"] + d["y2"]) / 2 if FORBIDDEN.contains(Point(cx, cy)): events.append({"type": "intrusion", "conf": d["conf"], "pos": (cx, cy)}) return events def check_crowd(detections, threshold=8): persons = [d for d in detections if d["label"] == "person"] if len(persons) >= threshold: return [{"type": "crowd", "count": len(persons)}] return []逻辑说明:shapely的Polygon.contains判断点是否在多边形内,比手写射线法稳。禁区坐标要按实际画面分辨率标,1280x720 和 1920x1080 的坐标不能混用,标定前先确认frame.shape。check_crowd的阈值 8 是示例,按场景调,食堂门口 8 人正常,实验室门口 3 人就该报。参数threshold建议做成配置项,别硬编码。
4.2 Flask 接口设计与 MySQL 落库
Flask 这边至少三个路由:/video_feed推 MJPEG 流、/events返回最近事件列表、/alert接收前端确认。事件落库用 MySQL,表结构简单点:id、event_type、confidence、position、created_at。写库别在推理循环里同步写,会拖慢帧率,用队列或后台线程异步落。
from flask import Flask, Response, jsonify import mysql.connector from queue import Queue import threading, json, datetime app = Flask(__name__) event_queue = Queue(maxsize=1000) def db_worker(): conn = mysql.connector.connect(host='localhost', user='root', password='your_pwd', database='campus_sec') cur = conn.cursor() while True: ev = event_queue.get() if ev is None: break cur.execute( "INSERT INTO events (event_type, confidence, position, created_at) " "VALUES (%s, %s, %s, %s)", (ev["type"], ev.get("conf", 0), json.dumps(ev.get("pos")), datetime.datetime.now())) conn.commit() threading.Thread(target=db_worker, daemon=True).start() @app.route('/events') def events(): conn = mysql.connector.connect(host='localhost', user='root', password='your_pwd', database='campus_sec') cur = conn.cursor(dictionary=True) cur.execute("SELECT * FROM events ORDER BY created_at DESC LIMIT 50") rows = cur.fetchall() return jsonify(rows)逻辑说明:db_worker是独立线程,从队列取事件写库,推理循环只event_queue.put(ev),不阻塞。maxsize=1000防止队列无限涨,满了就丢最老的,校园场景丢几条事件比卡死强。dictionary=True让查询结果直接是 dict,jsonify能序列化。参数password和database按实际改,别用 root 裸奔,建个只对campus_sec库有权限的账号。
建表 SQL 放sql/init.sql,跑一次就行:
CREATE DATABASE IF NOT EXISTS campus_sec DEFAULT CHARSET utf8mb4; USE campus_sec; CREATE TABLE IF NOT EXISTS events ( id INT AUTO_INCREMENT PRIMARY KEY, event_type VARCHAR(32) NOT NULL, confidence FLOAT DEFAULT 0, position JSON, created_at DATETIME NOT NULL, INDEX idx_created (created_at) );逻辑说明:utf8mb4支持中文,position用 JSON 类型存坐标,idx_created加速按时间倒序查。事件表会一直涨,加个定时清理或分区,别等磁盘满了才想起来。
5. 避坑与排查:这类项目最容易翻车的五个地方
5.1 现象:ModuleNotFoundError: No module named 'cv2',但明明装了 opencv
原因通常是装到了别的 Python 环境,或者装了opencv-python-headless但代码里用了cv2.imshow。conda 环境和系统 Python 混用是重灾区,pip和pip3指向不同解释器。
解决:先which python和which pip确认指向同一环境,再pip list | grep opencv看装没装。要 GUI 调试就pip uninstall opencv-python-headless && pip install opencv-python。装完在同一个终端里python -c "import cv2; print(cv2.__version__)"验证,别开新终端。
5.2 现象:推理帧率只有 2~3fps,GPU 占用却很低
原因一般是每帧都重新加载模型,或者model(frame)传的是文件路径而不是 numpy 数组,导致每次读盘。也有可能是torch.hub.load的force_reload=True没关。
解决:模型加载提到循环外,全局只 load 一次。确认infer里传的是frame数组。force_reload=False。如果还是慢,看torch.cuda.is_available()是不是 False,是的话检查 CUDA 版本和驱动,或者干脆用yolov5n.pt(nano 版)换速度。
5.3 现象:RTSP 流跑几分钟就断,日志里read()一直返回 False
原因:RTSP 服务端有超时,或者网络抖动,OpenCV 的VideoCapture不会自动重连。
解决:用 3.1 里的read_frames重连逻辑,连续失败超过阈值就cap.release()再open。另外把cv2.VideoCapture的缓冲设小:cap.set(cv2.CAP_PROP_BUFFERSIZE, 1),减少延迟累积。如果服务端支持 TCP 传输,在 url 后加?tcp,比 UDP 稳。
5.4 现象:MySQL 写入报Packet too large或连接超时
原因:事件里带了 base64 图片或大 JSON,超过max_allowed_packet;或者连接长时间空闲被服务端断开。
解决:事件表只存坐标和类型,别存图。图片存文件系统,库里存路径。连接加connection_timeout和autocommit,或者用连接池。max_allowed_packet默认 4MB,改到 16MB 也行,但治本是不存大字段。
5.5 现象:误报太多,保安把告警关了
原因:置信度阈值太低、禁区多边形标得太随意、人群阈值一刀切。
解决:conf提到 0.5 以上,禁区标定时用实际画面截图对着标,别拍脑袋。人群阈值按点位分,出入口和实验室用不同值。加一个「告警冷却」:同一区域 30 秒内只报一次,避免刷屏。这些参数都放配置文件,改完重启服务就行,别改代码。
6. 进阶技巧:把 YOLOv5 换成自定义训练模型,以及验证推理一致性
6.1 用校园自己的数据微调 YOLOv5
预训练的yolov5s.pt是在 COCO 上训的,对「人」的检出没问题,但校园里有些特殊目标(比如校服、特定车辆、实验器材)它不认识。要提精度就得自己标数据微调。流程是:抽帧 → labelImg 标注 → 转 YOLO 格式 → 改data.yaml→train.py。抽帧用 OpenCV 每 30 帧存一张,避免相邻帧太像。
# 抽帧,每 30 帧存一张 python -c " import cv2, os cap = cv2.VideoCapture('campus.mp4') os.makedirs('frames', exist_ok=True) i = 0 while True: ret, frame = cap.read() if not ret: break if i % 30 == 0: cv2.imwrite(f'frames/f_{i:06d}.jpg', frame) i += 1 " # 标注完转 YOLO 格式后,训练 cd yolov5 python train.py --img 640 --batch 16 --epochs 100 \ --data ../data/campus.yaml --weights yolov5s.pt \ --project ../runs/train --name campus_v1逻辑说明:--img 640是输入尺寸,和推理时一致,不一致会导致精度掉。--batch 16按显存调,8G 显存跑 640 大概能到 16,不够就降到 8。--epochs 100是起点,看mAP曲线,早停可以加--patience 20。data/campus.yaml里写train、val路径和nc(类别数)、names。训练完的best.pt替换推理用的权重,路径改一下就行。
6.2 验证推理一致性:同一段视频跑两遍,框的位置差多少
模型换版本、换权重、换输入尺寸后,得验证推理结果稳不稳。我一般用同一段视频跑两遍,逐帧比xyxy的 IoU,平均 IoU 低于 0.9 就说明有抖动,得查原因。这个验证脚本不长,但能省掉很多「感觉不对」的扯皮。
import cv2, numpy as np def iou(box1, box2): x1 = max(box1[0], box2[0]); y1 = max(box1[1], box2[1]) x2 = min(box1[2], box2[2]); y2 = min(box1[3], box2[3]) inter = max(0, x2 - x1) * max(0, y2 - y1) a1 = (box1[2]-box1[0]) * (box1[3]-box1[1]) a2 = (box2[2]-box2[0]) * (box2[3]-box2[1]) return inter / (a1 + a2 - inter + 1e-6) def compare(video, model_a, model_b): cap = cv2.VideoCapture(video) ious = [] while True: ret, frame = cap.read() if not ret: break da = infer_with(model_a, frame) db = infer_with(model_b, frame) # 按最近邻匹配框,简化处理:取数量相同的前 N 个 for a, b in zip(da, db): ious.append(iou( (a["x1"], a["y1"], a["x2"], a["y2"]), (b["x1"], b["y1"], b["x2"], b["y2"]))) print(f"平均 IoU: {np.mean(ious):.3f}, 帧数: {len(ious)}")逻辑说明:iou算两个框的交并比,compare逐帧跑两个模型,按顺序配对算 IoU。这里简化成按顺序 zip,实际场景框数量可能不一致,得用匈牙利匹配或按中心点距离配对。np.mean(ious)低于 0.9 就查:是不是输入尺寸变了、NMS 阈值变了、权重版本不对。参数1e-6防止除零。
从那以后我每次换权重或改推理参数,都强制走一遍这个一致性验证,不跑不放心。这套流程跑通后,校园监控的端到端链路就算立住了:流进来、框出来、事件判出来、库写进去、接口吐出来。剩下的就是按自己场景调阈值、加规则、补数据。希望帮到你。
本文还有配套的精品资源,点击获取