hytos / DTI_PID / DTI_PID / DTI_PID.py @ cf1f887b
이력 | 보기 | 이력해설 | 다운로드 (55.1 KB)
1 |
# coding: utf-8
|
---|---|
2 | |
3 |
#region import libs
|
4 |
import http.client |
5 |
import urllib, base64, json |
6 |
import cv2 |
7 |
import numpy as np |
8 |
import SymbolBase |
9 |
import symbol |
10 |
import TextInfo as ti |
11 |
import azure_ocr_module as OCR |
12 |
from PIL import Image |
13 |
from io import BytesIO |
14 |
import gc |
15 |
import os |
16 |
import glob |
17 |
import math, operator |
18 |
import threading |
19 |
import concurrent.futures as futures |
20 |
import XmlGenerator as xg |
21 |
import pytesseract |
22 |
import tesseract_ocr_module as TOCR |
23 |
import potrace |
24 |
import sys |
25 |
from PyQt5.QtCore import * |
26 |
from PyQt5.QtGui import * |
27 |
from PyQt5.QtWidgets import * |
28 |
from PyQt5.QtSvg import * |
29 |
import QtImageViewer |
30 | |
31 |
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)) + '\\Commands') |
32 |
import CreateCommand |
33 |
import CropCommand |
34 | |
35 |
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)) + '\\Shapes') |
36 |
import QGraphicsPolylineItem |
37 |
from QEngineeringLineItem import QEngineeringLineItem |
38 |
from SymbolSvgItem import SymbolSvgItem |
39 |
from QGraphicsBoundingBoxItem import QGraphicsBoundingBoxItem |
40 |
from AppDocData import AppDocData |
41 |
#endregion
|
42 | |
43 |
## Tesseract path
|
44 |
pytesseract.pytesseract.tesseract_cmd = os.path.join(os.environ['TESSERACT_HOME'], 'tesseract.exe') |
45 |
tesseract_cmd = os.path.join(os.environ['TESSERACT_HOME'], 'tesseract.exe') |
46 | |
47 |
#region Symbol Image path List for test
|
48 |
targetSymbolList = [] |
49 |
#endregion
|
50 | |
51 |
#region Global variables
|
52 |
searchedSymbolList = [] |
53 |
src = [] |
54 |
#srcGray = []
|
55 |
ocrCompletedSrc = [] |
56 |
afterDenoising = [] |
57 |
canvas = [] |
58 |
textInfoList = [] |
59 |
noteTextInfoList = [] |
60 | |
61 |
WHITE_LIST_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-"
|
62 | |
63 |
MIN_TEXT_SIZE = 10
|
64 | |
65 |
THREAD_MAX_WORKER = os.cpu_count() |
66 |
threadLock = threading.Lock() |
67 | |
68 |
ACCEPT_OVERLAY_AREA = 10
|
69 |
#endregion
|
70 | |
71 |
'''
|
72 |
@history 2018.06.28 Jeongwoo Remove useless condition
|
73 |
'''
|
74 |
def checkTextInSymbol(pt): |
75 |
global searchedSymbolList
|
76 | |
77 |
result = False
|
78 |
for sym in searchedSymbolList: |
79 |
#symId = sym.getId()
|
80 |
symSp = sym.getSp() |
81 |
symWidth = sym.getWidth() |
82 |
symHeight = sym.getHeight() |
83 |
symOcrOption = sym.getOcrOption() |
84 | |
85 |
categoryCode = symId // 100
|
86 | |
87 |
if symOcrOption != SymbolBase.OCR_OPTION_NOT_EXEC:
|
88 |
if (pt[0] >= symSp[0] and pt[0] <= symSp[0] + symWidth) and (pt[1] >= symSp[1] and pt[1] <= symSp[1] + symHeight): |
89 |
result = True
|
90 |
break
|
91 | |
92 |
return result
|
93 | |
94 |
#Convert into Grayscale image
|
95 |
def cvtGrayImage(img): |
96 |
return cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
97 | |
98 |
'''
|
99 |
@brief rotate (x,y) by given angle
|
100 |
@author Jeongwoo
|
101 |
@date 2018.??.??
|
102 |
@history humkyung 2018.04.13 fixed code when angle is 90 or 270
|
103 |
Jeongwoo 2018.04.27 Change calculation method with QTransform
|
104 |
'''
|
105 |
def getCoordOnRotatedImage(rAngle, x, y, originImageWidth, originImageHeight): |
106 |
rx = None
|
107 |
ry = None
|
108 |
transform = QTransform() |
109 |
if rAngle == 90 or rAngle == 270: |
110 |
transform.translate(originImageHeight*0.5, originImageWidth*0.5) |
111 |
elif rAngle == 0 or rAngle == 180: |
112 |
transform.translate(originImageWidth*0.5, originImageHeight*0.5) |
113 |
transform.rotate(-abs(rAngle))
|
114 |
transform.translate(-originImageWidth*0.5, -originImageHeight*0.5) |
115 |
point = QPoint(x, y) |
116 |
point = transform.map(point) |
117 |
rx = point.x() |
118 |
ry = point.y() |
119 |
return (rx, ry)
|
120 | |
121 |
def convertDirectionCodeToValue(directionCode): |
122 |
if directionCode == "UP": |
123 |
return 0 |
124 |
elif directionCode == "RIGHT": |
125 |
return 1 |
126 |
elif directionCode == "DOWN": |
127 |
return 2 |
128 |
elif directionCode == "LEFT": |
129 |
return 3 |
130 |
else:
|
131 |
return -1 |
132 | |
133 |
def convertValueToDirectionCode(value): |
134 |
if value == 0: |
135 |
return "UP" |
136 |
elif value == 1: |
137 |
return "RIGHT" |
138 |
elif value == 2: |
139 |
return "DOWN" |
140 |
elif value == 3: |
141 |
return "LEFT" |
142 |
else:
|
143 |
return "NONE" |
144 | |
145 |
'''
|
146 |
@brief Remake rotated child symbol info
|
147 |
'''
|
148 |
def getRotatedChildInfo(additionalSymbol): |
149 |
tempChildInfo = ""
|
150 |
if additionalSymbol:
|
151 |
childList = additionalSymbol.split("/")
|
152 |
for index in range(len(childList)): |
153 |
child = childList[index] |
154 |
direction = convertDirectionCodeToValue(child.split(",")[0]) |
155 |
childName = child.split(",")[1] |
156 |
direction = (direction - 1) if direction > 0 else 3 |
157 |
if index != 0: |
158 |
tempChildInfo = tempChildInfo + "/"
|
159 |
tempChildInfo = tempChildInfo + convertValueToDirectionCode(direction) + "," + childName
|
160 |
return tempChildInfo
|
161 | |
162 | |
163 |
#Check object contains pt
|
164 |
#obj is item in searchedSymbolList
|
165 |
def contains(obj, pt, tw, th): |
166 |
sp = obj.getSp() |
167 |
width = obj.getWidth() |
168 |
height = obj.getHeight() |
169 | |
170 |
if sp[0] > pt[0]+tw: |
171 |
return 0 |
172 |
if sp[0]+width < pt[0]: |
173 |
return 0 |
174 |
if sp[1] > pt[1]+th: |
175 |
return 0 |
176 |
if sp[1]+height < pt[1]: |
177 |
return 0 |
178 |
|
179 |
#shared area
|
180 |
x = max(sp[0], pt[0]); |
181 |
y = max(sp[1], pt[1]); |
182 |
w = min(sp[0] + width, pt[0] + tw) - x; |
183 |
h = min(sp[1] + height, pt[1] + th) - y; |
184 | |
185 |
return float((w * h)) / float((tw * th)) * 100 |
186 | |
187 |
'''
|
188 |
@history 2018.06.12 Jeongwoo Type changed (int → float)
|
189 |
humkyung 2018.07.07 change return type as like [x,y]
|
190 |
'''
|
191 |
def getCalculatedOriginalPoint(additionalSymbol, symbolOriginalPoint, symbolRotatedAngle, rotateSymbolWidth, rotateSymbolHeight, originalSymbolWidth, originalSymbolHeight): |
192 |
res = [] |
193 | |
194 |
if additionalSymbol is None and symbolOriginalPoint is None: |
195 |
res.append(rotateSymbolWidth//2)
|
196 |
res.append(rotateSymbolHeight//2)
|
197 |
else:
|
198 |
opx = float(symbolOriginalPoint.split(',')[0]) |
199 |
opy = float(symbolOriginalPoint.split(',')[1]) |
200 |
rPt = getCoordOnRotatedImage(symbolRotatedAngle, opx, opy, originalSymbolWidth, originalSymbolHeight) |
201 |
res.append(float(rPt[0])) |
202 |
res.append(float(rPt[1])) |
203 | |
204 |
return res
|
205 | |
206 |
'''
|
207 |
@history 2018.06.12 Jeongwoo Type changed (int → float)
|
208 |
humkyung 2018.07.07 change return type as like [[x,y],...]
|
209 |
'''
|
210 |
def getCalculatedConnectionPoint(symbolConnectionPointStr, symbolRotatedAngle, rotateSymbolWidth, rotateSymbolHeight, originalSymbolWidth, originalSymbolHeight): |
211 |
res = [] |
212 |
if symbolConnectionPointStr is not None: |
213 |
splitConnectionPointStr = symbolConnectionPointStr.split("/")
|
214 |
for index in range(len(splitConnectionPointStr)): |
215 |
item = splitConnectionPointStr[index] |
216 |
cpx = float(item.split(',')[0]) |
217 |
cpy = float(item.split(',')[1]) |
218 |
rPt = getCoordOnRotatedImage(symbolRotatedAngle, cpx, cpy, originalSymbolWidth, originalSymbolHeight) |
219 |
res.append([float(rPt[0]), float(rPt[1])]) |
220 | |
221 |
return res
|
222 |
|
223 |
'''
|
224 |
@brief Add symbols
|
225 |
@author jwkim
|
226 |
@date
|
227 |
@history Change parameter (mpCount → hitRate)
|
228 |
Yecheol 2018.07.04 Delete Symbol Id
|
229 |
'''
|
230 |
def addSearchedSymbol(sName, sType |
231 |
, sp, w, h, threshold, minMatchCount, hitRate, rotatedAngle |
232 |
, isDetectOnOrigin, rotateCount, ocrOption, isContainChild |
233 |
, originalPoint, connectionPoint, baseSymbol, additionalSymbol, isExceptDetect): |
234 |
global searchedSymbolList
|
235 |
newSym = symbol.Symbol(sName, sType |
236 |
, sp, w, h, threshold, minMatchCount, hitRate, rotatedAngle |
237 |
, isDetectOnOrigin, rotateCount, ocrOption, isContainChild |
238 |
, ','.join(str(x) for x in originalPoint), '/'.join('{},{}'.format(x[0],x[1]) for x in connectionPoint), baseSymbol, additionalSymbol, isExceptDetect) |
239 | |
240 |
searchedSymbolList.append(newSym) |
241 | |
242 |
return newSym
|
243 | |
244 |
#Calculate count of keypoint match result
|
245 |
def getMatchPointCount(src, cmp): |
246 |
orb = cv2.ORB_create(1000, 2.0, 2, 1) |
247 | |
248 |
kp1, des1 = orb.detectAndCompute(src, None)
|
249 |
kp2, des2 = orb.detectAndCompute(cmp, None) |
250 |
|
251 |
FLANN_INDEX_LSH = 6
|
252 |
# table_number : The number of hash tables use
|
253 |
# key_size : The length of the key in the hash tables
|
254 |
# multi_probe_level : Number of levels to use in multi-probe (0 for standard LSH)
|
255 |
# It controls how neighboring buckets are searched
|
256 |
# Recommended value is 2
|
257 |
# checks : specifies the maximum leafs to visit when searching for neighbours.
|
258 |
# LSH : Locality-Sensitive Hashing
|
259 |
# ref : https://www.cs.ubc.ca/research/flann/uploads/FLANN/flann_manual-1.8.4.pdf
|
260 |
index_params = dict(algorithm = FLANN_INDEX_LSH, table_number = 20, key_size = 10, multi_probe_level = 4) |
261 |
search_params = dict(checks = 100) |
262 |
|
263 |
flann = cv2.FlannBasedMatcher(index_params,search_params) |
264 |
|
265 |
matches = flann.knnMatch(des1, des2, k = 2)
|
266 |
matchesMask = [[0, 0] for i in range(len(matches))] #Python 3.x |
267 |
|
268 |
count = 0
|
269 |
# ratio test as per Lowe's paper
|
270 |
for i in range(len(matches)): |
271 |
if len(matches[i]) == 2: |
272 |
m = matches[i][0]
|
273 |
n = matches[i][1]
|
274 |
if m.distance < 0.85 * n.distance: |
275 |
count = count + 1
|
276 | |
277 |
matchCount = count |
278 | |
279 |
#print("match Count : " + str(matchCount))
|
280 |
return matchCount
|
281 | |
282 |
'''
|
283 |
@brief detect symbols on PID
|
284 |
@history humkyung 2018.06.08 add parameteres for signal
|
285 |
'''
|
286 |
def detectSymbolsOnPid(mainRes, targetSymbols, listWidget, updateProgressSignal): |
287 |
for detailTarget in targetSymbols: |
288 |
detectSymbolOnPid(mainRes, detailTarget, listWidget, updateProgressSignal) |
289 | |
290 |
'''
|
291 |
@brief detect symbol on PID
|
292 |
@author jwkim
|
293 |
@date
|
294 |
@history humkyung 2018.04.06 check if symbol file exists
|
295 |
Jeongwoo 2018.05.29 Change method to adjust detail symbol location with hit-rate. Not feature point count
|
296 |
Change parameter on add symbol part (mpCount → hitRate)
|
297 |
Remove unusing calculation (avg)
|
298 |
Jeongwoo 2018.06.27 Remove part to split P&ID image and for loop
|
299 |
humkyung 2018.07.07 return searched symbols
|
300 |
'''
|
301 |
def detectSymbolOnPid(mainRes, targetSymbol, listWidget, updateProgressSignal): |
302 |
global ocrCompletedSrc
|
303 |
global afterDenoising
|
304 |
global threadLock
|
305 |
global searchedSymbolList
|
306 |
global maxProgressValue
|
307 |
|
308 |
try:
|
309 |
symbolName = targetSymbol.getName() |
310 |
symbolType = targetSymbol.getType() |
311 |
symbolPath = targetSymbol.getPath() |
312 |
symbolThreshold = targetSymbol.getThreshold() |
313 |
symbolMinMatchCount = targetSymbol.getMinMatchCount() |
314 |
isDetectOnOrigin = targetSymbol.getIsDetectOnOrigin() |
315 |
symbolRotateCount = targetSymbol.getRotationCount() |
316 |
symbolOcrOption = targetSymbol.getOcrOption() |
317 |
isContainChild = targetSymbol.getIsContainChild() |
318 |
symbolOriginalPoint = targetSymbol.getOriginalPoint() |
319 |
symbolConnectionPoint = targetSymbol.getConnectionPoint() |
320 |
baseSymbol = targetSymbol.getBaseSymbol() |
321 |
additionalSymbol = targetSymbol.getAdditionalSymbol() |
322 |
isExceptDetect = targetSymbol.getIsExceptDetect() |
323 | |
324 |
# check if symbol file is target or not
|
325 |
if isExceptDetect == 1: |
326 |
item = QListWidgetItem('{} file is not target'.format(os.path.split(os.path.basename(symbolPath))[0])) |
327 |
item.setBackground(QColor('green'))
|
328 |
listWidget.addItem(item) |
329 |
return
|
330 | |
331 |
foundSymbolCount = 0
|
332 | |
333 |
# check if symbol file exists
|
334 |
if not os.path.isfile(symbolPath): |
335 |
item = QListWidgetItem('{} file not found'.format(os.path.split(os.path.basename(symbolPath))[0])) |
336 |
item.setBackground(QColor('red'))
|
337 |
listWidget.addItem(item) |
338 |
return
|
339 |
# up to here
|
340 | |
341 |
sym = cv2.imread(symbolPath, 1)
|
342 |
symGray = cvtGrayImage(sym) |
343 |
## TODO: 이진화 시켰을때 심볼이 검출되지 않음
|
344 |
## symGray = cv2.threshold(cvtGrayImage(sym), 127, 255, cv2.THRESH_BINARY)[1]
|
345 |
## cv2.imshow('symbol', symGray)
|
346 |
## cv2.waitKey(0)
|
347 |
sow, soh = symGray.shape[::-1] # symbol original w, h |
348 | |
349 |
offsetDrawingArea=[] |
350 |
appDocData = AppDocData.instance() |
351 |
area = appDocData.getArea('Drawing')
|
352 |
if area is not None: |
353 |
copiedBasePid = area.img.copy() |
354 |
offsetDrawingArea.append(area.x) |
355 |
offsetDrawingArea.append(area.y) |
356 |
else:
|
357 |
offsetDrawingArea.append(0)
|
358 |
offsetDrawingArea.append(0)
|
359 |
if isDetectOnOrigin == 1: |
360 |
copiedBasePid = appDocData.imgSrc.copy() |
361 |
else:
|
362 |
copiedBasePid = ocrCompletedSrc.copy() |
363 |
srcWidth, srcHeight = copiedBasePid.shape[::-1]
|
364 | |
365 |
roiItemSp = (0,0) |
366 |
roiItemEp = (srcWidth, srcHeight) |
367 |
roiItem = copiedBasePid |
368 | |
369 |
symbolRotatedAngle = 0
|
370 |
for rc in range(symbolRotateCount + 1): ## Rotation Count를 사용자 기준으로 받아서 1을 더한 후 사용 |
371 |
sw, sh = symGray.shape[::-1]
|
372 |
roiw = (roiItemEp[0] - roiItemSp[0]) |
373 |
roih = (roiItemEp[1] - roiItemSp[1]) |
374 | |
375 |
## Case : Bigger Symbol than Split ROI
|
376 |
if roiw < sw or roih < sh: |
377 |
symGray = cv2.rotate(symGray, cv2.ROTATE_90_COUNTERCLOCKWISE) |
378 |
symbolRotatedAngle = symbolRotatedAngle + 90
|
379 | |
380 |
if baseSymbol is not None and additionalSymbol is not None: |
381 |
additionalSymbol = getRotatedChildInfo(additionalSymbol) |
382 |
continue
|
383 |
|
384 |
## get Rotated Original Point
|
385 |
originalPoint = getCalculatedOriginalPoint(additionalSymbol, symbolOriginalPoint, symbolRotatedAngle, sw, sh, sow, soh) |
386 |
connectionPoint = getCalculatedConnectionPoint(symbolConnectionPoint, symbolRotatedAngle, sw, sh, sow, soh) |
387 | |
388 |
## Template Matching
|
389 |
tmRes = cv2.matchTemplate(roiItem, symGray, cv2.TM_CCOEFF_NORMED) |
390 |
loc = np.where(tmRes >= symbolThreshold) |
391 | |
392 |
for pt in zip(*loc[::-1]): |
393 |
overlapArea = 0
|
394 |
mpCount = 0 # Match Point Count |
395 |
symbolIndex = -1
|
396 | |
397 |
roi = roiItem[pt[1]:pt[1]+sh, pt[0]:pt[0]+sw] |
398 | |
399 |
if symbolMinMatchCount > 0: |
400 |
mpCount = getMatchPointCount(roi, symGray) |
401 |
if not (mpCount >= symbolMinMatchCount): |
402 |
continue
|
403 | |
404 |
searchedItemSp = (roiItemSp[0]+pt[0] + round(offsetDrawingArea[0]), roiItemSp[1]+pt[1] + round(offsetDrawingArea[1])) |
405 | |
406 |
for i in range(len(searchedSymbolList)): |
407 |
'''
|
408 |
_pt = searchedSymbolList[i].getSp()
|
409 |
rect = QRectF(_pt[0], _pt[1], searchedSymbolList[i].getWidth(), searchedSymbolList[i].getHeight())
|
410 |
_rect = QRectF(searchedItemSp[0], searchedItemSp[1], sw, sh)
|
411 |
if rect.intersects(_rect):
|
412 |
intersect = rect.intersected(_rect)
|
413 |
overlapArea = intersect.width()*intersect.height()
|
414 |
if overlapArea > sw*sh*0.1:
|
415 |
symbolIndex = i
|
416 |
break
|
417 |
'''
|
418 |
overlapArea = contains(searchedSymbolList[i], searchedItemSp, sw, sh) |
419 |
if overlapArea > ACCEPT_OVERLAY_AREA:
|
420 |
symbolIndex = i |
421 |
break
|
422 |
|
423 |
hitRate = tmRes[pt[1], pt[0]] |
424 |
## DEBUG
|
425 |
#print('{}:{}-{}'.format(symbolName, searchedItemSp, hitRate))
|
426 |
## up to here
|
427 | |
428 |
## 겹치는 영역이 기준값보다 작을 경우
|
429 |
if overlapArea <= ACCEPT_OVERLAY_AREA:
|
430 |
threadLock.acquire() |
431 |
foundSymbolCount = foundSymbolCount + 1
|
432 |
addSearchedSymbol(symbolName, symbolType |
433 |
, searchedItemSp, sw, sh, symbolThreshold, symbolMinMatchCount, hitRate, symbolRotatedAngle |
434 |
, isDetectOnOrigin, symbolRotateCount, symbolOcrOption, isContainChild |
435 |
, originalPoint, connectionPoint, baseSymbol, additionalSymbol,isExceptDetect) |
436 |
threadLock.release() |
437 |
## 겹치는 영역이 기준값보다 클 경우
|
438 |
else:
|
439 |
if symbolIndex != -1 and symbolIndex < len(searchedSymbolList): |
440 |
searchedSymbol = searchedSymbolList[symbolIndex] |
441 |
## 현재 심볼과 검출된 심볼이 같을 경우 Match Point가 더 높은 정보로 교체
|
442 |
if symbolName == searchedSymbol.getName():
|
443 |
symbolHitRate = searchedSymbol.getHitRate() |
444 |
if symbolHitRate < hitRate:
|
445 |
threadLock.acquire() |
446 |
searchedSymbolList[symbolIndex] = symbol.Symbol(symbolName, symbolType |
447 |
, searchedItemSp, sw, sh, symbolThreshold, symbolMinMatchCount, hitRate, symbolRotatedAngle |
448 |
, isDetectOnOrigin, symbolRotateCount, symbolOcrOption, isContainChild |
449 |
, ','.join(str(x) for x in originalPoint), '/'.join('{},{}'.format(x[0],x[1]) for x in connectionPoint), baseSymbol, additionalSymbol,isExceptDetect) |
450 |
## DEBUG
|
451 |
#print('//// {}:{}-{} ////'.format(symbolName, searchedItemSp, hitRate))
|
452 |
## up to here
|
453 | |
454 |
threadLock.release() |
455 |
## 현재 심볼과 검출된 심볼이 같지 않을 경우 (포함)
|
456 |
elif appDocData.isEquipmentType(searchedSymbol.getType()):
|
457 |
## DEBUG
|
458 |
print('{}->{}:{}-{}'.format(searchedSymbol.getName(), symbolName, searchedItemSp, hitRate))
|
459 |
## up to here
|
460 | |
461 |
threadLock.acquire() |
462 |
foundSymbolCount = foundSymbolCount + 1
|
463 |
addSearchedSymbol(symbolName, symbolType |
464 |
, searchedItemSp, sw, sh, symbolThreshold, hitRate, hitRate, symbolRotatedAngle |
465 |
, isDetectOnOrigin, symbolRotateCount, symbolOcrOption, isContainChild |
466 |
, originalPoint, connectionPoint, baseSymbol, additionalSymbol,isExceptDetect) |
467 |
threadLock.release() |
468 |
|
469 |
## Rotate Symbol
|
470 |
symGray = cv2.rotate(symGray, cv2.ROTATE_90_COUNTERCLOCKWISE) |
471 |
symbolRotatedAngle = symbolRotatedAngle + 90
|
472 | |
473 |
if additionalSymbol is not None: |
474 |
additionalSymbol = getRotatedChildInfo(additionalSymbol) |
475 | |
476 |
threadLock.acquire() |
477 |
listWidget.addItem('Found Symbol : ' + os.path.splitext(os.path.basename(symbolPath))[0] + ' - (' + str(foundSymbolCount) + ')') |
478 |
threadLock.release() |
479 | |
480 |
updateProgressSignal.emit(maxProgressValue) |
481 | |
482 |
return [symbol for symbol in searchedSymbolList if symbol.getName() == symbolName] |
483 |
except Exception as ex: |
484 |
print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)) |
485 |
|
486 |
return []
|
487 | |
488 |
'''
|
489 |
@brief detect nozzle
|
490 |
@author humkyung
|
491 |
@date 2018.07.07
|
492 |
@history humkyhung 2018.07.17 pass equpment as parameter instead of image
|
493 |
'''
|
494 |
def detectNozzleOnPid(equipment, nozzle, listWidget, updateProgressSignal): |
495 |
global src
|
496 |
global threadLock
|
497 |
global searchedSymbolList
|
498 |
global maxProgressValue
|
499 |
|
500 |
try:
|
501 |
symbolName = nozzle.getName() |
502 |
symbolType = nozzle.getType() |
503 |
symbolPath = nozzle.getPath() |
504 |
symbolThreshold = nozzle.getThreshold() |
505 |
symbolMinMatchCount = nozzle.getMinMatchCount() |
506 |
isDetectOnOrigin = nozzle.getIsDetectOnOrigin() |
507 |
symbolRotateCount = nozzle.getRotationCount() |
508 |
symbolOcrOption = nozzle.getOcrOption() |
509 |
isContainChild = nozzle.getIsContainChild() |
510 |
symbolOriginalPoint = nozzle.getOriginalPoint() |
511 |
symbolConnectionPoint = nozzle.getConnectionPoint() |
512 |
baseSymbol = nozzle.getBaseSymbol() |
513 |
additionalSymbol = nozzle.getAdditionalSymbol() |
514 |
isExceptDetect = nozzle.getIsExceptDetect() |
515 | |
516 |
foundSymbolCount = 0
|
517 | |
518 |
# check if symbol file exists
|
519 |
if not os.path.isfile(symbolPath): |
520 |
item = QListWidgetItem('{} file not found'.format(os.path.split(os.path.basename(symbolPath))[0])) |
521 |
item.setBackground(QColor('red'))
|
522 |
listWidget.addItem(item) |
523 |
return
|
524 |
# up to here
|
525 | |
526 |
symGray = cvtGrayImage(cv2.imread(symbolPath, 1))
|
527 |
sow, soh = symGray.shape[::-1] # symbol original w, h |
528 | |
529 |
# get image of equipment with offset of nozzle size
|
530 |
appDocData = AppDocData.instance() |
531 |
pt = equipment.getSp() |
532 |
nozzleSize = max(sow, soh)
|
533 |
sx = round(pt[0]) - nozzleSize |
534 |
sy = round(pt[1]) - nozzleSize |
535 |
ex = round(pt[0] + equipment.getWidth()) + nozzleSize |
536 |
ey = round(pt[1] + equipment.getHeight()) + nozzleSize |
537 |
offset = (sx, sy) |
538 |
eqpSize = (pt[0], pt[1], equipment.getWidth(), equipment.getHeight()) |
539 |
img = appDocData.imgSrc[sy:ey, sx:ex] |
540 |
srcWidth, srcHeight = img.shape[::-1]
|
541 |
# up to here
|
542 | |
543 |
roiItemSp = (0,0) |
544 |
roiItemEp = (srcWidth, srcHeight) |
545 |
roiItem = img |
546 | |
547 |
symbolAngle = 0
|
548 |
for rc in range(symbolRotateCount + 1): ## Rotation Count를 사용자 기준으로 받아서 1을 더한 후 사용 |
549 |
sw, sh = symGray.shape[::-1]
|
550 |
roiw = (roiItemEp[0] - roiItemSp[0]) |
551 |
roih = (roiItemEp[1] - roiItemSp[1]) |
552 | |
553 |
## get Rotated Original Point
|
554 |
originalPoint = getCalculatedOriginalPoint(additionalSymbol, symbolOriginalPoint, symbolAngle, sw, sh, sow, soh) |
555 |
connectionPoint = getCalculatedConnectionPoint(symbolConnectionPoint, symbolAngle, sw, sh, sow, soh) |
556 |
dx = connectionPoint[0][0] - originalPoint[0] |
557 |
dy = connectionPoint[0][1] - originalPoint[1] |
558 | |
559 |
## Template Matching
|
560 |
tmRes = cv2.matchTemplate(roiItem, symGray, cv2.TM_CCOEFF_NORMED) |
561 |
loc = np.where(tmRes >= symbolThreshold) |
562 | |
563 |
for pt in zip(*loc[::-1]): |
564 |
mpCount = 0 # Match Point Count |
565 |
symbolIndex = -1
|
566 | |
567 |
roi = roiItem[pt[1]:pt[1]+sh, pt[0]:pt[0]+sw] |
568 | |
569 |
if symbolMinMatchCount > 0: |
570 |
mpCount = getMatchPointCount(roi, symGray) |
571 |
if not (mpCount >= symbolMinMatchCount): |
572 |
continue
|
573 |
|
574 |
mid = (offset[0] + pt[0] + (originalPoint[0] + connectionPoint[0][0])*0.5, offset[1] + pt[1] + (originalPoint[1] + connectionPoint[0][1])*0.5) |
575 |
searchedItemSp = (roiItemSp[0]+pt[0]+offset[0], roiItemSp[1]+pt[1]+offset[1]) |
576 |
print('---{}---{}---{}---'.format(eqpSize,searchedItemSp,mid))
|
577 |
# check searched nozzle location
|
578 |
if abs(dx) > abs(dy): |
579 |
if dx > 0: |
580 |
if mid[0] < eqpSize[0] + eqpSize[2]*0.5: continue |
581 |
else:
|
582 |
if mid[0] > eqpSize[0] + eqpSize[2]*0.5: continue |
583 |
else:
|
584 |
if dy > 0: |
585 |
if mid[1] < eqpSize[1] + eqpSize[3]*0.5: continue |
586 |
else:
|
587 |
if mid[1] > eqpSize[1] + eqpSize[3]*0.5: continue |
588 |
# up to here
|
589 | |
590 |
overlapArea = 0
|
591 |
nozzles = [symbol for symbol in searchedSymbolList if symbol.getType() == 'Nozzles'] |
592 |
for i in range(len(nozzles)): |
593 |
_pt = nozzles[i].getSp() |
594 |
rect = QRectF(_pt[0], _pt[1], nozzles[i].getWidth(), nozzles[i].getHeight()) |
595 |
_rect = QRectF(searchedItemSp[0], searchedItemSp[1], sw, sh) |
596 |
if rect.intersects(_rect):
|
597 |
intersect = rect.intersected(_rect) |
598 |
overlapArea = intersect.width()*intersect.height() |
599 |
if overlapArea > ACCEPT_OVERLAY_AREA:
|
600 |
symbolIndex = i |
601 |
break
|
602 |
|
603 |
hitRate = tmRes[pt[1], pt[0]] |
604 | |
605 |
## 겹치는 영역이 기준값보다 작을 경우
|
606 |
if overlapArea <= ACCEPT_OVERLAY_AREA:
|
607 |
threadLock.acquire() |
608 |
foundSymbolCount = foundSymbolCount + 1
|
609 |
searched = addSearchedSymbol(symbolName, symbolType |
610 |
, searchedItemSp, sw, sh, symbolThreshold, symbolMinMatchCount, hitRate, symbolAngle |
611 |
, isDetectOnOrigin, symbolRotateCount, symbolOcrOption, isContainChild |
612 |
, originalPoint, connectionPoint, baseSymbol, additionalSymbol,isExceptDetect) |
613 |
searched.owner = equipment |
614 |
threadLock.release() |
615 |
## 겹치는 영역이 기준값보다 클 경우
|
616 |
else:
|
617 |
if symbolIndex != -1 and symbolIndex < len(nozzles): |
618 |
searchedSymbol = nozzles[symbolIndex] |
619 |
## 현재 심볼과 검출된 심볼이 같을 경우 Match Point가 더 높은 정보로 교체
|
620 |
if symbolName == searchedSymbol.getName():
|
621 |
symbolHitRate = searchedSymbol.getHitRate() |
622 |
if symbolHitRate < hitRate:
|
623 |
threadLock.acquire() |
624 |
nozzles[symbolIndex] = symbol.Symbol(symbolName, symbolType |
625 |
, searchedItemSp, sw, sh, symbolThreshold, symbolMinMatchCount, hitRate, symbolAngle |
626 |
, isDetectOnOrigin, symbolRotateCount, symbolOcrOption, isContainChild |
627 |
, ','.join(str(x) for x in originalPoint), '/'.join('{},{}'.format(x[0],x[1]) for x in connectionPoint), baseSymbol, additionalSymbol,isExceptDetect) |
628 |
threadLock.release() |
629 |
## 현재 심볼과 검출된 심볼이 같지 않을 경우 (포함)
|
630 |
elif appDocData.isEquipmentType(searchedSymbol.getType()):
|
631 |
threadLock.acquire() |
632 |
foundSymbolCount = foundSymbolCount + 1
|
633 |
searched = addSearchedSymbol(symbolName, symbolType |
634 |
, searchedItemSp, sw, sh, symbolThreshold, hitRate, hitRate, symbolAngle |
635 |
, isDetectOnOrigin, symbolRotateCount, symbolOcrOption, isContainChild |
636 |
, originalPoint, connectionPoint, baseSymbol, additionalSymbol,isExceptDetect) |
637 |
searched.owner = equipment |
638 |
threadLock.release() |
639 |
|
640 |
## Rotate Symbol
|
641 |
symGray = cv2.rotate(symGray, cv2.ROTATE_90_COUNTERCLOCKWISE) |
642 |
symbolAngle = symbolAngle + 90
|
643 | |
644 |
if additionalSymbol is not None: |
645 |
additionalSymbol = getRotatedChildInfo(additionalSymbol) |
646 | |
647 |
threadLock.acquire() |
648 |
listWidget.addItem('Found Symbol : ' + os.path.splitext(os.path.basename(symbolPath))[0] + ' - (' + str(foundSymbolCount) + ')') |
649 |
threadLock.release() |
650 | |
651 |
updateProgressSignal.emit(maxProgressValue) |
652 | |
653 |
return [symbol for symbol in searchedSymbolList if symbol.getName() == symbolName] |
654 |
except Exception as ex: |
655 |
print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)) |
656 | |
657 |
return []
|
658 | |
659 |
'''
|
660 |
@brief detect equipment
|
661 |
@author humkyung
|
662 |
@date 2018.07.07
|
663 |
'''
|
664 |
def detectEquipmentOnPid(mainRes, targetSymbol, listWidget, updateProgressSignal): |
665 |
try:
|
666 |
equipments = detectSymbolOnPid(mainRes, targetSymbol, listWidget, updateProgressSignal) |
667 |
for equipment in equipments: |
668 |
# detect nozzles around equimpent
|
669 |
for nozzle in targetSymbolList[1]: |
670 |
detectNozzleOnPid(equipment, nozzle, listWidget, updateProgressSignal) |
671 |
# up to here
|
672 |
except Exception as ex: |
673 |
print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)) |
674 |
|
675 |
'''
|
676 |
@history 2018.05.17 Jeongwoo Bitwise_not target changed (Original Image → Symbol Image)
|
677 |
humkyung 2018.07.11 add parameter for image
|
678 |
'''
|
679 |
def removeDetectedSymbol(sym, imgSrc): |
680 |
global ocrCompletedSrc
|
681 |
global threadLock
|
682 |
|
683 |
path = sym.getPath() |
684 |
sp = sym.getSp() |
685 |
sw = sym.getWidth() |
686 |
sh = sym.getHeight() |
687 |
angle = sym.getRotatedAngle() |
688 |
symImg = cv2.imread(path) |
689 |
symImg = cv2.threshold(cvtGrayImage(symImg), 127, 255, cv2.THRESH_BINARY)[1] |
690 |
|
691 |
for i in range(angle//90): |
692 |
symImg = cv2.rotate(symImg, cv2.ROTATE_90_COUNTERCLOCKWISE) |
693 | |
694 |
threadLock.acquire() |
695 |
temp = [] |
696 |
temp = imgSrc[sp[1]:sp[1]+sh, sp[0]:sp[0]+sw] |
697 |
symImgBin = cv2.bitwise_not(symImg) |
698 |
result = cv2.bitwise_xor(symImgBin, temp) |
699 |
result = cv2.dilate(result, np.ones((5, 5), np.uint8)) |
700 |
imgSrc[sp[1]:sp[1]+sh, sp[0]:sp[0]+sw] = result |
701 |
threadLock.release() |
702 | |
703 |
'''
|
704 |
def drawRectOnSrc(sym):
|
705 |
global src
|
706 |
global srcGray
|
707 |
global ocrCompletedSrc
|
708 |
|
709 |
path = sym.getPath()
|
710 |
sp = sym.getSp()
|
711 |
sw = sym.getWidth()
|
712 |
sh = sym.getHeight()
|
713 |
sAngle = sym.getRotatedAngle()
|
714 |
symImg = cv2.imread(path)
|
715 |
symImg = cvtGrayImage(symImg)
|
716 |
|
717 |
cv2.rectangle(src, sp, (sp[0]+sw, sp[1]+sh), (0, 0, 255), 2)
|
718 |
'''
|
719 | |
720 |
'''
|
721 |
@history 2018.04.27 Jeongwoo Remove Tesseract Log on listWidget
|
722 |
2018.05.04 Jeongwoo Change method to OCR with tesseract_ocr_module.py
|
723 |
2018.05.09 Jeongwoo Add global variable textInfoList, Remove text in symbol and Add tesseract result text
|
724 |
2018.05.10 Jeongwoo Remove not used if-statement
|
725 |
2018.06.19 Jeongwoo When detect text in symbol, use getTextAreaInfo() and Tesseract
|
726 |
2018.06.21 Jeongwoo Add if-statement for way to detect text by Type A
|
727 |
'''
|
728 |
def drawFoundSymbols(symbol, listWidget): |
729 |
global src
|
730 |
global canvas
|
731 |
global WHITE_LIST_CHARS
|
732 |
global searchedSymbolList
|
733 |
global textInfoList
|
734 | |
735 |
#symbolId = symbol.getId()
|
736 |
symbolPath = symbol.getPath() |
737 |
symbolSp = symbol.getSp() |
738 |
symbolWidth = symbol.getWidth() |
739 |
symbolHeight = symbol.getHeight() |
740 |
symbolRotatedAngle = symbol.getRotatedAngle() |
741 |
symbolOcrOption = symbol.getOcrOption() |
742 | |
743 |
symImg = cv2.cvtColor(cv2.imread(symbolPath, 1), cv2.COLOR_BGR2GRAY)
|
744 |
for i in range(symbolRotatedAngle//90): |
745 |
symImg = cv2.rotate(symImg, cv2.ROTATE_90_COUNTERCLOCKWISE) |
746 | |
747 |
w, h = symImg.shape[::-1]
|
748 |
canvas[symbolSp[1]:symbolSp[1]+h, symbolSp[0]:symbolSp[0]+w] = cv2.bitwise_and(canvas[symbolSp[1]:symbolSp[1]+h, symbolSp[0]:symbolSp[0]+w], symImg) |
749 |
|
750 |
'''
|
751 |
@brief draw found symbols and texts
|
752 |
@author Jeongwoo
|
753 |
'''
|
754 |
def drawFoundSymbolsOnCanvas(drawingPath , textInfos , listWidget): |
755 |
global src
|
756 |
global ocrCompletedSrc
|
757 |
global searchedSymbolList
|
758 |
global canvas
|
759 | |
760 |
appDocData = AppDocData.instance() |
761 |
canvas = np.zeros(appDocData.imgSrc.shape, np.uint8) |
762 |
canvas[::] = 255
|
763 | |
764 |
try:
|
765 |
appDocData = AppDocData.instance() |
766 |
project = appDocData.getCurrentProject() |
767 | |
768 |
for symbol in searchedSymbolList: |
769 |
drawFoundSymbols(symbol, listWidget) |
770 | |
771 |
for text in textInfos: |
772 |
#if not checkTextInSymbol((text.getX(), text.getY())):
|
773 |
left = text.getX() |
774 |
top = text.getY() |
775 |
right = text.getX() + text.getW() |
776 |
bottom = text.getY() + text.getH() |
777 | |
778 |
canvas[top:bottom, left:right] = appDocData.imgSrc[top:bottom, left:right] |
779 | |
780 |
cv2.imwrite(os.path.join(project.getTempPath(), "FOUND_" + os.path.basename(drawingPath)), canvas)
|
781 |
except Exception as ex: |
782 |
print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)) |
783 |
|
784 |
'''
|
785 |
@history 2018.04.24 Jeongwoo Add isExceptDetect Field
|
786 |
2018.05.09 Jeongwoo Add targetSymbolList clear
|
787 |
humkyung 2018.07.07 store symbols to list as like [equipments],[nozzles],[symbols]
|
788 |
'''
|
789 |
def initTargetSymbolDataList(): |
790 |
global targetSymbolList
|
791 | |
792 |
targetSymbolList.clear() |
793 |
appDocData = AppDocData.instance() |
794 |
symbolList = appDocData.getTargetSymbolList() |
795 |
equipments = [item for item in symbolList if appDocData.getSymbolCategoryByType(item.getType()) == 'Equipment'] |
796 |
nozzles = [item for item in symbolList if item.getType() == 'Nozzles'] |
797 |
# [[equipments],[nozzles],[symbols]]
|
798 |
targetSymbolList.append(equipments) |
799 |
targetSymbolList.append(nozzles) |
800 |
targetSymbolList.append([item for item in symbolList if item not in equipments and item not in nozzles]) |
801 | |
802 |
return targetSymbolList
|
803 | |
804 |
'''
|
805 |
@brief remove small objects from given image
|
806 |
@author humkyung
|
807 |
@date 2018.04.26
|
808 |
@history 2018.05.25 Jeongwoo Moved from MainWindow
|
809 |
'''
|
810 |
def removeSmallObjects(image): |
811 |
try:
|
812 |
appDocData = AppDocData.instance() |
813 |
configs = appDocData.getConfigs('Small Object Size', 'Min Area') |
814 |
minArea = int(configs[0].value) if 1 == len(configs) else 20 |
815 |
configs = appDocData.getConfigs('Small Object Size', 'Max Area') |
816 |
maxArea = int(configs[0].value) if 1 == len(configs) else 50 |
817 | |
818 |
_,contours,_ = cv2.findContours(image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE); |
819 |
selectedContours=[] |
820 |
for contour in contours: |
821 |
area = cv2.contourArea(contour) |
822 |
if area > minArea and area < maxArea: selectedContours.append(contour) |
823 |
contourImage = cv2.drawContours(image, selectedContours, -1, (255,255,255), -1); # draw contour with white color |
824 |
except Exception as ex: |
825 |
print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)) |
826 | |
827 |
return contourImage
|
828 | |
829 |
'''
|
830 |
@history 2018.05.25 Jeongwoo Moved from MainWindow
|
831 |
2018.05.28 Jeongwoo Add xmlPath Parameter and append LineInfo into xml
|
832 |
2018.05.29 Jeongwoo Change method to add item
|
833 |
2018.05.30 Jeongwoo Remove parameter (xmlPath)
|
834 |
humkyung 2018.06.11 add drawing path to parameter and write recognized lines to image
|
835 |
humkyung 2018.07.04 call arrangeLinePosition after creating line
|
836 |
'''
|
837 |
def recognizeLine(path, listWidget, graphicsView): |
838 |
from shapely.geometry import Point, LineString |
839 |
from SymbolSvgItem import SymbolSvgItem |
840 |
from QEngineeringFlowArrowItem import QEngineeringFlowArrowItem |
841 |
from QEngineeringLineNoTextItem import QEngineeringLineNoTextItem |
842 |
from QEngineeringTextItem import QEngineeringTextItem |
843 |
from QEngineeringLineItem import QEngineeringLineItem |
844 |
from LineDetector import LineDetector |
845 | |
846 |
try:
|
847 |
listWidget.addItem('Starting line recognization')
|
848 | |
849 |
#remove already existing line and flow arrow item
|
850 |
items = [item for item in graphicsView.scene.items() if (type(item) is QEngineeringLineItem) or (type(item) is QEngineeringFlowArrowItem)] |
851 |
for item in items: |
852 |
graphicsView.scene.removeItem(item) |
853 |
#up to here
|
854 | |
855 |
# detect line
|
856 |
connectedLines = [] |
857 | |
858 |
area = AppDocData.instance().getArea('Drawing')
|
859 |
area.img = removeSmallObjects(area.img) |
860 |
detector = LineDetector(area.img) |
861 | |
862 |
symbols = [] |
863 |
for item in graphicsView.scene.items(): |
864 |
if issubclass(type(item), SymbolSvgItem): |
865 |
symbols.append(item) |
866 |
res = detector.detectConnectedLine(item, round(area.x), round(area.y)) |
867 |
if res is not None: |
868 |
connectedLines.extend(res) |
869 | |
870 |
listWidget.addItem('Connecting lines')
|
871 |
if len(connectedLines) > 1: |
872 |
detector.mergeLines(connectedLines, toler=5)
|
873 |
# connect line to symbol
|
874 |
try:
|
875 |
for line in connectedLines: |
876 |
matches = [symbol for symbol in symbols if symbol.isConnectable(line, (round(area.x), round(area.y)), toler=20)] |
877 |
for symbol in matches: |
878 |
detector.connectLineToSymbol(line, (round(area.x), round(area.y)), symbol) |
879 |
except Exception as ex: |
880 |
print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)) |
881 |
# up to here
|
882 | |
883 |
# connect line to line
|
884 |
toler = 10
|
885 |
try:
|
886 |
for line in connectedLines: |
887 |
matches = [it for it in connectedLines if (it is not line) and (not detector.isParallel(line, it))] |
888 | |
889 |
# get closest line
|
890 |
selected = [] |
891 |
shapelyLine = LineString(line) |
892 |
for match in matches: |
893 |
dist = [shapelyLine.distance(Point(match[0][0], match[0][1])),shapelyLine.distance(Point(match[1][0], match[1][1]))] |
894 |
if dist[0] < toler or dist[1] < toler: |
895 |
selected.append(match) |
896 |
# up to here
|
897 | |
898 |
for match in selected: |
899 |
detector.connectLineToLine(match, line, toler) |
900 |
except Exception as ex: |
901 |
print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)) |
902 |
# up to here
|
903 | |
904 |
lines = [] |
905 |
for pts in connectedLines: |
906 |
processLine = QEngineeringLineItem(vertices=[(area.x + param[0], area.y + param[1]) for param in pts]) |
907 |
processLine.buildItem() |
908 |
processLine.addLineItemToScene(graphicsView.scene) |
909 |
lines.append(processLine) |
910 | |
911 |
if processLine.length() > 100: # TODO: check critical length |
912 |
processLine.addFlowArrow() |
913 |
|
914 |
# re-order process line's start,end according to flow mark
|
915 |
arrangeLinePosition(lines, symbols, listWidget) |
916 |
# up to here
|
917 |
except Exception as ex: |
918 |
print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)) |
919 |
finally:
|
920 |
listWidget.addItem('Finished line recognization')
|
921 |
|
922 |
'''
|
923 |
@brief arrange line's position
|
924 |
@author humkyung
|
925 |
@date 2018.07.04
|
926 |
'''
|
927 |
def arrangeLinePosition(lines, symbols, listWidget): |
928 |
try:
|
929 |
listWidget.addItem('Apply flow direction')
|
930 |
pool = [line for line in lines if line.flowMark is not None] |
931 |
visited = [] |
932 |
visited.extend(pool) |
933 |
while len(pool) > 0: |
934 |
line = pool.pop() |
935 |
print('{} - ({})'.format(line, len(pool))) |
936 |
rhs = [item for item in lines if item not in visited and item.isJointed(line)] |
937 |
if rhs:
|
938 |
pool.extend(rhs) |
939 |
visited.extend(rhs) |
940 |
for item in rhs: |
941 |
item.arrangeVertexOrder(line) |
942 |
|
943 |
# skip jointed symbols
|
944 |
symbolPool = [item for item in symbols if item not in visited and item.isJointed(line)] |
945 |
if symbolPool:
|
946 |
selected = [] |
947 |
visited.extend(symbolPool) |
948 |
while len(symbolPool) > 0: |
949 |
symbol = symbolPool.pop() |
950 | |
951 |
rhs = [item for item in symbols if item not in visited and item.isJointed(symbol)] |
952 |
if rhs:
|
953 |
symbolPool.extend(rhs) |
954 |
visited.extend(rhs) |
955 |
selected.extend(rhs) |
956 |
else:
|
957 |
selected.append(symbol) |
958 | |
959 |
# find lines which are connected last symbol
|
960 |
for symbol in selected: |
961 |
rhs = [item for item in lines if item not in visited and item.isJointed(symbol)] |
962 |
if rhs:
|
963 |
pool.extend(rhs) |
964 |
visited.extend(rhs) |
965 |
for item in rhs: |
966 |
item.arrangeVertexOrder(line) |
967 |
# up to here
|
968 |
# up to here
|
969 |
except Exception as ex: |
970 |
print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)) |
971 | |
972 |
'''
|
973 |
@brief Main function
|
974 |
@author Jeongwoo
|
975 |
@date
|
976 |
@history humkyung 2018.04.06 change error display from message box to print
|
977 |
Jeongwoo 2018.04.25 Remove 'Current Symbol : ' QListItem
|
978 |
Jeongwoo 2018.05.09 Make Comments OCR.removeTextFromNpArray block
|
979 |
Jeongwoo 2018.05.25 Remove imgLineList variable and parameter on writeXml()
|
980 |
humkyung 2018.05.26 add parameters(graphicsView, isSymbolTextChecked, isLineChecked)
|
981 |
Jeongwoo 2018.05.28 Add/Remove Parameters(Add : signal / Remove : graphicsView, isLineChecked)
|
982 |
Jeongwoo 2018.05.30 Remove return value
|
983 |
humkyung 2018.06.08 add signal for progressbar to parameter
|
984 |
humkyung 2018.06.11 get difference between original and recognized image
|
985 |
Jeongwoo 2018.06.21 If noteTextInfoList is None, change from None to empty list
|
986 |
'''
|
987 |
def executeRecognition(signal, updateProgressSignal, path, listWidget, isSymbolTextChecked): |
988 |
import re |
989 |
from TextDetector import TextDetector |
990 | |
991 |
global ocrCompletedSrc
|
992 |
global searchedSymbolList
|
993 |
global threadLock
|
994 |
global textInfoList
|
995 |
global noteTextInfoList
|
996 |
global maxProgressValue
|
997 |
|
998 |
try:
|
999 |
appDocData = AppDocData.instance() |
1000 |
project = appDocData.getCurrentProject() |
1001 |
textDetector = TextDetector() |
1002 | |
1003 |
srcList = [] |
1004 |
srcList.append(path) |
1005 | |
1006 |
initTargetSymbolDataList() |
1007 | |
1008 |
for mainRes in srcList: |
1009 |
ocrCompletedSrc = [] |
1010 |
searchedSymbolList = [] |
1011 |
textInfoList = [] |
1012 | |
1013 |
if not os.path.isfile(mainRes): |
1014 |
item = QListWidgetItem('{} file is not found'.format(os.path.basename(mainRes)))
|
1015 |
item.setBackground(Qt.red) |
1016 |
listWidget.addItem(item) |
1017 |
continue
|
1018 | |
1019 |
# remove equipment desc. area
|
1020 |
configs = appDocData.getConfigs('{} Equipment Desc Area'.format(appDocData.imgName))
|
1021 |
for config in configs: |
1022 |
found = re.findall('\d+', config.value)
|
1023 |
if len(found) == 4: |
1024 |
cv2.rectangle(appDocData.imgSrc, (int(found[0]), int(found[1])), (int(found[0])+int(found[2]), int(found[1])+int(found[3])), 255, -1) |
1025 |
# up to here
|
1026 |
|
1027 |
area = appDocData.getArea('Drawing')
|
1028 |
if area is not None: |
1029 |
area.img = appDocData.imgSrc[round(area.y):round(area.y+area.height), round(area.x):round(area.x+area.width)] |
1030 | |
1031 |
listWidget.addItem("Start recognition : " + mainRes)
|
1032 | |
1033 |
if isSymbolTextChecked:
|
1034 |
threadLock.acquire() |
1035 |
offset = (area.x, area.y) if area is not None else (0,0) |
1036 |
textAreas = textDetector.detectTextAreas(area.img if area is not None else appDocData.imgSrc, offset) |
1037 |
### calculate total count of symbol
|
1038 |
maxProgressValue = len(textAreas) + 1 |
1039 |
for targetItem in targetSymbolList: |
1040 |
if type(targetItem) is list: |
1041 |
maxProgressValue += len(targetItem)
|
1042 |
else:
|
1043 |
maxProgressValue += 1
|
1044 |
### up to here
|
1045 |
threadLock.release() |
1046 | |
1047 |
# detect equipments
|
1048 |
pool = futures.ThreadPoolExecutor(max_workers = THREAD_MAX_WORKER) |
1049 |
for symbol in targetSymbolList[0]: |
1050 |
pool.submit(detectEquipmentOnPid, mainRes, symbol, listWidget, updateProgressSignal) |
1051 |
pool.shutdown(wait = True)
|
1052 |
# up to here
|
1053 | |
1054 |
pool = futures.ThreadPoolExecutor(max_workers = THREAD_MAX_WORKER) |
1055 |
for symbol in targetSymbolList[2]: |
1056 |
if type(symbol) is list: |
1057 |
pool.submit(detectSymbolsOnPid, mainRes, symbol, listWidget, updateProgressSignal) |
1058 |
else:
|
1059 |
pool.submit(detectSymbolOnPid, mainRes, symbol, listWidget, updateProgressSignal) |
1060 |
pool.shutdown(wait = True)
|
1061 | |
1062 |
## DEBUG
|
1063 |
print('----------')
|
1064 |
for item in searchedSymbolList: |
1065 |
print('{}:{}-{}'.format(item.getName(), item.getSp(), item.hitRate))
|
1066 |
_img = appDocData.imgSrc[round(item.getSp()[1]):round(item.getSp()[1]+item.getHeight()), round(item.getSp()[0]):round(item.getSp()[0]+item.getWidth())] |
1067 |
cv2.imwrite(os.path.join(project.getTempPath(), 'Tile', item.getName()+'.png'), _img) |
1068 |
## up to here
|
1069 | |
1070 |
textDetector.recognizeText(appDocData.imgSrc, offset, textAreas, searchedSymbolList, updateProgressSignal, listWidget, maxProgressValue) |
1071 |
textInfoList = textDetector.textInfoList.copy() |
1072 |
noteTextInfoList = textDetector.noteTextInfoList.copy() |
1073 | |
1074 |
appDocData.imgWidth, appDocData.imgHeight = appDocData.imgSrc.shape[::-1]
|
1075 |
drawFoundSymbolsOnCanvas(mainRes, textInfoList, listWidget) |
1076 |
|
1077 |
appDocData.imgName = os.path.splitext(os.path.basename(mainRes))[0]
|
1078 | |
1079 |
pool = futures.ThreadPoolExecutor(max_workers = THREAD_MAX_WORKER) |
1080 |
for sym in searchedSymbolList: |
1081 |
pool.submit(removeDetectedSymbol, sym, appDocData.imgSrc) |
1082 |
#pool.submit(drawRectOnSrc, sym)
|
1083 |
pool.shutdown(wait = True)
|
1084 | |
1085 |
'''
|
1086 |
global MIN_TEXT_SIZE
|
1087 |
for textInfo in textInfoList:
|
1088 |
if textInfo.getW() >= MIN_TEXT_SIZE or textInfo.getH() >= MIN_TEXT_SIZE:
|
1089 |
removeText(srcGray, (textInfo.getX(), textInfo.getY()), srcGray[textInfo.getY():textInfo.getY()+textInfo.getH(), textInfo.getX():textInfo.getX()+textInfo.getW()])
|
1090 |
'''
|
1091 | |
1092 |
## Remove Noise
|
1093 |
kernel1 = np.ones((2, 2), np.uint8) |
1094 |
appDocData.imgSrc = cv2.dilate(appDocData.imgSrc, kernel1) |
1095 |
appDocData.imgSrc = cv2.erode(appDocData.imgSrc, kernel1) |
1096 | |
1097 |
removedSymbolImgPath = os.path.join(project.getTempPath(), os.path.basename(path)) |
1098 |
cv2.imwrite(removedSymbolImgPath, appDocData.imgSrc) |
1099 | |
1100 |
area = AppDocData.instance().getArea('Drawing')
|
1101 |
if area is not None: |
1102 |
area.img = appDocData.imgSrc[round(area.y+1):round(area.y+area.height), round(area.x+1):round(area.x+area.width)] |
1103 |
cv2.imwrite(os.path.join(project.getTempPath(), "RECT_" + os.path.basename(path)), appDocData.imgSrc)
|
1104 | |
1105 |
listWidget.addItem("Recognized symbol count : " + str(len(searchedSymbolList))) |
1106 |
|
1107 |
# get difference between original and recognized image
|
1108 |
foundFilePath = os.path.join(project.getTempPath(), "FOUND_" + os.path.basename(path))
|
1109 |
getDifference(path, foundFilePath) |
1110 |
# up to here
|
1111 |
|
1112 |
signal.emit(searchedSymbolList, textInfoList, noteTextInfoList if noteTextInfoList is not None else []) |
1113 |
except Exception as ex: |
1114 |
print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)) |
1115 |
|
1116 |
'''
|
1117 |
@brief draw contour to image
|
1118 |
@author humkyung
|
1119 |
@date 2018.06.18
|
1120 |
'''
|
1121 |
def drawContour(img, contour): |
1122 |
area = cv2.contourArea(contour, True)
|
1123 |
if area >= 0: |
1124 |
cv2.drawContours(img, [contour], -1, (0,0,0), -1) |
1125 |
cv2.drawContours(img, [contour], -1, (255,255,255), 1) |
1126 |
else:
|
1127 |
cv2.drawContours(img, [contour], -1, (255,255,255), -1) |
1128 |
|
1129 |
'''
|
1130 |
@brief get difference between given original and recognized image
|
1131 |
@author humkyung
|
1132 |
@date 2018.06.11
|
1133 |
'''
|
1134 |
def getDifference(orgImagePath, recImagePath): |
1135 |
import re |
1136 | |
1137 |
global ocrCompletedSrc
|
1138 |
global textInfoList
|
1139 |
global noteTextInfoList
|
1140 | |
1141 |
try:
|
1142 |
appDocData = AppDocData.instance() |
1143 |
if os.path.isfile(orgImagePath) and os.path.isfile(recImagePath): |
1144 |
imgOriginal = cv2.threshold(cvtGrayImage(cv2.imread(orgImagePath, 1)), 127, 255, cv2.THRESH_BINARY)[1] |
1145 |
# remove equipment desc. area
|
1146 |
configs = appDocData.getConfigs('{} Equipment Desc Area'.format(appDocData.imgName))
|
1147 |
for config in configs: |
1148 |
found = re.findall('\d+', config.value)
|
1149 |
if len(found) == 4: |
1150 |
cv2.rectangle(imgOriginal, (int(found[0]), int(found[1])), (int(found[0])+int(found[2]), int(found[1])+int(found[3])), 255, -1) |
1151 |
# up to here
|
1152 | |
1153 |
imgRecognized = cv2.threshold(cvtGrayImage(cv2.imread(recImagePath, 1)), 127, 255, cv2.THRESH_BINARY)[1] |
1154 | |
1155 |
imgDiff = np.ones(imgOriginal.shape, np.uint8)*255
|
1156 | |
1157 |
area = AppDocData.instance().getArea('Drawing')
|
1158 |
if area is not None: |
1159 |
x = round(area.x)
|
1160 |
y = round(area.y)
|
1161 |
width = round(area.width)
|
1162 |
height = round(area.height)
|
1163 |
imgNotOper = cv2.bitwise_not(imgRecognized[y:y+height, x:x+width]) |
1164 |
imgDiff[y:y+height, x:x+width] = cv2.bitwise_xor(imgOriginal[y:y+height, x:x+width], imgNotOper) |
1165 |
|
1166 |
# remove noise
|
1167 |
imgDiff = cv2.dilate(imgDiff, np.ones((2, 2), np.uint8)) |
1168 | |
1169 |
appDocData = AppDocData.instance() |
1170 |
project = appDocData.getCurrentProject() |
1171 |
cv2.imwrite(os.path.join(project.getTempPath(), "DIFF_" + os.path.basename(orgImagePath)), imgDiff)
|
1172 |
except Exception as ex: |
1173 |
print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)) |
1174 |
|
1175 |
if __name__ == '__main__': |
1176 |
import DTI_PID_UI |
1177 |
from ProjectDialog import Ui_Dialog |
1178 |
import timeit |
1179 |
from PyQt5.QtCore import QRect |
1180 |
from operator import itemgetter, attrgetter |
1181 | |
1182 |
start = timeit.default_timer() |
1183 |
img = cv2.imread('D:/Visual Studio Project/DTIPID/DTIPID/DTI_PID/DTI_PID/res/Result/PC-K/PC-K-2203_P1_800DPI.png', 1) |
1184 |
imgGray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) |
1185 |
contourImg = np.ones(imgGray.shape, np.uint8)*255
|
1186 |
contourOcrImg = contourImg.copy() |
1187 |
binaryImg,mask = cv2.threshold(imgGray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) |
1188 |
imgFinal = cv2.bitwise_and(imgGray, imgGray, mask = mask) |
1189 |
ret, newImg = cv2.threshold(imgFinal, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) |
1190 | |
1191 |
image, contours, hierarchy = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE) |
1192 |
#holes = [contours[i] for i in range(len(contours)) if hierarchy[i][3] >= 0]
|
1193 |
for contour in contours: |
1194 |
area = cv2.contourArea(contour, True)
|
1195 |
if area >= 0: |
1196 |
[x, y, w, h] = cv2.boundingRect(contour) |
1197 | |
1198 |
# remove too big or small one
|
1199 |
if (w > 100 or h > 100) or (w < 5 or h < 5): continue |
1200 | |
1201 |
cv2.drawContours(contourImg, [contour], -1, (0,0,0), 1) |
1202 |
cv2.drawContours(contourOcrImg, [contour], -1, (0,0,0), -1) |
1203 |
else:
|
1204 |
cv2.drawContours(contourOcrImg, [contour], -1, (255,255,255), -1) |
1205 | |
1206 |
''' contourImg = cv2.bitwise_not(contourImg) circles = cv2.HoughCircles(contourImg, cv2.HOUGH_GRADIENT, 1, 100) circles = np.uint16(np.around(circles))
|
1207 |
for i in circles[0,:]:
|
1208 |
cv2.circle(contourImg, (i[0], i[1]), i[2], (255,255,0), 1)
|
1209 |
'''
|
1210 | |
1211 |
rects = [] |
1212 |
kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (8, 8)) |
1213 |
#kernel1 = cv2.getStructuringElement(cv2.MORPH_CROSS, (2,2))
|
1214 |
#eroded = cv2.dilate(contourImg, kernel1)
|
1215 |
#cv2.imwrite('D:/Visual Studio Project/DTIPID/DTIPID/DTI_PID/DTI_PID/res/Result/PC-K/Temp/Dilate_PC-K-2203_P1_800DPI___partial.png', eroded)
|
1216 |
eroded = cv2.erode(contourImg, kernel) |
1217 |
image, contours, hierarchy = cv2.findContours(eroded, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE) |
1218 |
for contour in contours: |
1219 |
area = cv2.contourArea(contour, True)
|
1220 |
if area >= 0: |
1221 |
[x, y, w, h] = cv2.boundingRect(contour) |
1222 | |
1223 |
# remove small one less than character size
|
1224 |
if (w < 20 or h < 20): continue |
1225 |
#if w > h:
|
1226 |
# rects.append(QRect(x, y, w, h)) # expand rect
|
1227 |
#elif w < h:
|
1228 |
# rects.append(QRect(x, y, w, h)) # expand rect
|
1229 |
rects.append(QRect(x, y, w, h)) # expand rect
|
1230 | |
1231 |
intersected = True
|
1232 |
while intersected:
|
1233 |
intersected = False
|
1234 |
for rect in rects[:]: |
1235 |
matches = [x for x in rects if rect.intersects(x)] |
1236 |
if len(matches) > 1: |
1237 |
united = matches[0]
|
1238 |
for _rect in matches: |
1239 |
united = united.united(_rect) |
1240 |
if _rect in rects: rects.remove(_rect) |
1241 |
rects.append(united) |
1242 |
intersected = True
|
1243 |
break
|
1244 | |
1245 |
for rect in rects: |
1246 |
cv2.rectangle(img, (rect.x(), rect.y()), (rect.x() + rect.width(), rect.y() + rect.height()), (255, 0, 255), 1) |
1247 | |
1248 |
cv2.imwrite('D:/Visual Studio Project/DTIPID/DTIPID/DTI_PID/DTI_PID/res/Result/PC-K/Temp/PC-K-2203_P1_800DPI___partial.png', img)
|
1249 |
cv2.imwrite('D:/Visual Studio Project/DTIPID/DTIPID/DTI_PID/DTI_PID/res/Result/PC-K/Temp/Contour_PC-K-2203_P1_800DPI___partial.png', contourOcrImg)
|
1250 |
cv2.imwrite('D:/Visual Studio Project/DTIPID/DTIPID/DTI_PID/DTI_PID/res/Result/PC-K/Temp/Erode_PC-K-2203_P1_800DPI___partial.png', eroded)
|
1251 | |
1252 |
chan, imgW, imgH = img.shape[::-1]
|
1253 |
index = 0
|
1254 |
for rect in rects: |
1255 |
index = index + 1
|
1256 |
isVertical = False
|
1257 |
textInfoList = None
|
1258 |
if rect.width() >= rect.height() or rect.height() < 50: |
1259 |
isVertical = False
|
1260 |
textInfoList = TOCR.getTextInfo(contourOcrImg[rect.y():rect.y()+rect.height(), rect.x():rect.x()+rect.width()], (rect.x(), rect.y())) |
1261 |
else:
|
1262 |
isVertical = True
|
1263 |
transform = QTransform() |
1264 |
transform.translate(imgH*0.5, imgW*0.5) |
1265 |
transform.rotate(90)
|
1266 |
transform.translate(-imgW*0.5, -imgH*0.5) |
1267 |
transRect = transform.mapRect(rect) |
1268 |
rotatedContourOcrImg = cv2.rotate(contourOcrImg, cv2.ROTATE_90_CLOCKWISE) |
1269 |
textInfoList = TOCR.getTextInfo(rotatedContourOcrImg[transRect.y():transRect.y()+transRect.height(), transRect.x():transRect.x()+transRect.width()], (transRect.x(), transRect.y())) |
1270 | |
1271 |
if isVertical:
|
1272 |
img = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE) |
1273 |
if textInfoList is not None: |
1274 |
for textInfo in textInfoList: |
1275 |
cv2.putText(img, textInfo.getText(), (textInfo.getX(), textInfo.getY()+textInfo.getH()), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 3) |
1276 |
if isVertical:
|
1277 |
img = cv2.rotate(img, cv2.ROTATE_90_COUNTERCLOCKWISE) |
1278 | |
1279 |
print(str(index) + " / " + str(len(rects)) + " Finished") |
1280 |
#cv2.imshow('test', img[rect.y():rect.y()+rect.height(), rect.x():rect.x()+rect.width()])
|
1281 |
#cv2.waitKey(0)
|
1282 |
#cv2.destroyAllWindows()
|
1283 |
|
1284 |
cv2.imwrite('D:/Visual Studio Project/DTIPID/DTIPID/DTI_PID/DTI_PID/res/Result/PC-K/Temp/OCR_PC-K-2203_P1_800DPI___partial.png', img)
|
1285 |
stop = timeit.default_timer() |
1286 |
print('FINISHED : ' + str((stop-start)/60) + ' min') |
1287 |
|
1288 |
#app = QApplication(sys.argv)
|
1289 | |
1290 |
#try:
|
1291 |
# dlg = Ui_Dialog()
|
1292 |
# selectedProject = dlg.showDialog()
|
1293 |
# if selectedProject is not None:
|
1294 |
# form = ExampleApp()
|
1295 |
# form.show()
|
1296 |
#except Exception as ex:
|
1297 |
# print('에러가 발생했습니다.\n', ex)
|
1298 | |
1299 |
#sys.exit(app.exec_())
|