在前面两篇中,篇一介绍了启动界面的制作,篇二介绍了如何修改PyDracula的界面,那么这一篇我们学习一下yolo要融合进入软件中,需要了解的两个类。
1YoloPredictor类——检测器
class YoloPredictor(BasePredictor, QObject):
# 定义信号,传递不同类型的数据给GUI或其他部分
yolo2main_pre_img = Signal(np.ndarray) # 原始图像信号(图像数据类型为NumPy数组)
yolo2main_res_img = Signal(np.ndarray) # 测试结果图像信号(图像数据类型为NumPy数组)
yolo2main_status_msg = Signal(str) # 状态消息信号(如:检测中、暂停、停止、错误等)
yolo2main_fps = Signal(str) # FPS(帧率)信号(类型为字符串,显示每秒处理的帧数)
yolo2main_labels = Signal(dict) # 检测到的目标类别及其数量(字典类型)
yolo2main_progress = Signal(int) # 检测进度信号(表示任务完成的进度百分比)
yolo2main_class_num = Signal(int) # 检测到的类别数量信号(整数类型)
yolo2main_target_num = Signal(int) # 检测到的目标数量信号(整数类型)
def __init__(self, cfg=DEFAULT_CFG, overrides=None):
super(YoloPredictor, self).__init__() # 调用父类BasePredictor的构造函数进行初始化
QObject.__init__(self) # 初始化QObject,支持信号和槽机制
self.args = get_cfg(cfg, overrides) # 获取配置信息,最终会从这里获取DEFAULT_CFG_PATH = ROOT / 'yolo/cfg/default.yaml'
''''下面这个配置信息是为了填补yaml文件的空白'''
# 设置项目路径:如果args.project为空,则使用默认路径并加上任务名称
project = self.args.project or Path(SETTINGS['runs_dir']) / self.args.task
# 设置模型名称:根据模式(mode)命名
name = f'{
self.args.mode}'
# 设置保存目录:调用increment_path以避免覆盖现有文件夹
self.save_dir = increment_path(Path(project) / name, exist_ok=self.args.exist_ok)
self.done_warmup = False # 标记是否完成预热阶段(如:模型加载、预处理等)
if self.args.show:
self.args.show = check_imshow(warn=True) # 如果需要显示图像,则检查imshow是否可用
# GUI相关参数初始化
self.used_model_name = None # 当前使用的模型名称
self.new_model_name = None # 实时更新的模型名称
self.source = '' # 输入源路径(如视频、图像路径)
self.stop_dtc = False # 停止检测标志
self.continue_dtc = True # 是否继续检测(用于暂停检测)
self.save_res = False # 是否保存测试结果
self.save_txt = False # 是否保存标签文件(txt格式)
self.iou_thres = 0.45 # IoU阈值(用于判断是否为目标)
self.conf_thres = 0.25 # 置信度阈值(检测到目标的置信度要求)
self.speed_thres = 10 # 延迟阈值,单位毫秒(检测的最大延迟时间)
self.labels_dict = {
} # 存储检测结果的字典(类别名和数量)
self.progress_value = 0 # 进度条的当前进度(0-100)
# 以下为后续任务需要的变量初始化
self.model = None # 存储YOLO检测模型
self.data = self.args.data # 存储数据字典(包含数据集路径等)
self.imgsz = None # 输入图像的大小(例如:640x640)
self.device = None # 设备(GPU或CPU)
self.dataset = None # 数据集对象
self.vid_path, self.vid_writer = None, None # 存储视频路径和视频写入器(如果需要处理视频)
self.annotator = None # 注释器对象(用于图像标注)
self.data_path = None # 数据路径(用于加载数据)
self.source_type = None # 输入源类型(视频、图像等)
self.batch = None # 批次大小(用于批处理预测)
# 添加回调函数(用于处理检测过程中的各种操作)
self.callbacks = defaultdict(list, callbacks.default_callbacks) # 默认为空列表的回调字典
callbacks.add_integration_callbacks(self) # 将回调集成到当前对象中
# 主函数用于目标检测
@smart_inference_mode() # 装饰器,可能用于切换推理模式(例如加速或禁用某些操作)
def run(self):
try:
# 如果开启详细模式,则输出空行日志
if self.args.verbose:
LOGGER.info('')
# 设置模型
self.yolo2main_status_msg.emit('Loding Model...') # 向主界面发射模型加载的状态信息
if not self.model: # 如果没有已加载模型
self.setup_model(self.new_model_name) # 初始化并加载新模型
self.used_model_name = self.new_model_name # 设置当前使用的模型名称
# 设置输入源
self.setup_source(self.source if self.source is not None else self.args.source)
# 检查保存路径/标签文件夹
if self.save_res or self.save_txt: # 如果需要保存结果或标签
(self.save_dir / 'labels' if self.save_txt else self.save_dir).mkdir(parents=True, exist_ok=True)
# 热身模型
if not self.done_warmup: # 如果模型未热身
self.model.warmup(imgsz=(1 if self.model.pt or self.model.triton else self.dataset.bs, 3, *self.imgsz))
self.done_warmup = True # 设置热身完成标志
# 初始化状态变量
self.seen, self.windows, self.dt, self.batch = 0, [], (ops.Profile(), ops.Profile(), ops.Profile()), None
# 开始目标检测
count = 0 # 当前帧数
start_time = time.time() # 用于计算帧率的开始时间
batch = iter(self.dataset) # 将数据集转化为迭代器
while True:
# 检查是否终止检测
if self.stop_dtc:
if isinstance(self.vid_writer[-1], cv2.VideoWriter):
self.vid_writer[-1].release() # 释放视频写入器
self.yolo2main_status_msg.emit('Detection terminated!') # 向主界面发射终止信息
break
# 检查是否需要切换模型
if self.used_model_name != self.new_model_name:
self.setup_model(self.new_model_name) # 加载新的模型
self.used_model_name = self.new_model_name # 更新当前使用的模型名称
# 检查是否暂停检测
if self.continue_dtc:
self.yolo2main_status_msg.emit('Detecting...') # 向主界面发射“正在检测”信息
batch = next(self.dataset) # 获取下一批数据
self.batch = batch # 保存当前批次
path, im, im0s, vid_cap, s = batch # 从批次中提取路径、图像、原始图像、视频捕获和其他信息
visualize = increment_path(self.save_dir / Path(path).stem,
mkdir=True) if self.args.visualize else False # 是否可视化
# 计算进度和帧率(待优化)
count += 1 # 帧计数加1
if vid_cap:
all_count = vid_cap.get(cv2.CAP_PROP_FRAME_COUNT) # 获取视频总帧数
else:
all_count = 1 # 如果不是视频,则设置总帧数为1
self.progress_value = int(count / all_count * 1000) # 更新进度条(0~1000)
if count % 5 == 0 and count >= 5: # 每5帧计算一次帧率
self.yolo2main_fps.emit(str(int(5 / (time.time() - start_time)))) # 向主界面发射每秒帧率
start_time = time.time() # 重置开始时间
# 预处理图像
with self.dt[0]:
im = self.preprocess(im) # 预处理图像
if len(im.shape) == 3: # 如果图像维度为3,则扩展批次维度
im = im[None] # 扩展为批次维度
# 推理过程
with self.dt[1]:
preds = self.model(im, augment=self.args.augment, visualize=visualize) # 进行推理
# 后处理
with self.dt[2]:
self.results = self.postprocess(preds, im, im0s) # 后处理推理结果
# 可视化、保存和写入结果
n = len(im) # 图像数量(通常是1,除非是批次推理)
for i in range(n):
# 保存速度信息
self.results[i].speed = {
'preprocess': self.dt[0].dt * 1E3 / n,
'inference': self.dt[1].dt * 1E3 / n,
'postprocess': self.dt[2].dt * 1E3 / n}
p, im0 = (path[i], im0s[i].copy()) if self.source_type.webcam or self.source_type.from_img \
else (path, im0s.copy()) # 设置路径
p = Path(p) # 转换路径为Path对象
# 获取检测框和标签
label_str = self.write_results(i, self.results, (p, im, im0)) # 获取标签字符串
# 处理标签和目标数量
class_nums = 0 # 类别数
target_nums = 0 # 目标数
self.labels_dict = {
} # 标签字典
if 'no detections' in label_str: # 如果没有检测到目标
pass
else:
# 解析标签字符串,更新目标和类别数量
for ii in label_str.split