这个图像标注版本在前面多标注框基础上,增加了标注标签的选择,同时修正了一下之前绘制最后一个标注框的显示问题,现在看起来更像一个标注软件了。
参照labelImg的样式定义了一个自定义Dialog窗口,在这个窗口中加载了标注标签列表文件,同时这个标签是要必须选择的,或者取消。
对多标注框的代码重新做了优化,一个是关于正在绘制的标注框的显示问题,如果标签取消,则不予绘制,如果选择了标签才绘制出来
一、通过qt designer设计一个标签选择的自定义Dialog窗口
代码语言:javascript复制# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'ui_labelchoose.ui'
#
# Created by: PyQt5 UI code generator 5.15.4
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(285, 336)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(Dialog.sizePolicy().hasHeightForWidth())
Dialog.setSizePolicy(sizePolicy)
Dialog.setMinimumSize(QtCore.QSize(285, 336))
Dialog.setMaximumSize(QtCore.QSize(285, 336))
self.buttonBox = QtWidgets.QDialogButtonBox(Dialog)
self.buttonBox.setGeometry(QtCore.QRect(80, 39, 193, 28))
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
self.buttonBox.setObjectName("buttonBox")
self.leditChoosedLabel = QtWidgets.QLineEdit(Dialog)
self.leditChoosedLabel.setGeometry(QtCore.QRect(11, 11, 261, 21))
self.leditChoosedLabel.setObjectName("leditChoosedLabel")
self.leditChoosedLabel.setEnabled(False)
self.lviewLabelList = QtWidgets.QListView(Dialog)
self.lviewLabelList.setGeometry(QtCore.QRect(10, 80, 261, 241))
self.lviewLabelList.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.lviewLabelList.setObjectName("lviewLabelList")
self.retranslateUi(Dialog)
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
二、实现自定义Dialog的相关功能
一个是初始化过程中对标签列表文件的加载
一个是QListView的点击事件
一个是Dialog的返回值
最后一个是对OK按钮事件的校验,确保已经选择了标签
代码语言:javascript复制from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QMainWindow, QApplication, QDialog,QMessageBox
from PyQt5.QtCore import QStringListModel
class DialogChoooseLabelWin(QDialog, Ui_Dialog):
def __init__(self,parent=None):
# super(DialogChoooseLabelWin, self).__init__()
QDialog.__init__(self,parent)
self.setupUi(self)
self.labelList=[]
self.initLableList()
self.lviewLabelList.clicked.connect(self.clickedlist)
self.buttonBox.accepted.connect(self.validate)
self.buttonBox.rejected.connect(self.reject)
def initLableList(self):
with open('datalabellistbak.txt', 'r',encoding='utf-8') as f:
self.labelList=[line.strip() for line in f]
self.labelslm=QStringListModel()
self.labelslm.setStringList(self.labelList)
self.lviewLabelList.setModel(self.labelslm)
def clickedlist(self, qModelIndex):
self.leditChoosedLabel.setText(self.labelList[qModelIndex.row()])
def getValue(self):
return self.leditChoosedLabel.text()
def validate(self):
if self.leditChoosedLabel.text()!='':
self.accept()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
Dialog=DialogChoooseLabelWin()
print('dialogChooseLabel.exec_()=', Dialog.exec_())
print('dialogChooseLabel.getValue()=', Dialog.getValue())
sys.exit(app.exec_())
三、对原来的MyLabel进行重写,参见加粗字体部分
引入了一个实时坐标的概念
在鼠标移动事件中,不断根据鼠标位置进行实时绘制
在鼠标释放事件中,增加了一个对话框选择项,确认后将相关标注项加入到bboxlist中(bboxlist相对于2.0版本有所调整)
在绘制事件中,修正了对实时标注框的单独绘制
代码语言:javascript复制from PyQt5.QtWidgets import QWidget, QApplication, QLabel
from PyQt5.QtCore import QRect, Qt
from PyQt5.QtGui import QPixmap, QPainter, QPen
from ui_labelchoose import DialogChoooseLabelWin
import sys
# 重定义QLabel,实现绘制事件和各类鼠标事件
class MyLabel(QLabel):
def __init__(self, parent=None):
'''
:param parent:
初始化基本参数
'''
super(MyLabel, self).__init__(parent)
self.x0 = 0
self.y0 = 0
self.x1 = 0
self.y1 = 0
self.x1RealTime = 0
self.y1RealTime = 0
self.rect = QRect()
self.flag = False
# 增加一个存储标注框坐标的列表
self.bboxList=[]
# 单击鼠标触发事件
# 获取鼠标事件的开始位置
def mousePressEvent(self, event):
# 将绘制标志设置为True
self.flag = True
self.x0 = event.pos().x()
self.y0 = event.pos().y()
# 鼠标移动事件
# 绘制鼠标行进过程中的矩形框
def mouseMoveEvent(self, event):
if self.flag:
self.x1RealTime = event.pos().x()
self.y1RealTime = event.pos().y()
self.update()
# 鼠标释放事件
def mouseReleaseEvent(self, event):
# 将绘制标志设置为False
self.flag = False
self.x1 = event.pos().x()
self.y1 = event.pos().y()
self.x1RealTime = self.x0
self.y1RealTime = self.y0 # 这样就不用画出实时框了
# 将标注框的四个坐标轴存储到bboxList
dialogChooseLabel = DialogChoooseLabelWin()
if dialogChooseLabel.exec_():
labelname = dialogChooseLabel.getValue()
self.saveBBbox(self.x0, self.y0, self.x1, self.y1, labelname)
event.ignore()
# 绘制事件
def paintEvent(self, event):
super().paintEvent(event)
painter = QPainter()
# 增加绘制开始和结束时间
painter.begin(self)
# 遍历之前存储的标注框坐标列表
for point in self.bboxList:
rect = QRect(point[0], point[1], abs(point[0]-point[2]), abs(point[1]-point[3]))
painter.setPen(QPen(Qt.red, 2, Qt.SolidLine))
painter.drawRect(rect)
painter.drawText(point[0], point[1], point[4])
# 绘制当前标注框的举行
# 构造矩形框的起始坐标和宽度、高度
tempx0 = min(self.x0, self.x1RealTime)
tempy0 = min(self.y0, self.y1RealTime)
tempx1 = max(self.x0, self.x1RealTime)
tempy1 = max(self.y0, self.y1RealTime)
width=tempx1-tempx0
height=tempy1-tempy0
currect = QRect(tempx0, tempy0, width, height)
# 构造QPainter,进行矩形框绘制
painter.setPen(QPen(Qt.blue, 1, Qt.SolidLine))
painter.drawRect(currect)
painter.end()
# 保存到bbox列表
def saveBBbox(self,x0,y0,x1,y1,labelname):
tempx0 = min(x0, x1)
tempy0 = min(y0, y1)
tempx1 = max(x0, x1)
tempy1 = max(y0, y1)
bbox = (tempx0, tempy0, tempx1, tempy1,labelname)
self.bboxList.append(bbox)
四、窗口类和main函数未做调整
代码语言:javascript复制# 窗口类
class LabelV1(QWidget):
def __init__(self):
super(LabelV1, self).__init__()
self.initUI()
def initUI(self):
self.resize(960, 540)
self.move(100, 50)
self.setWindowTitle('Label标注框3.0版本')
# 加载重定义的label
self.lbl = MyLabel(self)
# 构造QPixmap,加载待标注图片
img = QPixmap('010.png')
# 在自定义label中显示QImage
self.lbl.setPixmap(img)
self.lbl.setCursor(Qt.CrossCursor)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
labelwin = LabelV1()
sys.exit(app.exec())