diff --git a/Athena/.idea/workspace.xml b/Athena/.idea/workspace.xml index 6ad3f9f..ca39b0b 100644 --- a/Athena/.idea/workspace.xml +++ b/Athena/.idea/workspace.xml @@ -2,9 +2,7 @@ - - @@ -21,7 +19,7 @@ - + - + - - + + - - - - - + + + - + - - + + - - - - - + + - + - - - - - - - - - - - - @@ -97,6 +78,11 @@ @@ -148,7 +135,6 @@ - @@ -169,14 +155,15 @@ + - + - + @@ -482,26 +469,56 @@ - + - + + - + - @@ -524,104 +541,121 @@ - - - + - + - - - - + + + + - + - + - + + + + + + + + + - + - - - - - + + + - + - + - + - + - + + - + - + - - - - + + + + - + - + - + + + + + + + + + - - - - - + + + @@ -649,19 +683,6 @@ - - - - - - - - - - - - - @@ -669,14 +690,12 @@ - + - - - - - + + + @@ -684,7 +703,7 @@ - + @@ -704,34 +723,29 @@ - - - - + - + - + + + + - + - + - - - - - - + @@ -746,22 +760,9 @@ - + - - - - - - - - - - - - - @@ -772,11 +773,9 @@ - - - - - + + + @@ -794,19 +793,6 @@ - - - - - - - - - - - - - @@ -814,14 +800,12 @@ - + - - - - - + + + @@ -829,29 +813,16 @@ - + - + - - - - - - - - - - - - - @@ -862,31 +833,26 @@ - - - - - + + + - + - + - + - - - @@ -894,44 +860,45 @@ - + - - - - - + + + - + - + + + + - + - + - - - - + + + + - + - + - + @@ -995,16 +962,6 @@ - - - - - - - - - - @@ -1012,54 +969,41 @@ - + - - + + - + - - - - - - - - - + - + - + - - - - + + + + - + - - + + - - - - - - + diff --git a/Athena/README b/Athena/README new file mode 100644 index 0000000..9c51fec --- /dev/null +++ b/Athena/README @@ -0,0 +1,7 @@ +#Branch for Circle Annotation + +##Useage + +Select three points by click left mouse button, click right mouse button to fit the circle. + +Circles will be saved in ./result \ No newline at end of file diff --git a/Athena/athena.py b/Athena/athena.py index ae4df76..7b7c367 100644 --- a/Athena/athena.py +++ b/Athena/athena.py @@ -12,8 +12,8 @@ import pickle FILE_TYPE = ['jpg', 'jpeg', 'tif', 'bmp', 'gif', 'png'] -RECT = 0 -POLY = 1 +CIRC = 0 +ELLI = 1 class MainWindow(QtWidgets.QMainWindow): # _signal = QtCore.pyqtSignal() @@ -25,7 +25,6 @@ def __init__(self): self.settings = QtCore.QSettings('setting.ini', QtCore.QSettings.IniFormat) # 读入路径 self.imageFolder = self.showFileDialog() - # print(self.imageFolder,"............") # self.imageFolder = '/Users/philokey/Comic' #调试方便 # 屏幕居中 @@ -108,25 +107,10 @@ def initButton(self): slGroup.addButton(self.larRad) buttonLayout.addLayout(slButtonLayout) - - # radio group - fbGroup = QtWidgets.QButtonGroup(self) - fbradButtonLayout = QtWidgets.QHBoxLayout() - self.frameRad = QtWidgets.QRadioButton("Frame") - self.frameRad.clicked.connect(self.clickFrameRad) - self.ballRad = QtWidgets.QRadioButton("Balloon") - self.ballRad.clicked.connect(self.clickBallRad) - self.frameRad.setChecked(True) - fbradButtonLayout.addWidget(self.frameRad) - fbradButtonLayout.addWidget(self.ballRad) - fbGroup.addButton(self.frameRad) - fbGroup.addButton(self.ballRad) - buttonLayout.addLayout(fbradButtonLayout) - # box shape rpGroup = QtWidgets.QButtonGroup(self) rpradButtonLayout = QtWidgets.QHBoxLayout() - self.recRad = QtWidgets.QRadioButton("Rectangle") + self.recRad = QtWidgets.QRadioButton("Circle") self.recRad.clicked.connect(self.clickRecRad) self.recRad.setChecked(True) self.polyRad = QtWidgets.QRadioButton("Polygon") @@ -137,24 +121,6 @@ def initButton(self): rpGroup.addButton(self.polyRad) buttonLayout.addLayout(rpradButtonLayout) - # checkBox - self.confusedCheckBox = QtWidgets.QCheckBox("Confused") - self.confusedCheckBox.clicked.connect(self.clickConfusedCheckBox) - buttonLayout.addWidget(self.confusedCheckBox) - - # detect - self.detectOfflineBox = QtWidgets.QCheckBox("Detect Offline") - # self.detectModeBox.clicked.connect(self.clickDetectModeBox) - buttonLayout.addWidget(self.detectOfflineBox) - - decLayout = QtWidgets.QVBoxLayout() - self.detectBtn = QtWidgets.QPushButton('Auto Detect') - self.detectBtn.clicked.connect(self.clickDetectBtn) - decLayout.addWidget(self.detectBtn) - self.dectState = QtWidgets.QLabel('') - self.dectState.setMaximumHeight(10) - decLayout.addWidget(self.dectState) - buttonLayout.addLayout(decLayout) self.deleteBtn = QtWidgets.QPushButton('Delete') self.deleteBtn.clicked.connect(self.clickDeleteBtnBtn) @@ -193,66 +159,18 @@ def clickLarRad(self): self.imageContainer.isLarge = True self.imageContainer.loadImage(self.imageContainer.imagePath) - def clickFrameRad(self): - self.imageContainer.type = 0 - - def clickBallRad(self): - self.imageContainer.type = 1 - def clickRecRad(self): self.imageContainer.boxShape = 0 def clickPolyRad(self): self.imageContainer.boxShape = 1 - def clickConfusedCheckBox(self): - if self.confusedCheckBox.isChecked(): - self.imageContainer.isConfused = True - else: - self.imageContainer.isConfused = False - def clickClearBtn(self): self.imageContainer.clearImage() def clickReloadBtn(self): self.imageContainer.loadImage(self.imageContainer.imagePath) - def clickDetectBtn(self): - if self.imageContainer.imagePath == '': - print("Where is image?") - return - exeDir = osp.join(os.getcwd(), 'Storyboard') - if self.detectOfflineBox.isChecked(): - if osp.isfile(exeDir): - print("local") - self.dectState.setText("detecting...") - self.repaint() - resultDir, resultName = self.imageContainer.getOutputFileName(0) # 0 is frame - fullPath = os.path.join(resultDir, resultName) - # print(exeDir+' ' + self.imageContainer.imagePath + ' ' + fullPath) - subprocess.run([exeDir, self.imageContainer.imagePath, fullPath]) - # os.system(exeDir+' ' + self.imageContainer.imagePath + ' ' + fullPath) - text = open(fullPath).read() - self.dectState.setText("") - else: - QtWidgets.QMessageBox.critical(self, "Error", "Local detecting program is NOT FOUND!") - return - else: - url = 'http://10.1.89.8:8080/' - img = {'file':open(self.imageContainer.imagePath, 'rb')} - self.dectState.setText("detecting...") - self.repaint() - try: - # print(url) - r = requests.post(url, files=img) - except: - QtWidgets.QMessageBox.critical(self, "Error", "Failed to establish connection.") - return - text = r.text - self.repaint() - self.imageContainer.parseDetectedResult(text, 0) - self.imageContainer.paintTotalResult() - self.imageContainer.isModified = True def clickDeleteBtnBtn(self): tp = self.imageContainer.type @@ -280,20 +198,12 @@ def clickPreBtn(self): def dirTreeClicked(self): #获取选择的路径 pathSelected = self.dirModel.filePath(self.dirTreeView.selectedIndexes()[0]) + self.imageContainer.setMinimumWidth(self.geometry().width()*0.6) if os.path.isfile(pathSelected) and pathSelected.split('.')[-1].lower() in FILE_TYPE: - self.confusedCheckBox.setChecked(False) self.imageContainer.loadImage(pathSelected) resultDir, r0 = self.imageContainer.getOutputFileName(0) resultDir, r1 = self.imageContainer.getOutputFileName(1) - if not os.path.isfile(os.path.join(resultDir, r0)) and not os.path.isfile(os.path.join(resultDir, r1)): - self.dectState.setText("Not Detected") - else: - self.dectState.setText("") - if self.frameRad.isChecked(): - self.imageContainer.type = 0 - else: - self.imageContainer.type = 1 def showFileDialog(self): # 获取标注图片的文件夹 # fname = QtWidgets.QFileDialog.getOpenFileName(self, 'Open file', '/home') diff --git a/Athena/imageContainer.py b/Athena/imageContainer.py index 48861f0..0893bb7 100644 --- a/Athena/imageContainer.py +++ b/Athena/imageContainer.py @@ -1,11 +1,14 @@ from PyQt5 import QtWidgets, QtCore, QtGui import os +import math +import cv2 +import numpy as np MAXWIDTH = 750 MAXHEIGHT = 600 -RECT = 0 -POLY = 1 +CIRC = 0 +ELLI = 1 class ImageContainer(QtWidgets.QFrame): def __init__(self, widgets = None): @@ -13,7 +16,7 @@ def __init__(self, widgets = None): containerLayout = QtWidgets.QVBoxLayout() self.graphicsView = QtWidgets.QGraphicsView() - self.graphicsView.setCursor(QtCore.Qt.CrossCursor) + # self.graphicsView.setCursor(QtCore.Qt.CrossCursor) self.graphicsView.setObjectName("graphicsView") # self.image can be painted self.image = QtGui.QPixmap() @@ -25,26 +28,22 @@ def __init__(self, widgets = None): # 当前绘制的多边形的顶点 self.vertexes = [] # frames and balloons - self.result = [[], []] + self.result = [] self.isModified = False - self.isConfused = False self.isLarge = False # 0 for rectangle, 1 for polygon - self.boxShape = 0 + self.boxShape = ELLI self.scale = 1 self.imagePath = '' self.preImagePath = '' - self.type = 0 # 0 frame, 1 Balloon self.boxColor = (QtCore.Qt.blue, QtCore.Qt.red) - self.typeName = ('Frame', 'Balloon') self.typeId = (0, 1) def loadImage(self, path): self.vertexes = [] - self.result = [[], []] + self.result = [] self.isModified = False - self.isConfused = False print(path) self.image.load(path, '1') #read image self.imagePath = path @@ -72,9 +71,13 @@ def loadImage(self, path): else: self.scale = 1 self.oriImage = self.image.copy() - self.loadExistResult(path) + # self.loadExistResult(path) + scene = QtWidgets.QGraphicsScene() + scene.addPixmap(self.image) + self.graphicsView.setScene(scene) def loadExistResult(self, path): + ''' splitedPath = self.imagePath.split('/') baseDir = '/'.join(splitedPath[:-1]) imageName = splitedPath[-1] @@ -86,11 +89,13 @@ def loadExistResult(self, path): resultTxt = open(fullPath).read() self.parseDetectedResult(resultTxt, t) self.paintTotalResult() + ''' + pass def clearImage(self): if self.imagePath != '': self.isModified = True - self.result = ([], []) + self.result = [] self.vertexes.clear() self.image = self.oriImage.copy() scene = QtWidgets.QGraphicsScene() @@ -102,39 +107,15 @@ def clearImage(self): def mousePressEvent(self, event): if event.button() == QtCore.Qt.LeftButton: self.paintVertex(event) - if self.boxShape == POLY: - self.paintLine(True) elif event.button() == QtCore.Qt.RightButton: self.isModified = True if len(self.vertexes) < 2: return - if self.boxShape == RECT: - if len(self.vertexes) == 2: - self.paintRectangle() - else: - self.paintPolygon() - else: - self.paintLine(False) - - def paintLine(self, isLeft): - n = len(self.vertexes) - if n < 2: return - tp = self.type - painter = self.initMyPainter(tp) - if isLeft: - painter.drawLine(self.vertexes[n - 2], self.vertexes[n - 1]) - else: - mx = sum(v.x() for v in self.vertexes) / n - my = sum(v.y() for v in self.vertexes) / n - self.result[tp].append(self.vertexes[:]) - painter.drawLine(self.vertexes[-1], self.vertexes[0]) - painter.drawText(QtCore.QPoint(mx, my), str(len(self.result[tp]))) - self.vertexes.clear() - scene = QtWidgets.QGraphicsScene() - # scene.addPixmap(QtGui.QPixmap.fromImage(self.image)) - scene.addPixmap(self.image) - self.graphicsView.setScene(scene) + if self.boxShape == CIRC: + self.paintCircle() + elif self.boxShape == ELLI: + self.fitEllipse() def paintVertex(self, event): painter = QtGui.QPainter(self.image) @@ -147,7 +128,7 @@ def paintVertex(self, event): p = self.graphicsView.mapToScene(event.pos()) - self.graphicsView.pos() - QtCore.QPoint(2, 1) p = QtCore.QPoint(p.x(), p.y()) painter.drawPoint(p) - + print(p) # print('maptosence: ', p) self.vertexes.append(p) scene = QtWidgets.QGraphicsScene() @@ -167,36 +148,44 @@ def initMyPainter(self, tp): painter.setFont(font) return painter - def paintRectangle(self): - painter = self.initMyPainter(self.type) - topLeft = QtCore.QPoint(min(self.vertexes[0].x(), self.vertexes[1].x()), - min(self.vertexes[0].y(), self.vertexes[1].y())) - topRight = QtCore.QPoint(min(self.vertexes[0].x(), self.vertexes[1].x()), - max(self.vertexes[0].y(), self.vertexes[1].y())) - bottomleft = QtCore.QPoint(max(self.vertexes[0].x(), self.vertexes[1].x()), - min(self.vertexes[0].y(), self.vertexes[1].y())) - bottomRight = QtCore.QPoint(max(self.vertexes[0].x(), self.vertexes[1].x()), - max(self.vertexes[0].y(), self.vertexes[1].y())) - self.result[self.type].append([topLeft, topRight, bottomRight, bottomleft]) - rect = QtCore.QRect(topLeft, bottomRight) - painter.drawRect(rect) - mx = (self.vertexes[0].x() + self.vertexes[1].x()) / 2 - my = (self.vertexes[0].y() + self.vertexes[1].y()) / 2 - painter.drawText(QtCore.QPoint(mx, my), str(len(self.result[self.type]))) - scene = QtWidgets.QGraphicsScene() - scene.addPixmap(self.image) - self.graphicsView.setScene(scene) + + def fitCircle(self): + p1x, p1y = self.vertexes[-3].x(), self.vertexes[-3].y() + p2x, p2y = self.vertexes[-2].x(), self.vertexes[-2].y() + p3x, p3y = self.vertexes[-1].x(), self.vertexes[-1].y() + + midP1x = (p2x + p1x) / 2 + midP1y = (p2y + p1y) / 2 + + midP2x = (p3x + p1x) / 2 + midP2y = (p3y + p1y) / 2 + + k1 = -(p2x - p1x) / (p2y - p1y) + k2 = -(p3x - p1x) / (p3y - p1y) + cx = (midP2y - midP1y - k2 * midP2x + k1 * midP1x) / (k1 - k2) + cy = midP1y + k1 * (midP2y - midP1y - k2 * midP2x + k2 * midP1x) / (k1 - k2) + r = math.sqrt((cx - p1x)*(cx - p1x) + (cy - p1y) * (cy - p1y)) + print(cx, cy, r) + return cx, cy, r + + def fitEllipse(self): + points = [] + for v in self.vertexes: + x, y = v.x(), v.y() + points.append((x, y)) + points = np.array(points) + # print(points) + ell = cv2.fitEllipse(points) + print("%.6f %.6f %.6f %.6f %.3f" %(ell[0][0], ell[0][1], ell[1][0] / 2, ell[1][1] / 2, ell[2])) self.vertexes.clear() - def paintPolygon(self): - self.result[self.type].append(self.vertexes[:]) - n = len(self.vertexes) + def paintCircle(self): + circle = self.fitCircle() + self.result.append(circle) painter = self.initMyPainter(self.type) - mx = sum(v.x() for v in self.vertexes) / n - my = sum(v.y() for v in self.vertexes) / n - painter.drawPolygon(QtGui.QPolygon(self.vertexes)) - - painter.drawText(QtCore.QPoint(mx, my), str(len(self.result[self.type]))) + cx, cy, r = circle[0], circle[1], circle[2] + painter.drawEllipse(cx - r, cy - r, r*2, r*2) + # painter.drawText(QtCore.QPoint(mx, my), str(len(self.result[self.type]))) scene = QtWidgets.QGraphicsScene() scene.addPixmap(self.image) self.graphicsView.setScene(scene) @@ -204,49 +193,49 @@ def paintPolygon(self): def parseDetectedResult(self, result, tp): lines = result.strip().split('\n') - self.result[tp].clear() + self.result.clear() try: n = int(lines[0]) for i in range(1, n + 1): points = list(map(int, lines[i].strip().split())) - m = points[0] * 2 + 1 + if self.scale != 1: points = [p / self.scale for p in points] vs = [] - for j in range(1, m, 2): - vs.append(QtCore.QPoint(points[j], points[j + 1])) - self.result[tp].append(vs) + for j in range(3): + vs.append(points[j]) + self.result.append(vs) except: print("Parse Error! Result's format is wrong!") - def paintTotalResult(self): - for tp in self.typeId: - painter = self.initMyPainter(tp) - for id, vertexes in enumerate(self.result[tp]): - n = len(vertexes) - mx = sum(v.x() for v in vertexes) / n - my = sum(v.y() for v in vertexes) / n - painter.drawPolygon(QtGui.QPolygon(vertexes)) - painter.drawText(QtCore.QPoint(mx, my), str(id + 1)) - painter.end() - scene = QtWidgets.QGraphicsScene() - scene.addPixmap(self.image) - self.graphicsView.setScene(scene) + # def paintTotalResult(self): + # for tp in self.typeId: + # painter = self.initMyPainter(tp) + # for id, vertexes in enumerate(self.result[tp]): + # n = len(vertexes) + # mx = sum(v.x() for v in vertexes) / n + # my = sum(v.y() for v in vertexes) / n + # painter.drawPolygon(QtGui.QPolygon(vertexes)) + # painter.drawText(QtCore.QPoint(mx, my), str(id + 1)) + # painter.end() + # scene = QtWidgets.QGraphicsScene() + # scene.addPixmap(self.image) + # self.graphicsView.setScene(scene) - def deleteBoxes(self, dlst): - # A naive way to delete boxes. To be update. - l = [] - tp = self.type - n = len(self.result[tp]) - for i in range(n): - if i not in dlst: - l.append(self.result[tp][i]) - - self.result[tp] = l - self.image = self.oriImage.copy() - self.paintTotalResult() - self.isModified = True - # self.paintTotalResult(tp ^ 1) + # def deleteBoxes(self, dlst): + # # A naive way to delete boxes. To be update. + # l = [] + # tp = self.type + # n = len(self.result[tp]) + # for i in range(n): + # if i not in dlst: + # l.append(self.result[tp][i]) + # + # self.result[tp] = l + # self.image = self.oriImage.copy() + # self.paintTotalResult() + # self.isModified = True + # # self.paintTotalResult(tp ^ 1) def getOutputFileName(self, t): splitedPath = self.imagePath.split('/') @@ -255,36 +244,26 @@ def getOutputFileName(self, t): resultDir = os.path.join(baseDir, 'result') if os.path.exists(resultDir) == False: os.mkdir(resultDir) - resultName = self.typeName[t] + '_' + imageName + '.txt' + resultName = 'circle' + '_' + imageName + '.txt' return resultDir, resultName def saveResult(self): if self.isModified: for t in (0, 1): - n = len(self.result[t]) + n = len(self.result) resultDir, resultName = self.getOutputFileName(t) outFile = open(os.path.join(resultDir, resultName), 'w') outFile.write(str(n)+'\n') - for res in self.result[t]: - m = len(res) - outFile.write(str(m)) + print(self.result) + for res in self.result: if self.scale != 1: res = [p * self.scale for p in res] - for i in range(m): - outFile.write(' ' + str(res[i].x()) + ' ' + str(res[i].y())) - outFile.write('\n') + # print(res) + outFile.write(str(res[0]) + ' ' + str(res[1]) + ' ' + str(res[2]) + '\n') outFile.close() else: print("Nothing to save!") - if self.isConfused: - splitedPath = self.imagePath.split('/') - baseDir = '/'.join(splitedPath[:-1]) - resultDir = os.path.join(baseDir, 'result') - if os.path.exists(resultDir) == False: - os.mkdir(resultDir) - outFile = open(os.path.join(resultDir, 'confused.txt'), 'a+', encoding='utf-8') - outFile.write(self.imagePath + '\n') - outFile.close() +