在实际智慧公安和智慧警务系统中,单纯依赖单一算法或单一数据源已经难以满足复杂案情研判的需求。一个嫌疑人可能出现在多个监控视频片段中,涉及人脸、行为、车辆、时间、地点等多维度信息。传统系统往往需要民警在不同子系统间反复切换、手动关联,效率低下且容易遗漏关键线索。将 YOLO 目标检测、LLM 大语言模型、人脸识别和视频检索技术整合到一个统一平台,正是为了解决跨模态数据融合和智能研判的难题。
本文将以一个毕业设计级别的智慧公安综合研判平台为例,带你从零搭建一个支持多模态分析的 Web 系统。前端使用 Vue.js 构建交互界面,后端采用 Flask 提供 RESTful API,AI 算法部分集成 YOLO 进行实时目标检测、人脸识别模块处理身份比对、LLM 大模型辅助语义理解和案情摘要生成,并通过视频检索技术快速定位关键片段。你将学会如何将多个 AI 模型串联成完整业务流,如何处理视频流数据,以及如何设计前后端接口让算法结果可视化。最终实现一个可演示、可扩展的研判平台原型。
1. 理解多模态智慧研判平台的技术架构
1.1 什么是多模态大模型在公安场景下的价值
多模态大模型(Multimodal Large Language Model)是指能够同时理解和处理文本、图像、视频、音频等多种类型数据的 AI 模型。在公安研判场景中,它的价值在于打破数据孤岛。例如,系统可以从一段监控视频中提取出嫌疑人的人脸图片(图像模态)、动作描述(文本模态)、出现时间段(时间模态)和地理位置(空间模态),然后通过大模型的能力进行关联推理,生成一段自然语言的研判报告:“嫌疑人甲于 5 月 10 日 14:30 出现在 A 路口,身穿黑色上衣,随后在 B 商场入口再次被捕获,两次出现间隔 15 分钟,行为轨迹可疑。”
单纯使用 YOLO 只能检测出“有什么”,单纯使用人脸识别只能知道“是谁”,单纯使用视频检索只能找到“在哪里”。而多模态大模型能够将这些信息串联成“谁在什么时间什么地点做了什么,接下来可能去哪里”的完整叙事,这正是智慧警务需要的研判能力。
1.2 平台核心组件与工作流程
一个完整的智慧公安研判平台包含以下核心组件:
- 前端交互层 (Vue.js):负责呈现视频流、检测结果、检索列表和研判报告。提供上传视频、查询嫌疑人、查看轨迹等交互功能。
- 后端服务层 (Flask):提供 REST API 接收前端请求,调度算法模块,处理业务逻辑,并返回结构化数据。
- 算法能力层:
- YOLO 目标检测:从视频帧中实时检测行人、车辆、人脸等目标。
- 人脸识别模块:对检测到的人脸进行特征提取和身份比对。
- 视频检索引擎:基于内容(如颜色、纹理、目标)或元数据(时间、摄像头位置)快速检索相关视频片段。
- LLM 大模型:对多模态信息进行语义理解、摘要生成和线索推理。
典型工作流程如下:
- 民警上传一段案发视频或选择实时摄像头流。
- 后端调用 YOLO 模型逐帧检测,标记出关键目标(如行人、车辆)。
- 对检测到的人脸调用人脸识别模块,与底库进行比对,识别身份。
- 根据识别结果(如嫌疑人身份证号、车辆车牌)或视觉特征(如衣服颜色),调用视频检索模块查找历史视频中出现的所有相关片段。
- LLM 大模型接收所有结构化信息(时间、地点、人物、行为),生成一份综合研判报告。
- 前端可视化展示检测框、识别结果、检索列表和自然语言报告。
1.3 技术选型理由与版本考量
- YOLO 选型:YOLOv5 或 YOLOv8 是常见选择。YOLOv5 社区资源丰富,易于部署;YOLOv8 精度更高,支持实例分割。毕业设计阶段建议使用 YOLOv5s(轻量版)平衡速度与精度。
- LLM 选型:考虑到本地部署成本和硬件要求,可以选择较小参数规模的开源模型,如 ChatGLM-6B 或 Qwen-7B,它们可以在单张显卡上运行,并提供足够的语义理解能力。
- 人脸识别:使用成熟的 ArcFace 或 InsightFace 框架,它们提供了高质量的人脸特征提取和比对算法。
- 视频检索:对于毕业设计,可以先用目标检测结果(如行人重识别特征)或简单特征(颜色直方图)实现基础检索,后期可升级到深度学习特征。
- 前后端选型:Vue.js 和 Flask 都是轻量级、学习曲线平缓的框架,适合快速原型开发。
2. 准备开发环境与项目结构
2.1 硬件与软件环境要求
由于涉及多个深度学习模型,建议配置如下:
- GPU:至少 NVIDIA GTX 1660 Ti 或同等算力,6GB 显存以上更佳。YOLO 和 LLM 都可以利用 GPU 加速。
- 内存:16GB RAM 最低,32GB 推荐。视频处理和模型加载比较耗内存。
- 存储:至少 100GB 可用空间,用于存放模型文件、视频数据和数据库。
- 操作系统:Ubuntu 18.04/20.04 LTS 或 Windows 10/11。Linux 在深度学习部署上通常更顺畅。
- Python:3.8 或 3.9 版本。避免使用过高版本,以免某些库不兼容。
2.2 创建项目目录结构
一个清晰的项目结构是后续开发的基础。建议按以下方式组织:
police_judgment_platform/ ├── backend/ # Flask 后端 │ ├── app.py # Flask 主应用 │ ├── models/ # 数据模型(ORM) │ ├── routes/ # API 路由模块 │ ├── services/ # 业务逻辑服务 │ ├── utils/ # 工具函数(如视频处理、图像预处理) │ ├── ai_models/ # AI 模型加载与推理模块 │ │ ├── yolo_detector.py │ │ ├── face_recognizer.py │ │ ├── video_retriever.py │ │ └── llm_service.py │ ├── config.py # 配置文件 │ ├── requirements.txt # Python 依赖 │ └── uploads/ # 上传文件临时目录 ├── frontend/ # Vue.js 前端 │ ├── public/ │ ├── src/ │ │ ├── components/ # Vue 组件 │ │ ├── views/ # 页面视图 │ │ ├── router/ # 路由配置 │ │ ├── api/ # 前端 API 调用封装 │ │ └── assets/ │ ├── package.json │ └── vue.config.js ├── models/ # 存放预训练模型文件 │ ├── yolo/ # YOLO 模型权重 │ ├── face/ # 人脸识别模型 │ └── llm/ # LLM 模型文件 ├── data/ # 数据集和测试数据 │ ├── videos/ # 示例视频 │ └── face_db/ # 人脸底库 └── docs/ # 项目文档2.3 安装 Python 后端依赖
在backend目录下创建requirements.txt,内容如下:
flask==2.3.3 flask-cors==4.0.0 flask-sqlalchemy==3.0.5 opencv-python==4.8.1.78 numpy==1.24.3 torch==2.0.1 torchvision==0.15.2 ultralytics==8.0.186 # 用于 YOLOv8 insightface==0.7.3 # 人脸识别 transformers==4.34.0 # 用于加载 LLM accelerate==0.24.1 # 加速 LLM 推理 pillow==10.0.1 sqlite3 # 内置库,无需安装安装命令:
cd backend pip install -r requirements.txt注意:PyTorch 的安装命令可能因系统和 CUDA 版本而异。请根据 PyTorch 官方指南 选择适合你环境的命令。例如,对于 CUDA 11.8 的用户可能是
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118。
2.4 初始化前端 Vue 项目
在前端目录中,使用 Vue CLI 创建项目(确保已安装 Node.js 和 npm):
cd frontend npm install -g @vue/cli vue create . # 选择默认配置或手动选择需要的特性(如 Router, Vuex)然后安装必要的依赖:
npm install axios element-plus --saveaxios用于向后端发送 HTTP 请求,element-plus是常用的 UI 组件库,可以快速构建界面。
3. 实现后端 Flask 服务与 AI 算法集成
3.1 搭建 Flask 应用骨架
首先创建backend/app.py作为应用入口:
from flask import Flask from flask_cors import CORS import os def create_app(): app = Flask(__name__) app.config.from_pyfile('config.py') # 允许跨域请求,方便前后端分离开发 CORS(app) # 注册蓝图(API 路由) from routes import video_bp, face_bp, analysis_bp app.register_blueprint(video_bp, url_prefix='/api/video') app.register_blueprint(face_bp, url_prefix='/api/face') app.register_blueprint(analysis_bp, url_prefix='/api/analysis') # 创建上传目录 os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) return app if __name__ == '__main__': app = create_app() app.run(host='0.0.0.0', port=5000, debug=True)配置文件backend/config.py:
import os basedir = os.path.abspath(os.path.dirname(__file__)) # 基础配置 SECRET_KEY = 'your-secret-key-change-in-production' UPLOAD_FOLDER = os.path.join(basedir, 'uploads') MAX_CONTENT_LENGTH = 100 * 1024 * 1024 # 限制上传文件 100MB # 数据库配置(使用 SQLite 便于演示) SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'app.db') SQLALCHEMY_TRACK_MODIFICATIONS = False # AI 模型路径 MODEL_DIR = os.path.join(basedir, '../models') YOLO_MODEL_PATH = os.path.join(MODEL_DIR, 'yolo', 'yolov5s.pt') FACE_MODEL_PATH = os.path.join(MODEL_DIR, 'face', 'buffalo_l')3.2 实现 YOLO 目标检测服务
创建backend/ai_models/yolo_detector.py:
import cv2 import numpy as np from ultralytics import YOLO import os class YOLODetector: def __init__(self, model_path): # 加载 YOLO 模型 self.model = YOLO(model_path) self.class_names = self.model.names def detect_image(self, image_path): """对单张图片进行目标检测""" results = self.model(image_path) detections = [] for result in results: boxes = result.boxes for box in boxes: # 获取坐标、置信度和类别 x1, y1, x2, y2 = box.xyxy[0].tolist() conf = box.conf[0].item() cls_id = int(box.cls[0].item()) cls_name = self.class_names[cls_id] detections.append({ 'class': cls_name, 'confidence': round(conf, 2), 'bbox': [round(x1), round(y1), round(x2), round(y2)] }) return detections def process_video(self, video_path, output_path=None, conf_threshold=0.5): """处理视频,逐帧检测并返回结果""" cap = cv2.VideoCapture(video_path) fps = cap.get(cv2.CAP_PROP_FPS) frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) # 用于存储每帧的检测结果 video_results = { 'fps': fps, 'frame_count': frame_count, 'detections_per_frame': [] } frame_idx = 0 while cap.isOpened(): ret, frame = cap.read() if not ret: break # 使用 YOLO 检测当前帧 results = self.model(frame, conf=conf_threshold) frame_detections = [] for result in results: boxes = result.boxes for box in boxes: x1, y1, x2, y2 = box.xyxy[0].tolist() conf = box.conf[0].item() cls_id = int(box.cls[0].item()) cls_name = self.class_names[cls_id] # 只关注人、车等关键类别 if cls_name in ['person', 'car', 'truck', 'bus']: frame_detections.append({ 'class': cls_name, 'confidence': round(conf, 2), 'bbox': [round(x1), round(y1), round(x2), round(y2)] }) video_results['detections_per_frame'].append({ 'frame_index': frame_idx, 'detections': frame_detections }) frame_idx += 1 cap.release() return video_results3.3 实现人脸识别模块
创建backend/ai_models/face_recognizer.py:
import insightface from insightface.app import FaceAnalysis import cv2 import numpy as np import os class FaceRecognizer: def __init__(self, model_path): # 初始化人脸分析应用 self.app = FaceAnalysis(name='buffalo_l', root=model_path) self.app.prepare(ctx_id=0, det_size=(640, 640)) # 人脸数据库:{face_id: {name: '', embedding: []}} self.face_db = {} self.next_face_id = 1 def extract_face_features(self, image_path): """从图片中提取人脸特征""" img = cv2.imread(image_path) if img is None: return [] faces = self.app.get(img) results = [] for face in faces: # 人脸特征向量(用于比对) embedding = face.embedding # 人脸 bounding box bbox = face.bbox.astype(int).tolist() # 人脸关键点(可选) landmarks = face.kps.astype(int).tolist() if hasattr(face, 'kps') else [] results.append({ 'embedding': embedding, 'bbox': bbox, 'landmarks': landmarks }) return results def register_face(self, image_path, person_name): """注册新的人脸到底库""" faces = self.extract_face_features(image_path) if not faces: return False, "未检测到人脸" # 使用第一张检测到的人脸 face_data = faces[0] face_id = self.next_face_id self.face_db[face_id] = { 'name': person_name, 'embedding': face_data['embedding'] } self.next_face_id += 1 return True, f"成功注册人脸,ID: {face_id}" def recognize_face(self, image_path, threshold=0.6): """识别图片中的人脸""" faces = self.extract_face_features(image_path) recognition_results = [] for face in faces: best_match = None best_score = 0 # 与底库中的每个人脸进行比对 for face_id, db_face in self.face_db.items(): # 计算余弦相似度 similarity = self.cosine_similarity(face['embedding'], db_face['embedding']) if similarity > best_score and similarity > threshold: best_score = similarity best_match = { 'face_id': face_id, 'name': db_face['name'], 'similarity': round(similarity, 3) } recognition_results.append({ 'bbox': face['bbox'], 'best_match': best_match, 'embedding_size': len(face['embedding']) }) return recognition_results @staticmethod def cosine_similarity(embedding1, embedding2): """计算两个向量的余弦相似度""" dot_product = np.dot(embedding1, embedding2) norm1 = np.linalg.norm(embedding1) norm2 = np.linalg.norm(embedding2) return dot_product / (norm1 * norm2)3.4 设计视频检索模块
创建backend/ai_models/video_retriever.py:
import os import cv2 import numpy as np from datetime import datetime class VideoRetriever: def __init__(self, video_database_path): self.video_db_path = video_database_path # 视频索引:{video_id: {path, metadata, keyframes}} self.video_index = {} def index_video(self, video_path, metadata=None): """将视频加入检索数据库""" video_id = os.path.basename(video_path) # 提取关键帧特征(简化版:使用颜色直方图) keyframes = self.extract_keyframes(video_path) self.video_index[video_id] = { 'path': video_path, 'metadata': metadata or {}, 'keyframes': keyframes, 'indexed_at': datetime.now().isoformat() } return video_id def extract_keyframes(self, video_path, interval_seconds=5): """提取视频关键帧特征""" cap = cv2.VideoCapture(video_path) fps = cap.get(cv2.CAP_PROP_FPS) interval_frames = int(fps * interval_seconds) keyframes = [] frame_count = 0 while cap.isOpened(): ret, frame = cap.read() if not ret: break if frame_count % interval_frames == 0: # 简化特征:使用颜色直方图 hist = self.compute_color_histogram(frame) keyframes.append({ 'frame_index': frame_count, 'timestamp': frame_count / fps, 'features': hist }) frame_count += 1 cap.release() return keyframes def compute_color_histogram(self, frame, bins=32): """计算图像颜色直方图作为特征""" # 转换到 HSV 颜色空间 hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) # 计算直方图 hist_h = cv2.calcHist([hsv], [0], None, [bins], [0, 180]) hist_s = cv2.calcHist([hsv], [1], None, [bins], [0, 256]) hist_v = cv2.calcHist([hsv], [2], None, [bins], [0, 256]) # 归一化并拼接 hist_h = cv2.normalize(hist_h, hist_h).flatten() hist_s = cv2.normalize(hist_s, hist_s).flatten() hist_v = cv2.normalize(hist_v, hist_v).flatten() return np.concatenate([hist_h, hist_s, hist_v]) def search_by_visual_similarity(self, query_frame, max_results=10): """基于视觉相似性检索视频""" query_features = self.compute_color_histogram(query_frame) similarities = [] for video_id, video_data in self.video_index.items(): for keyframe in video_data['keyframes']: # 计算特征相似度(使用欧氏距离的倒数) distance = np.linalg.norm(query_features - keyframe['features']) similarity = 1 / (1 + distance) # 转换为相似度分数 similarities.append({ 'video_id': video_id, 'similarity': similarity, 'timestamp': keyframe['timestamp'], 'video_metadata': video_data['metadata'] }) # 按相似度排序并返回前 N 个结果 similarities.sort(key=lambda x: x['similarity'], reverse=True) return similarities[:max_results]3.5 集成 LLM 大模型服务
创建backend/ai_models/llm_service.py:
from transformers import AutoTokenizer, AutoModelForCausalLM import torch class LLMService: def __init__(self, model_path): # 加载 tokenizer 和模型 self.tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True) self.model = AutoModelForCausalLM.from_pretrained( model_path, torch_dtype=torch.float16, device_map="auto", trust_remote_code=True ) def generate_analysis_report(self, context_data): """根据多模态数据生成研判报告""" # 构建提示词 prompt = self.build_prompt(context_data) # 生成回答 inputs = self.tokenizer(prompt, return_tensors="pt") with torch.no_grad(): outputs = self.model.generate( inputs.input_ids.cuda(), max_new_tokens=500, temperature=0.7, do_sample=True ) response = self.tokenizer.decode(outputs[0], skip_special_tokens=True) # 提取生成的报告部分 report = self.extract_report_from_response(response, prompt) return report def build_prompt(self, context_data): """构建给 LLM 的提示词""" prompt_template = """ 你是一个公安研判专家。请根据以下案件信息生成一份综合研判报告: 时间信息:{time_info} 地点信息:{location_info} 涉及人员:{person_info} 监控视频分析结果:{video_analysis} 人脸识别结果:{face_recognition} 请从以下角度进行分析: 1. 嫌疑人的行为轨迹分析 2. 时间空间关联性 3. 可能的作案动机推断 4. 下一步侦查建议 请用专业、简洁的语言撰写报告: """ return prompt_template.format( time_info=context_data.get('time_info', '暂无'), location_info=context_data.get('location_info', '暂无'), person_info=context_data.get('person_info', '暂无'), video_analysis=context_data.get('video_analysis', '暂无'), face_recognition=context_data.get('face_recognition', '暂无') ) def extract_report_from_response(self, full_response, prompt): """从 LLM 完整响应中提取报告部分""" if prompt in full_response: return full_response.split(prompt)[1].strip() return full_response4. 设计 API 接口与前端交互
4.1 实现核心 API 路由
创建backend/routes/video.py:
from flask import Blueprint, request, jsonify import os from services.video_service import process_video_upload video_bp = Blueprint('video', __name__) @video_bp.route('/upload', methods=['POST']) def upload_video(): """上传视频文件并进行分析""" if 'video' not in request.files: return jsonify({'error': '未提供视频文件'}), 400 video_file = request.files['video'] if video_file.filename == '': return jsonify({'error': '未选择文件'}), 400 # 保存上传的文件 upload_dir = current_app.config['UPLOAD_FOLDER'] filename = os.path.join(upload_dir, video_file.filename) video_file.save(filename) try: # 调用视频处理服务 result = process_video_upload(filename) return jsonify(result) except Exception as e: return jsonify({'error': f'处理失败: {str(e)}'}), 500 @video_bp.route('/analyze/<video_id>') def analyze_video(video_id): """获取视频分析结果""" # 这里应该从数据库查询分析结果 # 简化演示,直接返回示例数据 return jsonify({ 'video_id': video_id, 'analysis': { 'object_detection': [], 'face_recognition': [], 'summary': '分析完成' } })创建backend/routes/face.py:
from flask import Blueprint, request, jsonify import os from services.face_service import register_face, recognize_faces face_bp = Blueprint('face', __name__) @face_bp.route('/register', methods=['POST']) def register_face_api(): """注册新人脸到底库""" if 'face_image' not in request.files: return jsonify({'error': '未提供人脸图片'}), 400 face_image = request.files['face_image'] person_name = request.form.get('person_name', '') if not person_name: return jsonify({'error': '未提供人员姓名'}), 400 # 保存图片 upload_dir = current_app.config['UPLOAD_FOLDER'] filename = os.path.join(upload_dir, f"face_{person_name}.jpg") face_image.save(filename) success, message = register_face(filename, person_name) if success: return jsonify({'message': message}) else: return jsonify({'error': message}), 400 @face_bp.route('/recognize', methods=['POST']) def recognize_face_api(): """识别图片中的人脸""" if 'image' not in request.files: return jsonify({'error': '未提供图片'}), 400 image_file = request.files['image'] upload_dir = current_app.config['UPLOAD_FOLDER'] filename = os.path.join(upload_dir, 'temp_recognition.jpg') image_file.save(filename) results = recognize_faces(filename) return jsonify({'recognitions': results})4.2 构建 Vue.js 前端界面
在frontend/src/views/Analysis.vue中创建主要分析页面:
<template> <div class="analysis-container"> <el-container> <el-header> <h1>智慧公安综合研判平台</h1> </el-header> <el-main> <el-row :gutter="20"> <el-col :span="12"> <el-card class="upload-section"> <template #header> <span>视频上传与分析</span> </template> <el-upload class="video-upload" action="/api/video/upload" :on-success="handleVideoUploadSuccess" :before-upload="beforeVideoUpload" accept="video/*"> <el-button type="primary">点击上传视频文件</el-button> <template #tip> <div class="el-upload__tip">支持 mp4、avi 等格式,大小不超过 100MB</div> </template> </el-upload> <div v-if="analysisResult" class="result-section"> <h3>分析结果</h3> <el-tabs v-model="activeTab"> <el-tab-pane label="目标检测" name="detection"> <object-detection-result :results="analysisResult.detections" /> </el-tab-pane> <el-tab-pane label="人脸识别" name="faces"> <face-recognition-result :faces="analysisResult.faces" /> </el-tab-pane> <el-tab-pane label="研判报告" name="report"> <llm-report :report="analysisResult.llm_report" /> </el-tab-pane> </el-tabs> </div> </el-card> </el-col> <el-col :span="12"> <el-card class="realtime-section"> <template #header> <span>实时视频分析</span> </template> <video-player :src="liveVideoUrl" @frame-captured="handleFrameCaptured" controls autoplay muted /> <div class="realtime-results"> <h4>实时检测结果</h4> <el-table :data="realtimeDetections" size="small"> <el-table-column prop="class" label="类别" width="100" /> <el-table-column prop="confidence" label="置信度" width="100" /> <el-table-column prop="bbox" label="位置" /> </el-table> </div> </el-card> </el-col> </el-row> </el-main> </el-container> </div> </template> <script> import { ref, reactive } from 'vue' import { ElMessage } from 'element-plus' import ObjectDetectionResult from '@/components/ObjectDetectionResult.vue' import FaceRecognitionResult from '@/components/FaceRecognitionResult.vue' import LLMReport from '@/components/LLMReport.vue' import VideoPlayer from '@/components/VideoPlayer.vue' export default { name: 'AnalysisView', components: { ObjectDetectionResult, FaceRecognitionResult, LLMReport, VideoPlayer }, setup() { const activeTab = ref('detection') const analysisResult = ref(null) const liveVideoUrl = ref('') const realtimeDetections = ref([]) const beforeVideoUpload = (file) => { const isVideo = file.type.startsWith('video/') const isLt100M = file.size / 1024 / 1024 < 100 if (!isVideo) { ElMessage.error('只能上传视频文件!') return false } if (!isLt100M) { ElMessage.error('视频大小不能超过 100MB!') return false } return true } const handleVideoUploadSuccess = (response) => { analysisResult.value = response.data ElMessage.success('视频分析完成!') } const handleFrameCaptured = (detections) => { realtimeDetections.value = detections } return { activeTab, analysisResult, liveVideoUrl, realtimeDetections, beforeVideoUpload, handleVideoUploadSuccess, handleFrameCaptured } } } </script> <style scoped> .analysis-container { padding: 20px; } .upload-section, .realtime-section { height: 600px; } .video-upload { margin-bottom: 20px; } </style>4.3 实现视频播放与帧捕获组件
创建frontend/src/components/VideoPlayer.vue:
<template> <div class="video-player"> <video ref="videoElement" :src="src" @loadeddata="initializeCanvas" @timeupdate="captureFrame" controls autoplay muted crossorigin="anonymous"> 您的浏览器不支持视频播放 </video> <canvas ref="canvasElement" style="display: none;"></canvas> </div> </template> <script> import { ref, onMounted, watch } from 'vue' export default { name: 'VideoPlayer', props: { src: String, captureInterval: { type: Number, default: 1000 // 每1秒捕获一帧 } }, emits: ['frame-captured'], setup(props, { emit }) { const videoElement = ref(null) const canvasElement = ref(null) const canvasContext = ref(null) let lastCaptureTime = 0 const initializeCanvas = () => { if (videoElement.value && canvasElement.value) { canvasElement.value.width = videoElement.value.videoWidth canvasElement.value.height = videoElement.value.videoHeight canvasContext.value = canvasElement.value.getContext('2d') } } const captureFrame = () => { if (!videoElement.value || !canvasContext.value) return const currentTime = Date.now() if (currentTime - lastCaptureTime < props.captureInterval) return lastCaptureTime = currentTime // 绘制当前帧到 canvas canvasContext.value.drawImage( videoElement.value, 0, 0, canvasElement.value.width, canvasElement.value.height ) // 获取图像数据发送给后端分析 canvasElement.value.toBlob((blob) => { emit('frame-captured', blob) }, 'image/jpeg', 0.8) } onMounted(() => { initializeCanvas() }) watch(() => props.src, () => { // 视频源变化时重新初始化 setTimeout(initializeCanvas, 100) }) return { videoElement, canvasElement, initializeCanvas, captureFrame } } } </script>5. 系统集成测试与常见问题排查
5.1 启动完整系统
- 启动后端服务:
cd backend python app.py后端服务将在 http://localhost:5000 启动。
- 启动前端开发服务器:
cd frontend npm run serve前端服务将在 http://localhost:8080 启动。
- 访问系统: 打开浏览器访问 http://localhost:8080,即可使用智慧公安研判平台。
5.2 常见问题与解决方案
| 问题现象 | 可能原因 | 检查方式 | 解决方案 |
|---|---|---|---|
| 前端无法连接后端 API | 1. 后端服务未启动 2. 端口被占用 3. CORS 配置错误 | 1. 检查后端服务状态 2. 查看浏览器控制台错误 3. 检查网络请求 | 1. 确保后端在 5000 端口运行 2. 在前端配置正确的 API 基础URL 3. 确认 Flask-CORS 已正确配置 |
| YOLO 模型加载失败 | 1. 模型文件不存在 2. PyTorch 版本不兼容 3. CUDA 不可用 | 1. 检查模型文件路径 2. 查看错误日志 3. 测试 CUDA |