news 2026/9/5 10:00:56

基于YOLO与LLM的多模态智慧公安研判平台开发实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于YOLO与LLM的多模态智慧公安研判平台开发实战

在实际智慧公安和智慧警务系统中,单纯依赖单一算法或单一数据源已经难以满足复杂案情研判的需求。一个嫌疑人可能出现在多个监控视频片段中,涉及人脸、行为、车辆、时间、地点等多维度信息。传统系统往往需要民警在不同子系统间反复切换、手动关联,效率低下且容易遗漏关键线索。将 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 大模型:对多模态信息进行语义理解、摘要生成和线索推理。

典型工作流程如下:

  1. 民警上传一段案发视频或选择实时摄像头流。
  2. 后端调用 YOLO 模型逐帧检测,标记出关键目标(如行人、车辆)。
  3. 对检测到的人脸调用人脸识别模块,与底库进行比对,识别身份。
  4. 根据识别结果(如嫌疑人身份证号、车辆车牌)或视觉特征(如衣服颜色),调用视频检索模块查找历史视频中出现的所有相关片段。
  5. LLM 大模型接收所有结构化信息(时间、地点、人物、行为),生成一份综合研判报告。
  6. 前端可视化展示检测框、识别结果、检索列表和自然语言报告。

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 --save

axios用于向后端发送 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_results

3.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_response

4. 设计 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 启动完整系统

  1. 启动后端服务
cd backend python app.py

后端服务将在 http://localhost:5000 启动。

  1. 启动前端开发服务器
cd frontend npm run serve

前端服务将在 http://localhost:8080 启动。

  1. 访问系统: 打开浏览器访问 http://localhost:8080,即可使用智慧公安研判平台。

5.2 常见问题与解决方案

问题现象可能原因检查方式解决方案
前端无法连接后端 API1. 后端服务未启动
2. 端口被占用
3. CORS 配置错误
1. 检查后端服务状态
2. 查看浏览器控制台错误
3. 检查网络请求
1. 确保后端在 5000 端口运行
2. 在前端配置正确的 API 基础URL
3. 确认 Flask-CORS 已正确配置
YOLO 模型加载失败1. 模型文件不存在
2. PyTorch 版本不兼容
3. CUDA 不可用
1. 检查模型文件路径
2. 查看错误日志
3. 测试 CUDA
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/5 9:59:50

开关电源PCB设计实战指南:从EMI抑制到热管理,打造高可靠电源

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/5 9:56:22

机器人关节模组编码器选型:四种多圈掉电不丢失方案对比

1. 引言在机器人关节模组的设计中&#xff0c;编码器的选择至关重要&#xff0c;尤其是对于直线关节模组。直线关节的位置计算公式为&#xff1a;位置 零点 导程 圈数。这意味着&#xff0c;机器人上电后必须能够立即获知关节的绝对位置&#xff0c;这就要求编码器具备多圈掉…

作者头像 李华
网站建设 2026/9/5 9:54:20

Codex 破局:前端组件秒级生成的技术实践

1. 引言&#xff1a;从「手写组件」到「秒级生成」的范式转变 前端开发的效率瓶颈&#xff1a;重复性组件开发、样式调试、样板代码Codex 等 AI 编程助手的崛起&#xff0c;如何改变前端工程师的日常本文目标&#xff1a;拆解 Codex 生成前端组件的完整链路、核心技巧与落地经验…

作者头像 李华
网站建设 2026/9/5 9:52:44

技术术语精准使用:提升代码质量与团队协作效率的工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/5 9:51:33

MCP 2026-07-28协议更新详解:AI工具集成的标准化通信桥梁

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华