基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的暴力行为检测系统(深度学习模型+UI界面+Python代码+训练数据集)
暴力行为检测是计算机视觉在公共安全领域的一项关键应用。它旨在通过监控视频实时自动识别打架、斗殴、袭击等危险行为,从而及时预警,提升安防效率。本文将深入探讨如何利用YOLO系列最新模型(v5至v8)构建一个端到端的暴力行为检测系统,内容涵盖算法原理、数据集处理、模型训练优化、系统集成以及一个完整的PySide6 UI界面。
1. 项目背景与意义
传统的安防监控严重依赖人工盯守,存在易疲劳、反应延迟、漏报率高等问题。基于深度学习的自动行为检测系统能够7x24小时不间断工作,快速定位异常事件,将安保人员从“看屏幕”的被动监控中解放出来,转向对预警事件的主动响应。暴力行为检测作为其中的核心技术,对维护机场、车站、学校、广场等公共场所的安全具有重大实用价值。
YOLO(You Only Look Once)系列因其在速度和精度间的卓越平衡,成为实时目标检测的首选架构。从YOLOv5的易用性到YOLOv8的最新SOTA性能,本项目将展示如何利用这一强大的算法家族解决实际问题。
2. 暴力行为检测数据集
一个高质量的数据集是模型成功的基石。由于“暴力行为”的界定和隐私问题,公开可用的数据集相对有限。
2.1 参考数据集
-
暴力检测数据集(Surveillance Fight Dataset):
-
来源: Hockey Fight, Movies Fight 等早期数据集整合。
-
内容: 包含监控视角和电影视角的打架视频片段。
-
特点: 数据量较小,适合初期验证。
-
-
UCF-Crime:
-
来源: 中佛罗里达大学。
-
内容: 一个大规模的真实世界监控视频数据集,包含13类异常事件,其中
Fighting类别与我们的任务直接相关。 -
特点: 数据真实、复杂、具有挑战性,但需自行从长视频中裁剪和标注“打架”片段。
-
-
自定义数据集(推荐):
-
来源: 从公开网络(如YouTube、特定安防资源网站)收集符合场景的视频,或使用仿真环境生成。
-
标注: 使用标注工具(如LabelImg、CVAT、Roboflow)对视频帧中的暴力行为个体或群体进行边界框标注。
-
类别: 通常简化为单类别
fight, 也可细化为punch,kick,strangle等。
-
2.2 数据集结构(以YOLO格式为例)
项目目录结构如下:
text
暴力行为检测项目/ │ ├── datasets/ │ ├── fight_dataset/ │ │ ├── train/ │ │ │ ├── images/ # 训练图片 .jpg │ │ │ └── labels/ # 对应YOLO格式标签 .txt │ │ ├── val/ │ │ │ ├── images/ │ │ │ └── labels/ │ │ └── data.yaml # 数据集配置文件 │ ├── yolov5/ # YOLOv5官方代码 ├── yolov8/ # YOLOv8官方代码 ├── train.py # 通用训练脚本 ├── detect.py # 通用检测脚本 ├── ui.py # PySide6 UI主程序 └── ...其他文件
data.yaml 示例:
yaml
path: ../datasets/fight_dataset # 数据集根目录 train: train/images # 训练集路径(相对path) val: val/images # 验证集路径(相对path) test: # 测试集路径(可选) # 类别数量 nc: 1 # 类别名称列表 names: ['fight']
3. YOLO模型原理与选型
3.1 YOLO核心思想
YOLO将目标检测重构为单一的回归问题,直接从图像像素到边界框坐标和类别概率。其核心流程为:
-
划分网格: 将输入图像划分为 S×S 的网格。
-
预测与负责: 每个网格负责预测中心落在该网格内的物体。每个预测包含:边界框(x, y, w, h, confidence)和 C 个类别概率。
-
非极大值抑制(NMS): 过滤冗余的、重叠度高的预测框。
3.2 YOLOv5 vs. YOLOv6 vs. YOLOv7 vs. YOLOv8
-
YOLOv5: Ultralytics发布,以极致的工程友好性著称。提供多种尺寸模型(n, s, m, l, x),包含完整的数据增强、训练、验证、部署流水线,是快速落地的绝佳选择。
-
YOLOv6: 美团视觉智能部发布,专注于工业应用。在Backbone和Neck中大量使用RepVGG风格的重参数化结构,在精度和速度上取得了很好平衡。
-
YOLOv7: 原作者Chien-Yao Wang等人发布,在当时达到了实时检测的SOTA。引入了E-ELAN高效架构、模型缩放策略和“可训练的Bag-of-Freebies”训练技巧。
-
YOLOv8: Ultralytics最新一代,在YOLOv5的基础上进行了全面升级。提供了分类、检测、分割三种任务模型。其检测模型使用了新的Anchor-Free检测头、更优秀的Backbone和损失函数,在多数基准上取得了最佳性能。
本项目选择建议: 对于追求最新性能和丰富生态(如分割)的用户,推荐 YOLOv8。对于最成熟稳定、文档社区最丰富的选择,推荐 YOLOv5。
4. 完整代码实现
4.1 环境配置
bash
# 创建conda环境(以YOLOv8为例) conda create -n yolo_fight python=3.8 conda activate yolo_fight # 安装PyTorch (请根据CUDA版本选择) pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 # 安装YOLOv8 pip install ultralytics # 安装UI库及依赖 pip install PySide6 opencv-python pillow numpy pandas
4.2 模型训练脚本 (train_fight_detection.py)
这是一个适配YOLOv5/v8的通用训练脚本。
python
import os
import sys
import argparse
import subprocess
import yaml
def train_yolov5(data_yaml, weights='yolov5s.pt', epochs=100, imgsz=640, batch=16, project='runs/train'):
"""训练YOLOv5模型"""
train_cmd = [
'python', 'yolov5/train.py',
'--data', data_yaml,
'--weights', weights,
'--epochs', str(epochs),
'--imgsz', str(imgsz),
'--batch-size', str(batch),
'--project', project,
'--name', 'exp',
'--exist-ok'
]
print(f"Running command: {' '.join(train_cmd)}")
subprocess.run(train_cmd)
def train_yolov8(data_yaml, model='yolov8s.pt', epochs=100, imgsz=640, batch=16, project='runs/detect'):
"""训练YOLOv8模型 (使用Ultralytics API)"""
from ultralytics import YOLO
model = YOLO(model)
results = model.train(
data=data_yaml,
epochs=epochs,
imgsz=imgsz,
batch=batch,
project=project,
name='exp',
exist_ok=True,
verbose=True
)
return results
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Train YOLO model for fight detection.')
parser.add_argument('--model-family', type=str, default='yolov8', choices=['yolov5', 'yolov8'],
help='Which YOLO family to use.')
parser.add_argument('--data', type=str, required=True, help='Path to data.yaml')
parser.add_argument('--epochs', type=int, default=100, help='Number of training epochs')
parser.add_argument('--imgsz', type=int, default=640, help='Image size for training')
parser.add_argument('--batch', type=int, default=16, help='Batch size')
parser.add_argument('--weights', type=str, default='', help='Pretrained weights')
args = parser.parse_args()
# 设置默认权重
if not args.weights:
if args.model_family == 'yolov5':
args.weights = 'yolov5s.pt'
elif args.model_family == 'yolov8':
args.weights = 'yolov8s.pt'
# 检查数据集配置文件
if not os.path.exists(args.data):
print(f"Error: data.yaml not found at {args.data}")
sys.exit(1)
print(f"Training {args.model_family.upper()} model on {args.data}")
print(f"Epochs: {args.epochs}, Imgsz: {args.imgsz}, Batch: {args.batch}")
print(f"Initial weights: {args.weights}")
if args.model_family == 'yolov5':
train_yolov5(args.data, args.weights, args.epochs, args.imgsz, args.batch)
elif args.model_family == 'yolov8':
train_yolov8(args.data, args.weights, args.epochs, args.imgsz, args.batch)
print("Training completed!")
4.3 推理检测脚本 (detect_fight.py)
python
import cv2
import torch
import numpy as np
from pathlib import Path
import time
class FightDetector:
def __init__(self, model_path, model_family='yolov8', conf_thres=0.5, iou_thres=0.45):
"""
初始化检测器
Args:
model_path: 训练好的模型权重路径 (.pt)
model_family: 模型家族 'yolov5' 或 'yolov8'
conf_thres: 置信度阈值
iou_thres: NMS IoU阈值
"""
self.conf_thres = conf_thres
self.iou_thres = iou_thres
self.model_family = model_family
# 加载模型
if model_family == 'yolov5':
self.model = torch.hub.load('ultralytics/yolov5', 'custom', path=model_path, force_reload=False)
self.model.conf = conf_thres
self.model.iou = iou_thres
self.is_v5 = True
elif model_family == 'yolov8':
from ultralytics import YOLO
self.model = YOLO(model_path)
self.is_v5 = False
else:
raise ValueError(f"Unsupported model family: {model_family}")
self.class_names = ['fight'] # 根据实际类别修改
print(f"Model loaded from {model_path}")
def detect(self, image):
"""
在单张图像上进行检测
Args:
image: numpy数组 (H, W, C)
Returns:
results: 包含边界框、置信度、类别的字典
"""
if self.is_v5:
# YOLOv5推理
results = self.model(image)
detections = results.xyxy[0].cpu().numpy() # [x1, y1, x2, y2, conf, cls]
boxes = []
confidences = []
class_ids = []
for det in detections:
x1, y1, x2, y2, conf, cls_id = det
if conf >= self.conf_thres:
boxes.append([int(x1), int(y1), int(x2-x1), int(y2-y1)]) # 转为 [x, y, w, h]
confidences.append(float(conf))
class_ids.append(int(cls_id))
else:
# YOLOv8推理
results = self.model(image, conf=self.conf_thres, iou=self.iou_thres)[0]
boxes = []
confidences = []
class_ids = []
for box in results.boxes:
xywh = box.xywh[0].cpu().numpy() # [x_center, y_center, w, h]
conf = box.conf[0].cpu().numpy()
cls_id = int(box.cls[0].cpu().numpy())
x, y, w, h = xywh
boxes.append([int(x-w/2), int(y-h/2), int(w), int(h)]) # 转为 [x, y, w, h]
confidences.append(float(conf))
class_ids.append(cls_id)
return {
'boxes': boxes,
'confidences': confidences,
'class_ids': class_ids,
'class_names': [self.class_names[idx] for idx in class_ids]
}
def detect_video(self, video_path, output_path=None, show=True):
"""
处理视频文件
Args:
video_path: 输入视频路径
output_path: 输出视频路径 (可选)
show: 是否实时显示
"""
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
print(f"Error opening video file {video_path}")
return
# 获取视频属性
fps = int(cap.get(cv2.CAP_PROP_FPS))
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
# 初始化视频写入器
if output_path:
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
frame_count = 0
start_time = time.time()
while True:
ret, frame = cap.read()
if not ret:
break
# 检测
results = self.detect(frame)
# 在帧上绘制结果
annotated_frame = self.draw_detections(frame, results)
# 计算并显示FPS
frame_count += 1
if frame_count % 30 == 0:
elapsed = time.time() - start_time
fps_det = frame_count / elapsed
cv2.putText(annotated_frame, f'FPS: {fps_det:.1f}', (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
# 显示结果
if show:
cv2.imshow('Fight Detection', annotated_frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 写入输出视频
if output_path:
out.write(annotated_frame)
# 清理
cap.release()
if output_path:
out.release()
cv2.destroyAllWindows()
elapsed = time.time() - start_time
print(f"Processed {frame_count} frames in {elapsed:.2f}s, Average FPS: {frame_count/elapsed:.2f}")
def draw_detections(self, image, results):
"""
在图像上绘制检测框
"""
img_copy = image.copy()
boxes = results['boxes']
confidences = results['confidences']
class_names = results['class_names']
for i, (box, conf, cls_name) in enumerate(zip(boxes, confidences, class_names)):
x, y, w, h = box
# 绘制边界框
color = (0, 0, 255) if cls_name == 'fight' else (0, 255, 0) # 暴力行为用红色框
cv2.rectangle(img_copy, (x, y), (x+w, y+h), color, 2)
# 绘制标签和置信度
label = f'{cls_name} {conf:.2f}'
(label_width, label_height), baseline = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 2)
cv2.rectangle(img_copy, (x, y-label_height-10), (x+label_width, y), color, -1)
cv2.putText(img_copy, label, (x, y-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)
# 在左上角添加警告文字(如果检测到暴力行为)
if cls_name == 'fight' and conf > 0.7:
warning_text = "WARNING: VIOLENT BEHAVIOR DETECTED!"
(warn_width, warn_height), _ = cv2.getTextSize(warning_text, cv2.FONT_HERSHEY_SIMPLEX, 1, 3)
cv2.putText(img_copy, warning_text, (10, 60),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 3, cv2.LINE_AA)
return img_copy
# 使用示例
if __name__ == '__main__':
# 初始化检测器
detector = FightDetector(
model_path='runs/detect/exp/weights/best.pt', # 替换为你的模型路径
model_family='yolov8',
conf_thres=0.5
)
# 检测图片
img = cv2.imread('test.jpg')
results = detector.detect(img)
output_img = detector.draw_detections(img, results)
cv2.imwrite('result.jpg', output_img)
# 检测视频
detector.detect_video('test_video.mp4', output_path='output_video.mp4', show=True)
4.4 PySide6图形用户界面 (fight_detection_ui.py)
python
import sys
import os
from pathlib import Path
import cv2
import numpy as np
from PySide6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
QHBoxLayout, QPushButton, QLabel, QFileDialog,
QComboBox, QSlider, QSpinBox, QCheckBox, QTextEdit,
QGroupBox, QMessageBox, QProgressBar)
from PySide6.QtCore import Qt, QTimer, Signal, QThread
from PySide6.QtGui import QImage, QPixmap, QFont, QIcon
from detect_fight import FightDetector # 导入上面的检测器类
class DetectionThread(QThread):
"""检测线程,防止UI卡顿"""
frame_processed = Signal(np.ndarray, list, float) # 发送处理后的帧、检测结果、FPS
video_finished = Signal()
def __init__(self, detector, video_path=None, camera_id=0, is_live=False):
super().__init__()
self.detector = detector
self.video_path = video_path
self.camera_id = camera_id
self.is_live = is_live
self.is_running = True
self.conf_thres = 0.5
def run(self):
if self.is_live:
self.process_camera()
else:
self.process_video()
def process_video(self):
cap = cv2.VideoCapture(self.video_path)
if not cap.isOpened():
print(f"Cannot open video: {self.video_path}")
return
fps = cap.get(cv2.CAP_PROP_FPS)
frame_count = 0
start_time = cv2.getTickCount()
while self.is_running:
ret, frame = cap.read()
if not ret:
break
# 检测
self.detector.conf_thres = self.conf_thres
results = self.detector.detect(frame)
# 绘制检测结果
processed_frame = self.detector.draw_detections(frame, results)
# 计算FPS
frame_count += 1
elapsed_time = (cv2.getTickCount() - start_time) / cv2.getTickFrequency()
current_fps = frame_count / elapsed_time
# 发送信号
self.frame_processed.emit(processed_frame, results['boxes'], current_fps)
# 控制处理速度,接近真实FPS
if fps > 0:
self.msleep(int(1000 / fps))
cap.release()
self.video_finished.emit()
def process_camera(self):
cap = cv2.VideoCapture(self.camera_id)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
frame_count = 0
start_time = cv2.getTickCount()
while self.is_running:
ret, frame = cap.read()
if not ret:
break
# 检测
self.detector.conf_thres = self.conf_thres
results = self.detector.detect(frame)
# 绘制检测结果
processed_frame = self.detector.draw_detections(frame, results)
# 计算FPS
frame_count += 1
elapsed_time = (cv2.getTickCount() - start_time) / cv2.getTickFrequency()
current_fps = frame_count / elapsed_time
# 发送信号
self.frame_processed.emit(processed_frame, results['boxes'], current_fps)
# 小延迟,防止过度占用CPU
self.msleep(1)
cap.release()
self.video_finished.emit()
def stop(self):
self.is_running = False
self.wait()
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.detector = None
self.detection_thread = None
self.current_video_path = None
self.is_live = False
self.init_ui()
self.load_default_model()
def init_ui(self):
self.setWindowTitle("基于YOLO的暴力行为检测系统 v2.0")
self.setGeometry(100, 100, 1600, 900)
# 设置字体
font = QFont()
font.setPointSize(10)
self.setFont(font)
# 中央部件
central_widget = QWidget()
self.setCentralWidget(central_widget)
main_layout = QHBoxLayout(central_widget)
# 左侧控制面板
control_panel = QGroupBox("控制面板")
control_panel.setMaximumWidth(350)
control_layout = QVBoxLayout()
# 模型选择
model_group = QGroupBox("模型配置")
model_layout = QVBoxLayout()
self.model_family_combo = QComboBox()
self.model_family_combo.addItems(['YOLOv5', 'YOLOv8'])
model_layout.addWidget(QLabel("模型架构:"))
model_layout.addWidget(self.model_family_combo)
self.model_path_btn = QPushButton("选择模型文件 (.pt)")
self.model_path_btn.clicked.connect(self.select_model_file)
model_layout.addWidget(self.model_path_btn)
self.model_path_label = QLabel("未选择模型")
self.model_path_label.setWordWrap(True)
model_layout.addWidget(self.model_path_label)
model_group.setLayout(model_layout)
control_layout.addWidget(model_group)
# 检测设置
detect_group = QGroupBox("检测设置")
detect_layout = QVBoxLayout()
detect_layout.addWidget(QLabel("置信度阈值:"))
self.conf_slider = QSlider(Qt.Horizontal)
self.conf_slider.setRange(10, 90)
self.conf_slider.setValue(50)
self.conf_slider.valueChanged.connect(self.update_conf_thres)
detect_layout.addWidget(self.conf_slider)
self.conf_label = QLabel("0.50")
detect_layout.addWidget(self.conf_label)
self.auto_save_check = QCheckBox("自动保存检测结果")
detect_layout.addWidget(self.auto_save_check)
self.warning_check = QCheckBox("检测到暴力行为时声音报警")
self.warning_check.setChecked(True)
detect_layout.addWidget(self.warning_check)
detect_group.setLayout(detect_layout)
control_layout.addWidget(detect_group)
# 输入源选择
input_group = QGroupBox("输入源")
input_layout = QVBoxLayout()
self.video_btn = QPushButton("选择视频文件")
self.video_btn.clicked.connect(self.select_video_file)
input_layout.addWidget(self.video_btn)
self.camera_btn = QPushButton("开启摄像头")
self.camera_btn.clicked.connect(self.start_camera)
input_layout.addWidget(self.camera_btn)
self.image_btn = QPushButton("选择图片文件")
self.image_btn.clicked.connect(self.select_image_file)
input_layout.addWidget(self.image_btn)
self.input_path_label = QLabel("未选择输入源")
self.input_path_label.setWordWrap(True)
input_layout.addWidget(self.input_path_label)
input_group.setLayout(input_layout)
control_layout.addWidget(input_group)
# 控制按钮
btn_group = QGroupBox("控制")
btn_layout = QVBoxLayout()
self.start_btn = QPushButton("开始检测")
self.start_btn.clicked.connect(self.start_detection)
self.start_btn.setEnabled(False)
btn_layout.addWidget(self.start_btn)
self.stop_btn = QPushButton("停止检测")
self.stop_btn.clicked.connect(self.stop_detection)
self.stop_btn.setEnabled(False)
btn_layout.addWidget(self.stop_btn)
self.export_btn = QPushButton("导出结果")
self.export_btn.clicked.connect(self.export_results)
btn_layout.addWidget(self.export_btn)
btn_group.setLayout(btn_layout)
control_layout.addWidget(btn_group)
# 统计信息
stats_group = QGroupBox("统计信息")
stats_layout = QVBoxLayout()
self.fps_label = QLabel("FPS: 0.0")
stats_layout.addWidget(self.fps_label)
self.detection_count_label = QLabel("检测到暴力行为: 0")
stats_layout.addWidget(self.detection_count_label)
self.progress_bar = QProgressBar()
stats_layout.addWidget(self.progress_bar)
stats_group.setLayout(stats_layout)
control_layout.addWidget(stats_group)
control_layout.addStretch()
control_panel.setLayout(control_layout)
main_layout.addWidget(control_panel)
# 右侧显示区域
display_panel = QGroupBox("检测结果显示")
display_layout = QVBoxLayout()
self.video_label = QLabel()
self.video_label.setAlignment(Qt.AlignCenter)
self.video_label.setMinimumSize(800, 600)
self.video_label.setStyleSheet("border: 2px solid gray; background-color: black;")
display_layout.addWidget(self.video_label)
# 日志区域
self.log_text = QTextEdit()
self.log_text.setReadOnly(True)
self.log_text.setMaximumHeight(150)
display_layout.addWidget(self.log_text)
display_panel.setLayout(display_layout)
main_layout.addWidget(display_panel)
# 状态栏
self.statusBar().showMessage("就绪")
def load_default_model(self):
"""尝试加载默认模型"""
default_paths = [
'runs/detect/exp/weights/best.pt',
'best.pt',
'weights/best.pt'
]
for path in default_paths:
if os.path.exists(path):
self.load_model(path)
self.log_message(f"已加载默认模型: {path}")
break
def select_model_file(self):
file_path, _ = QFileDialog.getOpenFileName(
self, "选择模型文件", "", "PyTorch Model Files (*.pt);;All Files (*)"
)
if file_path:
self.load_model(file_path)
def load_model(self, model_path):
try:
model_family = self.model_family_combo.currentText().lower()
self.detector = FightDetector(
model_path=model_path,
model_family=model_family,
conf_thres=0.5
)
self.model_path_label.setText(f"已加载: {os.path.basename(model_path)}")
self.start_btn.setEnabled(True)
self.log_message(f"模型加载成功: {model_path}")
except Exception as e:
QMessageBox.critical(self, "错误", f"加载模型失败: {str(e)}")
def select_video_file(self):
file_path, _ = QFileDialog.getOpenFileName(
self, "选择视频文件", "", "Video Files (*.mp4 *.avi *.mov *.mkv);;All Files (*)"
)
if file_path:
self.current_video_path = file_path
self.is_live = False
self.input_path_label.setText(f"视频: {os.path.basename(file_path)}")
self.start_btn.setEnabled(True)
self.preview_video(file_path)
def start_camera(self):
self.current_video_path = 0 # 默认摄像头
self.is_live = True
self.input_path_label.setText("摄像头: 默认")
self.start_btn.setEnabled(True)
def select_image_file(self):
file_path, _ = QFileDialog.getOpenFileName(
self, "选择图片文件", "", "Image Files (*.jpg *.png *.bmp);;All Files (*)"
)
if file_path and self.detector:
self.process_image(file_path)
def preview_video(self, video_path):
"""预览视频第一帧"""
cap = cv2.VideoCapture(video_path)
if cap.isOpened():
ret, frame = cap.read()
if ret:
self.display_frame(frame)
cap.release()
def process_image(self, image_path):
"""处理单张图片"""
if not self.detector:
QMessageBox.warning(self, "警告", "请先加载模型")
return
try:
image = cv2.imread(image_path)
results = self.detector.detect(image)
processed_image = self.detector.draw_detections(image, results)
# 保存结果
if self.auto_save_check.isChecked():
output_path = f"result_{os.path.basename(image_path)}"
cv2.imwrite(output_path, processed_image)
self.log_message(f"结果已保存: {output_path}")
self.display_frame(processed_image)
self.update_statistics(len(results['boxes']), 0)
except Exception as e:
QMessageBox.critical(self, "错误", f"处理图片失败: {str(e)}")
def start_detection(self):
if not self.detector:
QMessageBox.warning(self, "警告", "请先加载模型")
return
if self.current_video_path is None:
QMessageBox.warning(self, "警告", "请先选择输入源")
return
self.start_btn.setEnabled(False)
self.stop_btn.setEnabled(True)
self.video_btn.setEnabled(False)
self.camera_btn.setEnabled(False)
self.detection_count = 0
self.detection_count_label.setText("检测到暴力行为: 0")
# 创建检测线程
self.detection_thread = DetectionThread(
detector=self.detector,
video_path=self.current_video_path,
camera_id=self.current_video_path if self.is_live else None,
is_live=self.is_live
)
self.detection_thread.frame_processed.connect(self.update_frame)
self.detection_thread.video_finished.connect(self.detection_finished)
self.detection_thread.conf_thres = self.conf_slider.value() / 100.0
self.detection_thread.start()
self.log_message("开始检测...")
def stop_detection(self):
if self.detection_thread and self.detection_thread.isRunning():
self.detection_thread.stop()
self.detection_finished()
def detection_finished(self):
self.start_btn.setEnabled(True)
self.stop_btn.setEnabled(False)
self.video_btn.setEnabled(True)
self.camera_btn.setEnabled(True)
self.log_message("检测停止")
def update_frame(self, frame, detections, fps):
"""更新显示帧"""
self.display_frame(frame)
self.fps_label.setText(f"FPS: {fps:.1f}")
if detections:
self.detection_count += len(detections)
self.detection_count_label.setText(f"检测到暴力行为: {self.detection_count}")
# 声音报警(模拟)
if self.warning_check.isChecked() and len(detections) > 0:
QApplication.beep()
def display_frame(self, frame):
"""在QLabel中显示OpenCV帧"""
if frame is None:
return
# 调整大小以适应显示区域
h, w, ch = frame.shape
bytes_per_line = ch * w
# 转换为RGB
rgb_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# 创建QImage
qt_image = QImage(rgb_image.data, w, h, bytes_per_line, QImage.Format_RGB888)
# 缩放图像以适应标签大小
label_size = self.video_label.size()
scaled_image = qt_image.scaled(label_size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
self.video_label.setPixmap(QPixmap.fromImage(scaled_image))
def update_conf_thres(self, value):
conf = value / 100.0
self.conf_label.setText(f"{conf:.2f}")
if self.detector:
self.detector.conf_thres = conf
if self.detection_thread:
self.detection_thread.conf_thres = conf
def export_results(self):
"""导出检测结果"""
options = QFileDialog.Options()
file_path, _ = QFileDialog.getSaveFileName(
self, "导出结果", "detection_results.txt", "Text Files (*.txt);;All Files (*)", options=options
)
if file_path:
try:
with open(file_path, 'w') as f:
f.write(f"暴力行为检测报告\n")
f.write(f"====================\n")
f.write(f"检测总数: {self.detection_count}\n")
f.write(f"模型: {self.model_path_label.text()}\n")
f.write(f"置信度阈值: {self.conf_label.text()}\n")
self.log_message(f"结果已导出: {file_path}")
QMessageBox.information(self, "成功", "结果导出成功!")
except Exception as e:
QMessageBox.critical(self, "错误", f"导出失败: {str(e)}")
def log_message(self, message):
"""在日志区域添加消息"""
from datetime import datetime
timestamp = datetime.now().strftime("%H:%M:%S")
self.log_text.append(f"[{timestamp}] {message}")
def update_statistics(self, detections, fps):
"""更新统计信息"""
self.detection_count_label.setText(f"检测到暴力行为: {detections}")
self.fps_label.setText(f"FPS: {fps:.1f}")
def closeEvent(self, event):
"""窗口关闭事件"""
self.stop_detection()
event.accept()
def main():
app = QApplication(sys.argv)
app.setStyle('Fusion') # 设置界面风格
window = MainWindow()
window.show()
sys.exit(app.exec())
if __name__ == '__main__':
main()
更多推荐
所有评论(0)