hytos / DTI_PID / DTI_PID / DetectSymbolDialog.py @ d8ad8a2c
이력 | 보기 | 이력해설 | 다운로드 (15.7 KB)
1 |
# coding: utf-8
|
---|---|
2 |
"""
|
3 |
This is fluid code dialog module
|
4 |
"""
|
5 |
import sys |
6 |
import sqlite3 |
7 |
import os |
8 |
import cv2 |
9 |
import math |
10 |
import threading |
11 |
import multiprocessing |
12 |
from multiprocessing import Process, Queue |
13 |
|
14 |
from shapely.geometry import Point |
15 |
from xml.etree.ElementTree import Element, SubElement, dump, ElementTree, parse |
16 |
from PyQt5.QtCore import * |
17 |
from PyQt5.QtGui import * |
18 |
from PyQt5.QtWidgets import * |
19 |
|
20 |
from AppDocData import AppDocData |
21 |
import DetectSymbol_UI |
22 |
|
23 |
class QDetectSymbolDialog(QDialog): |
24 |
|
25 |
def __init__(self, parent): |
26 |
QDialog.__init__(self)
|
27 |
self.parent = parent
|
28 |
self.ui = DetectSymbol_UI.Ui_DetectSymbolDialog()
|
29 |
self.ui.setupUi(self) |
30 |
|
31 |
self.table = self.ui.tableWidgetSymbol |
32 |
self.currentRow = 0 |
33 |
self.currentColumn = 0 |
34 |
|
35 |
## box dir setting
|
36 |
self.boxDir = AppDocData.instance().getCurrentProject().path + '/box/' |
37 |
if not os.path.exists(self.boxDir): |
38 |
os.makedirs(self.boxDir)
|
39 |
## drawing file setting
|
40 |
self.drawingDir = AppDocData.instance().getCurrentProject().path + '/drawings/' |
41 |
if os.path.exists(self.drawingDir): |
42 |
files = os.listdir(self.drawingDir)
|
43 |
for file in files: |
44 |
listItem = QListWidgetItem() |
45 |
listItem.setData(32, file) |
46 |
listItem.setText(os.path.splitext(file)[0]) |
47 |
self.ui.listWidgetDrawings.addItem(listItem)
|
48 |
|
49 |
self.ui.listWidgetDrawings.currentTextChanged.connect(self.currentTextChangedEvent) |
50 |
self.ui.pushButtonDetectSymbol.clicked.connect(self.detectSymbol) |
51 |
|
52 |
if self.ui.listWidgetDrawings.count(): |
53 |
self.ui.listWidgetDrawings.setCurrentRow(0) |
54 |
|
55 |
self.table.cellDoubleClicked.connect(self.cellDoubleClickedEvent) |
56 |
self.table.currentCellChanged.connect(self.currentCellChangedEvent) |
57 |
|
58 |
self.setAllSymbolsTable()
|
59 |
|
60 |
'''
|
61 |
@brief current cell chage event
|
62 |
@author kyouho
|
63 |
@date 2018.10.01
|
64 |
'''
|
65 |
def currentCellChangedEvent(self, currentRow, currentColumn, previousRow, previousColumn): |
66 |
currentItem = self.table.cellWidget(currentRow, currentColumn)
|
67 |
if currentItem is not None: |
68 |
self.currentRow = currentRow
|
69 |
self.currentColumn = currentColumn
|
70 |
else:
|
71 |
self.currentRow = -1 |
72 |
self.currentColumn = -1 |
73 |
|
74 |
'''
|
75 |
@brief cell double click event
|
76 |
@author kyouho
|
77 |
@date 2018.10.01
|
78 |
'''
|
79 |
def cellDoubleClickedEvent(self, row, column): |
80 |
cell = self.table.cellWidget(row,column)
|
81 |
if cell is not None: |
82 |
import SymbolEditorDialog |
83 |
symbolEditorDialog = SymbolEditorDialog.QSymbolEditorDialog(self, cell.pixmap(), AppDocData.instance().getCurrentProject())
|
84 |
(isAccepted, isImmediateInsert, offsetX, offsetY, newSym) = symbolEditorDialog.showDialog() |
85 |
if isAccepted:
|
86 |
self.table.removeCellWidget(row, column)
|
87 |
self.parent.dirTreeWidget.initDirTreeWidget()
|
88 |
|
89 |
'''
|
90 |
@brief text changed Event
|
91 |
@author kyouho
|
92 |
@date 2018.09.18
|
93 |
'''
|
94 |
def currentTextChangedEvent(self, text): |
95 |
self.imageName = text
|
96 |
self.imgPath = self.drawingDir + self.ui.listWidgetDrawings.currentItem().data(32) |
97 |
self.tableSetting()
|
98 |
|
99 |
'''
|
100 |
@brief text changed Event
|
101 |
@author kyouho
|
102 |
@date 2018.10.11
|
103 |
'''
|
104 |
def setAllSymbolsTable(self): |
105 |
table = self.ui.tableWidgetAllSymbols
|
106 |
table.setRowCount(0)
|
107 |
|
108 |
imageFiles = [] |
109 |
imageDir = AppDocData.instance().getCurrentProject().path + '/image/'
|
110 |
self.findFiles(imageDir, imageFiles)
|
111 |
|
112 |
table.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
|
113 |
size = table.maximumWidth() - 40
|
114 |
|
115 |
table.setRowCount(len(imageFiles))
|
116 |
row = 0
|
117 |
for path in imageFiles: |
118 |
cell = QLabel() |
119 |
pixmap = QPixmap(path) |
120 |
cell.setPixmap(pixmap.scaled(size, size, Qt.KeepAspectRatio)) |
121 |
table.setCellWidget(row, 0, cell)
|
122 |
row = row + 1
|
123 |
|
124 |
table.resizeRowsToContents() |
125 |
|
126 |
'''
|
127 |
@brief find image files
|
128 |
@author kyouho
|
129 |
@date 2018.10.11
|
130 |
'''
|
131 |
def findFiles(self, dir, list): |
132 |
fileList = os.listdir(dir)
|
133 |
for file in fileList: |
134 |
path = os.path.join(dir, file) |
135 |
if os.path.isdir(path):
|
136 |
self.findFiles(path, list) |
137 |
elif os.path.splitext(path)[1] == '.png': |
138 |
list.append(path)
|
139 |
|
140 |
|
141 |
|
142 |
'''
|
143 |
@brief text changed Event
|
144 |
@author kyouho
|
145 |
@date 2018.09.18
|
146 |
'''
|
147 |
def detectSymbol(self): |
148 |
if not self.ui.listWidgetDrawings.count(): |
149 |
return
|
150 |
|
151 |
self.progress = QProgressDialog("잠시만 기다려주세요", "Cancel", 0, 100, self) |
152 |
self.progress.setWindowModality(Qt.WindowModal)
|
153 |
self.progress.setAutoReset(True) |
154 |
self.progress.setAutoClose(True) |
155 |
self.progress.setMinimum(0) |
156 |
self.progress.resize(600,100) |
157 |
self.progress.setWindowTitle("인식 중...") |
158 |
self.progress.show()
|
159 |
self.progress.setMaximum(100) |
160 |
self.progress.setValue(0) |
161 |
QApplication.processEvents() |
162 |
|
163 |
from TextDetector import TextDetector |
164 |
import numpy as np |
165 |
|
166 |
appDocData = AppDocData.instance() |
167 |
## textDetector에서 사용하기 때문에 설정
|
168 |
appDocData.imgName = self.imageName
|
169 |
|
170 |
## 흑색 이미지로 변환
|
171 |
img = cv2.imread(self.imgPath, 1) |
172 |
imgGray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) |
173 |
|
174 |
## 프로젝트의 Drawing Area data
|
175 |
area = appDocData.getArea('Drawing')
|
176 |
|
177 |
## area 영역 자른 offset
|
178 |
offset = (area.x, area.y) if area is not None else (0,0) |
179 |
|
180 |
## 이미지 이진화
|
181 |
thresholdImg = cv2.threshold(imgGray , 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1] |
182 |
## 프로젝트의 Area 영역만큼만 자른 이미지
|
183 |
areaImg = thresholdImg[round(area.y):round(area.y+area.height), round(area.x):round(area.x+area.width)] |
184 |
|
185 |
## 선제거 전에 먼저 작은 영역 제거
|
186 |
## contours 추출을 위한 색반전
|
187 |
areaImg = cv2.bitwise_not(areaImg) |
188 |
## contours 추출
|
189 |
image, contours, hierarchy = cv2.findContours(areaImg, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) |
190 |
for contour in contours: |
191 |
[x, y, w, h] = cv2.boundingRect(contour) |
192 |
|
193 |
if (w < 40 or h < 40): |
194 |
areaImg[y:y+h,x:x+w] = 0
|
195 |
## 다시 색반전
|
196 |
areaImg = cv2.bitwise_not(areaImg) |
197 |
|
198 |
## find lines
|
199 |
verticalLineList = [] |
200 |
horizontalLineList = [] |
201 |
self.findLines(areaImg, verticalLineList, horizontalLineList)
|
202 |
|
203 |
## chage color
|
204 |
for vLine in verticalLineList: |
205 |
p1 = vLine[0]
|
206 |
p2 = vLine[1]
|
207 |
x = p1[0]
|
208 |
areaImg[p1[1]:p2[1], x] = 255 |
209 |
## chage color
|
210 |
for vLine in horizontalLineList: |
211 |
p1 = vLine[0]
|
212 |
p2 = vLine[1]
|
213 |
y = p1[1]
|
214 |
areaImg[y, p1[0]:p2[0]] = 255 |
215 |
|
216 |
## contours 추출을 위한 색반전
|
217 |
areaImg = cv2.bitwise_not(areaImg) |
218 |
## contours 추출
|
219 |
recList = self.getContours(areaImg)
|
220 |
|
221 |
## to xml
|
222 |
self.toXml(recList, offset)
|
223 |
|
224 |
## table setting
|
225 |
self.tableSetting()
|
226 |
|
227 |
self.progress.setValue(self.progress.maximum()) |
228 |
|
229 |
'''
|
230 |
@brief find lines
|
231 |
@author kyouho
|
232 |
@date 2018.10.01
|
233 |
'''
|
234 |
def findLines(self, areaImg, verticalLineList, horizontalLineList): |
235 |
|
236 |
## for multiprocessing
|
237 |
verticalProcessList = [] |
238 |
horizontalProcessList = [] |
239 |
|
240 |
## Find VerticalLine using multiprocessing
|
241 |
## Set Vertical Line
|
242 |
jumpValue = int(areaImg.shape[1] / os.cpu_count()) |
243 |
value = 0
|
244 |
for cpuIndex in range(os.cpu_count()): |
245 |
_queue = Queue() |
246 |
_range = value + jumpValue |
247 |
if os.cpu_count() -1 == cpuIndex: |
248 |
_range = areaImg.shape[1]
|
249 |
|
250 |
_process = Process(target=isVerticalLineThread, args=(value, _range, areaImg, _queue)) |
251 |
_process.daemon = True
|
252 |
verticalProcessList.append((_process, _queue)) |
253 |
value = value + jumpValue |
254 |
|
255 |
## Set Horizontal Line
|
256 |
jumpValue = int(areaImg.shape[0] / os.cpu_count()) |
257 |
value = 0
|
258 |
for cpuIndex in range(os.cpu_count()): |
259 |
_queue = Queue() |
260 |
_range = value + jumpValue |
261 |
if os.cpu_count() -1 == cpuIndex: |
262 |
_range = areaImg.shape[0]
|
263 |
|
264 |
_process = Process(target=isHorizontalLineThread, args=(value, _range, areaImg, _queue)) |
265 |
_process.daemon = True
|
266 |
horizontalProcessList.append((_process, _queue)) |
267 |
value = value + jumpValue |
268 |
|
269 |
## set percent
|
270 |
progressCount = len(verticalProcessList) + len(horizontalProcessList) + 1 |
271 |
percentGage = int(100 / progressCount) |
272 |
percent = percentGage |
273 |
self.progress.setValue(percent)
|
274 |
QApplication.processEvents() |
275 |
|
276 |
## process start
|
277 |
for process in verticalProcessList: |
278 |
process[0].start()
|
279 |
|
280 |
## Wait Vertical And Start Horizontal
|
281 |
for index in range(len(verticalProcessList)): |
282 |
verticalLineList.extend(verticalProcessList[index][1].get())
|
283 |
verticalProcessList[index][0].join()
|
284 |
verticalProcessList[index][1].close()
|
285 |
|
286 |
percent = percent + percentGage |
287 |
self.progress.setValue(percent)
|
288 |
QApplication.processEvents() |
289 |
|
290 |
horizontalProcessList[index][0].start()
|
291 |
|
292 |
## Wait Horizontal
|
293 |
for process in horizontalProcessList: |
294 |
horizontalLineList.extend(process[1].get())
|
295 |
process[0].join()
|
296 |
process[1].close()
|
297 |
|
298 |
percent = percent + percentGage |
299 |
self.progress.setValue(percent)
|
300 |
QApplication.processEvents() |
301 |
|
302 |
'''
|
303 |
@brief get contours
|
304 |
@author kyouho
|
305 |
@date 2018.10.01
|
306 |
'''
|
307 |
def getContours(self, areaImg): |
308 |
|
309 |
image, contours, hierarchy = cv2.findContours(areaImg, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) |
310 |
|
311 |
## RecList 정리
|
312 |
lineWidth = 5
|
313 |
recList = [] |
314 |
tempRecList = [] |
315 |
for contour in contours: |
316 |
[x, y, w, h] = cv2.boundingRect(contour) |
317 |
tempRecList.append([x-lineWidth, y-lineWidth, x+w+lineWidth, y+h+lineWidth]) |
318 |
|
319 |
## Overlap Rec 합침
|
320 |
while len(tempRecList): |
321 |
rec1 = tempRecList[0]
|
322 |
_temp = [] |
323 |
for index in range(1, len(tempRecList)): |
324 |
rec2 = tempRecList[index] |
325 |
if rec1[0] <= rec2[2] and rec1[2] >= rec2[0] and rec1[1] <= rec2[3] and rec1[3] >= rec2[1]: |
326 |
_temp.append(rec2) |
327 |
|
328 |
if len(_temp): |
329 |
x1 = rec1[0]
|
330 |
y1 = rec1[1]
|
331 |
x2 = rec1[2]
|
332 |
y2 = rec1[3]
|
333 |
|
334 |
for rec in _temp: |
335 |
x1 = min(x1, rec[0]) |
336 |
y1 = min(y1, rec[1]) |
337 |
x2 = max(x2, rec[2]) |
338 |
y2 = max(y2, rec[3]) |
339 |
tempRecList.remove(rec) |
340 |
tempRecList.append([x1, y1, x2, y2]) |
341 |
|
342 |
else:
|
343 |
recList.append(rec1) |
344 |
|
345 |
tempRecList.remove(rec1) |
346 |
|
347 |
return recList
|
348 |
|
349 |
'''
|
350 |
@brief key press event
|
351 |
@author kyouho
|
352 |
@date 2018.10.01
|
353 |
'''
|
354 |
def keyPressEvent(self, event): |
355 |
if event.key() == Qt.Key_Delete and self.currentRow != -1 and self.currentColumn != -1: |
356 |
self.table.removeCellWidget(self.currentRow, self.currentColumn) |
357 |
|
358 |
|
359 |
'''
|
360 |
@brief box to xml
|
361 |
@author kyouho
|
362 |
@date 2018.10.01
|
363 |
'''
|
364 |
def toXml(self, recList, offset): |
365 |
xml = Element('BOXES')
|
366 |
|
367 |
## to xml
|
368 |
for rec in recList: |
369 |
[x1, y1, x2, y2] = rec |
370 |
w = x2-x1 |
371 |
h = y2-y1 |
372 |
if w < 20 or h < 20: continue |
373 |
elif w*4<h or h*4<w: continue |
374 |
|
375 |
boxElement = Element('BOX')
|
376 |
|
377 |
xElement = Element('X')
|
378 |
xElement.text = str(x1 + offset[0]) |
379 |
boxElement.append(xElement) |
380 |
|
381 |
yElement = Element('Y')
|
382 |
yElement.text = str(y1 + offset[1]) |
383 |
boxElement.append(yElement) |
384 |
|
385 |
widthElement = Element('WIDTH')
|
386 |
widthElement.text = str(x2-x1)
|
387 |
boxElement.append(widthElement) |
388 |
|
389 |
heightElement = Element('HEIGHT')
|
390 |
heightElement.text = str(y2-y1)
|
391 |
boxElement.append(heightElement) |
392 |
|
393 |
xml.append(boxElement) |
394 |
|
395 |
ElementTree(xml).write(self.boxDir + self.imageName + '.box') |
396 |
|
397 |
'''
|
398 |
@brief table setting
|
399 |
@author kyouho
|
400 |
@date 2018.09.18
|
401 |
'''
|
402 |
def tableSetting(self): |
403 |
columnCount = 3
|
404 |
self.table.setColumnCount(columnCount)
|
405 |
self.table.setRowCount(0) |
406 |
boxCount = 0
|
407 |
|
408 |
xmlPath = self.boxDir + self.imageName + '.box' |
409 |
if os.path.exists(xmlPath):
|
410 |
_pixmap = QPixmap(self.imgPath)
|
411 |
|
412 |
xml = parse(xmlPath) |
413 |
root = xml.getroot() |
414 |
|
415 |
for box in root.iter('BOX'): |
416 |
rowIndex = int(boxCount / columnCount)
|
417 |
self.table.setRowCount(rowIndex + 1) |
418 |
|
419 |
_x = int(box.find('X').text) |
420 |
_y = int(box.find('Y').text) |
421 |
_width = int(box.find('WIDTH').text) |
422 |
_height = int(box.find('HEIGHT').text) |
423 |
|
424 |
rect = QRect(_x, _y, _width, _height) |
425 |
boxImg = _pixmap.copy(rect) |
426 |
|
427 |
cell = QLabel() |
428 |
cell.setPixmap(boxImg) |
429 |
cell.rect = [_x, _y, _width, _height] |
430 |
self.table.setCellWidget(rowIndex, boxCount % columnCount, cell)
|
431 |
|
432 |
boxCount = boxCount + 1
|
433 |
|
434 |
|
435 |
self.table.resizeColumnsToContents()
|
436 |
self.table.resizeRowsToContents()
|
437 |
|
438 |
'''
|
439 |
@brief accept
|
440 |
@author kyouho
|
441 |
@date 2018.10.01
|
442 |
'''
|
443 |
def accept(self): |
444 |
columnCount = 3
|
445 |
recList = [] |
446 |
for rowIndex in range(self.table.rowCount()): |
447 |
for columnIndex in range(columnCount): |
448 |
cell = self.table.cellWidget(rowIndex, columnIndex)
|
449 |
if cell is not None: |
450 |
[x, y, w, h] = cell.rect |
451 |
recList.append([x, y, x+w, y+h]) |
452 |
|
453 |
self.toXml(recList, (0, 0)) |
454 |
|
455 |
QDialog.accept(self)
|
456 |
|
457 |
'''
|
458 |
@brief Check Vertical Line using Multiprocessing
|
459 |
@author kyouho
|
460 |
@date 2018.09.27
|
461 |
'''
|
462 |
def isVerticalLineThread(start, end, img, _queue): |
463 |
minLineSize = 40
|
464 |
|
465 |
## Vertical
|
466 |
find = False
|
467 |
startY = 0
|
468 |
lineList = [] |
469 |
for x in range(start, end): |
470 |
for y in range(0, img.shape[0]): |
471 |
if img[y,x] == 0: |
472 |
if find:
|
473 |
continue
|
474 |
else:
|
475 |
find = True
|
476 |
startY = y |
477 |
else:
|
478 |
if find:
|
479 |
if y - startY > minLineSize:
|
480 |
lineList.append(((x,startY), (x,y))) |
481 |
find = False
|
482 |
_queue.put(lineList) |
483 |
|
484 |
'''
|
485 |
@brief Check Horizontal Line using Multiprocessing
|
486 |
@author kyouho
|
487 |
@date 2018.09.27
|
488 |
'''
|
489 |
def isHorizontalLineThread(start, end, img, _queue): |
490 |
minLineSize = 40
|
491 |
|
492 |
## Horizontal
|
493 |
find = False
|
494 |
startX = 0
|
495 |
lineList = [] |
496 |
for y in range(start, end): |
497 |
for x in range(0, img.shape[1]): |
498 |
if img[y,x] == 0: |
499 |
if find:
|
500 |
continue
|
501 |
else:
|
502 |
find = True
|
503 |
startX = x |
504 |
else:
|
505 |
if find:
|
506 |
if x - startX > minLineSize:
|
507 |
lineList.append(((startX,y), (x,y))) |
508 |
|
509 |
find = False
|
510 |
_queue.put(lineList) |