PyQt5学习-简易的看图工具

本文介绍了一个使用PyQt5库开发的简易看图工具,该工具支持矩形框标注,允许用户通过鼠标操作进行绘制、选择和移动矩形框。工具集成了图像显示、标注功能,并能保存和加载标注信息。通过重载QLabel类,实现了鼠标事件的处理,包括左键绘制、中键选择和右键缩放矩形框。
摘要由CSDN通过智能技术生成
import tkinter as tk
from tkinter import filedialog
import sys  # 引用sys库,输入sys模块,主要用于启动时进行参数传递,PyQt5的QApplication可以接收系统参数
from PyQt5 import QtWidgets, QtCore, QtGui, Qt
from PyQt5 import QtWidgets  # 引用PyQt5库里QtWidgets类
from PyQt5.QtWidgets import *  # 导入PyQt5.QtWidgets里所有的方法
from PyQt5.QtGui import *  # 导入PyQt5.QtGui里所有的方法
from PyQt5.QtCore import *
from PyQt5.Qt import *
import os  # 引用os库
import math
from PIL import Image
import xml.dom.minidom
from xml.dom.minidom import Document
from xml.dom.minidom import parse


'''重定义QLabel,实现绘制事件和各类鼠标事件'''
class MyLabel(QLabel):
    def __init__(self, parent=None):
        '''
        :param parent:
        初始化基本参数
        '''
        super(MyLabel, self).__init__(parent)
        self.x0 = ""  # 矩形框左上角坐标
        self.y0 = ""
        self.x1 = ""  # 矩形框右下角坐标
        self.y1 = ""
        self.move_x = ""
        self.move_y = ""
        self.release = 1
        self.text = ""  # 标签标注
        self.row = -1  # 选中矩形框信息
        self.loc = 0
        self.rect = QRectF()
        self.leftflag = False
        self.midflag = False
        self.rightflag = False
        # 增加一个存储标注框坐标的列表
        self.bboxList = []  # 储存矩形框信息
        self.c = 0  # 判断是否为左键释放 1:是,0:不是

    '''单击鼠标触发事件'''
    def mousePressEvent(self, event):
        # 将绘制标志设置为True
        if event.buttons() == Qt.LeftButton:  # 左键点击触发
            self.leftflag = True
            self.x0 = event.pos().x()  # 获取鼠标事件的开始位置
            self.y0 = event.pos().y()
            self.c = 1

        if event.buttons() == Qt.MidButton:  # 中键点击触发
            self.midflag = True
            if self.bboxList != []:  # 中键选择矩形框
                for i in range(len(self.bboxList)):
                    x = event.pos().x()
                    y = event.pos().y()
                    x0 = self.bboxList[i][0]
                    y0 = self.bboxList[i][1]
                    x1 = self.bboxList[i][2]
                    y1 = self.bboxList[i][3]
                    if x > x0 and y > y0 and x < x1 and y < y1:
                        self.row = i
                        print("选择的矩形框信息为:", self.bboxList[self.row])
                        break
            self.update()

        if event.buttons() == Qt.RightButton:  # 右键点击触发
            if self.bboxList != []:
                lt = self.bboxList[self.row]
                x0, y0, x1, y1 = lt[0], lt[1], lt[2], lt[3]  # 指定矩形框端点信息
                d = 10  # 端点缩放区域半径
                x = event.pos().x()
                y = event.pos().y()
                self.move_x = x
                self.move_y = y
                if x > x0 + d and x < x1 - d and y > y0 + d and y < y1 - d:  # 矩形框移动
                    self.rightflag = True
                    self.loc = -1  # 矩形框内
                    self.setCursor(Qt.PointingHandCursor)
                if x > x0 - d and x < x0 + d and y > y0 - d and y < y0 + d:  # 缩放端点区域
                    self.rightflag = True
                    self.loc = 0  # 左上角
                if x > x1 - d and x < x1 + d and y > y0 - d and y < y0 + d:
                    self.rightflag = True
                    self.loc = 1  # 右上角
                if x > x0 - d and x < x0 + d and y > y1 - d and y < y1 + d:
                    self.rightflag = True
                    self.loc = 2  # 左下角
                if x > x1 - d and x < x1 + d and y > y1 - d and y < y1 + d:
                    self.rightflag = True
                    self.loc = 3  # 右下角
                print("右键点击")

    '''鼠标释放事件'''
    def mouseReleaseEvent(self, event):
        # 将绘制标志设置为False
        # if self.leftflag == True:
        #     print("已松开鼠标")
        if self.rightflag == True:  # 右键缩放矩形框后更新该矩形框信息
            print("右键松开")
            xmin = min(self.bboxList[self.row][0], self.bboxList[self.row][2])
            ymin = min(self.bboxList[self.row][1], self.bboxList[self.row][3])
            xmax = max(self.bboxList[self.row][0], self.bboxList[self.row][2])
            ymax = max(self.bboxList[self.row][1], self.bboxList[self.row][3])
            self.bboxList[self.row] = (xmin, ymin, xmax, ymax, self.bboxList[self.row][4], self.bboxList[self.row][5])
            self.setCursor(Qt.CrossCursor)
        self.rightflag = False
        self.leftflag = False
        self.midflag = False
        self.move_x = ""
        self.move_y = ""
        self.release = 1

    '''绘制事件,绘制矩形框'''
    def PaintEvent(self, event):
        super().paintEvent(event)
        # 构造矩形框的起始坐标和宽度、高度
        try:
            # 构造QPainter,进行矩形框绘制
            painter = QPainter()
            # 增加绘制开始和结束时间
            painter.begin(self)
            # 绘制矩形框
            if self.x0 != "" and self.y0 != "" and self.x1 != "" and self.y1 != "":
                if self.leftflag == True:
                    tempx0 = min(self.x0, self.x1)  # 矩形框左上角与右下角坐标
                    tempy0 = min(self.y0, self.y1)
                    tempx1 = max(self.x0, self.x1)
                    tempy1 = max(self.y0, self.y1)
                    width = tempx1 - tempx0  # 矩形框宽、高
                    height = tempy1 - tempy0
                    currect = QRectF(tempx0, tempy0, width, height)  # QRect类使用整数精度在平面上定义了一个矩形
                    painter.setPen(QPen(Qt.blue, 2, Qt.SolidLine))  # QPen(画笔颜色,画笔粗细,画笔风格)
                    # painter.setBrush(QBrush(QColor(0, 255, 50, 100), Qt.Dense1Pattern))
                    painter.setBrush(QBrush(QColor(0, 255, 50, 100)))
                    painter.drawRect(currect)  # 绘制图形

            # 遍历之前存储的标注框坐标列表
            # if self.bboxList != []:
            if self.row == len(self.bboxList):  # 如果选择删除的是最后一个,修改为-1,以防判定超出范围
                self.row = -1
            # 遍历矩形框信息
            for point in self.bboxList:
                if point == self.bboxList[self.row]:  # 选中的矩形框用特定颜色体现
                    xmin = min(point[0], point[2])  # 指定矩形框端点发生不定改变,重新判断大小
                    ymin = min(point[1], point[3])
                    xmax = max(point[0], point[2])
                    ymax = max(point[1], point[3])
                    rect = QRectF(xmin, ymin, xmax - xmin, ymax - ymin)
                    painter.setPen(QPen(QColor(155, 0, 255), 2, Qt.SolidLine))
                    painter.setBrush(QBrush(Qt.NoBrush))
                else:
                    rect = QRectF(point[0], point[1], abs(point[0] - point[2]), abs(point[1] - point[3]))
                    painter.setPen(QPen(Qt.red, 2, Qt.SolidLine))
                    painter.setBrush(QBrush(Qt.NoBrush))
                painter.drawRect(rect)  # 绘制矩形框
            # 遍历文字标注
            for point in self.bboxList:
                if point == self.bboxList[self.row]:
                    xmin = min(point[0], point[2])
                    ymin = min(point[1], point[3])
                    painter.setPen(QPen(QColor(150, 0, 255), 2, Qt.SolidLine))
                    painter.drawText(int(xmin), int(ymin) - 10, point[5])  # 绘制文本字符串
                else:
                    painter.setPen(QPen(Qt.red, 2, Qt.SolidLine))
                    painter.drawText(int(point[0]), int(point[1]) - 10, point[5])  # 绘制文本字符串
            painter.end()
        except TypeError:
            return

'''定义一个类,继承于QtWidgets.QWidget'''
class MainWindow(QtWidgets.QWidget):
    def __init__(self, *args, **kwargs):  # 构建方法
        super().__init__(*args, **kwargs)
        self.move_flag = False  # 默认没有移动
        self.resize(1530, 750)  # 设置窗口大小
        self.r = 0  # 备份尺寸因子
        self.groupbox_x = 170  # 组合框在主窗口位置
        self.groupbox_y = -3
        self.label_x = 100  # label当前坐标,这里选其为图片初始位置
        self.label_y = 60
        self.resize_point = 50  # 尺寸因子
        self.count = 0  # 图片序号
        self.warning = 1  # 0:读图无报错, 1:读图报错
        self.left_flag = False  # 鼠标左击
        self.right_flag = False  # 鼠标右击
        self.mid_flag = False  # 鼠标中键
        self.mouse_mv_x = ""  # 鼠标移动上一次坐标
        self.mouse_mv_y = ""
        self.m_x = ""  # 鼠标移动后坐标
        self.m_y = ""
        self.pix = ""  # 标签图片
        self.f_path = ""  # 上一文件路径
        self.sure_path = ""  # 当前文件路径
        self.now_file_name = ""  # 当前文件的文件名
        self.files = ""  # 储存文件夹所有文件
        self.s = []  # 空列表,储存图片名
        self.name = []  # 储存文件名
        self.add = []  # 中转self.name
        self.de_save = []  # 保存删除矩形框信息
        self.save_label = []  # 储存所有矩形框
        self.setup_ui()  # 调用创建控件的方法
        self.Tool_button()  # 定义控件方法

    '''绘图事件,设置背景'''
    def paintEvent(self, event):
        painter = QPainter(self)  # QPainter类
        painter.setOpacity(0.3)  # 设置背景透明度
        pixmap = QPixmap(r"D:\PyQt5\icon\timg.jpg")  # 获取背景图片
        # 绘制窗口背景,平铺到整个窗口,随着窗口改变而改变
        painter.drawPixmap(self.rect(), pixmap)

    '''设置框架'''
    def setup_ui(self):
        '''图片(目前为标签控件,需要后续将其转化为图片控件)'''
        # self.detect_image = QLabel(self)
        # 设置标签活动框
        self.groupbox = QtWidgets.QGroupBox(self)
        desktop = QApplication.desktop()  # 获取桌面信息
        # 设置组合框位置和大小
        self.groupbox.setGeometry(self.groupbox_x, self.groupbox_y,
                                  desktop.width() - self.groupbox_x - 60, desktop.height() - 17)
        # 设置组合框边框类型(宽度为3px,边框类型groove(3D), solid(线型),边框颜色为白色)
        self.groupbox.setStyleSheet(
            'border-width: 3px;border-style: groove;border-color: rgb(255, 255, 255);')
        # 标签
        self.detect_image = MyLabel(self.groupbox)  # 设置标签
        self.detect_image.setVisible(False)  # 展示隐藏标签
        self.detect_image.setStyleSheet('border-style: none')  # 设置标签无边框
        self.detect_image.paintEvent = self.detect_image.PaintEvent  # 重写paintEvent函数
        self.detect_image.setCursor(Qt.CrossCursor)  # 图片可以绘制(十字架)
        # 设置列表项
        self.img_list = QListWidget(self)  # 设置列表项,展示文件夹文件
        self.img_list.setGeometry(10, 260, 150, desktop.height() - 360)  # 设置列表框位置,尺寸
        # 设置列表框边框类型
        self.img_list.setStyleSheet(
            'border-width: 3px;border-style: groove;border-color: rgb(255, 170, 0);')
        self.img_list.clicked.connect(self.show_path)
        self.img_list.itemClicked.connect(self.change_path)

        self.label_list = QListWidget(self)  # 设置列表项,展示标注
        self.label_list.setGeometry(10, 50, 150, 200)
        self.label_list.setStyleSheet(
            'border-width: 3px;border-style: groove;border-color: rgb(255, 170, 0);')
        self.label_list.clicked.connect(self.select_box)
        # self.label_list.itemClicked.connect(self.select_box)

    '''设置控件'''
    def Tool_button(self):
        '''工具按钮'''
        self.tool_button = QPushButton(self)  # 工具栏
        self.tool_button.setStyleSheet("border-width: 3px;"
                                       "border-style: groove;"
                                       "border-color: rgb(255, 170, 0);"
                                       "color:rgb(100, 100, 255)")
        self.tool_button.setText('  File  ')  # 按钮文本
        self.tool_button.setFont(QFont("隶书"))  # 文本字体
        self.tool_button.setIcon(QIcon(r"D:\PyQt5\icon\file.png"))  # 按钮标签
        menu = QMenu(self.tool_button)  # 菜单
        act1 = QAction("Open File", menu)  # 添加行动
        act1.triggered.connect(self.openfile)  # 监控
        act1.triggered.connect(self.show_path)
        act1.setShortcut("Ctrl+Q")  # 无文本的创建方法
        act2 = QAction("Save As", menu)
        act2.triggered.connect(self.saveImage)
        act2.setShortcut("Ctrl+S")
        act3 = QAction("Exit", menu)
        act3.triggered.connect(self.close_w)
        act3.setShortcut("Ctrl+E")
        act4 = QAction("Save Result", menu)
        act4.triggered.connect(self.get_image_information)
        act4.triggered.connect(self.savetoXML)
        act4.setShortcut("Ctrl+Shift+S")
        act5 = QAction("Withdraw", menu)
        act5.triggered.connect(self.withdraw)
        act5.setShortcut("Ctrl+Z")
        act6 = QAction("Label Save", menu)
        act6.triggered.connect(self.saveBBbox)
        act6.setShortcut("V")
        act7 = QAction("Delete Box", menu)
        act7.triggered.connect(self.remove_delete)
        act7.setShortcut("Ctrl+D")
        # sub_menu = QMenu("子菜单", menu)
        menu.addAction(act1)  # 行动加入菜单
        menu.addAction(act2)
        menu.addAction(act4)
        menu.addAction(act6)
        menu.addSeparator()
        menu.addAction(act7)
        menu.addAction(act5)
        menu.addAction(act3)
        # # menu.addMenu(sub_menu)
        self.tool_button.setMenu(menu)  # 设置菜单
        # 上一张
        self.red_s_Button = QPushButton(self)
        self.red_s_Button.setStyleSheet("border-width: 3px;"
                                        "border-style: groove;"
                                        "border-color: rgb(255, 170, 0);"
                                        "color:rgb(100, 100, 255)")
        self.red_s_Button.setText(" 上一张  ")  # 设置按钮文本
        self.red_s_Button.setIcon(QIcon(r"D:\PyQt5\icon\book.png"))  # 设置按钮图标
        self.red_s_Button.pressed.connect(self.red_Select_image)  # 点击连接监控事件
        self.red_s_Button.pressed.connect(self.show_path)
        # 下一张
        self.add_s_Button = QPushButton(self)
        self.add_s_Button.setStyleSheet("border-width: 3px;"
                                        "border-style: groove;"
                                        "border-color: rgb(255, 170, 0);"
                                        "color:rgb(100, 100, 255)")
        self.add_s_Button.setText(" 下一张  ")  # 设置按钮文本
        self.add_s_Button.setIcon(QIcon(r"D:\PyQt5\icon\book.png"))
        self.add_s_Button.pressed.connect(self.add_Select_image)
        self.add_s_Button.pressed.connect(self.show_path)

        hbox = QHBoxLayout()  # 水平布局
        hbox.addWidget(self.tool_button, 1, Qt.AlignLeft | Qt.AlignTop)  # 添加控件(默认的,我们添加控件至水平布局中,默认都是垂直方向居中对齐的)
        hbox.addWidget(self.red_s_Button, 1, Qt.AlignRight | Qt.AlignBottom)
        hbox.addWidget(self.add_s_Button, 0, Qt.AlignRight | Qt.AlignBottom)
        self.setLayout(hbox)  # 实现控件布局

    '''提取图像的shape到txt文件里'''
    def get_image_information(self):
        if self.warning == 0:
            try:
                file_txt = open('./save_txt/%s.txt' % str(self.now_file_name).split(".")[0], "w")
                image_shape = (self.pix.width(), self.pix.height())
                file_txt.write(
                    str(self.now_file_name) + ' ' + '3 ' + '5 ' + '5 ' + str(image_shape[0] - 5) + " " + str(
                        image_shape[1] - 5))
                file_txt.close()
            except IndexError:
                print("list index out of range")

    '''保存当前文件的xml文件'''
    def savetoXML(self):
        if self.warning == 0:
            try:
                ann_path = "./save_txt/%s.txt" % str(self.now_file_name).split(".")[0]  # txt文件路径
                img_path = self.dirname  # 当前图片文件夹路径
                xml_path = self.dirname  # xml文件的保存路径
                if not os.path.exists(xml_path):
                    os.mkdir(xml_path)

                def writeXml(imgname, imgpath, w, h, wxml):
                    # 创建xml根节点
                    doc = Document()
                    annotation = doc.createElement('annotation')
                    doc.appendChild(annotation)
                    # 指定文件夹
                    folder = doc.createElement('folder')
                    annotation.appendChild(folder)
                    folder_txt = doc.createTextNode(self.dirname.split("/")[-1])
                    folder.appendChild(folder_txt)
                    # 文件名
                    filename = doc.createElement('filename')
                    annotation.appendChild(filename)
                    filename_txt = doc.createTextNode(imgname)
                    filename.appendChild(filename_txt)
                    # 文件路径
                    path = doc.createElement('path')
                    annotation.appendChild(path)
                    path_txt = doc.createTextNode(imgpath)
                    path.appendChild(path_txt)
                    # 存储了文件来源相关信息
                    source = doc.createElement('source')
                    annotation.appendChild(source)
                    # 数据库
                    database = doc.createElement('database')
                    source.appendChild(database)
                    database_txt = doc.createTextNode("Unknown")
                    database.appendChild(database_txt)
                    # 文件尺寸大小
                    size = doc.createElement('size')
                    annotation.appendChild(size)

                    width = doc.createElement('width')
                    size.appendChild(width)
                    width_txt = doc.createTextNode(str(w))
                    width.appendChild(width_txt)

                    height = doc.createElement('height')
                    size.appendChild(height)
                    height_txt = doc.createTextNode(str(h))
                    height.appendChild(height_txt)

                    depth = doc.createElement('depth')
                    size.appendChild(depth)
                    depth_txt = doc.createTextNode("3")
                    depth.appendChild(depth_txt)
                    # 1、0值判断该图片是否参与分割任务
                    segmented = doc.createElement('segmented')
                    annotation.appendChild(segmented)
                    segmented_txt = doc.createTextNode("0")
                    segmented.appendChild(segmented_txt)

                    # 创建对象标签节点
                    for i in range(len(self.detect_image.bboxList)):
                        r_point = self.detect_image.bboxList[i][4]
                        # print(self.detect_image.bboxList[0][0])
                        # 创建对象标签节点
                        object = doc.createElement('object')
                        annotation.appendChild(object)
                        # 添加标签信息
                        name = doc.createElement('name')
                        object.appendChild(name)
                        name_content = doc.createTextNode(self.detect_image.bboxList[i][5])
                        name.appendChild(name_content)
                        # 观察角度
                        pose = doc.createElement('pose')
                        object.appendChild(pose)
                        pose_content = doc.createTextNode("Unspecified")
                        pose.appendChild(pose_content)
                        # 标注对象是否为截断的
                        truncated = doc.createElement('truncated')
                        object.appendChild(truncated)
                        truncated_content = doc.createTextNode("0")
                        truncated.appendChild(truncated_content)
                        # 是否为识别困难目标
                        difficult = doc.createElement('difficult')
                        object.appendChild(difficult)
                        difficult_content = doc.createTextNode("0")
                        difficult.appendChild(difficult_content)

                        # 添加边框信息
                        bndbox = doc.createElement('bndbox')
                        object.appendChild(bndbox)

                        xmin = doc.createElement('xmin')
                        bndbox.appendChild(xmin)
                        xmin_content = doc.createTextNode(str(self.detect_image.bboxList[i][0]/(r_point * 0.005)))
                        xmin.appendChild(xmin_content)

                        ymin = doc.createElement('ymin')
                        bndbox.appendChild(ymin)
                        ymin_content = doc.createTextNode(str(self.detect_image.bboxList[i][1]/(r_point * 0.005)))
                        ymin.appendChild(ymin_content)

                        xmax = doc.createElement('xmax')
                        bndbox.appendChild(xmax)
                        xmax_content = doc.createTextNode(str(self.detect_image.bboxList[i][2]/(r_point * 0.005)))
                        xmax.appendChild(xmax_content)

                        ymax = doc.createElement('ymax')
                        bndbox.appendChild(ymax)
                        ymax_content = doc.createTextNode(str(self.detect_image.bboxList[i][3]/(r_point * 0.005)))
                        ymax.appendChild(ymax_content)

                    with open(wxml, "w") as f:
                        # f.write(doc.toprettyxml(indent='\t', encoding='utf-8'))
                        f.write(doc.toprettyxml())  # 美化树形格式
                    f.close()
                    return

                f = open(ann_path, 'r')
                txt_list = f.readlines()
                f.close()
                im_name_list = []

                for line in txt_list:
                    line = line.strip()
                    line_split = line.split(' ')
                    img_name = line_split[0]
                    im_name_list.append(img_name)

                    fileimgpath = os.path.join(img_path + "/" + img_name)  # 文件路径
                    im = Image.open(fileimgpath)  # 获取文件尺寸大小
                    width = int(im.size[0])
                    height = int(im.size[1])
                    # print width,height
                    # print label_list
                    savename = os.path.join(xml_path, img_name.split('.')[0] + '.xml')  # 文件保存路径
                    writeXml(img_name, fileimgpath, width, height, savename)
                # curPath = QDir.currentPath()  # 获取系统当前目录
                # 最后的Yes表示弹框的按钮显示为Yes,默认按钮显示为OK,不填QMessageBox.Yes即为默认
                QMessageBox.information(self, "保存提示",
                                        "文件保存成功\n路径:%s/" % self.dirname)
            except IndexError:
                print("list index out of range")

    '''矩形框撤回'''
    def withdraw(self):
        if self.de_save != []:
            k = self.resize_point / self.de_save[4]  # 实时更新删除的矩形框信息
            dx0 = self.de_save[0] * k
            dy0 = self.de_save[1] * k
            dx1 = (self.de_save[2] - self.de_save[0]) * k + dx0
            dy1 = (self.de_save[3] - self.de_save[1]) * k + dy0
            if self.detect_image.row == -1:  # 如果删除的为最后一个,直接添加至列表,否则,按指定位置添加
                self.detect_image.bboxList.append((dx0, dy0, dx1, dy1, self.resize_point, self.de_save[-1]))
            else:
                self.detect_image.bboxList.insert(self.detect_image.row,
                                                  (dx0, dy0, dx1, dy1, self.resize_point, self.de_save[-1]))
            # 将标注加入列表
            self.label_list.clear()
            for i in range(len(self.detect_image.bboxList)):
                self.label_list.addItem(self.detect_image.bboxList[i][5])

            # print(self.save_label[0][1])
            # self.label_list.addItem(self.de_save[-1])

            print("已撤回")
            # print(self.bboxList)
            self.update()  # 更新数据
            self.de_save = []

    '''矩形框指定删除'''
    def remove_delete(self):
        if self.detect_image.bboxList != []:
            # select_box = self.detect_image.bboxList[self.label_list.currentRow()]
            select_box = self.detect_image.bboxList[self.detect_image.row]
            self.de_save = select_box  # 保留删除矩形框信息
            self.detect_image.bboxList.remove(select_box)  # 删除指定矩形框信息
            # 将标注加入列表
            self.label_list.clear()
            for i in range(len(self.detect_image.bboxList)):
                self.label_list.addItem(self.detect_image.bboxList[i][5])
            print("已删除")
            # print(self.bboxList)
            self.update()  # 更新数据

    '''保存到bbox列表'''
    def saveBBbox(self):
        if self.detect_image.x != self.detect_image.x0 and self.detect_image.y != self.detect_image.y0:  # 鼠标点击不保存
            if self.detect_image.c == 1:  # 图元内左键释放进行保存
                try:
                    tempx0 = min(self.detect_image.x0, self.detect_image.x1)  # 图元左上角与右下角坐标
                    tempy0 = min(self.detect_image.y0, self.detect_image.y1)
                    tempx1 = max(self.detect_image.x0, self.detect_image.x1)
                    tempy1 = max(self.detect_image.y0, self.detect_image.y1)
                    # 做标注
                    self.detect_image.text, ok = QInputDialog.getText(self, '标签标注', '请输入标注:')
                    if ok and self.detect_image.text != "":  # 判断标注是否成功
                        print(self.detect_image.text)
                        bbox = (tempx0, tempy0, tempx1, tempy1, self.resize_point, self.detect_image.text)  # 记录矩形框信息
                        self.detect_image.bboxList.append(bbox)
                    print("保存后矩形框信息:", self.detect_image.bboxList)
                    # 将标注加入列表
                    self.label_list.clear()
                    for i in range(len(self.detect_image.bboxList)):
                        self.label_list.addItem(self.detect_image.bboxList[i][5])
                    self.update()  # 信息更新
                except TypeError:
                    return

    '''选择矩形框'''
    def select_box(self):
        # print(self.label_list.currentRow())  # 列表项当前序号
        # print(self.detect_image.bboxList[self.label_list.currentRow()])  # 所选矩形框信息
        self.detect_image.row = self.label_list.currentRow()
        print(self.detect_image.bboxList[self.detect_image.row])
        self.detect_image.update()

    def change_mark(self):
        # 做标注
        if self.detect_image.bboxList != []:
            self.detect_image.text, ok = QInputDialog.getText(self, '标签标注', '请输入标注:')
            if ok and self.detect_image.text != "":  # 判断标注是否成功
                print(self.detect_image.text)
                box = self.detect_image.bboxList[self.detect_image.row]
                self.detect_image.bboxList[self.detect_image.row] = (box[0], box[1], box[2], box[3], box[4],
                                                                     self.detect_image.text)  # 记录矩形框信息
                # 将标注加入列表
                self.label_list.clear()
                for i in range(len(self.detect_image.bboxList)):
                    self.label_list.addItem(self.detect_image.bboxList[i][5])
                self.update()  # 信息更新

    '''鼠标点击击事件'''
    def mousePressEvent(self, event):
        if event.buttons() == Qt.LeftButton:
            self.left_flag = True
        if event.buttons() == Qt.RightButton:
            self.right_flag = True
        if event.buttons() == Qt.MidButton:
            self.mid_flag = True

    '''鼠标释放事件'''
    def mouseReleaseEvent(self, event):
        self.left_flag = False
        self.right_flag = False
        self.mid_flag = False
        self.mouse_mv_y = ""
        self.mouse_mv_x = ""

    '''鼠标移动事件'''
    def mouseMoveEvent(self, event):
        if self.warning == 0:
            if self.detect_image.rightflag == True:  # 右键对选择的矩形框端点缩放
                lt = self.detect_image.bboxList[self.detect_image.row]
                x0, y0, x1, y1, r, m = lt[0], lt[1], lt[2], lt[3], lt[4], lt[5]
                x = event.pos().x() - self.groupbox_x - self.label_x
                y = event.pos().y() - self.groupbox_y - self.label_y
                if self.detect_image.loc == 0:
                    self.detect_image.bboxList[self.detect_image.row] = (x, y, x1, y1, r, m)
                elif self.detect_image.loc == 1:
                    self.detect_image.bboxList[self.detect_image.row] = (x0, y, x, y1, r, m)
                elif self.detect_image.loc == 2:
                    self.detect_image.bboxList[self.detect_image.row] = (x, y0, x1, y, r, m)
                elif self.detect_image.loc == 3:
                    self.detect_image.bboxList[self.detect_image.row] = (x0, y0, x, y, r, m)
                elif self.detect_image.loc == -1:
                    if self.detect_image.move_x != "" and self.detect_image.move_y != "":  # 指定矩形框移动
                        dx = x - float(self.detect_image.move_x)
                        dy = y - float(self.detect_image.move_y)
                        self.detect_image.move_x = x
                        self.detect_image.move_y = y
                        self.detect_image.bboxList[self.detect_image.row] = (x0 + dx, y0 + dy, x1 + dx, y1 + dy, r, m)
            self.update()

            if self.mid_flag:  # 鼠标点击中键
                if self.detect_image.release == 1:  # 判断图元的中键是否释放(1:释放),是则初始化;为0时更新鼠标记录点
                    self.mouse_mv_y = ""
                    self.mouse_mv_x = ""
                    self.detect_image.release = 0
                self.m_x = event.pos().x()  # 鼠标移动坐标
                self.m_y = event.pos().y()
                if self.mouse_mv_x != "" and self.mouse_mv_y != "":
                    self.label_x = self.label_x + (self.m_x - self.mouse_mv_x)  # 鼠标移动距离
                    self.label_y = self.label_y + (self.m_y - self.mouse_mv_y)
                self.mouse_mv_x = self.m_x  # 记录鼠标之前坐标
                self.mouse_mv_y = self.m_y
                width = int(self.pix.width() * self.resize_point * 0.005)
                height = int(self.pix.height() * self.resize_point * 0.005)
                self.detect_image.setGeometry(int(self.label_x), int(self.label_y), width, height)
            if self.detect_image.midflag:  # 鼠标在图元里点击中键
                # print("在图片点击了中键")
                self.m_x = event.pos().x()
                self.m_y = event.pos().y()
                if self.detect_image.move_x != "" and self.detect_image.move_y != "":
                    self.label_x = self.label_x + (self.m_x - self.mouse_mv_x)
                    self.label_y = self.label_y + (self.m_y - self.mouse_mv_y)
                self.mouse_mv_x = self.m_x  # 记录鼠标之前坐标
                self.mouse_mv_y = self.m_y
                self.detect_image.move_x = self.m_x  # 不能为空,以确保图元坐标更新
                self.detect_image.move_y = self.m_y
                width = int(self.pix.width() * self.resize_point * 0.005)
                height = int(self.pix.height() * self.resize_point * 0.005)
                self.detect_image.setGeometry(int(self.label_x), int(self.label_y), width, height)
            # 绘制鼠标行进过程中的矩形框
            if self.detect_image.leftflag:  # 左键触发移动
                self.detect_image.x1 = event.pos().x() - self.groupbox_x - self.label_x
                self.detect_image.y1 = event.pos().y() - self.groupbox_y - self.label_y
                self.update()  # 实现画面更新

    '''鼠标滚轮事件'''
    def wheelEvent(self, event):
        if self.warning == 0:
            self.angle = event.angleDelta() / 8
            self.y = self.angle.y()
            # print(self.y)
            foot_l = 5  # 设置步长
            if self.pix != "":
                # 获取图元各端点
                x1_0 = self.label_x + self.groupbox_x
                x1_1 = self.label_x + self.pix.width() * self.resize_point * 0.005 + self.groupbox_x
                y1_0 = self.label_y + self.groupbox_y
                y1_1 = self.label_y + self.pix.height() * self.resize_point * 0.005 + self.groupbox_y
                self.r = self.resize_point  # 记录图元变化之前的大小因子
                k = 1
                m = []
                if event.x() > x1_0 and event.x() < x1_1 and event.y() > y1_0 and event.y() < y1_1:  # 鼠标在图元中
                    # print("鼠标在图内")
                    if int(self.y) > 0:  # 判断滚轮滚动方向
                        if self.resize_point >= 1 and self.resize_point <= (400 - foot_l):  # 放缩限制
                            self.resize_point += foot_l
                            k = self.resize_point / self.r  # 放缩倍率
                    else:
                        if self.resize_point > foot_l:
                            self.resize_point -= foot_l
                            k = self.resize_point / self.r
                    x2 = (1 - k) * event.x() + k * x1_0  # 放大后图元到主窗口的位置
                    y2 = (1 - k) * event.y() + k * y1_0
                    self.label_x = x2 - self.groupbox_x  # 获取图元在分组框坐标
                    self.label_y = y2 - self.groupbox_y
                    # 修改矩形框移动后坐标
                    if self.detect_image.bboxList != []:
                        if self.detect_image.bboxList[0][0] != "":
                            for i in range(len(self.detect_image.bboxList)):
                                d_x0 = self.detect_image.bboxList[i][0]  # 矩形框左上角在图元中的坐标
                                d_y0 = self.detect_image.bboxList[i][1]
                                d_x1 = self.detect_image.bboxList[i][2]  # 矩形框右下角在图元中的坐标
                                d_y1 = self.detect_image.bboxList[i][3]
                                d_x = d_x1 - d_x0  # 矩形框宽
                                d_y = d_y1 - d_y0  # 矩形框高
                                abl_x2_0 = d_x0 * k  # 矩形框缩放之后左上角在图元中的位置
                                abl_y2_0 = d_y0 * k
                                i_0 = float(abl_x2_0)
                                i_1 = float(abl_y2_0)
                                i_2 = i_0 + d_x * k  # 矩形框缩放之后右下角在图元中的位置
                                i_3 = i_1 + d_y * k
                                m.append(
                                    (i_0, i_1, i_2, i_3, self.resize_point, self.detect_image.bboxList[i][5]))
                            self.detect_image.bboxList = m  # 更新各矩形框数据
                            # print(self.detect_image.bboxList)

                else:  # 鼠标不在图元中
                    # print("鼠标不在图内")
                    x2 = (1 - k) * event.x() + k * x1_0  # 放大后图元到主窗口的位置
                    y2 = (1 - k) * event.y() + k * y1_0
                    self.label_x = x2 - self.groupbox_x  # 获取图元在分组框坐标
                    self.label_y = y2 - self.groupbox_y
                    if self.detect_image.bboxList != []:
                        if self.detect_image.bboxList[0][0] != "":
                            for i in range(len(self.detect_image.bboxList)):
                                d_x0 = self.detect_image.bboxList[i][0]
                                d_y0 = self.detect_image.bboxList[i][1]
                                d_x1 = self.detect_image.bboxList[i][2]
                                d_y1 = self.detect_image.bboxList[i][3]
                                d_x = d_x1 - d_x0
                                d_y = d_y1 - d_y0
                                abl_x2_0 = d_x0 * k
                                abl_y2_0 = d_y0 * k
                                i_0 = float(abl_x2_0)
                                i_1 = float(abl_y2_0)
                                i_2 = i_0 + d_x * k
                                i_3 = i_1 + d_y * k
                                m.append((i_0, i_1, i_2, i_3, self.resize_point, self.detect_image.bboxList[i][5]))
                            self.detect_image.bboxList = m
                            # print(self.detect_image.bboxList)
                width1 = int(self.pix.width() * self.resize_point * 0.005)  # 鼠标滚动图片宽度变化
                height1 = int(self.pix.height() * self.resize_point * 0.005)  # 鼠标滚动图片高度变化
                self.detect_image.resize(width1, height1)
                self.detect_image.move(int(self.label_x), int(self.label_y))

                # else:  # 鼠标不在图元上,按图元中心缩放
                #     dx = foot_l * self.pix.width() * 0.005
                #     dy = foot_l * self.pix.height() * 0.005
                #     if int(self.y) > 0:  # 判断滚轮滚动方向
                #         if self.resize_point >= 1 and self.resize_point <= 395:
                #             self.resize_point += foot_l
                #             x2 = x1_0 - dx/2
                #             y2 = y1_0 - dy/2
                #             self.label_x = x2 - self.groupbox_x
                #             self.label_y = y2 - self.groupbox_y
                #     else:
                #         if self.resize_point > foot_l:
                #             self.resize_point -= foot_l
                #             x2 = x1_0 + dx/2
                #             y2 = y1_0 + dy/2
                #             self.label_x = x2 - self.groupbox_x
                #             self.label_y = y2 - self.groupbox_y

    '''设置打开文件事件'''
    def openfile(self):
        '''如果打开文件夹而不选图片会报错!'''
        # 获取文件夹路径
        self.sure_path = filedialog.askopenfilename()
        if self.sure_path != "":
            self.f_path = self.sure_path  # 记忆文件夹路径
            self.resize_point = 50  # 重新设置图元大小
        try:
            self.img_list.clear()  # 清理所有列表项
            self.s = []  # 清空所有图片路径信息
            self.name = []  # 清空所有图片名
            self.dirname, self.filename = os.path.split(self.f_path)  # 获取文件夹路径,文件名
            # print(self.dirname)
            self.files = os.listdir(self.dirname)  # 遍历文件夹所有文件
            point_list = ['tif', 'TIF', 'tiff', 'TIFF', 'png', 'PNG', 'jpg', 'JPG', 'jpeg', 'JPEG']  # 文件类型
            for file in self.files:   # 遍历文件夹,查看是否有当前文件的xml文件
                for i in range(len(point_list)):
                    if file.split('.')[-1] == point_list[i]:
                        fin = self.dirname + r"/" + file
                        self.s.append(fin)  # 图片路径添加
                        self.name.append(file)  # 图片名添加
                # 判断文件夹是否有当前图片的xml文件
                if file.split('.')[0] == self.filename.split('.')[0] and file.split('.')[-1] == 'xml':
                    # print(file)
                    # 读取xml文件,获取xml内容
                    # 使用minidom解析器打开 XML 文档
                    DOMTree = xml.dom.minidom.parse(self.dirname + '/' + file)
                    collection = DOMTree.documentElement
                    # if collection.hasAttribute("annotation"):
                    #     print("根元素 : %s" % collection.getAttribute("annotation"))
                    # 在集合中获取所有矩形框
                    objects = collection.getElementsByTagName("object")
                    # 遍历文件所有矩形框
                    box = []  # 储存所有矩形框信息
                    for object in objects:
                        # print("*****object*****")
                        name = object.getElementsByTagName('name')[0]  # 矩形框标注
                        # print("标注为:", name.childNodes[0].data)
                        bndbox = object.getElementsByTagName('bndbox')[0]  # 矩形框信息
                        xmin = bndbox.getElementsByTagName('xmin')[0]
                        ymin = bndbox.getElementsByTagName('ymin')[0]
                        xmax = bndbox.getElementsByTagName('xmax')[0]
                        ymax = bndbox.getElementsByTagName('ymax')[0]
                        # print("xmin = %s" % xmin.childNodes[0].data)
                        # print("ymin = %s" % ymin.childNodes[0].data)
                        # print("xmax = %s" % xmax.childNodes[0].data)
                        # print("ymax = %s" % ymax.childNodes[0].data)
                        x0 = float(xmin.childNodes[0].data) * self.resize_point * 0.005
                        y0 = float(ymin.childNodes[0].data) * self.resize_point * 0.005
                        x1 = float(xmax.childNodes[0].data) * self.resize_point * 0.005
                        y1 = float(ymax.childNodes[0].data) * self.resize_point * 0.005
                        box.append((x0, y0, x1, y1, self.resize_point, name.childNodes[0].data))
                    self.detect_image.bboxList = box  # 转移矩形框信息
                    # 将标注加入列表
                    self.label_list.clear()
                    for i in range(len(self.detect_image.bboxList)):
                        self.label_list.addItem(self.detect_image.bboxList[i][5])
            self.add = self.name  # 另存图片名,添加到列表项,防止重复添加
        except FileNotFoundError:
            return
        # print('\n获取的文件地址:', self.save_path)

    '''文件展示'''
    def show_path(self):
        '''如果打开文件夹而不选图片会报错!'''
        try:
            # 控制图片大小
            # self.resize_point = 50
            print("上一个文件路径:", self.f_path)
            print("当前图片路径为:", self.sure_path)  # 获取当前图片路径
            if self.sure_path != '':
                now_folder_path, self.now_file_name = os.path.split(self.sure_path)  # 获取当前文件的 文件夹路径,文件名
                m = 0  # 记录列表是否有之前图片信息 (1/0:有/无)
                if self.detect_image.bboxList!= []:  # 判断当前矩形框信息是否为空
                    if self.save_label != []:  # 判断列表是否为空
                        # print("更新前", self.save_label)
                        # 更新换图之前的图片信息
                        for i in range(len(self.save_label)):  # 遍历列表
                            if self.save_label[i][0] == self.f_path:  # 列表有上一张图片信息
                                del self.save_label[i]
                                self.save_label.append((self.f_path, self.detect_image.bboxList))
                                m = 1
                                break
                        if m != 1:  # 如果列表没有之前图片信息,将图片信息添加进列表
                            self.save_label.append((self.f_path, self.detect_image.bboxList))
                        # print("更新后", self.save_label)
                    else:  # 如果储存列表为空,直接添加图片信息
                        self.save_label.append((self.f_path, self.detect_image.bboxList))

                n = 0  # 记录列表是否有当前图片信息 (1/0:有/无)
                for i in range(len(self.save_label)):  # 遍历列表,查看列表是否有当前图片信息
                    if self.save_label[i][0] == self.sure_path:  # 列表有当前图片信息
                        self.detect_image.bboxList = self.save_label[i][1]  # 更新当前图片矩形框信息
                        print("当前图片矩形框信息为:", self.save_label[i][1])
                        n = 1  # 列表有当前图片信息
                        self.resize_point = self.save_label[i][1][0][4]  # 调整当前图片大小因子
                        break  # 更新信息后跳出循环
                if n != 1:  # 当前图片无矩形框信息
                    self.detect_image.bboxList = []

                # 将标注加入列表
                self.label_list.clear()
                for i in range(len(self.detect_image.bboxList)):
                    self.label_list.addItem(self.detect_image.bboxList[i][5])

                self.f_path = self.sure_path
                # print(self.f_path)
            else:
                now_folder_path, self.now_file_name = os.path.split(self.f_path)  # 获取上一个文件的 文件夹路径,文件名
            self.de_save = []  # 切图,暂存的矩形框信息为空

            if self.add == self.name:  # 添加列表项
                self.img_list.addItems(self.name)
                self.name = []  # 添加后设定self.name和self.add不等,确保不重复添加
            # 选择文件夹任意一张图片
            for i in range(len(self.s)):
                if self.f_path == self.s[i]:
                    self.count = i
            # 获取图片名称再加上路径,利用QPixmap来展示出来,将pix变成你选择的图片
            self.pix = QPixmap(self.f_path)
            # 再利用setPixmap函数将界面上的detect_image控件从标签转换成图片
            self.detect_image.setPixmap(self.pix)
            # if self.pix.width() < 200 and self.pix.height() < 200:
            #     self.resize_point = 150
            # if self.pix.width() > 3000 or self.pix.height() > 3000:
            #     self.resize_point = 10
            width = int(self.pix.width() * self.resize_point * 0.005)
            height = int(self.pix.height() * self.resize_point * 0.005)
            # 图片初始大小和位置
            self.detect_image.resize(width, height)
            self.detect_image.move(int(self.label_x), int(self.label_y))
            # 由于图片的分辨率不为统一,所以可以加上setScaledContents(True)这个函数将其设置为自适应大小
            # (之前设置界面时所设置的控件大小)
            self.detect_image.setScaledContents(True)
            self.detect_image.setVisible(True)
            # groove 3D, solid 实线
            # self.detect_image.setStyleSheet(
            #     'border-width: 1px;border-style: groove;border-color: rgb(255, 170, 0);')
            # 图片属性
            # if os.path.getsize(self.f_path) < 1024 * 1024:
            #     self.view_size = (os.path.getsize(self.f_path) / 1024)
            #     self.dw = 'KB'
            # if os.path.getsize(self.f_path) > 1024 * 1024:
            #     self.view_size = (os.path.getsize(self.f_path) / (1024 * 1024))
            #     self.dw = 'MB'
            # self.detect_image.setToolTip("[ %s ] 属性" % self.filename + '\n' +
            #                              "项目类型:%s 文件" % self.f_path.split('.')[-1] + '\n' +
            #                              "分辨率:%s x %s" % (self.pix.width(), self.pix.height()) + '\n' +
            #                              "大小:%.2f %s" % (self.view_size, self.dw) + '\n' +
            #                              "位深度:%d" % self.pix.depth() + '\n' +
            #                              "路径:%s" % self.f_path)
            self.warning = 0
            # self.detect_image.update()  # 更新读入图片的信息

        except FileNotFoundError:
            print("未选择图片路径")
            self.warning = 1
            self.f_path = self.sure_path

    '''设置图片属性'''
    def Attribute(self):
        '''如果打开文件夹而不选图片会报错!'''
        if self.warning == 0:
            try:
                if os.path.getsize(self.f_path) < 1024 * 1024:
                    self.view_size = (os.path.getsize(self.f_path) / 1024)
                    self.dw = 'KB'
                if os.path.getsize(self.f_path) > 1024 * 1024:
                    self.view_size = (os.path.getsize(self.f_path) / (1024 * 1024))
                    self.dw = 'MB'
                QMessageBox.about(self, "属性", "项目类型:%s 文件" % self.f_path.split('.')[-1] + '\n' +
                                  "分辨率:%s x %s" % (self.pix.width(), self.pix.height()) + '\n' +
                                  "大小:%.2f %s" % (self.view_size, self.dw) + '\n' +
                                  "位深度:%d" % self.pix.depth() + '\n' +
                                  "路径:%s" % self.f_path)
            except FileNotFoundError:
                print("系统找不到指定的路径")
                self.f_path = self.sure_path

    '''设置图片另存'''
    def saveImage(self):
        # 提取Qlabel中的图片
        if self.warning == 0:
            try:
                img = self.detect_image.pixmap().toImage()  # 标签转图片
                fpath, ftype = QFileDialog.getSaveFileName(self, "保存图片", "d:\\",
                                                           "*.jpg;;*.png;;*.jpeg;;*.tif;;All Files(*)")
                img.save(fpath)  # 保存路径
                print(ftype)
            except AttributeError:
                print("'NoneType' object has no attribute 'toImage'")

    '''设置路径改变'''
    def change_path(self):
        # print(self.mylist.currentRow())  # 列表项当前序号
        self.sure_path = self.s[self.img_list.currentRow()]  # 所选列表项文件的路径
        # print(self.add[self.mylist.currentRow()])

    '''设置右键单击事件'''
    def contextMenuEvent(self, evt):
        # 右击窗口,会调用这个方法,只要在里面创建菜单就可以了
        # 创建对象
        menu = QMenu(self)
        act_o = QAction("打开文件", menu)  # 打开图片
        act_o.triggered.connect(self.openfile)
        act_o.triggered.connect(self.show_path)
        act_s = QAction("图片另存", menu)  # 保存图片
        act_s.triggered.connect(self.saveImage)
        act_attribute = QAction("图片属性", menu)  # 图片属性
        act_attribute.triggered.connect(self.Attribute)
        act_e = QAction("退出", menu)  # 退出
        act_e.triggered.connect(self.close_w)
        act_change = QAction("修改标注", menu)  # 修改标注
        act_change.triggered.connect(self.change_mark)
        # 子菜单设置
        # menu_s.addAction(act_r)
        # 添加动作
        menu.addAction(act_o)
        menu.addAction(act_s)
        menu.addAction(act_attribute)
        menu.addAction(act_change)
        menu.addSeparator()  # 添加隔开线
        menu.addAction(act_e)
        # menu.addAction(act_r)
        # menu.addMenu(menu_s)
        # 展示菜单
        menu.exec_(evt.globalPos())  # 在鼠标右键的位置打开菜单,参数是窗口坐标

    '''设置退出'''
    def close_w(self):
        self.deleteLater()  # 删除主窗口

    '''设置下一张'''
    def add_Select_image(self):  # 下一张图
        if self.warning == 0:
            self.count += 1
            try:
                self.sure_path = self.s[self.count]  # 切换图片路径
            except:
                print("超出范围")
                self.sure_path = self.s[0]  # 超出范围重新读图
                self.count = 0

    '''设置上一张'''
    def red_Select_image(self):  # 上一张图
        if self.warning == 0:
            self.count -= 1
            try:
                self.sure_path = self.s[self.count]  # 切换图片路径
                # print(self.count)
            except:
                print("低出范围")
                self.sure_path = self.s[len(self.s) - 1]
                self.count = len(self.s) - 1


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)  # 建立QApplicaion对象
    win = MainWindow()
    win.setWindowTitle("My Photo")  # 设置标题
    win.setWindowIcon(QIcon(r'D:\PyQt5\icon\pic.png'))  # 设置标题图标
    # 显示窗口
    win.show()
    # 执行事件循环,至此程序就运行起来了。当主窗体销毁的时候(例如我们点击了主窗体右上角的关闭按钮),该函数将返回0。正好被sys.exit函数接收,程序退出
    sys.exit(app.exec_())
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值