0


从零开始完成YOLOv5目标识别(三)用PyQt5展示YOLOv5的识别结果

往期内容

从零开始完成Yolov5目标识别(二)制作并训练自己的训练集

从零开始完成Yolov5目标识别(一)准备工作

一、项目框架:

其中main.py和MainWindow.py是pyqt5的功能文件。

二、核心内容:

pyqt5的安装过程略过;

1. QtDesign设计:

用来显示视频、图像和摄像头内容的label、textBrowser和按钮控件采用水平布局;

窗口空白处单击右击-》布局-》水平布局,可以使控件自适应页面大小。

用转换工具使.ui文件转化成python代码

  1. pyuic5.bat -o MainWindow.py MainWindow.ui

2. 检测部分

2.1 导包

  1. import sys
  2. import cv2
  3. import argparse
  4. import random
  5. import torch
  6. import numpy as np
  7. import torch.backends.cudnn as cudnn
  8. from PyQt5 import QtCore, QtGui, QtWidgets
  9. from PyQt5.QtCore import *
  10. from PyQt5.QtGui import *
  11. from PyQt5.QtWidgets import *
  12. from utils.torch_utils import select_device
  13. from models.experimental import attempt_load
  14. from utils.general import check_img_size, non_max_suppression, scale_coords
  15. from utils.datasets import letterbox
  16. from utils.plots import *

util文件夹下plots.py随着YOLOv5版本更新发生了比较大的变化,这个文件主要负责各种图像的绘制。

plots中一些常用的方法:(不完整)

butter_lowpass_filtfilt:做平滑曲线

plot_images:在test.py中绘制预测框‘’

plot_lr_scheduler:train.py中学习率可视化

plot_labels:train.py中对label进行可视化

plot_results:训练结果可视化

为了方便PyQt5调用plots对图像锚框,在plots.py中加入上几个版本的YOLOv5的plot_one_box方法:

  1. def plot_one_box(x, im, color=(128, 128, 128), label=None, line_thickness=3):
  2. """一般会用在detect.py中在nms之后变量每一个预测框,再将每个预测框画在原图上
  3. 使用opencv在原图im上画一个bounding box
  4. :params x: 预测得到的bounding box [x1 y1 x2 y2]
  5. :params im: 原图 要将bounding box画在这个图上 array
  6. :params color: bounding box线的颜色
  7. :params labels: 标签上的框框信息 类别 + score
  8. :params line_thickness: bounding box的线宽
  9. """
  10. # check im内存是否连续
  11. assert im.data.contiguous, 'Image not contiguous. Apply np.ascontiguousarray(im) to plot_on_box() input image.'
  12. # tl = 框框的线宽 要么等于line_thickness要么根据原图im长宽信息自适应生成一个
  13. tl = line_thickness or round(0.002 * (im.shape[0] + im.shape[1]) / 2) + 1 # line/font thickness
  14. # c1 = (x1, y1) = 矩形框的左上角 c2 = (x2, y2) = 矩形框的右下角
  15. c1, c2 = (int(x[0]), int(x[1])), (int(x[2]), int(x[3]))
  16. # cv2.rectangle: 在im上画出框框 c1: start_point(x1, y1) c2: end_point(x2, y2)
  17. # 注意: 这里的c1+c2可以是左上角+右下角 也可以是左下角+右上角都可以
  18. cv2.rectangle(im, c1, c2, color, thickness=tl, lineType=cv2.LINE_AA)
  19. # 如果label不为空还要在框框上面显示标签label + score
  20. if label:
  21. tf = max(tl - 1, 1) # label字体的线宽 font thickness
  22. # cv2.getTextSize: 根据输入的label信息计算文本字符串的宽度和高度
  23. # 0: 文字字体类型 fontScale: 字体缩放系数 thickness: 字体笔画线宽
  24. # 返回retval 字体的宽高 (width, height), baseLine 相对于最底端文本的 y 坐标
  25. t_size = cv2.getTextSize(label, 0, fontScale=tl / 3, thickness=tf)[0]
  26. c2 = c1[0] + t_size[0], c1[1] - t_size[1] - 3
  27. # 同上面一样是个画框的步骤 但是线宽thickness=-1表示整个矩形都填充color颜色
  28. cv2.rectangle(im, c1, c2, color, -1, cv2.LINE_AA) # filled
  29. # cv2.putText: 在图片上写文本 这里是在上面这个矩形框里写label + score文本
  30. # (c1[0], c1[1] - 2)文本左下角坐标 0: 文字样式 fontScale: 字体缩放系数
  31. # [225, 255, 255]: 文字颜色 thickness: tf字体笔画线宽 lineType: 线样式
  32. cv2.putText(im, label, (c1[0], c1[1] - 2), 0, tl / 3, [225, 255, 255], thickness=tf, lineType=cv2.LINE_AA)

2.2 main.py要实现的主要功能

  • 调用yolov5中的方法,设置权重
  1. def model_init(self):
  2. # 模型相关参数配置
  3. parser = argparse.ArgumentParser()
  4. parser.add_argument('--weights', nargs='+', type=str, default='weights/yolov5s.pt', help='model.pt path(s)')
  5. parser.add_argument('--source', type=str, default='data/images', help='source') # file/folder, 0 for webcam
  6. parser.add_argument('--img-size', type=int, default=640, help='inference size (pixels)')
  7. parser.add_argument('--conf-thres', type=float, default=0.25, help='object confidence threshold')
  8. parser.add_argument('--iou-thres', type=float, default=0.45, help='IOU threshold for NMS')
  9. parser.add_argument('--device', default='cpu', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
  10. parser.add_argument('--view-img', action='store_true', help='display results')
  11. parser.add_argument('--save-txt', action='store_true', help='save results to *.txt')
  12. parser.add_argument('--save-conf', action='store_true', help='save confidences in --save-txt labels')
  13. parser.add_argument('--nosave', action='store_true', help='do not save images/videos')
  14. parser.add_argument('--classes', nargs='+', type=int, help='filter by class: --class 0, or --class 0 2 3')
  15. parser.add_argument('--agnostic-nms', action='store_true', help='class-agnostic NMS')
  16. parser.add_argument('--augment', action='store_true', help='augmented inference')
  17. parser.add_argument('--update', action='store_true', help='update all models')
  18. parser.add_argument('--project', default='runs/detect', help='save results to project/name')
  19. parser.add_argument('--name', default='exp', help='save results to project/name')
  20. parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment')
  21. self.opt = parser.parse_args()
  22. print(self.opt)
  23. # 默认使用opt中的设置(权重等)来对模型进行初始化
  24. source, weights, view_img, save_txt, imgsz = self.opt.source, self.opt.weights, self.opt.view_img, self.opt.save_txt, self.opt.img_size
  25. # 若openfile_name_model不为空,则使用此权重进行初始化
  26. if self.openfile_name_model:
  27. weights = self.openfile_name_model
  28. print("Using button choose model")
  29. self.device = select_device(self.opt.device)
  30. self.half = self.device.type != 'cpu' # half precision only supported on CUDA
  31. cudnn.benchmark = True
  32. # Load model
  33. self.model = attempt_load(weights, map_location=self.device) # load FP32 model
  34. stride = int(self.model.stride.max()) # model stride
  35. self.imgsz = check_img_size(imgsz, s=stride) # check img_size
  36. if self.half:
  37. self.model.half() # to FP16
  38. # Get names and colors
  39. self.names = self.model.module.names if hasattr(self.model, 'module') else self.model.names
  40. self.colors = [[random.randint(0, 255) for _ in range(3)] for _ in self.names]
  41. print("model initial done")
  42. # 设置提示框
  43. QtWidgets.QMessageBox.information(self, u"Notice", u"模型加载完成", buttons=QtWidgets.QMessageBox.Ok,
  44. defaultButton=QtWidgets.QMessageBox.Ok)
  • detect方法,输入原始图像返回监测信息
  1. def detect(self, name_list, img):
  2. showimg = img
  3. with torch.no_grad():
  4. img = letterbox(img, new_shape=self.opt.img_size)[0]
  5. # Convert
  6. img = img[:, :, ::-1].transpose(2, 0, 1) # BGR to RGB, to 3x416x416
  7. img = np.ascontiguousarray(img)
  8. img = torch.from_numpy(img).to(self.device)
  9. img = img.half() if self.half else img.float() # uint8 to fp16/32
  10. img /= 255.0 # 0 - 255 to 0.0 - 1.0
  11. if img.ndimension() == 3:
  12. img = img.unsqueeze(0)
  13. # Inference
  14. pred = self.model(img, augment=self.opt.augment)[0]
  15. # Apply NMS
  16. pred = non_max_suppression(pred, self.opt.conf_thres, self.opt.iou_thres, classes=self.opt.classes,
  17. agnostic=self.opt.agnostic_nms)
  18. info_show = ""
  19. # Process detections
  20. for i, det in enumerate(pred):
  21. if det is not None and len(det):
  22. # Rescale boxes from img_size to im0 size
  23. det[:, :4] = scale_coords(img.shape[2:], det[:, :4], showimg.shape).round()
  24. for *xyxy, conf, cls in reversed(det):
  25. label = '%s %.2f' % (self.names[int(cls)], conf)
  26. name_list.append(self.names[int(cls)])
  27. #single_info = Annotator.box_label(xyxy, showimg, label=label, color=self.colors[int(cls)], line_thickness=2)
  28. #single_info = Annotator.box_label(xyxy, showimg, label=label, color=self.colors[int(cls)])
  29. single_info = plot_one_box(xyxy, showimg, label=label, color=self.colors[int(cls)], line_thickness=2)
  30. # print(single_info)
  31. # info_show = info_show + single_info + "\n"
  32. return info_show
  • 检测和显示各帧图像
  1. def show_video_frame(self):
  2. name_list = []
  3. flag, img = self.cap.read()
  4. if img is not None:
  5. info_show = self.detect(name_list, img) # 检测结果写入到原始img上
  6. print(info_show)
  7. # 检测信息显示在界面
  8. self.ui.textBrowser.setText(info_show)
  9. show = cv2.resize(img, (640, 480)) # 直接将原始img上的检测结果进行显示
  10. self.result = cv2.cvtColor(show, cv2.COLOR_BGR2RGB)
  11. showImage = QtGui.QImage(self.result.data, self.result.shape[1], self.result.shape[0],
  12. QtGui.QImage.Format_RGB888)
  13. self.ui.label.setPixmap(QtGui.QPixmap.fromImage(showImage))
  14. self.ui.label.setScaledContents(True) # 设置图像自适应界面大小
  15. else:
  16. self.timer_video.stop()
  17. self.cap.release()
  18. self.ui.label.clear()
  19. # 视频帧显示期间,禁用其他检测按键功能
  20. self.ui.pushButton_video.setDisabled(False)
  21. self.ui.pushButton_img.setDisabled(False)
  22. self.ui.pushButton_camer1.setDisabled(False)
  23. self.ui.pushButton_camer0.setDisabled(False)
  • 各种控件功能
  1. # 暂停与继续检测
  2. def button_video_stop(self):
  3. self.timer_video.blockSignals(False)
  4. # 暂停检测
  5. # 若QTimer已经触发,且激活
  6. if self.timer_video.isActive() == True and self.num_stop%2 == 1:
  7. self.ui.pushButton_stop.setText(u'暂停检测') # 当前状态为暂停状态
  8. self.num_stop = self.num_stop + 1 # 调整标记信号为偶数
  9. self.timer_video.blockSignals(True)
  10. # 继续检测
  11. else:
  12. self.num_stop = self.num_stop + 1
  13. self.ui.pushButton_stop.setText(u'继续检测')
  14. # 结束视频检测
  15. def finish_detect(self):
  16. # self.timer_video.stop()
  17. self.cap.release() # 释放cap
  18. self.ui.label.clear() # 清空label画布
  19. # 启动其他检测按键功能
  20. self.ui.pushButton_video.setDisabled(False)
  21. self.ui.pushButton_img.setDisabled(False)
  22. self.ui.pushButton_camer1.setDisabled(False)
  23. self.ui.pushButton_camer0.setDisabled(False)
  24. # 结束检测时,查看暂停功能是否复位,将暂停功能恢复至初始状态
  25. # Note:点击暂停之后,num_stop为偶数状态
  26. if(self.num_stop%2 == 0):
  27. print("Reset stop/begin!")
  28. self.ui.pushButton_stop.setText(u'暂停/继续')
  29. self.num_stop = self.num_stop + 1
  30. self.timer_video.blockSignals(False)

信号与槽不在展示了。

三、效果

特别感谢:使用PyQt5为YoloV5添加界面(一)_叼着狗骨头的猫的博客-CSDN博客_qt yolov5带来的帮助


本文转载自: https://blog.csdn.net/WZT725/article/details/123573512
版权归原作者 明天才有空 所有, 如有侵权,请联系我们删除。

“从零开始完成YOLOv5目标识别(三)用PyQt5展示YOLOv5的识别结果”的评论:

还没有评论