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_())
PyQt5学习-简易的看图工具
于 2024-06-06 11:20:12 首次发布
本文介绍了一个使用PyQt5库开发的简易看图工具,该工具支持矩形框标注,允许用户通过鼠标操作进行绘制、选择和移动矩形框。工具集成了图像显示、标注功能,并能保存和加载标注信息。通过重载QLabel类,实现了鼠标事件的处理,包括左键绘制、中键选择和右键缩放矩形框。
摘要由CSDN通过智能技术生成