做计算机毕业设计时,很多同学会选择“目标检测”方向,但做到后面往往会出现一个尴尬情况:模型跑通了,框也画出来了,却不知道下一步怎么“用起来”。尤其像智慧交通监控预警系统这类题目,如果只是把 YOLO 检测结果用 OpenCV 画在画面上,答辩时就会显得很单薄。本文将围绕“基于 YOLO + 多模态大语言模型 LLM 的智慧交通监控预警系统”这一题目,完整拆解如何把目标检测、多模态语义分析、预警触发、消息通知串成一套可运行的工程原型,并给出源码结构、核心代码和常见踩坑点,适合毕业设计、课程设计以及想入门大模型应用的读者参考。
1. 系统背景与核心概念
1.1 什么是智慧交通监控预警系统
智慧交通监控预警系统的目标是利用摄像头、图像识别、数据分析等手段,对城市道路、高速路口、园区停车场等场景进行实时监控,并在检测到异常事件时自动预警。例如:车辆违停、行人闯入机动车道、车辆逆行、车道拥堵、事故停留等。
传统做法主要依赖人工盯屏,监控人员需要同时观看几十路画面,很难长时间保持注意力。后来开始出现纯规则式的数字图像处理方案,比如帧差法、背景建模,但它们在复杂光照和遮挡场景下误报率很高。近几年,基于深度学习的目标检测模型逐步成为主流,YOLO 系列因为速度快、精度高、部署简单,在工业界和学术界的监控类项目中非常常见。
但这里有一个容易被忽略的认知:目标检测解决的是“画面里有什么、在哪里”的问题。比如,YOLO 可以输出“car 0.87 345 220 510 400”这样的框,但它不理解“一辆白色轿车在禁停区域停留超过 5 分钟”意味着什么。想要让系统具备“理解场景并决策预警等级”的能力,就需要引入大语言模型或多模态大模型。
1.2 YOLO 在系统中的职责
YOLO(You Only Look Once)是一种单阶段目标检测算法,核心思想是把目标检测当作回归问题,直接预测边界框和类别,不需要两阶段检测器那样先生成候选区域再分类。因此它的推理速度非常快,非常适合视频流实时检测场景。
在本系统中,YOLO 负责以下工作:
- 检测车辆(car、bus、truck)和行人(person)等目标。
- 输出边界框坐标、置信度和类别标签。
- 通过目标中心点与 ROI 区域的位置关系判断目标是否进入特定区域。
- 通过历史帧数据判断目标是否长时间停留。
YOLO 的检测结果经过规则封装后,会形成结构化信息,例如“在监控区域 A 检测到 1 辆轿车,停留时间约 65 秒”。这将成为大语言模型的输入素材。
1.3 多模态大语言模型在系统中的职责
多模态大语言模型(Multimodal Large Language Model,简称 MLLM)是指能够同时处理文本、图像、音频等多种模态数据的大模型。在智慧交通场景中,多模态模型可以直接接收“监控画面 + 文本提示”,输出对画面的语义理解。
不过在毕业设计项目中,直接使用 Qwen-VL、GPT-4V 等多模态模型会面临几个问题:GPU 显存要求高、API 费用不确定、延迟较高。因此本项目采用一种更轻量、更适合教学和演示的架构:
YOLO 的输出结果先被结构化整理成文本,再交给大语言模型进行逻辑判断和预警等级评估。
这种设计相当于把“视觉感知”和“语义理解”分层处理,既保留了 YOLO 的高效检测能力,又显著降低了大模型的调用成本,也方便在答辩时解释系统各部分职责。
1.4 为什么会选择“YOLO + LLM”组合
目标检测模型已经能识别很多目标,但真实监控场景里的决策判断往往是模糊的:
- 一辆车停在路边,是临时上下客还是违章停车?
- 一个行人出现在车道边缘,是正常通行还是危险闯入?
- 检测到车流速度明显变慢,是否需要上报拥堵事件?
这些判断隐含了上下文信息、时间信息和位置语义。LLM 的强项恰好在于:根据结构化的提示词进行推理,输出格式可控、逻辑可解释的判断结果。把 YOLO 的“感知能力”和 LLM 的“认知能力”叠加,系统就不再是一个单纯的检测 Demo,而是一个具备基础“预警决策”能力的完整应用——这正是毕业设计里比较出彩的亮点。
2. 系统整体架构设计
2.1 模块划分
本系统按功能可以拆分为 4 个核心模块:
| 模块 | 核心功能 | 主要技术 |
|---|---|---|
| 视频接入模块 | 读取本地视频、RTSP 摄像头流 | OpenCV、FFmpeg |
| 目标检测模块 | 识别车辆、行人等目标,输出结构化结果 | YOLO(ultralytics) |
| 多模态 LLM 决策模块 | 接收检测结果文本,输出预警等级与原因 | 大模型 API / 本地模型 |
| 预警通知与展示模块 | 保存预警记录、截图,发送通知,提供查询接口 | FastAPI、SQLite、SMTP |
这 4 个模块之间是解耦的。视频接入模块只负责生产帧数据,目标检测模块只关心当前帧的目标位置,LLM 决策模块只接收结构化文本,展示模块只负责保存和推送。这样的分层设计方便后续单独替换某一模块,例如把 YOLOv8 换成 YOLO11,或者把线上大模型换成本地部署的 Ollama 模型。
2.2 数据流
系统运行时的数据流可以简化如下:
- 从本地视频文件或摄像头 RTSP 地址读取视频帧。
- 每间隔 N 帧执行一次 YOLO 推理,得到目标框列表。
- 规则引擎根据目标框和 ROI 区域计算停留时间、区域入侵等事件。
- 事件数据被格式化为文本描述,发送给大语言模型。
- 大语言模型返回 JSON,包含是否预警、预警等级、预警原因、建议措施。
- 预警模块保存记录、截图,并通过日志或邮件输出通知。
2.3 技术栈选择说明
本项目后端采用 Python 语言,主要考虑到 YOLO 生态和大模型生态都以 Python 为中心。Web 展示层使用 FastAPI,它能快速暴露 REST API,方便前端或答辩演示时查询预警历史。数据库方面采用 SQLite,零配置文件,适合中小型项目和毕设演示。预警通知可以选择 SMTP 邮件通知,也可以只写日志文件,根据环境灵活调整。
这种设计不是为了“堆技术栈”,而是每个组件都有明确用途。FastAPI 负责“让系统可以被外部访问”,SQLite 负责“让预警记录可以追溯”,大模型负责“让判断结果有语义解释能力”。答辩时,你可以把这条技术链路讲得很清晰。
3. 环境准备与项目结构
3.1 硬件与系统要求
建议使用 NVIDIA 显卡,因为 YOLO 的 GPU 加速依赖 CUDA。如果你使用的是 AMD 显卡,需要额外关注 PyTorch 的 ROCm 支持情况。需要注意的是,不是所有版本的 PyTorch 都原生支持 AMD GPU,配置成本会更高,因此如果条件允许,优先使用 NVIDIA 显卡。
显存方面,YOLOv8n 和 YOLOv8s 的显存需求并不高,6GB 左右的显存可以流畅运行;但如果要加载 7B 以上的本地大模型,建议 16GB 显存或直接使用云端 API。在本文示例中,默认不加载本地大模型权重,而是调用远端 API 或本地 Ollama 服务,对硬件要求更低。
3.2 Python 环境与依赖版本
本文代码以 Python 3.10 为基础编写。版本需要根据你的项目实际情况调整,重点演示配置思路。
建议创建独立的虚拟环境,避免和其他项目冲突。
conda create -n traffic-ai python=3.10 -y conda activate traffic-ai核心依赖如下:
pip install ultralytics pip install torch torchvision pip install opencv-python pip install fastapi uvicorn pip install pyyaml pip install requests如果你的电脑没有 NVIDIA 显卡,可以安装 CPU 版 PyTorch,但检测速度会明显下降。关于 CUDA 版本,请以 PyTorch 官方安装页的对应关系为准,不要盲目装最新版 CUDA。踩坑最多的就是“显卡驱动支持 CUDA 12.1,但当前 PyTorch 编译版本只支持 CUDA 11.8”,安装前先执行nvidia-smi查看驱动支持的最高 CUDA 版本。
3.3 项目结构说明
推荐的项目结构如下:
traffic_ai/ ├── main.py # 主流程入口 ├── config.yaml # 全局配置 ├── detector.py # YOLO 检测模块 ├── llm_client.py # 大模型调用模块 ├── early_warner.py # 预警规则与决策模块 ├── app.py # FastAPI 展示服务 ├── requirements.txt ├── data/ │ ├── test_video.mp4 # 测试视频 │ └── records.db # SQLite 数据库 ├── outputs/ │ ├── snapshots/ # 预警截图 │ └── logs/ # 运行日志 └── weights/ └── yolov8n.pt # YOLO 权重文件这种结构的好处是:每个文件职责单一,导师或评阅老师看到代码时能快速定位功能模块,也方便写入设计文档。
4. YOLO 交通目标检测模块实现
4.1 加载 YOLO 模型
YOLO 的加载在 ultralytics 库中非常简单,只需指定权重文件路径即可。第一次运行时会自动下载权重,如果下载较慢,可以配置国内镜像源或提前把.pt文件放到weights/目录。
# detector.py from ultralytics import YOLO class TrafficDetector: def __init__(self, model_path: str, conf_thres: float = 0.5): self.model = YOLO(model_path) self.conf_thres = conf_thres self.target_classes = { 0: "person", # COCO 类别 2: "car", 3: "motorcycle", 5: "bus", 7: "truck" } def detect(self, frame): results = self.model(frame, conf=self.conf_thres, verbose=False) boxes = [] if results and len(results) > 0: r = results[0] for box in r.boxes: cls_id = int(box.cls[0]) if cls_id not in self.target_classes: continue x1, y1, x2, y2 = box.xyxy[0].tolist() conf = float(box.conf[0]) boxes.append({ "class": self.target_classes[cls_id], "box": [round(x1, 2), round(y1, 2), round(x2, 2), round(y2, 2)], "confidence": round(conf, 3) }) return boxes这里只保留了 COCO 中和交通场景相关的类别。实际项目中,你可以使用在自有数据集上微调过的 YOLO 模型,自定义类别列表,检测精度会更好。
4.2 检测结果结构化
为了让后续大模型能理解检测结果,不能把原始坐标直接丢给 LLM,需要先把坐标转成有语义的文本。比如把“car 的边界框中心点”和“ROI 区域是否重合”计算出来,再生成描述。
这里定义两个辅助函数:一个是计算目标中心点,另一个是判断中心点是否在某个多边形区域内。
# detector.py 追加 def get_center(box): x1, y1, x2, y2 = box return ((x1 + x2) / 2, (y1 + y2) / 2) def point_in_polygon(point, polygon): x, y = point count = 0 n = len(polygon) for i in range(n): x1, y1 = polygon[i] x2, y2 = polygon[(i + 1) % n] if (y1 > y) != (y2 > y) and x < (x2 - x1) * (y - y1) / ((y2 - y1) or 1) + x1: count += 1 return count % 2 == 1需要说明的是,这里的多边形判断属于“射线法”,实现简单,适合课堂演示。如果实际项目对精度要求更高,可以使用 shapely 库。
4.3 基于规则的异常事件初步判断
在把数据交给 LLM 之前,先用规则做一次预筛选,可以减少无效调用。例如:
- 如果目标中心点进入“禁停区域”,并且该目标连续 30 帧都存在于该区域附近,则触发“疑似违停”事件。
- 如果行人目标穿越“机动车道区域”,则触发“行人闯入”事件。
- 如果检测到车辆数量突然从 5 辆升到 30 辆,则触发“疑似拥堵”事件。
这里用“连续帧计数”来判断停留时间。具体实现是维护一个字典,key 是目标 ID,value 是连续出现的次数。由于 YOLO 本身不提供跨帧跟踪 ID,本文简化处理:通过目标中心点与上一帧的欧式距离做最近邻匹配,模拟一个简易跟踪逻辑。若要工程级效果,建议接入 ByteTrack 或 BoT-SORT。
# early_warner.py class SimpleTracker: def __init__(self, max_distance=80): self.max_distance = max_distance self.track_dict = {} self.next_id = 0 def update(self, boxes): matched = {} for box in boxes: cx, cy = get_center(box["box"]) best_id = None best_dist = self.max_distance for tid, tinfo in self.track_dict.items(): last = tinfo["last"] d = abs(last[0] - cx) + abs(last[1] - cy) if d < best_dist: best_dist = d best_id = tid if best_id is None: best_id = self.next_id self.next_id += 1 self.track_dict[best_id] = { "last": (cx, cy), "cls": box["class"], "frames": self.track_dict.get(best_id, {}).get("frames", 0) + 1 } matched[best_id] = self.track_dict[best_id] return matched这是一个简化版实现,核心思路是“将当前帧的目标与上一帧的目标按距离匹配”。它无法处理目标交叉、遮挡、ID Switch 等问题,但作为毕业设计演示足够,也让读者理解跟踪模块的基本原理。
4.4 YOLO 推理性能优化
视频检测是实时的,如果逐帧推理,对显卡压力较大。常规做法是设置skip_frames参数,每 2 帧或 3 帧检测一次,中间帧可以继续使用上一次的检测结果,或者不处理。这种方法能让系统承载多路视频流。
frame_count = 0 skip_frames = 2 while cap.isOpened(): ret, frame = cap.read() if not ret: break frame_count += 1 if frame_count % skip_frames != 0: continue boxes = detector.detect(frame) ...另一个优化点是限制推理分辨率,例如imgsz=640。分辨率越高,精度越高,但速度越慢。视频监控场景下,640 已经是性价比比较高的选择。
5. 多模态大语言模型接入层
5.1 LLM 在系统中的定位
LLM 模块接收的是规则引擎生成的文本事件,而不是直接接收图像。这样做的好处是成本低、速度快,而且输出结果的格式和内容都比较可控。相当于我们用 YOLO 做了一次“压缩”,把一张 1920x1080 的图片压缩成几行结构化数据,再让 LLM 基于这些关键信息做判断。
5.2 大模型接口通用封装
为了适配不同模型服务,这里封装一个LLMClient类,支持 OpenAI 兼容接口和本地 Ollama 服务。通过配置切换模型来源,方便答辩前离线演示或在线演示。
# llm_client.py import json import requests class LLMClient: def __init__(self, api_type: str, base_url: str, model: str, api_key: str = ""): self.api_type = api_type self.base_url = base_url self.model = model self.api_key = api_key def chat(self, system_prompt: str, user_prompt: str) -> str: if self.api_type == "openai": headers = {"Authorization": f"Bearer {self.api_key}"} payload = { "model": self.model, "messages": [ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt} ], "temperature": 0.2 } resp = requests.post( f"{self.base_url}/chat/completions", headers=headers, json=payload, timeout=30 ) resp.raise_for_status() return resp.json()["choices"][0]["message"]["content"] elif self.api_type == "ollama": payload = { "model": self.model, "messages": [ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt} ], "stream": False } resp = requests.post(f"{self.base_url}/api/chat", json=payload, timeout=60) resp.raise_for_status() return resp.json()["message"]["content"] else: raise ValueError(f"Unknown api_type: {self.api_type}")这个封装方式属于通用思路,不同大模型服务商的 SDK 可能略有差异,但基本都是围绕 messages 数组组织数据。建议先把 OpenAI 兼容接口跑通,再扩展其他模型。
5.3 提示词设计与结构化输出
大模型能不能输出稳定的 JSON,很大程度上由提示词决定。为了让模型只输出 JSON,不给多余解释,提示词里要明确输出格式和字段定义。
下面是一个适合交通预警场景的 System Prompt:
你是智慧交通监控系统的预警分析助手。你会收到一段由目标检测系统生成的结构化事件描述。 请根据该描述判断当前场景是否值得预警,并输出 JSON,格式如下: { "should_warn": true 或 false, "level": "low" / "medium" / "high" / "critical", "reason": "简要中文原因", "suggestion": "给监控人员的建议" } 要求: 1. 结果必须是可以直接 json.loads 的 JSON 字符串。 2. 不要输出代码块标记。 3. 如果信息不足以判断,请将 should_warn 设为 false。User Prompt 的例子:
当前监控区域配置: - 区域名称:A区入口 - 禁停区域多边形坐标:[(520, 300), (820, 300), (820, 520), (520, 520)] 当前检测事件: - 1辆 car 在禁停区域内停留约 65 秒 - 目标中心点坐标:(680, 400) - 当前画面中车辆总数为 3 请分析是否预警。这样写 Prompt 的好处非常明显:模型只需要“做判断题 + 写理由”,犯错的概率更小。实际使用中,如果模型偶尔输出非 JSON 内容,可以在代码里加一个重试或解析恢复逻辑。
5.4 多模态升级方向
如果你的显卡是 16GB 显存,且希望系统直接“看图说话”,也可以把架构调整成:YOLO 检测结果 + 原始图像裁剪区域,一并发送给 Qwen-VL 等多模态模型。例如,检测到禁停区域有一辆车,就把该区域的裁剪图保存下来,和文本描述一起发送给多模态模型,让它判断车辆状态。
这种方式的实现思路是:在 LLMClient 中增加 base64 图像字段,把图片编码进 messages 的 image_url 字段。但需要注意:并不是所有模型都支持这种输入格式,使用前要确认所调用的模型服务文档。本文不展开这部分,因为基础版本已经足够覆盖毕业设计的核心流程。
6. 预警触发与通知模块
6.1 预警等级划分
预警等级可以定义为 4 级,方便后续做分级处理:
| 等级 | 含义 | 建议响应 |
|---|---|---|
| low | 提示级别,例如某区域车辆较多 | 仅记录 |
| medium | 一般预警,例如行人靠近机动车道 | 通知监控员 |
| high | 较严重,例如违停超过 5 分钟 | 通知 + 截图 |
| critical | 严重事件,例如行人闯入高速 | 通知 + 截图 + 告警音 |
6.2 预警去重与冷却时间
如果不做去重,同一事件会被重复发送给大模型,浪费接口资源,也会导致日志刷屏。这里引入一个冷却机制:同一区域、同一类型的事件,在冷却时间内只发一次。
class AlertManager: def __init__(self, cooldown_seconds=60): self.cooldown_seconds = cooldown_seconds self.last_alert_time = {} def should_alert(self, event_key: str, current_time: float) -> bool: last = self.last_alert_time.get(event_key, 0) if current_time - last > self.cooldown_seconds: self.last_alert_time[event_key] = current_time return True return False这里应该强调:冷却时间不是越长越好。时间太长会漏报真实事件,太短会重复告警。建议根据场景设为 30~120 秒。
6.3 预警记录与截图保存
预警记录需要持久化,这里使用 SQLite。同时,为了后续人工复核,预警触发时可以同时保存当前帧的截图。
import sqlite3 import cv2 from datetime import datetime class AlertStorage: def __init__(self, db_path): self.conn = sqlite3.connect(db_path, check_same_thread=False) self.create_table() def create_table(self): self.conn.execute(""" CREATE TABLE IF NOT EXISTS alerts ( id INTEGER PRIMARY KEY AUTOINCREMENT, level TEXT, reason TEXT, suggestion TEXT, snapshot_path TEXT, created_at TEXT ) """) self.conn.commit() def save(self, level, reason, suggestion, frame, snapshot_dir): now = datetime.now().strftime("%Y%m%d_%H%M%S") snapshot_path = "" if frame is not None: snapshot_name = f"alert_{now}.jpg" snapshot_path = f"{snapshot_dir}/{snapshot_name}" cv2.imwrite(snapshot_path, frame) self.conn.execute( "INSERT INTO alerts (level, reason, suggestion, snapshot_path, created_at) VALUES (?, ?, ?, ?, ?)", (level, reason, suggestion, snapshot_path, datetime.now().isoformat()) ) self.conn.commit() return snapshot_path截图保存功能在答辩演示时非常有用,可以直接展示“系统在什么时间、因为什么原因、保存了什么样的现场画面”,比纯文字日志更有说服力。
6.4 邮件通知与 API 查询接口
邮件通知不是必须的,但如果条件允许,可以用 SMTP 发送简单告警邮件。这里只做一个最小示例:
import smtplib from email.mime.text import MIMEText class MailNotifier: def __init__(self, smtp_server, smtp_port, sender, password): self.smtp_server = smtp_server self.smtp_port = smtp_port self.sender = sender self.password = password def send(self, receiver, subject, content): msg = MIMEText(content, "plain", "utf-8") msg["Subject"] = subject msg["From"] = self.sender msg["To"] = receiver with smtplib.SMTP(self.smtp_server, self.smtp_port) as server: server.starttls() server.login(self.sender, self.password) server.sendmail(self.sender, [receiver], msg.as_string())实际使用中,应该用环境变量或配置文件保存邮箱密码,不要硬编码在代码里。
查询接口用 FastAPI 暴露:
# app.py from fastapi import FastAPI from alert_storage import AlertStorage app = FastAPI(title="智慧交通监控预警系统") storage = AlertStorage("data/records.db") @app.get("/alerts") def list_alerts(limit: int = 20): rows = storage.conn.execute( "SELECT * FROM alerts ORDER BY id DESC LIMIT ?", (limit,) ).fetchall() return [dict(row) for row in rows]运行uvicorn app:app --reload --host 0.0.0.0 --port 8000后,就能在浏览器访问http://127.0.0.1:8000/alerts查看预警记录。
7. 完整实战案例:将各模块串联起来
7.1 全局配置文件
为了让系统可以灵活调整参数,建议把所有配置集中到config.yaml。
# config.yaml model: path: "weights/yolov8n.pt" conf_thres: 0.5 imgsz: 640 video: source: "data/test_video.mp4" skip_frames: 2 roi: no_parking: [[520, 300], [820, 300], [820, 520], [520, 520]] crosswalk: [[300, 600], [900, 600], [900, 750], [300, 750]] llm: api_type: "openai" # openai / ollama base_url: "http://localhost:8001" model: "qwen2.5-7b-instruct" api_key: "" alert: cooldown_seconds: 60 snapshot_dir: "outputs/snapshots" log_dir: "outputs/logs" notify: email: enable: false smtp_server: "smtp.example.com" smtp_port: 587 sender: "" password: "" receiver: ""建议在答辩前先跑通 Ollama 本地模式,这样演示过程不依赖外部网络,稳定性更高。如果使用在线 API,最好提前准备好备用 key,避免现场出现网络超时。
7.2 主流程 main.py
下面是系统主流程的简化版:
# main.py import cv2 import yaml import json from detector import TrafficDetector from early_warner import AlertManager, AlertStorage from llm_client import LLMClient def load_config(path="config.yaml"): with open(path, "r", encoding="utf-8") as f: return yaml.safe_load(f) def main(): cfg = load_config() detector = TrafficDetector(cfg["model"]["path"], cfg["model"]["conf_thres"]) llm = LLMClient(**cfg["llm"]) alert_mgr = AlertManager(cfg["alert"]["cooldown_seconds"]) storage = AlertStorage("data/records.db") cap = cv2.VideoCapture(cfg["video"]["source"]) frame_count = 0 while True: ret, frame = cap.read() if not ret: break frame_count += 1 if frame_count % cfg["video"]["skip_frames"] != 0: continue boxes = detector.detect(frame) # 这里简化事件描述,实际项目中应结合 ROI 与跟踪结果 event_text = f"当前画面检测到 {len(boxes)} 个目标。" for b in boxes: event_text += f"\n- {b['class']} 置信度 {b['confidence']} 位置 {b['box']}" # 构造大模型输入 system_prompt = "你是智慧交通监控预警分析助手,只输出 JSON。" user_prompt = f"监控区域配置:禁停区域坐标。\n{event_text}\n请分析是否预警。" if alert_mgr.should_alert("camera_1", frame_count / 25.0): try: raw = llm.chat(system_prompt, user_prompt) result = json.loads(raw) if result.get("should_warn"): storage.save( level=result.get("level", "low"), reason=result.get("reason", ""), suggestion=result.get("suggestion", ""), frame=frame, snapshot_dir=cfg["alert"]["snapshot_dir"] ) print("预警触发:", json.dumps(result, ensure_ascii=False)) except Exception as e: print("LLM 调用失败:", e) cap.release() if __name__ == "__main__": main()这里把事件描述简化了,但主流程已经完整体现出“视频帧 -> YOLO -> 结构化文本 -> LLM 判断 -> 预警保存 -> 通知”的链路。实际完成代码时,你需要把跟踪模块的输出填充到 event_text 中,让大模型获得更丰富的信息。
7.3 运行与验证
运行命令非常简单:
python main.py如果一切正常,你会看到类似输出:
预警触发: {"should_warn": true, "level": "medium", "reason": "检测到行人进入机动车道边缘区域", "suggestion": "请监控员关注该行人动向"} 预警触发: {"should_warn": true, "level": "high", "reason": "禁停区域车辆停留时间过长,疑似违章停车", "suggestion": "建议通知附近执勤人员查看"}同时outputs/snapshots/目录下会生成对应截图。用浏览器打开 FastAPI 接口,也能看到历史预警记录。
7.4 答辩演示建议
演示时建议准备三段内容:
- 读取一段包含车辆、行人的公开测试视频,演示 YOLO 实时检测和画框效果。
- 手动修改配置中的 ROI 区域,让系统检测“目标进入该区域”,配合 LLM 返回预警结果,突出“检测 + 理解”的链路。
- 打开 FastAPI 页面,展示历史预警记录和截图,证明系统具备数据持久化能力。
这三点能覆盖“视觉感知”、“语义决策”、“工程化落地”三个不同的能力维度,正好对应毕业设计评分中“工作量”和“创新性”两个方面。
8. 常见问题与排查思路
在开发和运行过程中,最容易遇到的坑有几类,这里做一个集中整理。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| YOLO 推理速度极慢 | 使用了 CPU 版本 PyTorch | 安装 CUDA 版 PyTorch,并使用 NVIDIA GPU |
| CUDA 不可用 | PyTorch 与显卡驱动版本不匹配 | 用nvidia-smi查看驱动支持的最高 CUDA 版本,再安装对应 PyTorch |
| 视频流打不开 | 路径错误或 RTSP 地址权限不对 | 先用 VLC 或 ffprobe 测试视频源是否可用 |
| 检测结果抖动明显 | 没有使用跟踪算法,目标 ID 频繁切换 | 引入 ByteTrack 或 BoT-SORT,提高跟踪稳定性 |
| 大模型返回的不是 JSON | Prompt 指令不够明确,或模型版本能力不足 | 调整 System Prompt,加入“只输出 JSON,不输出其他内容”的约束;也可以加入失败重试逻辑 |
| LLM 调用超时 | 网络问题或模型推理时间过长 | 增加超时时间;切换本地模型或更换镜像站 |
| 显存不足 | 输入分辨率太高或模型过大 | 降低 imgsz,切换更小的 YOLO 模型,或改用 API 调用 LLM |
| 预警重复上报 | 没有冷却机制 | 增加事件去重,设置冷却时间 |
| 数据库锁异常 | SQLite 多线程访问 | 使用check_same_thread=False,或改为单线程执行 |
| 邮件通知失败 | 邮箱未开启 SMTP 授权码 | 登录邮箱开启 SMTP 服务,使用授权码而非登录密码 |
如果你在运行中遇到报错,建议先按下面顺序排查:
- 确认 CUDA 是否可用:
python -c "import torch; print(torch.cuda.is_available())",如果输出 False,说明 PyTorch 和显卡环境有问题。 - 确认 YOLO 权重文件是否存在,如果使用在线下载,检查网络。
- 确认配置文件路径正确,尤其是 ROI 坐标格式是否为 List[List[int]]。
- 单独测试 LLMClient 是否能正常返回文本,再接入主流程。
- 查看
outputs/logs/下的运行日志,定位出错模块。
9. 工程实践与代码质量建议
9.1 配置与代码分离
上面的示例已经体现了配置和代码分离的思想。在实际项目中,不要把所有参数都写死在代码里,例如模型路径、检测置信度、ROI 坐标、大模型地址和 key。这些参数会因为环境不同而改变,集中管理在config.yaml或环境变量里,可以避免“换一台电脑就跑不起来”的问题。
9.2 异常处理与日志记录
监控系统通常是 7x24 小时运行的,任何一步出现异常都不能导致整个程序退出。所以:
- 读取视频帧失败时,记录 warn 日志,尝试重新连接。
- LLM 调用失败时,记录 error 日志,不影响下一步检测。
- 数据库写入失败时,要能回退到内存缓存,防止预警记录丢失。
建议使用 Python 标准库 logging,将日志同时输出到控制台和文件。日志格式建议包含时间、模块名、级别、消息内容,例如:
import logging logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", handlers=[ logging.FileHandler("outputs/logs/system.log", encoding="utf-8"), logging.StreamHandler() ] )9.3 关于监控数据的合规与隐私
这里需要特别提醒:智慧交通监控涉及真实的道路画面、车牌、行人面部等个人信息。在开发和测试阶段,建议使用公开数据集或自己录制的模拟视频,不要将未脱敏的实时监控画面上传到公网大模型服务。如果项目需要接入真实摄像头数据,应遵循最小权限原则,明确数据存储边界,并告知相关责任方。大模型处理环节如果使用云端 API,务必确认数据脱敏策略,必要时只在本地部署模型。
这一点在毕业设计答辩中也经常被老师追问,提前想清楚数据合规问题,反而能体现你的工程成熟度。
9.4 性能优化方向
当前的性能瓶颈主要在三个地方:
- YOLO 推理。
- LLM 推理。
- 视频帧读取和画框。
YOLO 层面可以开启 TensorRT 或 ONNX 导出,推理速度通常能提升 20%~50%。LLM 层面可以降低调用频率,例如 30 秒才调用一次大模型做综合判断,而不是每个事件都调用。视频帧方面,可以用多线程或队列缓冲,但不能盲目用多线程修改同一个 OpenCV 窗口,否则容易出现线程安全问题。
from collections import deque import threading frame_queue = deque(maxlen=10) def video_reader(cap): while True: ret, frame = cap.read() if not ret: break frame_queue.append(frame) # 实际使用时需要处理队列为空和销毁线程的情况这里只展示一种思路。毕业设计场景下,单线程逐帧处理也完全够用,关键是先保证逻辑正确。
10. 总结与下一步学习方向
本文梳理了一个完整的“YOLO + 多模态大语言模型”智慧交通监控预警系统的设计思路与核心代码实现。和单纯的目标检测 Demo 相比,这套系统多了一个“语义理解与决策层”,通过大语言模型把检测结果转成可解释的预警原因和建议,解决了“检测出来了但不知道怎么用”的问题。
按照本文的代码,读者可以完成一个最小可运行的预警系统,支持视频流输入、目标检测、ROI 规则判断、大模型预警决策、预警记录保存和 FastAPI 查询展示。如果你想继续深入这个项目,以下几个方向值得研究:
- 使用 ByteTrack 等跟踪算法替换简易最近邻匹配,让目标 ID 更稳定。
- 在自有数据上微调 YOLO 模型,增加工程车、锥桶、摩托车等特定类别。
- 将大模型输出接入企业微信、钉钉机器人,实现移动端告警。
- 使用 RAG 增强 LLM,让它结合本地交通法规知识库给出更合理的提醒建议。
最后一个重要提醒:毕业设计答辩时,不要只演示代码能跑。建议把“为什么用 YOLO 而不是其他检测算法”、“为什么引入大语言模型”、“各模块之间如何解耦”这几个问题提前讲透。当你能用一条完整链路解释清楚从画面到预警信息的流转,这套系统就不只是一个课程作业,而是一个具备项目架构思维能力的设计成果。