|
|
|
|
from wzq import Ui_MainWindow
|
|
|
|
|
from PySide6.QtWidgets import QFileDialog, QHBoxLayout, QDialog,QInputDialog
|
|
|
|
|
import sys
|
|
|
|
|
import torch
|
|
|
|
|
from PySide6 import QtGui, QtCore
|
|
|
|
|
import torch.backends.cudnn as cudnn
|
|
|
|
|
import json
|
|
|
|
|
from models.common import DetectMultiBackend
|
|
|
|
|
from utils.dataloaders import IMG_FORMATS, VID_FORMATS, LoadImages, LoadStreams
|
|
|
|
|
from PySide6.QtCore import Qt, Signal, QThread
|
|
|
|
|
import numpy as np
|
|
|
|
|
from PySide6.QtWidgets import QApplication, QMainWindow, QMessageBox, QPushButton,QTableWidgetItem,QVBoxLayout,QWidget
|
|
|
|
|
from utils.torch_utils import smart_inference_mode
|
|
|
|
|
from utils.plots import Annotator, colors, save_one_box
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
from PySide6.QtGui import QPixmap, QImage
|
|
|
|
|
from PySide6 import QtGui
|
|
|
|
|
from utils.torch_utils import select_device
|
|
|
|
|
from utils.general import (Profile, check_file, check_img_size, check_imshow, cv2,
|
|
|
|
|
increment_path, non_max_suppression, scale_boxes, xyxy2xywh)
|
|
|
|
|
from user.userLIst import Ui_MainWindow as userlist_Ui_MainWindow
|
|
|
|
|
from user.userManager import UserManager
|
|
|
|
|
from PySide6.QtWidgets import QDialog, QVBoxLayout, QLabel, QLineEdit, QPushButton
|
|
|
|
|
class MyThread(QThread):
|
|
|
|
|
# pyqtSignal是一个用于在类之间进行通信的信号,它可以连接到pyside6方法信号并执行相应的操作。
|
|
|
|
|
# 原图
|
|
|
|
|
send_raw = Signal(np.ndarray)
|
|
|
|
|
# 发送检测图片信号槽
|
|
|
|
|
send_img = Signal(np.ndarray)
|
|
|
|
|
# 发送检测的类别信息,速度等
|
|
|
|
|
send_detect_info = Signal(str)
|
|
|
|
|
# 发送检测速度
|
|
|
|
|
send_speed = Signal(str)
|
|
|
|
|
# 发送进度条百分比
|
|
|
|
|
send_percent = Signal(int)
|
|
|
|
|
# 检测到的目标数量
|
|
|
|
|
send_detect_num = Signal(int)
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
# 调用父类多线程,继承方法
|
|
|
|
|
super(MyThread, self).__init__()
|
|
|
|
|
self.weights = "yolov5s.pt" # 默认权重
|
|
|
|
|
self.conf = 0.25 # 置信度
|
|
|
|
|
self.iou = 0.45
|
|
|
|
|
self.is_save = None # 是否保存结果
|
|
|
|
|
self.end_loop = False # 跳出循环
|
|
|
|
|
self.play = True
|
|
|
|
|
self.names_dic = {} # 初始化字典
|
|
|
|
|
|
|
|
|
|
def run(self,
|
|
|
|
|
imgsz=(640, 640), # 网络模型训练的尺寸
|
|
|
|
|
max_det=1000, # 每张图像的最大检测数量,这里设置为1000个
|
|
|
|
|
data='data/coco128.yaml', # 数据集配置文件的路径,这里使用的是COCO数据集的128类版本
|
|
|
|
|
device='', # 使用的计算设备,可以是CPU或GPU
|
|
|
|
|
view_img=False, # 是否显示检测结果,这里设置为不显示
|
|
|
|
|
save_txt=False, # 是否将检测结果保存为文本文件,这里设置为不保存。
|
|
|
|
|
save_conf=False, # 是否在保存的标签中包含置信度信息,这里设置为不保存。
|
|
|
|
|
save_crop=False, # 是否保存裁剪后的预测框,这里设置为不保存
|
|
|
|
|
nosave=False, # 是否不保存图片或视频,这里设置为不保存
|
|
|
|
|
classes=None, # 根据类别进行过滤,可以指定一个或多个类别,例如--class 0,1,2表示只保留类别为0、1和2的检测结果。
|
|
|
|
|
agnostic_nms=False, # 是否使用无类别特定的非极大值抑制(NMS)
|
|
|
|
|
augment=False, # 是否使用数据增强进行推理
|
|
|
|
|
visualize=False, # 是否可视化特征图
|
|
|
|
|
update=False, # 是否更新所有模型
|
|
|
|
|
project='runs/detect', # 保存结果的目录
|
|
|
|
|
name='exp', # : 保存结果的文件名前缀
|
|
|
|
|
exist_ok=False, # 如果存在同名的项目或文件,是否覆盖
|
|
|
|
|
line_thickness=2, # 边界框的线条粗细(以像素为单位),这里设置为2
|
|
|
|
|
hide_labels=False, # 是否隐藏标签
|
|
|
|
|
hide_conf=False, # 是否隐藏置信度
|
|
|
|
|
half=False, # 是否使用半精度浮点数进行推理
|
|
|
|
|
dnn=False, # 是否使用OpenCV的DNN模块进行ONNX推理
|
|
|
|
|
vid_stride=1, # 视频帧率步长,这里设置为1,表示每帧都进行处理
|
|
|
|
|
):
|
|
|
|
|
try:
|
|
|
|
|
current_frame = 0 # 初始化计数器,用于计算视频的进度
|
|
|
|
|
# 重置进度条
|
|
|
|
|
self.send_percent.emit(0)
|
|
|
|
|
# 选择要使用的设备(如GPU或CPU)。
|
|
|
|
|
device = select_device(device)
|
|
|
|
|
# 如果设备不是CPU,则将变量half设置为True,表示使用半精度浮点数进行计算。
|
|
|
|
|
half &= device.type != 'cpu'
|
|
|
|
|
# 加载模型权重文件到指定设备上。
|
|
|
|
|
model = DetectMultiBackend(self.weights, device=device, dnn=dnn, data=data, fp16=half)
|
|
|
|
|
# 从模型中分别获取:步长、名称列表、权重
|
|
|
|
|
stride, names, pt = model.stride, model.names, model.pt
|
|
|
|
|
names_dic = {} # 初始化类目容器
|
|
|
|
|
# 提取valuename,赋值初始个数0,最终格式如{person:0}
|
|
|
|
|
for key, value in names.items():
|
|
|
|
|
names_dic[value] = 0
|
|
|
|
|
num_params = 0
|
|
|
|
|
# 遍历模型的所有参数,计算数量总和
|
|
|
|
|
for param in model.parameters():
|
|
|
|
|
num_params += param.numel()
|
|
|
|
|
# 根据步长调整图像大小。
|
|
|
|
|
imgsz = check_img_size(imgsz, s=stride)
|
|
|
|
|
|
|
|
|
|
# 如果支持GPU运行,将模型转换为半精度浮点数
|
|
|
|
|
if half:
|
|
|
|
|
model.half()
|
|
|
|
|
|
|
|
|
|
# 上面加载设备和模型参数,现在开始加载要检测的资源
|
|
|
|
|
source = str(self.source)
|
|
|
|
|
|
|
|
|
|
save_img = not nosave and not source.endswith('.txt') # 这个变量用于决定是否保存推理图像。
|
|
|
|
|
is_file = Path(source).suffix[1:] in (IMG_FORMATS + VID_FORMATS) # 用于判断source是否是一个文件
|
|
|
|
|
webcam = source.isnumeric() or source.endswith('.streams') # 这个变量用于判断是否是网络摄像头
|
|
|
|
|
bs = 1 # 批处理大小
|
|
|
|
|
# 如何图片路径和资源存在,调整合适尺寸
|
|
|
|
|
if is_file:
|
|
|
|
|
source = check_file(source) # 下载文件并返回文件路径
|
|
|
|
|
if webcam: # 如果数据源是网络摄像头
|
|
|
|
|
view_img = check_imshow(warn=True) # 调用check_imshow()函数检查当前环境是否支持显示图像,并将结果赋值给view_img
|
|
|
|
|
cudnn.benchmark = True # 设置cudnn的基准模式为True。
|
|
|
|
|
datasetStreams = LoadStreams(source, img_size=imgsz, stride=stride, auto=pt,
|
|
|
|
|
vid_stride=vid_stride) # 加载视频资源,设置大小和步长
|
|
|
|
|
bs = len(datasetStreams) # 获取数据集的长度,即视频帧数。
|
|
|
|
|
else:
|
|
|
|
|
# 来自于图片和视频
|
|
|
|
|
datasetStreams = LoadImages(source, img_size=imgsz, stride=stride, auto=pt, vid_stride=vid_stride)
|
|
|
|
|
vid_path, vid_writer = [None] * bs, [None] * bs # 初始化两个列表,分别用于存储视频路径和视频写入器
|
|
|
|
|
|
|
|
|
|
if self.is_save == Qt.Checked:
|
|
|
|
|
# 创建用于保存结果的目录
|
|
|
|
|
save_dir = increment_path(Path(project) / name, exist_ok=exist_ok)
|
|
|
|
|
# 创建保存的文件夹
|
|
|
|
|
(save_dir / 'labels' if save_txt else save_dir).mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
|
|
|
|
# 预热模型,即让模型在开始实际处理之前先进行一次或多次的预加载和初始化操作
|
|
|
|
|
model.warmup(imgsz=(1 if pt or model.triton else bs, 3, *imgsz))
|
|
|
|
|
seen, windows, dt = 0, [], (Profile(), Profile(),
|
|
|
|
|
Profile()) # seen用于记录已经处理过的图像数量;windows是一个列表,可能用于存储一些窗口信息;dt是一个包含三个Profile()对象的元组,可能用于性能分析
|
|
|
|
|
|
|
|
|
|
dataset = iter(datasetStreams) # 创建迭代器,遍历资源
|
|
|
|
|
|
|
|
|
|
while True:
|
|
|
|
|
if self.end_loop:
|
|
|
|
|
# 如果为真,跳出循环
|
|
|
|
|
# 如果是摄像头,设置状态,停止线程
|
|
|
|
|
if webcam:
|
|
|
|
|
datasetStreams.stop_threads = True
|
|
|
|
|
break
|
|
|
|
|
#如果self.play为真,继续循环遍历检测。否则暂停
|
|
|
|
|
if self.play:
|
|
|
|
|
# 如果是检测单张图片,不会自动跳出循环,继续检测会报错,所以try捕获空异常跳出循环
|
|
|
|
|
try:
|
|
|
|
|
path, im, im0s, vid_cap, s = next(dataset)
|
|
|
|
|
# 更新当前检测数量索引
|
|
|
|
|
current_frame += 1
|
|
|
|
|
if vid_cap is not None:
|
|
|
|
|
# 计算视频检测进度并发送当前检测的进度百分比
|
|
|
|
|
progress_percentage = int(
|
|
|
|
|
current_frame / int(vid_cap.get(cv2.CAP_PROP_FRAME_COUNT)) * 100)
|
|
|
|
|
self.send_percent.emit(progress_percentage)
|
|
|
|
|
else:
|
|
|
|
|
# 计算图片检测进度并发送当前检测的进度百分比
|
|
|
|
|
progress_percentage = int(
|
|
|
|
|
current_frame / int(len(dataset)) * 100)
|
|
|
|
|
self.send_percent.emit(progress_percentage)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
# 如果是图片检测结束了,跳出循环
|
|
|
|
|
break
|
|
|
|
|
with dt[0]:
|
|
|
|
|
|
|
|
|
|
im = torch.from_numpy(im).to(model.device)
|
|
|
|
|
im = im.half() if model.fp16 else im.float()
|
|
|
|
|
im /= 255 # 归一化
|
|
|
|
|
if len(im.shape) == 3:
|
|
|
|
|
im = im[None]
|
|
|
|
|
with dt[1]: # 用来测量模型预测的时间
|
|
|
|
|
visualize = increment_path(save_dir / Path(path).stem, mkdir=True) if visualize else False
|
|
|
|
|
pred = model(im, augment=augment, visualize=visualize)
|
|
|
|
|
|
|
|
|
|
with dt[2]:
|
|
|
|
|
pred = non_max_suppression(pred, self.conf, self.iou, classes, agnostic_nms, max_det=max_det)
|
|
|
|
|
|
|
|
|
|
for i, det in enumerate(pred):
|
|
|
|
|
count_label = "" # 定义空的变量,方便后面统计检测的数量
|
|
|
|
|
seen += 1
|
|
|
|
|
if webcam:
|
|
|
|
|
p, im0, frame = path[i], im0s[i].copy(), dataset.count
|
|
|
|
|
s += f'{i}: '
|
|
|
|
|
else:
|
|
|
|
|
p, im0, frame = path, im0s.copy(), getattr(dataset, 'frame', 0)
|
|
|
|
|
# 发送原图
|
|
|
|
|
self.send_raw.emit(im0)
|
|
|
|
|
p = Path(p) # to Path
|
|
|
|
|
if self.is_save == Qt.Checked:
|
|
|
|
|
save_path = str(save_dir / p.name) # im.jpg
|
|
|
|
|
txt_path = str(save_dir / 'labels' / p.stem) + (
|
|
|
|
|
'' if dataset.mode == 'image' else f'_{frame}') # im.txt
|
|
|
|
|
s += '%gx%g ' % im.shape[2:] # print string
|
|
|
|
|
gn = torch.tensor(im0.shape)[[1, 0, 1, 0]] # normalization gain whwh
|
|
|
|
|
imc = im0.copy() if save_crop else im0 # for save_crop
|
|
|
|
|
annotator = Annotator(im0, line_width=line_thickness, example=str(names))
|
|
|
|
|
# 当检测到物体个数大于1时执行
|
|
|
|
|
if len(det):
|
|
|
|
|
# 发送检测个数
|
|
|
|
|
self.send_detect_num.emit(len(det)) # 发送统计检测个数
|
|
|
|
|
|
|
|
|
|
# 在处理每一帧开始时,重置names_dic
|
|
|
|
|
for key, value in names.items():
|
|
|
|
|
names_dic[value] = 0
|
|
|
|
|
|
|
|
|
|
# Rescale boxes from img_size to im0 size
|
|
|
|
|
det[:, :4] = scale_boxes(im.shape[2:], det[:, :4], im0.shape).round()
|
|
|
|
|
|
|
|
|
|
# Print results
|
|
|
|
|
for c in det[:, 5].unique():
|
|
|
|
|
n = (det[:, 5] == c).sum() # detections per class
|
|
|
|
|
s += f"{n} {names[int(c)]}{'s' * (n > 1)}, " # add to string
|
|
|
|
|
count_label += f"{names[int(c)]}:{n} \n"
|
|
|
|
|
|
|
|
|
|
# 检测到物体了,画框.如果没检测到物体不会执行循环
|
|
|
|
|
for *xyxy, conf, cls in reversed(det):
|
|
|
|
|
if save_txt: # Write to file
|
|
|
|
|
xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(
|
|
|
|
|
-1).tolist() # normalized xywh
|
|
|
|
|
line = (cls, *xywh, conf) if save_conf else (cls, *xywh) # label format
|
|
|
|
|
with open(f'{txt_path}.txt', 'a') as f:
|
|
|
|
|
f.write(('%g ' * len(line)).rstrip() % line + '\n')
|
|
|
|
|
|
|
|
|
|
if save_img or save_crop or view_img: # Add bbox to image
|
|
|
|
|
|
|
|
|
|
c = int(cls) # integer class
|
|
|
|
|
label = None if hide_labels else (
|
|
|
|
|
names[c] if hide_conf else f'{names[c]}|{conf:.2f}')
|
|
|
|
|
annotator.box_label(xyxy, label, color=(201, 97, 72))
|
|
|
|
|
names_dic[names[c]] += 1 # 类别统计,数量+1
|
|
|
|
|
if save_crop:
|
|
|
|
|
save_one_box(xyxy, imc, file=save_dir / 'crops' / names[c] / f'{p.stem}.jpg',
|
|
|
|
|
BGR=True)
|
|
|
|
|
|
|
|
|
|
# 处理后的图像
|
|
|
|
|
im0 = annotator.result()
|
|
|
|
|
# 调用send_img方法,发送数据给它,send_img方法绑定这下面的show_image方法,把数据显示在pyqt5上面
|
|
|
|
|
self.send_img.emit(im0)
|
|
|
|
|
# 发送原图,isinstance()方法来检查一个变量的类型是否为真
|
|
|
|
|
self.send_raw.emit(im0s if isinstance(im0s, np.ndarray) else im0s[0])
|
|
|
|
|
|
|
|
|
|
# if save_img:
|
|
|
|
|
if self.is_save == Qt.Checked:
|
|
|
|
|
# 检测图片
|
|
|
|
|
if dataset.mode == 'image':
|
|
|
|
|
cv2.imwrite(save_path, im0)
|
|
|
|
|
# 检测视频流
|
|
|
|
|
else:
|
|
|
|
|
if vid_path[i] != save_path: # new video
|
|
|
|
|
vid_path[i] = save_path
|
|
|
|
|
if isinstance(vid_writer[i], cv2.VideoWriter):
|
|
|
|
|
vid_writer[i].release() # release previous video writer
|
|
|
|
|
if vid_cap: # video
|
|
|
|
|
fps = vid_cap.get(cv2.CAP_PROP_FPS)
|
|
|
|
|
w = int(vid_cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
|
|
|
|
h = int(vid_cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
|
|
|
|
else: # stream
|
|
|
|
|
fps, w, h = 30, im0.shape[1], im0.shape[0]
|
|
|
|
|
save_path = str(
|
|
|
|
|
Path(save_path).with_suffix('.mp4')) # force *.mp4 suffix on results videos
|
|
|
|
|
vid_writer[i] = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*'mp4v'), fps,
|
|
|
|
|
(w, h))
|
|
|
|
|
vid_writer[i].write(im0)
|
|
|
|
|
# 实时发送检测速度,小数点采取四舍五入,保留一位小数
|
|
|
|
|
self.send_speed.emit(f"{dt[1].dt * 1E3:.1f}")
|
|
|
|
|
info = (f"当前的权重路径为:{str(self.weights)}\n"
|
|
|
|
|
f"正在使用{str(device)}检测资源\n"
|
|
|
|
|
f"当前的置信度为:{round(self.conf,2)}\n"
|
|
|
|
|
f"当前的IOU为:{ round(self.iou, 2)}\n"
|
|
|
|
|
f"您选择{'保存' if self.is_save == Qt.Checked else '不保存'}文件夹,{'保存的位置为:' + str(save_dir) if self.is_save == Qt.Checked else ''}\n"
|
|
|
|
|
f"检测到的物体和数量如下:\n{count_label}")
|
|
|
|
|
|
|
|
|
|
# 发送检测信息,展示在图形界面右下角
|
|
|
|
|
self.send_detect_info.emit(info)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# t = tuple(x.t / seen * 1E3 for x in dt)
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"出错了,出错的问题是:: {e}")
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
#用户管理列表
|
|
|
|
|
class UserListWindow(QMainWindow, userlist_Ui_MainWindow):
|
|
|
|
|
def __init__(self):
|
|
|
|
|
super(UserListWindow, self).__init__()
|
|
|
|
|
self.setupUi(self)
|
|
|
|
|
|
|
|
|
|
# self.user_list_ui.setupUi(self.user_list_window)
|
|
|
|
|
self.user_manager = UserManager()
|
|
|
|
|
self.update_table_data()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def update_table_data(self):
|
|
|
|
|
|
|
|
|
|
users = self.user_manager.get_users()
|
|
|
|
|
self.tableWidget.setRowCount(len(users))
|
|
|
|
|
|
|
|
|
|
# 设置列宽
|
|
|
|
|
self.tableWidget.setColumnWidth(0, 80) # 设置第一列宽度为100像素
|
|
|
|
|
self.tableWidget.setColumnWidth(1, 300) # 设置第二列宽度为200像素
|
|
|
|
|
self.tableWidget.setColumnWidth(2, 300) # 设置第三列宽度为300像素
|
|
|
|
|
self.tableWidget.setColumnWidth(3, 120) # 设置第三列宽度为300像素
|
|
|
|
|
for row, user in enumerate(users):
|
|
|
|
|
|
|
|
|
|
for col, value in enumerate(user.values()):
|
|
|
|
|
item = QTableWidgetItem(str(value))
|
|
|
|
|
item.setTextAlignment(Qt.AlignCenter) # 设置文本居中对齐
|
|
|
|
|
self.tableWidget.setItem(row, col, item)
|
|
|
|
|
|
|
|
|
|
btn_widget = QWidget()
|
|
|
|
|
|
|
|
|
|
edit_btn = QPushButton('编辑')
|
|
|
|
|
edit_btn.setProperty("row", row)
|
|
|
|
|
edit_btn.setStyleSheet("""
|
|
|
|
|
QPushButton {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
background-color: #4CAF50;
|
|
|
|
|
color: white;
|
|
|
|
|
border: none;
|
|
|
|
|
padding: 5px 10px;
|
|
|
|
|
}
|
|
|
|
|
QPushButton:hover {
|
|
|
|
|
background-color: #45a049;
|
|
|
|
|
}
|
|
|
|
|
""")
|
|
|
|
|
edit_btn.clicked.connect(self.edit_user)
|
|
|
|
|
|
|
|
|
|
delete_btn = QPushButton('删除')
|
|
|
|
|
delete_btn.setProperty("row", row)
|
|
|
|
|
delete_btn.setStyleSheet("""
|
|
|
|
|
QPushButton {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
background-color: #f44336;
|
|
|
|
|
color: white;
|
|
|
|
|
border: none;
|
|
|
|
|
padding: 5px 10px;
|
|
|
|
|
}
|
|
|
|
|
QPushButton:hover {
|
|
|
|
|
background-color: #da190b;
|
|
|
|
|
}
|
|
|
|
|
""")
|
|
|
|
|
delete_btn.clicked.connect(self.delete_user)
|
|
|
|
|
|
|
|
|
|
layout = QHBoxLayout()
|
|
|
|
|
layout.addWidget(edit_btn)
|
|
|
|
|
layout.addWidget(delete_btn)
|
|
|
|
|
|
|
|
|
|
layout.setAlignment(Qt.AlignCenter)
|
|
|
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
|
|
|
|
|
|
btn_widget.setLayout(layout)
|
|
|
|
|
self.tableWidget.setCellWidget(row, 3, btn_widget)
|
|
|
|
|
self.show()
|
|
|
|
|
#管理员编辑用户
|
|
|
|
|
def edit_user(self):
|
|
|
|
|
btn = self.sender()
|
|
|
|
|
row = btn.property("row")
|
|
|
|
|
|
|
|
|
|
if row is not None:
|
|
|
|
|
user_id_item = self.tableWidget.item(row, 0)
|
|
|
|
|
username_item = self.tableWidget.item(row, 1)
|
|
|
|
|
password_item = self.tableWidget.item(row, 2)
|
|
|
|
|
|
|
|
|
|
if user_id_item and username_item and password_item:
|
|
|
|
|
user_id = int(user_id_item.text())
|
|
|
|
|
current_username = username_item.text()
|
|
|
|
|
current_password = password_item.text()
|
|
|
|
|
|
|
|
|
|
dialog = EditUserDialog()
|
|
|
|
|
dialog.username_input.setText(current_username)
|
|
|
|
|
dialog.password_input.setText(current_password)
|
|
|
|
|
|
|
|
|
|
if dialog.exec() == QDialog.Accepted:
|
|
|
|
|
new_username, new_password = dialog.get_input_data()
|
|
|
|
|
if self.user_manager.update_user(user_id, new_username, new_password):
|
|
|
|
|
self.update_table_data()
|
|
|
|
|
|
|
|
|
|
# 删除用户
|
|
|
|
|
def delete_user(self):
|
|
|
|
|
btn = self.sender()
|
|
|
|
|
row = btn.property("row")
|
|
|
|
|
user_id_item = self.tableWidget.item(row, 0) # Assuming user_id is in the second column
|
|
|
|
|
|
|
|
|
|
if user_id_item:
|
|
|
|
|
user_id = int(user_id_item.text())
|
|
|
|
|
msg_box = QMessageBox()
|
|
|
|
|
msg_box.setWindowTitle("删除用户")
|
|
|
|
|
msg_box.setText("确定要删除该用户吗?")
|
|
|
|
|
msg_box.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
|
|
|
|
msg_box.setDefaultButton(QMessageBox.No)
|
|
|
|
|
result = msg_box.exec()
|
|
|
|
|
|
|
|
|
|
if result == QMessageBox.Yes:
|
|
|
|
|
if self.user_manager.delete_user(user_id):
|
|
|
|
|
self.update_table_data()
|
|
|
|
|
|
|
|
|
|
class EditUserDialog(QDialog):
|
|
|
|
|
def __init__(self):
|
|
|
|
|
super(EditUserDialog, self).__init__(None) # 将父级窗口设置为None
|
|
|
|
|
self.setWindowTitle("修改用户信息")
|
|
|
|
|
|
|
|
|
|
layout = QVBoxLayout()
|
|
|
|
|
|
|
|
|
|
# 添加用户名输入框
|
|
|
|
|
self.username_label = QLabel("用户名:")
|
|
|
|
|
self.username_input = QLineEdit()
|
|
|
|
|
layout.addWidget(self.username_label)
|
|
|
|
|
layout.addWidget(self.username_input)
|
|
|
|
|
|
|
|
|
|
# 添加密码输入框
|
|
|
|
|
self.password_label = QLabel("密码:")
|
|
|
|
|
self.password_input = QLineEdit()
|
|
|
|
|
|
|
|
|
|
layout.addWidget(self.password_label)
|
|
|
|
|
layout.addWidget(self.password_input)
|
|
|
|
|
|
|
|
|
|
# 添加确定和取消按钮
|
|
|
|
|
self.ok_button = QPushButton("确定")
|
|
|
|
|
self.cancel_button = QPushButton("取消")
|
|
|
|
|
self.ok_button.clicked.connect(self.accept)
|
|
|
|
|
self.cancel_button.clicked.connect(self.reject)
|
|
|
|
|
layout.addWidget(self.ok_button)
|
|
|
|
|
layout.addWidget(self.cancel_button)
|
|
|
|
|
|
|
|
|
|
self.setLayout(layout)
|
|
|
|
|
|
|
|
|
|
def get_input_data(self):
|
|
|
|
|
return self.username_input.text(), self.password_input.text()
|
|
|
|
|
|
|
|
|
|
# 检测界面
|
|
|
|
|
class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
|
|
def __init__(self, user_type=None,parent=None):
|
|
|
|
|
super(MainWindow, self).__init__(parent)
|
|
|
|
|
self.setupUi(self)
|
|
|
|
|
self.methoBinding()
|
|
|
|
|
self.camera_isOpen = False # 摄像头开关状态,默认关
|
|
|
|
|
self.my_thread = MyThread() # 创建线程
|
|
|
|
|
self.my_thread.weights = ''
|
|
|
|
|
self.my_thread.source = '' # 默认检测资源为空
|
|
|
|
|
self.my_thread.is_save = Qt.Unchecked
|
|
|
|
|
|
|
|
|
|
self.my_thread.send_img.connect(lambda x: self.show_image(x, self.label_result))
|
|
|
|
|
self.my_thread.send_raw.connect(lambda x: self.show_image(x, self.label_raw))
|
|
|
|
|
# self.my_thread.send_detectinfo_dic.connect(lambda x: self.show_detect_info(x))
|
|
|
|
|
self.my_thread.send_detect_info.connect(lambda info: self.show_detect_info(info))
|
|
|
|
|
self.my_thread.send_speed.connect(lambda speed: self.show_speed(speed))
|
|
|
|
|
self.my_thread.send_detect_num.connect(lambda num: self.show_detect_num(num))
|
|
|
|
|
self.my_thread.send_percent.connect(lambda percent: self.progressBar.setValue(percent))
|
|
|
|
|
self.user_type = user_type
|
|
|
|
|
self.pushButton_userManager.setVisible(False)
|
|
|
|
|
self.load_config() # 加载配置
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 方法绑定
|
|
|
|
|
def methoBinding(self):
|
|
|
|
|
self.Button_select_images.clicked.connect(self.select_images) # 检测图片
|
|
|
|
|
self.Button_select_video.clicked.connect(self.check_video) # 检测视频
|
|
|
|
|
self.Button_select_camera.clicked.connect(self.open_camera) # 打开摄像头
|
|
|
|
|
self.Button_select_folder.clicked.connect(self.select_folder) # 文件夹检测
|
|
|
|
|
self.doubleSpinBox_conf.valueChanged.connect(lambda x: self.change_conf(x)) # 调整置信度
|
|
|
|
|
self.doubleSpinBox_iou.valueChanged.connect(lambda x: self.change_iou(x)) # 调整iou
|
|
|
|
|
self.horizontalSlider_conf.valueChanged.connect(lambda x: self.change_hor_conf(x))
|
|
|
|
|
self.horizontalSlider_iou.valueChanged.connect(lambda x: self.change_hor_iou(x))
|
|
|
|
|
self.Button_select_weights.clicked.connect(self.select_weights) # 选择权重
|
|
|
|
|
self.pushButton_stop.clicked.connect(self.clean) # 终止检测
|
|
|
|
|
self.pushButton_bofang.clicked.connect(self.play_and_pause) # 播放暂停
|
|
|
|
|
self.checkBox_isSave.clicked.connect(self.is_save) # 点击保存结果的复选框后
|
|
|
|
|
self.pushButton_userManager.clicked.connect(self.userManager)#点击用户管理按钮后
|
|
|
|
|
|
|
|
|
|
#文件夹检测
|
|
|
|
|
def select_folder(self):
|
|
|
|
|
self.clean()
|
|
|
|
|
options = QFileDialog.Options()
|
|
|
|
|
options |= QFileDialog.ReadOnly
|
|
|
|
|
folder_path = QFileDialog.getExistingDirectory(None, "选择文件夹", options=options)
|
|
|
|
|
if folder_path:
|
|
|
|
|
self.my_thread.source = folder_path
|
|
|
|
|
self.start_detect()
|
|
|
|
|
# 更新播放和暂停的图标
|
|
|
|
|
self.update_play_icon()
|
|
|
|
|
|
|
|
|
|
# 选择是否保存结果
|
|
|
|
|
def is_save(self):
|
|
|
|
|
if self.checkBox_isSave.checkState() == Qt.Unchecked:
|
|
|
|
|
# 如果现在等于0,那么也就是之前等于2。点击后应该变为0
|
|
|
|
|
self.my_thread.is_save = Qt.Unchecked
|
|
|
|
|
else:
|
|
|
|
|
self.my_thread.is_save = Qt.Checked
|
|
|
|
|
|
|
|
|
|
# 显示速度
|
|
|
|
|
def show_speed(self, speed):
|
|
|
|
|
self.label_speed.setText(speed)
|
|
|
|
|
|
|
|
|
|
# 关闭进程,重置显示窗口
|
|
|
|
|
def clean(self):
|
|
|
|
|
self.my_thread.end_loop = True # 跳出循环为真
|
|
|
|
|
self.my_thread.source = '' # 清空检测信息
|
|
|
|
|
self.my_thread.wait()
|
|
|
|
|
# 更新播放和暂停的图标
|
|
|
|
|
self.update_play_icon()
|
|
|
|
|
#检测图片
|
|
|
|
|
def select_images(self):
|
|
|
|
|
if self.my_thread.isRunning():
|
|
|
|
|
self.clean()
|
|
|
|
|
options = QFileDialog.Options()
|
|
|
|
|
options |= QFileDialog.ReadOnly
|
|
|
|
|
image_path, _ = QFileDialog.getOpenFileName(None, "选择图片", "", "图像文件 (*.png *.xpm *.jpg *.bmp)",
|
|
|
|
|
options=options)
|
|
|
|
|
if image_path:
|
|
|
|
|
self.my_thread.source = image_path
|
|
|
|
|
self.start_detect()
|
|
|
|
|
# 更新播放和暂停的图标
|
|
|
|
|
self.update_play_icon()
|
|
|
|
|
|
|
|
|
|
# 展示检测的信息到右下角
|
|
|
|
|
def show_detect_info(self, info):
|
|
|
|
|
self.result_info.clear()
|
|
|
|
|
self.result_info.append(info)
|
|
|
|
|
|
|
|
|
|
# 显示检测个数
|
|
|
|
|
def show_detect_num(self, num):
|
|
|
|
|
self.label_detect_num.setText(str(num))
|
|
|
|
|
|
|
|
|
|
# 显示检测结果
|
|
|
|
|
@staticmethod
|
|
|
|
|
def show_image(image_path, label):
|
|
|
|
|
try:
|
|
|
|
|
ih, iw, _ = image_path.shape
|
|
|
|
|
w = label.geometry().width()
|
|
|
|
|
h = label.geometry().height()
|
|
|
|
|
if iw / w > ih / h:
|
|
|
|
|
scal = w / iw
|
|
|
|
|
nw = w
|
|
|
|
|
nh = int(scal * ih)
|
|
|
|
|
img_src_ = cv2.resize(image_path, (nw, nh))
|
|
|
|
|
else:
|
|
|
|
|
scal = h / ih
|
|
|
|
|
nw = int(scal * iw)
|
|
|
|
|
nh = h
|
|
|
|
|
img_src_ = cv2.resize(image_path, (nw, nh))
|
|
|
|
|
|
|
|
|
|
frame = cv2.cvtColor(img_src_, cv2.COLOR_BGR2RGB)
|
|
|
|
|
img = QImage(frame.data, frame.shape[1], frame.shape[0], frame.shape[2] * frame.shape[1],
|
|
|
|
|
QImage.Format.Format_RGB888)
|
|
|
|
|
label.setPixmap(QPixmap.fromImage(img))
|
|
|
|
|
label.setAlignment(Qt.AlignCenter)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(repr(e))
|
|
|
|
|
|
|
|
|
|
#用户管理
|
|
|
|
|
def userManager(self):
|
|
|
|
|
self.user_list_window = UserListWindow()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 选择权重
|
|
|
|
|
def select_weights(self):
|
|
|
|
|
options = QFileDialog.Options()
|
|
|
|
|
options |= QFileDialog.ReadOnly
|
|
|
|
|
# 第一个参数是父窗口,这里我们传入了None表示没有父窗口。第二个参数是对话框的标题,第三个参数是默认打开的文件夹路径,第四个参数是文件过滤器
|
|
|
|
|
weights_path, _ = QFileDialog.getOpenFileName(None, "选择权重", "", "pt (*.pt)", options=options)
|
|
|
|
|
if weights_path:
|
|
|
|
|
self.my_thread.weights = weights_path
|
|
|
|
|
self.clean()
|
|
|
|
|
#检测视频
|
|
|
|
|
def check_video(self):
|
|
|
|
|
options = QFileDialog.Options()
|
|
|
|
|
options |= QFileDialog.ReadOnly
|
|
|
|
|
video_path, _ = QFileDialog.getOpenFileName(None, '选择视频', '',
|
|
|
|
|
'Videos (*.mp4 *.avi *.mkv);;All Files (*)', options=options)
|
|
|
|
|
if video_path:
|
|
|
|
|
self.clean()
|
|
|
|
|
self.my_thread.source = video_path
|
|
|
|
|
self.start_detect()
|
|
|
|
|
# 更新播放和暂停的图标
|
|
|
|
|
self.update_play_icon()
|
|
|
|
|
|
|
|
|
|
# 获取电脑摄像头的数量
|
|
|
|
|
def get_camera_num(self):
|
|
|
|
|
num = 0 # 计数器,用于记录找到的摄像头数量
|
|
|
|
|
deviceList = [] # 列表,用于存储找到的摄像头设备索引
|
|
|
|
|
for i in range(3): # 遍历预设的摄像头数量范围,因为电脑一般不超过3个摄像头
|
|
|
|
|
stream = cv2.VideoCapture(i, cv2.CAP_DSHOW) # 使用OpenCV库创建一个视频捕获对象,指定设备索引和捕获模式
|
|
|
|
|
exists = stream.grab() # 尝试从摄像头捕获一帧图像是否存在
|
|
|
|
|
stream.release() # 释放视频捕获对象
|
|
|
|
|
if not exists: # 如果未能成功捕获图像,则跳过当前设备继续循环
|
|
|
|
|
continue
|
|
|
|
|
else:
|
|
|
|
|
num += 1 # 如果成功捕获图像,数量+1
|
|
|
|
|
deviceList.append(i) # 把对应的索引加入设备列表中
|
|
|
|
|
return num, deviceList
|
|
|
|
|
|
|
|
|
|
# 开启/关闭摄像头检测
|
|
|
|
|
def open_camera(self):
|
|
|
|
|
self.clean()
|
|
|
|
|
self.dialog = QDialog(self.centralwidget) # 创建摄像头弹窗
|
|
|
|
|
self.dialog.setWindowTitle("请选择你的摄像头") # 设置弹窗标题
|
|
|
|
|
self.dialog.resize(400, 200)
|
|
|
|
|
|
|
|
|
|
# 判断摄像头开启状态,如何开着设置为关,反之则开
|
|
|
|
|
if self.camera_isOpen == True:
|
|
|
|
|
# 开着的,现在关闭
|
|
|
|
|
self.camera_isOpen = False
|
|
|
|
|
else:
|
|
|
|
|
num, deviceList = self.get_camera_num()
|
|
|
|
|
|
|
|
|
|
# 如果没有摄像头,弹出提示框
|
|
|
|
|
if num == 0:
|
|
|
|
|
QMessageBox.warning(self, "出错啦", "<p>未检测到有效的摄像头</p>")
|
|
|
|
|
# 如果有摄像头,弹出按钮,点击按钮后赋值对应的摄像头并打开开始检测
|
|
|
|
|
else:
|
|
|
|
|
self.camera_isOpen = True
|
|
|
|
|
# 设置水平布局
|
|
|
|
|
h_layout = QHBoxLayout(self.dialog)
|
|
|
|
|
for xuhao in deviceList:
|
|
|
|
|
button = QPushButton(f'摄像头{xuhao}', self.dialog)
|
|
|
|
|
button.clicked.connect(lambda: self.click_camera(xuhao))
|
|
|
|
|
h_layout.addWidget(button) # 为按钮添加布局
|
|
|
|
|
# 显示对话框
|
|
|
|
|
self.dialog.exec()
|
|
|
|
|
|
|
|
|
|
# 控制暂停和播放
|
|
|
|
|
def play_and_pause(self):
|
|
|
|
|
# 如果检测的资源为空,提示先选择资源
|
|
|
|
|
if self.my_thread.source == "":
|
|
|
|
|
QMessageBox.information(self, '错误提示', '你还没选择检测的资源呢!')
|
|
|
|
|
return
|
|
|
|
|
if self.my_thread.play:
|
|
|
|
|
# 原来是播放的,现在暂停了
|
|
|
|
|
self.my_thread.play = False
|
|
|
|
|
else:
|
|
|
|
|
# 原来是暂停的,现在播放了
|
|
|
|
|
self.my_thread.play = True
|
|
|
|
|
# 更新播放和暂停的图标
|
|
|
|
|
self.update_play_icon()
|
|
|
|
|
|
|
|
|
|
# 更新播放和暂停的图标
|
|
|
|
|
def update_play_icon(self):
|
|
|
|
|
icon = QtGui.QIcon()
|
|
|
|
|
if self.my_thread.play:
|
|
|
|
|
# 如果现在是播放着的,那么显示的是暂停的图标
|
|
|
|
|
icon.addPixmap(QtGui.QPixmap("icon/pause-circle.svg"),
|
|
|
|
|
QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
|
|
|
|
else:
|
|
|
|
|
# 如果现在是播放着的,那么显示的是暂停的图标
|
|
|
|
|
icon.addPixmap(QtGui.QPixmap("icon/play-circle.svg"),
|
|
|
|
|
QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
|
|
|
|
self.pushButton_bofang.setIcon(icon)
|
|
|
|
|
self.pushButton_bofang.setIconSize(QtCore.QSize(32, 32))
|
|
|
|
|
|
|
|
|
|
# 弹窗中选择摄像头
|
|
|
|
|
def click_camera(self, device):
|
|
|
|
|
self.my_thread.source = device
|
|
|
|
|
self.start_detect()
|
|
|
|
|
# 关闭对话框
|
|
|
|
|
self.dialog.close()
|
|
|
|
|
|
|
|
|
|
# 修改置信度
|
|
|
|
|
def change_conf(self, x):
|
|
|
|
|
self.my_thread.conf = round(x, 2)
|
|
|
|
|
# 修改滑杆的值
|
|
|
|
|
self.horizontalSlider_conf.setValue(x * 100)
|
|
|
|
|
|
|
|
|
|
# 修改滑杆置信度的值
|
|
|
|
|
def change_hor_conf(self, x):
|
|
|
|
|
# 修改置信度的值和按钮的值
|
|
|
|
|
conf = x * 0.01
|
|
|
|
|
self.my_thread.conf = conf
|
|
|
|
|
self.doubleSpinBox_conf.setValue(conf)
|
|
|
|
|
|
|
|
|
|
# 修改IOU
|
|
|
|
|
def change_iou(self, x):
|
|
|
|
|
self.my_thread.iou = round(x, 2)
|
|
|
|
|
# 修改滑杆的值
|
|
|
|
|
self.horizontalSlider_iou.setValue(x * 100)
|
|
|
|
|
|
|
|
|
|
# 修改滑杆iou的值
|
|
|
|
|
def change_hor_iou(self, x):
|
|
|
|
|
# 修改置信度的值和按钮的值
|
|
|
|
|
iou = x * 0.01
|
|
|
|
|
self.my_thread.iou = iou
|
|
|
|
|
self.doubleSpinBox_iou.setValue(iou)
|
|
|
|
|
|
|
|
|
|
# 从json中读取配置
|
|
|
|
|
def load_config(self):
|
|
|
|
|
|
|
|
|
|
# 读取 JSON 文件
|
|
|
|
|
with open("config.json", "r") as jsonfile:
|
|
|
|
|
loaded_config = json.load(jsonfile)
|
|
|
|
|
# 获取参数值
|
|
|
|
|
self.my_thread.weights = loaded_config["weights"]
|
|
|
|
|
self.my_thread.conf = loaded_config["conf"]
|
|
|
|
|
self.my_thread.iou = loaded_config["iou"]
|
|
|
|
|
|
|
|
|
|
self.my_thread.is_save = loaded_config["is_save"]
|
|
|
|
|
# 把读取的值,传递展现到pyside组件上
|
|
|
|
|
self.doubleSpinBox_conf.setProperty("value", self.my_thread.conf)
|
|
|
|
|
self.doubleSpinBox_iou.setProperty("value", self.my_thread.iou)
|
|
|
|
|
|
|
|
|
|
# 判断复选框的勾选状态
|
|
|
|
|
if self.my_thread.is_save == "checked":
|
|
|
|
|
self.checkBox_isSave.setCheckState(Qt.Checked)
|
|
|
|
|
self.my_thread.is_save = Qt.Checked
|
|
|
|
|
|
|
|
|
|
#通过用户的状态,判断是否显示【管理用户图标】。1是管理员,0是普通用户
|
|
|
|
|
if self.user_type==1:
|
|
|
|
|
self.pushButton_userManager.setVisible(True)
|
|
|
|
|
|
|
|
|
|
# 关闭窗口事件
|
|
|
|
|
def closeEvent(self, event):
|
|
|
|
|
confirm = QMessageBox.question(self, '关闭程序', '确定关闭?',
|
|
|
|
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
|
|
|
|
QMessageBox.StandardButton.No)
|
|
|
|
|
|
|
|
|
|
if confirm == QMessageBox.StandardButton.Yes:
|
|
|
|
|
# 确认关闭,保存用户数据
|
|
|
|
|
config = dict() # 创建一个空的字典
|
|
|
|
|
config["conf"] = self.my_thread.conf
|
|
|
|
|
config["iou"] = self.my_thread.iou
|
|
|
|
|
config["weights"] = self.my_thread.weights
|
|
|
|
|
|
|
|
|
|
if self.my_thread.is_save == Qt.Checked:
|
|
|
|
|
config["is_save"] = "checked"
|
|
|
|
|
else:
|
|
|
|
|
config["is_save"] = "Unchecked"
|
|
|
|
|
|
|
|
|
|
# 将修改后的配置参数写入 JSON 文件
|
|
|
|
|
with open("config.json", "w") as jsonfile:
|
|
|
|
|
json.dump(config, jsonfile, indent=4)
|
|
|
|
|
event.accept()
|
|
|
|
|
else:
|
|
|
|
|
event.ignore()
|
|
|
|
|
|
|
|
|
|
# 开始检测,play为真,跳出循环为假,star线程,更新播放图标
|
|
|
|
|
def start_detect(self):
|
|
|
|
|
self.my_thread.end_loop = False # 跳出循环为真
|
|
|
|
|
self.my_thread.start()
|
|
|
|
|
#
|
|
|
|
|
#
|
|
|
|
|
# if __name__ == "__main__":
|
|
|
|
|
# app = QApplication(sys.argv)
|
|
|
|
|
# myWin = MainWindow()
|
|
|
|
|
# myWin.show()
|
|
|
|
|
# sys.exit(app.exec())
|