Linux 小程序开发日记(终章)(pyqt+yolov5)

Linux 小程序开发日记(终章)

哈喽大家好,我是爱喝欢乐水的柳橙汁,这一篇,是最后一篇啦。我会将所有个过程都梳理一遍。

关于代码的详细注释,大家就参考一下之前的笔记吧。这一篇博客我就放上我精简的代码注释了

摄像头检测的默认设备序号更改为0,减少调试报错

温馨提示:

  1. 默认运行的是GPU,若发现无法执行,请在参数中替换为CUP
  2. 需要自己建数据库,和数据库的一张表,我会将表的结构附上

一、项目简介

使用PyQt5为YoloV5添加一个可视化检测界面,并实现数据的实时检测,和录入具体情况如下:

博客与B站:

博客地址:https://blog.csdn.net/Liuchengzhizhi/article/details/123678357?spm=1001.2014.3001.5501

B站视频:https://www.bilibili.com/video/BV1rZ4y1B7t8?share_source=copy_web

源码:https://gitee.com/wx_b915676bb6/yolo-pyqt.git

特点:

  1. 同时输出与检测结果与相应相关信息
  2. 支持视频暂停与继续检测
  3. 能够匹配相关信息表
  4. 能够将数据录入在数据库中

目的:

  1. 熟悉PYQT的使用
  2. 了解PyQt5基础控件与布局方法
  3. 了解信号与槽
  4. 熟悉视频在PyQt中的处理方法
  5. 了解数据库在python中的使用方法
  6. 了解csv文件的读取

项目图片:

在这里插入图片描述
在这里插入图片描述

二、快速开始

环境与相关文件配置:

  • 按照 ult-yolov5 中requirement的要求配置环境,自行安装PyQt5,注意都需要在一个evn环境中进行安装与配置
  • 下载或训练一个模型,将“.pt”文件放到weights文件夹,(权重文件可以自己选,程序默认打开weights文件夹)
  • 设置init中的opt
  • 自行安装mysql
  • 还需要自行创建数据库(还有个time字段)

在这里插入图片描述

两种程序使用方式:

  • 配置好数据库环境,直接运行detect_logical.py,进入检测界面
  • 将关于数据库部分的内容注释掉,然后运行detect_logical.py(可查看注释,很容易找到)

三、 参考与致谢

四、 版权声明

仅供交流学习使用,项目粗拙,勿商用,实际应用中出现的问题,个人不管哦~

代码如下

detect_logical

# 
# @Author: Cjuicy 
# @Date: 2022-03-21 16:00:23 
# @Last Modified by:   Cjuicys 
# @Last Modified time: 2022-03-21 16:00:23 
# 


from cProfile import label
from pickle import TRUE
import sys
from unittest import result
import cv2
import time
import argparse
import random
import torch
import numpy as np
import pandas as pd
import torch.backends.cudnn as cudnn


from utils.torch_utils import select_device
from models.experimental import attempt_load
from utils.general import check_img_size, non_max_suppression, scale_coords
from utils.datasets import letterbox
from utils.plots import plot_one_box2
from utils.torch_utils import select_device, load_classifier, time_synchronized

'''导入qt库'''
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *


'''导入自己的文件'''
from ui.food_ui import Ui_MainWindow
from utils import price_utils ,food_sql


class Ui_Logic_Window(QtWidgets.QWidget):
    def __init__(self,parent=None):
        super().__init__(parent)                            #父类的构造函数
        self.cap = cv2.VideoCapture()                       #视频流
        self.timer_video = QtCore.QTimer()                  #定义定时器,用于控制显示视频的帧率
        
        '''csv表格操作'''
        self.price_info = price_utils.get_price_info()      # 获取表格价钱信息
        
        '''GUI部分'''
        self.ui = Ui_MainWindow()
        layout_main = self.ui.set_ui()                      #初始化页面布局
        self.setLayout(layout_main)                         #到这步才会显示所有控件
        self.slot_init()                                    #初始化槽函数
        self.ui.button_settle_accounts.setDisabled(True)    #将结账按钮触发禁用


        '''先定义一些变量'''
        self.num_stop = 1                                   # 暂停与播放辅助信号,note:通过奇偶来控制暂停与播放
        self.output_folder = 'output/'
        self.vid_writer = None
        self.order_info = ''                                #用于存储订单的信息

        '''初始化模型'''
        self.openfile_name_model = "./weights/yolov5s.pt"
        self.model_init()

        self.button_camera_open()                           # 打开摄像头检测


    
 
    '''槽函数,用于一些触发事件'''
    def slot_init(self):
        
        self.ui.button_confirm.clicked.connect(self.button_video_stop)    
        self.ui.button_settle_accounts.clicked.connect(self.insert_order)
        self.timer_video.timeout.connect(self.show_video_frame) 
    

    '''加载相关参数,并初始化模型'''
    def model_init(self):
        parser = argparse.ArgumentParser()
        parser.add_argument('--weights', nargs='+', type=str, default='weights/yolov5s6.pt', help='model.pt path(s)')
        parser.add_argument('--source', type=str, default='data/images', help='source')  # file/folder, 0 for webcam
        parser.add_argument('--img-size', type=int, default=640, help='inference size (pixels)')
        parser.add_argument('--conf-thres', type=float, default=0.25, help='object confidence threshold')
        parser.add_argument('--iou-thres', type=float, default=0.45, help='IOU threshold for NMS')
        parser.add_argument('--device', default='0', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
        parser.add_argument('--view-img', action='store_true', help='display results')
        parser.add_argument('--save-txt', action='store_true', help='save results to *.txt')
        parser.add_argument('--save-conf', action='store_true', help='save confidences in --save-txt labels')
        parser.add_argument('--nosave', action='store_true', help='do not save images/videos')
        parser.add_argument('--classes', nargs='+', type=int, help='filter by class: --class 0, or --class 0 2 3')
        parser.add_argument('--agnostic-nms', action='store_true', help='class-agnostic NMS')
        parser.add_argument('--augment', action='store_true', help='augmented inference')
        parser.add_argument('--update', action='store_true', help='update all models')
        parser.add_argument('--project', default='runs/detect', help='save results to project/name')
        parser.add_argument('--name', default='exp', help='save results to project/name')
        parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment')
        self.opt = parser.parse_args()
        print(self.opt)
        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

        if self.openfile_name_model:
            weights = self.openfile_name_model
            print("Using button choose model")

        self.device = select_device(self.opt.device)
        self.half = self.device.type != 'cpu'  # half precision only supported on CUDA

        cudnn.benchmark = True

        # Load model
        self.model = attempt_load(weights, map_location=self.device)  # load FP32 model
        stride = int(self.model.stride.max())  # model stride
        self.imgsz = check_img_size(imgsz, s=stride)  # check img_size
        if self.half:
            self.model.half()  # to FP16
        #  Second-stage classifier
        classify = False
        if classify:
            modelc = load_classifier(name='resnet101', n=2)  # initialize
            modelc.load_state_dict(torch.load('weights/resnet101.pt', map_location=self.device)['model']).to(self.device).eval()

        # Get names and colors
        self.names = self.model.module.names if hasattr(self.model, 'module') else self.model.names
        self.colors = [[random.randint(0, 255) for _ in range(3)] for _ in self.names]
        print("model initial done")
        
        # 设置提示框
        # QtWidgets.QMessageBox.information(self, u"Notice", u"模型加载完成", buttons=QtWidgets.QMessageBox.Ok,
                                    #   defaultButton=QtWidgets.QMessageBox.Ok)




    # 目标检测
    def detect(self, name_list, img):
        '''
        :param name_list: 文件名列表
        :param img: 待检测图片
        :return: info_show:检测输出的文字信息
        '''
        showimg = img
        with torch.no_grad():
            img = letterbox(img, new_shape=self.opt.img_size)[0]
            # Convert
            img = img[:, :, ::-1].transpose(2, 0, 1)  # BGR to RGB, to 3x416x416
            img = np.ascontiguousarray(img)
            img = torch.from_numpy(img).to(self.device)
            img = img.half() if self.half else img.float()  # uint8 to fp16/32
            img /= 255.0  # 0 - 255 to 0.0 - 1.0
            if img.ndimension() == 3:
                img = img.unsqueeze(0)
            # Inference
            pred = self.model(img, augment=self.opt.augment)[0]
            # Apply NMS
            pred = non_max_suppression(pred, self.opt.conf_thres, self.opt.iou_thres, classes=self.opt.classes,
                                       agnostic=self.opt.agnostic_nms)
            info_show = ""

            # --定义标签变量名--
            label_names = ""

            # Process detections
            for i, det in enumerate(pred):
                if det is not None and len(det):
                    # Rescale boxes from img_size to im0 size
                    det[:, :4] = scale_coords(img.shape[2:], det[:, :4], showimg.shape).round()
                    for *xyxy, conf, cls in reversed(det):
                        label = '%s %.2f' % (self.names[int(cls)], conf)
                        name_list.append(self.names[int(cls)])
                        single_info = plot_one_box2(xyxy, showimg, label=label, color=self.colors[int(cls)], line_thickness=2)
                        # print(single_info)
                        info_show = info_show + single_info + "\n"

                        #获取检测到的所有标签
                        label_names = label_names+","+self.names[int(cls)]
                
            label_names = label_names.split(",")                                # 转化为数组(字符串头有一个',')
            label_names = [i for i in label_names if(len(str(i))!=0)]           # 去掉数组内的空值

        return  info_show , label_names

    '''设置保存的视频名字和路径'''
    def set_video_name_and_path(self):
        now = time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime(time.time()))
        # if vid_cap:  # video
        fps = self.cap.get(cv2.CAP_PROP_FPS)
        w = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
        h = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
        save_path = self.output_folder + 'video_output/' + now + '.mp4'
        return fps, w, h, save_path


    '''打开摄像头检测'''
    def button_camera_open(self):
        print("Open camera to detect")
        camera_num = 0
        self.cap = cv2.VideoCapture(camera_num)
        bool_open =self.cap.open(camera_num)
        if not bool_open:
            QtWidgets.QMessageBox.warning(self, u"Warning", u"打开摄像头失败", buttons=QtWidgets.QMessageBox.Ok,
                                          defaultButton=QtWidgets.QMessageBox.Ok)
        else:
            fps, w, h, save_path = self.set_video_name_and_path()
            # fps = 5 # 控制摄像头检测下的fps,Note:保存的视频,播放速度有点快,我只是粗暴的调整了FPS
            self.vid_writer = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*'mp4v'), fps, (w, h))
            self.timer_video.start(30)

    '''定义视频帧操作'''
    def show_video_frame(self):
        name_list = []
        flag, img = self.cap.read()
        if img is not None:
            info_show , label_names = self.detect(name_list, img)   # 检测结果写入到原始img上
            self.vid_writer.write(img)                              # 检测结果写入视频
            print(info_show)

            self.set_labels(label_names)                            # 处理lable并显示在GUI上
            
            

            show = cv2.resize(img, (640, 480))                      # 直接将原始img上的检测结果进行显示
            self.result = cv2.cvtColor(show, cv2.COLOR_BGR2RGB)
            showImage = QtGui.QImage(self.result.data, self.result.shape[1], self.result.shape[0],
                                     QtGui.QImage.Format_RGB888)
            # self.ui.label.setPixmap(QtGui.QPixmap.fromImage(showImage))
            self.ui.label_show_camera.setPixmap(QtGui.QPixmap.fromImage(showImage))  #往显示视频的Label里 显示QImage
            # self.ui.label.setScaledContents(True)  # 设置图像自适应界面大小

        else:
            self.timer_video.stop()         # 读写结束,释放资源
            self.cap.release()              # 释放video_capture资源
            self.vid_writer.release()       # 释放video_writer资源
            self.ui.label.clear()

            self.ui.button_settle_accounts.setDisabled(True)    # 视频帧显示期间,禁用结账按键功能


    ''' 暂停与继续检测 '''
    def button_video_stop(self):
        self.timer_video.blockSignals(False)
        # 暂停检测
        # 若QTimer已经触发,且激活
        if self.timer_video.isActive() == True and self.num_stop%2 == 1:
            self.ui.button_confirm.setText(u'重新确认')           #当前状态为暂停状态
            self.num_stop = self.num_stop + 1                   # 调整标记信号为偶数
            self.timer_video.blockSignals(True)
            
            self.ui.button_settle_accounts.setDisabled(False)   # 启动结账按钮
        # 继续检测
        else:
            self.num_stop = self.num_stop + 1
            self.ui.button_confirm.setText(u'确认')
            self.ui.button_settle_accounts.setDisabled(True)


    '''处理获取到的lable函数'''
    def set_labels(self ,labels ):
        self.ui.list_show.clearContents()                                               #对表格先进行一个清空
        labels = pd.value_counts(labels)                                                #对标签数组进行重复个数统计

        total_price = 0
        order_sql = []    
        
        if len(labels.index.values) != 0:
            labels_sum = len(labels.index.values)                                       #获取标签总数
            for i in range(labels_sum):
                label_num = str(labels.values[i])                                       #获取对应标签的个数
                self.ui.list_show.setItem(i,0,QTableWidgetItem(labels.index.values[i])) #添加对应标签名字
                self.ui.list_show.setItem(i,1,QTableWidgetItem(label_num))              #添加对应标签个数

                #记录数据库要插入的字符 
                order_sql.append(str(labels.index.values[i]))
                order_sql.append(str(label_num))     

                if labels.index.values[i] in self.price_info :
                    label_price = str(self.price_info[labels.index.values[i]])
                    self.ui.list_show.setItem(i,2,QTableWidgetItem(label_price))
                    # 计算总价
                    total_price = total_price + (float(label_price) * int(label_num))
                else :
                    self.ui.list_show.setItem(i,2,QTableWidgetItem("99"))
                    # 计算总价
                    total_price = total_price + (float(99) * int(label_num))

                #设置数据库内容
                if i+1>=labels_sum:                         #判断是不是遍历到了标签最后
                   while(i<5):                              #循环,添加剩余的例如数据库的内容
                       order_sql.append('null')
                       order_sql.append('null')
                       i = i+1


        account = "总价: "+ str(total_price)
        self.ui.label_account.setText(account)

        #设置数据库
        order_sql.insert(0,str(total_price))                #插入总价钱
        order_sql.insert(0,"null")                          #插入id变量
        order_sql.append("CURRENT_TIMESTAMP")               #插入当前时间
        
        print(order_sql)
        self.order_info = order_sql

    '''插入数据'''
    def insert_order(self):
        food_sql.insert_order_info(self.order_info)
        #  显示数据
        food_sql.select_order_info()


        
        
if __name__ == '__main__':
    app =  QtWidgets.QApplication(sys.argv)
    current_ui =Ui_Logic_Window()                   #实例化Ui_MainWindow
    current_ui.show()                               #调用ui的show()以显示。同样show()是源于父类QtWidgets.QWidget的
    sys.exit(app.exec_())


food_ui

# @Author: Cjuicy 
# @Date: 2022-03-21 16:07:16 
# @Last Modified by:   Cjuicys 

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *



class Ui_MainWindow(object):
    '''程序界面布局'''
    def set_ui(self):
        self.__layout_main = QtWidgets.QGridLayout()                #总布局
        self.__layout_fun_button1 = QtWidgets.QHBoxLayout()         #按键布局1
        self.__layout_fun_button2 = QtWidgets.QHBoxLayout()         #按键布局2
        self.__layout_data_show = QtWidgets.QVBoxLayout()           #数据(视频)显示布局
        self.__layout_list_show  = QtWidgets.QVBoxLayout()          #表格布局

        self.button_confirm = QtWidgets.QPushButton('确认')         #建立用于打开摄像头的按键
        self.button_settle_accounts = QtWidgets.QPushButton('结账') #建立结账的按钮
        self.list_show =  QtWidgets.QTableWidget(6,3)               #建立表格
        self.label_account = QtWidgets.QLabel("总价:")              #建立label

        '''set butten size '''
        self.button_confirm.setMinimumHeight(50)                    #设置按键大小
        self.button_settle_accounts.setMinimumHeight(50)
        self.label_account.setMinimumHeight(50)
        

        # self.button_close.move(10,100)                      #移动按键  这句话去掉好像也没关系
        '''设置标签的格式'''
        font = QtGui.QFont()
        font.setPixelSize(18)
        self.label_account.setFont(font)

        '''设置表格'''
        self.list_show.setHorizontalHeaderLabels(["名称","数量","单价"])
        self.list_show.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)# adaptive size
        self.list_show.setEditTriggers(QAbstractItemView.EditTrigger(False))    #将表格的内容设为不可编辑
        '''信息显示'''
        self.label_show_camera = QtWidgets.QLabel()                                 #定义显示视频的Label
        self.label_show_camera.setFixedSize(641,481)                                #给显示视频的Label设置大小为641x481
        '''把按键加入到按键布局中'''
        self.__layout_fun_button1.addWidget(self.button_confirm)                    #把重新确认的按键放到按键布局中
        self.__layout_fun_button2.addWidget(self.button_settle_accounts)            #把结账的按键放到按键布局中
        '''把表格加入到表格布局中'''
        self.__layout_list_show.addWidget(self.list_show)                           #将表格添加到表格布局中
        self.__layout_list_show.addWidget(self.label_account)                       #将总价label添加到表格布局中


        '''把某些控件加入到总布局中'''
        self.__layout_main.addLayout(self.__layout_list_show,0,0)        #将表格布局添加到总布局中
        self.__layout_main.addLayout(self.__layout_fun_button1,1,1)      #把按键布局加入到总布局中
        self.__layout_main.addLayout(self.__layout_fun_button2,1,0)      #把按键布局加入到总布局中
        self.__layout_main.addWidget(self.label_show_camera,0,1)        #把用于显示视频的Label加入到总布局中
        
        return self.__layout_main
 

food_sql

 # @Author: Cjuicy 
# @Date: 2022-03-23 09:37:21 
# @Last Modified by:   Cjuicys 
# @Last Modified time: 2022-03-23 09:37:21 


from logging import root
import pymysql

dbhost='localhost'
dbuser='root'
dbpass='12345678'
dbname='order_info'

'''连接数据库'''
try:
    db=pymysql.connect(host=dbhost,user=dbuser,password=dbpass,database=dbname)
    print("数据库连接成功")
except pymysql.Error as e:
    print("数据库连接失败:"+str(e))

cursor = db.cursor()

''' 插入字段 '''
def insert_order_info(order_info):
    # 对有字符串的内容,添加双引号,这样才能够读取   -1是应为要插入时间,时间不能加双引号
    for i in range(len(order_info)-1):
        if order_info[i] != 'null':
            order_info[i] = "'" + order_info[i] + "'"
    order_info = ','.join(order_info)
    
    #数据库文字
    sql = "INSERT INTO `order_info`.`OrderInfo`VALUES(%s);"%(order_info)
    # print(sql)
    cursor.execute(sql)
    db.commit
    print('插入成功')


''' 显示插入的字段 '''
def select_order_info(): 
    select = "SELECT * FROM order_info.OrderInfo;"
    cursor.execute(select)
    alldata = cursor.fetchall()
    print(alldata)


id_utils.py

 # @Author: Cjuicy 
# @Date: 2022-03-23 14:37:46 
# @Last Modified by:   Cjuicys 
# @Last Modified time: 2022-03-23 14:37:46 


'''
存放公用的账户读写函数
'''
import csv

# 写入账户信息到csv文件
def sava_id_info(user, pwd):
    headers = ['name', 'key']
    values = [{'name':user, 'key':pwd}]
    with open('userInfo.csv', 'a', encoding='utf-8', newline='') as fp:
        writer = csv.DictWriter(fp, headers)
        writer.writerows(values)

# 读取csv文件获得账户信息
def get_id_info():
    USER_PWD = {}
    with open('userInfo.csv', 'r') as csvfile: 
        spamreader = csv.reader(csvfile)
        for row in spamreader:
            USER_PWD[row[0]] = row[1]
    return USER_PWD


  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值