
简介这份资源面向计算机视觉与深度学习方向的毕业设计学生及校园安防系统开发者提供一套完整的智能校园安全监控与异常行为识别方案。系统以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 暴露后端 APIMySQL 落库事件与告警记录。它解决的不是「识别一切异常」这种大话而是三件具体的事第一把视频流里的目标框出来并打上类别和置信度第二按规则判定异常行为比如人员进入禁区、非工作时间出现移动目标、人群密度超阈值第三把判定结果通过接口推给前端或告警模块并写进数据库留痕。适合谁做计算机视觉大作业的学生、要交课程设计的中级学习者、以及想拿一套能跑通的端到端 pipeline 做二次开发的人。不适合指望开箱即用、直接上生产环境的团队——这份代码的价值在于流程完整、模块清晰而不是工程鲁棒性。我拆这类包的习惯是先看目录结构再决定要不要配环境。常见布局是detect/YOLOv5 推理、utils/OpenCV 图像处理与画框、app.pyFlask 入口、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 python3.9 -y conda activate campus_sec # 先装 PyTorchcu118 是示例按 nvidia-smi 显示的版本改 # 没有 GPU 就用 CPU 版pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu pip install torch2.0.1 torchvision0.15.2 --index-url https://download.pytorch.org/whl/cu118 # 再装 OpenCV 和 Flaskopencv-python 别装 headless 版除非你不需要 imshow 调试 pip install opencv-python4.8.1.78 flask2.3.3 pip install mysql-connector-python8.1.0 numpy1.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 卡或 FalseCPUopencv 4.8.xflask 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里如果有torch1.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 读出来是 BGRYOLOv5 的推理接口一般接受 numpy 数组并内部转 RGB如果你自己写了预处理别重复转。import cv2 import time def open_stream(url, retry5): cap cv2.VideoCapture(url) if not cap.isOpened(): raise RuntimeError(f无法打开视频流: {url}) return cap def read_frames(cap, reconnect_url, max_fail30): 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_fail30是按 25fps 估算的约 1.2 秒无帧就重连太短会频繁重连太长会丢事件。参数reconnect_url单独传是因为cap重连后是新对象原 url 得留着。3.2 YOLOv5 推理封装与结果解析YOLOv5 的推理结果是一个Results对象里面xyxy是框坐标conf是置信度cls是类别索引names是索引到类别名的映射。校园场景一般只关心person人、car、backpack这几类可以在推理后按类别过滤减少后续判定逻辑的干扰。置信度阈值conf_thres默认 0.25校园监控建议提到 0.40.5宁可漏检也别误报太多保安被误报烦了就会关掉系统。import torch # 加载模型只加载一次别在循环里 load model torch.hub.load(ultralytics/yolov5, custom, pathmodels/yolov5s.pt, force_reloadFalse) model.conf 0.45 # 置信度阈值 model.iou 0.5 # NMS 的 IoU 阈值 model.classes [0] # 只检测 person0 是 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_reloadFalse避免每次调用都重新加载权重。参数iou0.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 bContent-Type: image/jpeg\r\n\r\n buf.tobytes() b\r\n)逻辑说明gen_mjpeg是生成器Flask 路由里用Response(gen_mjpeg(...), mimetypemultipart/x-mixed-replace; boundaryframe)就能推流。IMWRITE_JPEG_QUALITY设 70 是画质和带宽的折中720p 下约 200KB/帧25fps 就是 5MB/s局域网够用公网要再降。参数boundaryframe必须和生成器里的--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, threshold8): 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(maxsize1000) def db_worker(): conn mysql.connector.connect(hostlocalhost, userroot, passwordyour_pwd, databasecampus_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(targetdb_worker, daemonTrue).start() app.route(/events) def events(): conn mysql.connector.connect(hostlocalhost, userroot, passwordyour_pwd, databasecampus_sec) cur conn.cursor(dictionaryTrue) cur.execute(SELECT * FROM events ORDER BY created_at DESC LIMIT 50) rows cur.fetchall() return jsonify(rows)逻辑说明db_worker是独立线程从队列取事件写库推理循环只event_queue.put(ev)不阻塞。maxsize1000防止队列无限涨满了就丢最老的校园场景丢几条事件比卡死强。dictionaryTrue让查询结果直接是 dictjsonify能序列化。参数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 现象推理帧率只有 23fpsGPU 占用却很低原因一般是每帧都重新加载模型或者model(frame)传的是文件路径而不是 numpy 数组导致每次读盘。也有可能是torch.hub.load的force_reloadTrue没关。解决模型加载提到循环外全局只 load 一次。确认infer里传的是frame数组。force_reloadFalse。如果还是慢看torch.cuda.is_available()是不是 False是的话检查 CUDA 版本和驱动或者干脆用yolov5n.ptnano 版换速度。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_okTrue) i 0 while True: ret, frame cap.read() if not ret: break if i % 30 0: cv2.imwrite(fframes/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防止除零。从那以后我每次换权重或改推理参数都强制走一遍这个一致性验证不跑不放心。这套流程跑通后校园监控的端到端链路就算立住了流进来、框出来、事件判出来、库写进去、接口吐出来。剩下的就是按自己场景调阈值、加规则、补数据。希望帮到你。本文还有配套的精品资源点击获取