hytos / DTI_PID / DTI_PID / DTI_PID.py @ 47bd6b4e
이력 | 보기 | 이력해설 | 다운로드 (36.6 KB)
1 |
#region import libs
|
---|---|
2 |
import http.client |
3 |
import urllib, base64, json |
4 |
import cv2 |
5 |
import numpy as np |
6 |
import SymbolBase |
7 |
import symbol |
8 |
import TextInfo as ti |
9 |
import azure_ocr_module as OCR |
10 |
from PIL import Image |
11 |
from io import BytesIO |
12 |
import gc |
13 |
import os |
14 |
import glob |
15 |
import timeit |
16 |
import math, operator |
17 |
import threading |
18 |
import concurrent.futures as futures |
19 |
import XmlGenerator as xg |
20 |
import pytesseract |
21 |
import tesseract_ocr_module as TOCR |
22 |
import potrace |
23 |
import sys |
24 |
from PyQt5.QtCore import * |
25 |
from PyQt5.QtGui import * |
26 |
from PyQt5.QtWidgets import * |
27 |
from PyQt5.QtSvg import * |
28 |
import DTI_PID_UI |
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.environ['TESSERACT_HOME'] + '\\tesseract.exe' |
45 |
tesseract_cmd = 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 |
#DB_NAME = "db/DTI_PID_test.db"
|
65 |
#DB_NAME = AppDocData.instance().getCurrentProject().getPath() + "/db/" + "DTI_PID.db"
|
66 |
#DB_NAME = os.path.dirname(os.path.realpath(__file__)) + "\\db\\DTI_PID.db"
|
67 |
|
68 |
THREAD_MAX_WORKER = 4
|
69 |
threadLock = threading.Lock() |
70 |
|
71 |
ACCEPT_OVERLAY_AREA = 10
|
72 |
#endregion
|
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 categoryCode != 99 and (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 |
def removeText(img, text, x, y, width, height): |
95 |
x = int(x)
|
96 |
y = int(y)
|
97 |
width = int(width)
|
98 |
height = int(height)
|
99 |
roi = img[y:y+height, x:x+width] |
100 |
temp = roi.copy() |
101 |
tempBin = cv2.bitwise_not(temp) |
102 |
img[y:y+height, x:x+width] = cv2.bitwise_xor(roi, tempBin) |
103 |
return img
|
104 |
|
105 |
#Convert into Grayscale image
|
106 |
def cvtGrayImage(img): |
107 |
return cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
108 |
|
109 |
'''
|
110 |
@brief rotate (x,y) by given angle
|
111 |
@author Jeongwoo
|
112 |
@date 2018.??.??
|
113 |
@history humkyung 2018.04.13 fixed code when angle is 90 or 270
|
114 |
Jeongwoo 2018.04.27 Change calculation method with QTransform
|
115 |
'''
|
116 |
def getCoordOnRotatedImage(rAngle, x, y, originImageWidth, originImageHeight): |
117 |
rx = None
|
118 |
ry = None
|
119 |
transform = QTransform() |
120 |
if rAngle == 90 or rAngle == 270: |
121 |
transform.translate(originImageHeight*0.5, originImageWidth*0.5) |
122 |
elif rAngle == 0 or rAngle == 180: |
123 |
transform.translate(originImageWidth*0.5, originImageHeight*0.5) |
124 |
transform.rotate(-abs(rAngle))
|
125 |
transform.translate(-originImageWidth*0.5, -originImageHeight*0.5) |
126 |
point = QPoint(x, y) |
127 |
point = transform.map(point) |
128 |
rx = point.x() |
129 |
ry = point.y() |
130 |
return (rx, ry)
|
131 |
|
132 |
def convertDirectionCodeToValue(directionCode): |
133 |
if directionCode == "UP": |
134 |
return 0 |
135 |
elif directionCode == "RIGHT": |
136 |
return 1 |
137 |
elif directionCode == "DOWN": |
138 |
return 2 |
139 |
elif directionCode == "LEFT": |
140 |
return 3 |
141 |
else:
|
142 |
return -1 |
143 |
|
144 |
def convertValueToDirectionCode(value): |
145 |
if value == 0: |
146 |
return "UP" |
147 |
elif value == 1: |
148 |
return "RIGHT" |
149 |
elif value == 2: |
150 |
return "DOWN" |
151 |
elif value == 3: |
152 |
return "LEFT" |
153 |
else:
|
154 |
return "NONE" |
155 |
|
156 |
def getRotatedChildInfo(additionalSymbol): |
157 |
tempChildInfo = ""
|
158 |
if additionalSymbol:
|
159 |
childList = additionalSymbol.split("/")
|
160 |
for index in range(len(childList)): |
161 |
child = childList[index] |
162 |
direction = convertDirectionCodeToValue(child.split(",")[0]) |
163 |
childName = child.split(",")[1] |
164 |
direction = (direction - 1) if direction > 0 else 3 |
165 |
if index != 0: |
166 |
tempChildInfo = tempChildInfo + "/"
|
167 |
tempChildInfo = tempChildInfo + convertValueToDirectionCode(direction) + "," + childName
|
168 |
return tempChildInfo
|
169 |
|
170 |
|
171 |
#Check object contains pt
|
172 |
#obj is item in searchedSymbolList
|
173 |
def contains(obj, pt, tw, th): |
174 |
sp = obj.getSp() |
175 |
width = obj.getWidth() |
176 |
height = obj.getHeight() |
177 |
|
178 |
if sp[0] > pt[0]+tw: |
179 |
return 0 |
180 |
if sp[0]+width < pt[0]: |
181 |
return 0 |
182 |
if sp[1] > pt[1]+th: |
183 |
return 0 |
184 |
if sp[1]+height < pt[1]: |
185 |
return 0 |
186 |
|
187 |
#shared area
|
188 |
x = max(sp[0], pt[0]); |
189 |
y = max(sp[1], pt[1]); |
190 |
w = min(sp[0] + width, pt[0] + tw) - x; |
191 |
h = min(sp[1] + height, pt[1] + th) - y; |
192 |
|
193 |
return float((w * h)) / float((tw * th)) * 100 |
194 |
|
195 |
def getSplitSrcList(srcPid, splitCount, splitWidth, splitHeight): |
196 |
splitRoiList = [] |
197 |
for hi in range(splitCount): |
198 |
for wi in range(splitCount): |
199 |
roiSp = (splitWidth*wi, splitHeight*hi) |
200 |
roiEp = (splitWidth*(wi+1), splitHeight*(hi+1)) |
201 |
splitRoiList.append((roiSp, roiEp, srcPid[roiSp[1]:roiEp[1], roiSp[0]:roiEp[0]])) |
202 |
return splitRoiList
|
203 |
|
204 |
def getCalculatedOriginalPoint(additionalSymbol, symbolOriginalPoint, symbolRotatedAngle, rotateSymbolWidth, rotateSymbolHeight, originalSymbolWidth, originalSymbolHeight): |
205 |
originalPoint = ''
|
206 |
if additionalSymbol is None and symbolOriginalPoint is None: |
207 |
originalPoint = str(rotateSymbolWidth//2)+','+str(rotateSymbolHeight//2) |
208 |
else:
|
209 |
opx = int(symbolOriginalPoint.split(',')[0]) |
210 |
opy = int(symbolOriginalPoint.split(',')[1]) |
211 |
rPt = getCoordOnRotatedImage(symbolRotatedAngle, opx, opy, originalSymbolWidth, originalSymbolHeight) |
212 |
originalPoint = str(int(rPt[0])) + ',' + str(int(rPt[1])) |
213 |
return originalPoint
|
214 |
|
215 |
def getCalculatedConnectionPoint(symbolConnectionPointStr, symbolRotatedAngle, rotateSymbolWidth, rotateSymbolHeight, originalSymbolWidth, originalSymbolHeight): |
216 |
convertedConnectionPoint = ""
|
217 |
if symbolConnectionPointStr is not None: |
218 |
splitConnectionPointStr = symbolConnectionPointStr.split("/")
|
219 |
for index in range(len(splitConnectionPointStr)): |
220 |
if index != 0: |
221 |
convertedConnectionPoint = convertedConnectionPoint + "/"
|
222 |
item = splitConnectionPointStr[index] |
223 |
cpx = int(item.split(',')[0]) |
224 |
cpy = int(item.split(',')[1]) |
225 |
rPt = getCoordOnRotatedImage(symbolRotatedAngle, cpx, cpy, originalSymbolWidth, originalSymbolHeight) |
226 |
temp = str(int(rPt[0])) + ',' + str(int(rPt[1])) |
227 |
convertedConnectionPoint = convertedConnectionPoint + temp |
228 |
return convertedConnectionPoint
|
229 |
|
230 |
'''
|
231 |
@brief Add symbols
|
232 |
@author jwkim
|
233 |
@date
|
234 |
'''
|
235 |
def addSearchedSymbol(id, sName, sType |
236 |
, sp, w, h, threshold, minMatchCount, mpCount, rotatedAngle |
237 |
, isDetectOnOrigin, rotateCount, ocrOption, isContainChild |
238 |
, originalPoint, connectionPoint, baseSymbol, additionalSymbol, isExceptDetect): |
239 |
global searchedSymbolList
|
240 |
newSym = symbol.Symbol(id, sName, sType
|
241 |
, sp, w, h, threshold, minMatchCount, mpCount, rotatedAngle |
242 |
, isDetectOnOrigin, rotateCount, ocrOption, isContainChild |
243 |
, originalPoint, connectionPoint, baseSymbol, additionalSymbol, isExceptDetect) |
244 |
##Add symbols
|
245 |
#def addSearchedSymbol(id, sName, sType, symbolPath
|
246 |
# , sp, w, h, threshold, minMatchCount, mpCount, rotatedAngle
|
247 |
# , isDetectOnOrigin, rotateCount, ocrOption, isContainChild
|
248 |
# , originalPoint, connectionPoint, baseSymbol, additionalSymbol):
|
249 |
# global searchedSymbolList
|
250 |
# newSym = symbol.Symbol(id, sName, sType, symbolPath
|
251 |
# , sp, w, h, threshold, minMatchCount, mpCount, rotatedAngle
|
252 |
# , isDetectOnOrigin, rotateCount, ocrOption, isContainChild
|
253 |
# , originalPoint, connectionPoint, baseSymbol, additionalSymbol)
|
254 |
searchedSymbolList.append(newSym) |
255 |
|
256 |
|
257 |
#Calculate count of keypoint match result
|
258 |
def getMatchPointCount(src, cmp): |
259 |
orb = cv2.ORB_create(1000, 2.0, 2, 1) |
260 |
|
261 |
kp1, des1 = orb.detectAndCompute(src, None)
|
262 |
kp2, des2 = orb.detectAndCompute(cmp, None) |
263 |
|
264 |
FLANN_INDEX_LSH = 6
|
265 |
# table_number : The number of hash tables use
|
266 |
# key_size : The length of the key in the hash tables
|
267 |
# multi_probe_level : Number of levels to use in multi-probe (0 for standard LSH)
|
268 |
# It controls how neighboring buckets are searched
|
269 |
# Recommended value is 2
|
270 |
# checks : specifies the maximum leafs to visit when searching for neighbours.
|
271 |
# LSH : Locality-Sensitive Hashing
|
272 |
# ref : https://www.cs.ubc.ca/research/flann/uploads/FLANN/flann_manual-1.8.4.pdf
|
273 |
index_params = dict(algorithm = FLANN_INDEX_LSH, table_number = 20, key_size = 10, multi_probe_level = 4) |
274 |
search_params = dict(checks = 100) |
275 |
|
276 |
flann = cv2.FlannBasedMatcher(index_params,search_params) |
277 |
|
278 |
matches = flann.knnMatch(des1, des2, k = 2)
|
279 |
matchesMask = [[0, 0] for i in range(len(matches))] #Python 3.x |
280 |
|
281 |
count = 0
|
282 |
# ratio test as per Lowe's paper
|
283 |
for i in range(len(matches)): |
284 |
if len(matches[i]) == 2: |
285 |
m = matches[i][0]
|
286 |
n = matches[i][1]
|
287 |
if m.distance < 0.85 * n.distance: |
288 |
count = count + 1
|
289 |
|
290 |
matchCount = count |
291 |
|
292 |
#print("match Count : " + str(matchCount))
|
293 |
return matchCount
|
294 |
|
295 |
|
296 |
#detect symbols on PID
|
297 |
def detectSymbolsOnPid(mainRes, targetSymbols, listWidget): |
298 |
for detailTarget in targetSymbols: |
299 |
detectSymbolOnPid(mainRes, detailTarget, listWidget) |
300 |
|
301 |
'''
|
302 |
@brief detect symbol on PID
|
303 |
@author jwkim
|
304 |
@date
|
305 |
@history humkyung 2018.04.06 check if symbol file exists
|
306 |
'''
|
307 |
def detectSymbolOnPid(mainRes, targetSymbol, listWidget): |
308 |
global src
|
309 |
global srcGray
|
310 |
global ocrCompletedSrc
|
311 |
global afterDenoising
|
312 |
global threadLock
|
313 |
global searchedSymbolList
|
314 |
|
315 |
symId = targetSymbol.getId() |
316 |
symbolName = targetSymbol.getName() |
317 |
symbolType = targetSymbol.getType() |
318 |
symbolPath = targetSymbol.getPath() |
319 |
symbolThreshold = targetSymbol.getThreshold() |
320 |
symbolMinMatchCount = targetSymbol.getMinMatchCount() |
321 |
isDetectOnOrigin = targetSymbol.getIsDetectOnOrigin() |
322 |
symbolRotateCount = targetSymbol.getRotationCount() |
323 |
symbolOcrOption = targetSymbol.getOcrOption() |
324 |
isContainChild = targetSymbol.getIsContainChild() |
325 |
symbolOriginalPoint = targetSymbol.getOriginalPoint() |
326 |
symbolConnectionPoint = targetSymbol.getConnectionPoint() |
327 |
baseSymbol = targetSymbol.getBaseSymbol() |
328 |
additionalSymbol = targetSymbol.getAdditionalSymbol() |
329 |
isExceptDetect = targetSymbol.getIsExceptDetect() |
330 |
|
331 |
if isExceptDetect == 1: |
332 |
item = QListWidgetItem('{} file is not target'.format(os.path.basename(symbolPath.replace('.png', '')))) |
333 |
item.setBackground(QColor('green'))
|
334 |
listWidget.addItem(item) |
335 |
return
|
336 |
|
337 |
foundSymbolCount = 0
|
338 |
|
339 |
# check if symbol file exists
|
340 |
if not os.path.isfile(symbolPath): |
341 |
item = QListWidgetItem('{} file not found'.format(os.path.basename(symbolPath.replace('.png', '')))) |
342 |
item.setBackground(QColor('red'))
|
343 |
listWidget.addItem(item) |
344 |
return
|
345 |
#else:
|
346 |
# listWidget.addItem('Current symbol : ' + os.path.basename(symbolPath.replace('.png', '')))
|
347 |
# up to here
|
348 |
|
349 |
sym = cv2.imread(symbolPath, 1)
|
350 |
symGray = cvtGrayImage(sym) |
351 |
sow, soh = symGray.shape[::-1] # symbol original w, h |
352 |
|
353 |
offsetDrawingArea=[] |
354 |
area = AppDocData.instance().getArea('Drawing')
|
355 |
if area is not None: |
356 |
copiedBasePid = area.img.copy() |
357 |
offsetDrawingArea.append(area.x) |
358 |
offsetDrawingArea.append(area.y) |
359 |
else:
|
360 |
offsetDrawingArea.append(0)
|
361 |
offsetDrawingArea.append(0)
|
362 |
if isDetectOnOrigin == 1: |
363 |
copiedBasePid = srcGray.copy() |
364 |
else:
|
365 |
copiedBasePid = ocrCompletedSrc.copy() |
366 |
srcWidth, srcHeight = copiedBasePid.shape[::-1]
|
367 |
|
368 |
if srcWidth > 10000 or srcHeight > 10000: |
369 |
splitCount = 2 ## splitCount = 2^n |
370 |
else:
|
371 |
splitCount = 1
|
372 |
|
373 |
totalMpCount = 0
|
374 |
while splitCount >= 1: |
375 |
splitWidth = srcWidth // splitCount |
376 |
splitHeight = srcHeight // splitCount |
377 |
|
378 |
##Setting ROI List - [splitCount * splitCount] size
|
379 |
splitRoiList = getSplitSrcList(copiedBasePid, splitCount, splitWidth, splitHeight) |
380 |
|
381 |
for roiIndex in range(len(splitRoiList)): |
382 |
roiItemSp = splitRoiList[roiIndex][0]
|
383 |
roiItemEp = splitRoiList[roiIndex][1]
|
384 |
roiItem = splitRoiList[roiIndex][2]
|
385 |
|
386 |
symbolRotatedAngle = 0
|
387 |
for rc in range(symbolRotateCount + 1): ## Rotation Count를 사용자 기준으로 받아서 1을 더한 후 사용 |
388 |
##Template Matching
|
389 |
sw, sh = symGray.shape[::-1]
|
390 |
roiw = (roiItemEp[0] - roiItemSp[0]) |
391 |
roih = (roiItemEp[1] - roiItemSp[1]) |
392 |
|
393 |
## Case : Bigger Symbol than Split ROI
|
394 |
if roiw < sw or roih < sh: |
395 |
symGray = cv2.rotate(symGray, cv2.ROTATE_90_COUNTERCLOCKWISE) |
396 |
symbolRotatedAngle = symbolRotatedAngle + 90
|
397 |
|
398 |
if baseSymbol is not None and additionalSymbol is not None: |
399 |
additionalSymbol = getRotatedChildInfo(additionalSymbol) |
400 |
continue
|
401 |
|
402 |
## get Rotated Original Point
|
403 |
originalPoint = getCalculatedOriginalPoint(additionalSymbol, symbolOriginalPoint, symbolRotatedAngle, sw, sh, sow, soh) |
404 |
connectionPoint = getCalculatedConnectionPoint(symbolConnectionPoint, symbolRotatedAngle, sw, sh, sow, soh) |
405 |
|
406 |
## Template Matching
|
407 |
tmRes = cv2.matchTemplate(roiItem, symGray, cv2.TM_CCOEFF_NORMED) |
408 |
loc = np.where(tmRes >= symbolThreshold) |
409 |
|
410 |
|
411 |
for pt in zip(*loc[::-1]): |
412 |
overlapArea = 0
|
413 |
mpCount = 0 # Match Point Count |
414 |
symbolIndex = -1
|
415 |
|
416 |
searchedItemSp = (roiItemSp[0]+pt[0] + round(offsetDrawingArea[0]) + 1, roiItemSp[1]+pt[1] + round(offsetDrawingArea[1]) + 1) |
417 |
|
418 |
for i in range(len(searchedSymbolList)): |
419 |
overlapArea = contains(searchedSymbolList[i], searchedItemSp, sw, sh) |
420 |
if overlapArea > ACCEPT_OVERLAY_AREA:
|
421 |
symbolIndex = i |
422 |
break
|
423 |
|
424 |
|
425 |
roi = roiItem[pt[1]:pt[1]+sh, pt[0]:pt[0]+sw] |
426 |
mpCount = getMatchPointCount(roi, symGray) |
427 |
|
428 |
|
429 |
## 겹치는 영역이 기준값보다 작을 경우
|
430 |
if overlapArea <= ACCEPT_OVERLAY_AREA:
|
431 |
if mpCount >= symbolMinMatchCount:
|
432 |
threadLock.acquire() |
433 |
foundSymbolCount = foundSymbolCount + 1
|
434 |
totalMpCount = totalMpCount + mpCount |
435 |
addSearchedSymbol(symId, symbolName, symbolType |
436 |
, searchedItemSp, sw, sh, symbolThreshold, symbolMinMatchCount, mpCount, symbolRotatedAngle |
437 |
, isDetectOnOrigin, symbolRotateCount, symbolOcrOption, isContainChild |
438 |
, originalPoint, connectionPoint, baseSymbol, additionalSymbol,isExceptDetect) |
439 |
threadLock.release() |
440 |
## 겹치는 영역이 기준값보다 클 경우
|
441 |
else:
|
442 |
if symbolIndex != -1 and symbolIndex < len(searchedSymbolList): |
443 |
searchedSymbol = searchedSymbolList[symbolIndex] |
444 |
## 현재 심볼과 검출된 심볼이 같을 경우 Match Point가 더 높은 정보로 교체
|
445 |
if symbolName == searchedSymbol.getName():
|
446 |
symbolMpCount = searchedSymbol.getMpCount() |
447 |
if symbolMpCount < mpCount:
|
448 |
threadLock.acquire() |
449 |
totalMpCount = totalMpCount - symbolMpCount + mpCount |
450 |
searchedSymbolList[symbolIndex] = symbol.Symbol(symId, symbolName, symbolType |
451 |
, searchedItemSp, sw, sh, symbolThreshold, symbolMinMatchCount, mpCount, symbolRotatedAngle |
452 |
, isDetectOnOrigin, symbolRotateCount, symbolOcrOption, isContainChild |
453 |
, originalPoint, connectionPoint, baseSymbol, additionalSymbol,isExceptDetect) |
454 |
threadLock.release() |
455 |
## 현재 심볼과 검출된 심볼이 같지 않을 경우 (포함)
|
456 |
else:
|
457 |
## 검출된 심볼 리스트 중에서 해당 영역에 같은 심볼이 있는지 여부 체크
|
458 |
matches = [sym for sym in searchedSymbolList if sym.getName() == symbolName and contains(sym, searchedItemSp, sw, sh) > ACCEPT_OVERLAY_AREA] |
459 |
|
460 |
## 현재 심볼과 검출된 심볼이 같을 경우 Match Point가 더 높은 정보로 교체
|
461 |
if len(matches) != 0: |
462 |
for s in matches: |
463 |
symbolMpCount = s.getMpCount() |
464 |
if symbolMpCount < mpCount:
|
465 |
threadLock.acquire() |
466 |
totalMpCount = totalMpCount - symbolMpCount + mpCount |
467 |
searchedSymbolList[searchedSymbolList.index(s)] = symbol.Symbol(symId, symbolName, symbolType |
468 |
, searchedItemSp, sw, sh, symbolThreshold, symbolMinMatchCount, mpCount, symbolRotatedAngle |
469 |
, isDetectOnOrigin, symbolRotateCount, symbolOcrOption, isContainChild |
470 |
, originalPoint, connectionPoint, baseSymbol, additionalSymbol,isExceptDetect) |
471 |
threadLock.release() |
472 |
else:
|
473 |
if mpCount >= symbolMinMatchCount:
|
474 |
if searchedSymbol.getIsContainChild() == 1: |
475 |
## 특정 카테고리 심볼을 걸러냄 (ex - 9900 대 Drum)
|
476 |
if (searchedSymbol.getId() // 100) == (symId // 100): |
477 |
continue
|
478 |
else:
|
479 |
threadLock.acquire() |
480 |
foundSymbolCount = foundSymbolCount + 1
|
481 |
totalMpCount = totalMpCount + mpCount |
482 |
addSearchedSymbol(symId, symbolName, symbolType |
483 |
, searchedItemSp, sw, sh, symbolThreshold, symbolMinMatchCount, mpCount, symbolRotatedAngle |
484 |
, isDetectOnOrigin, symbolRotateCount, symbolOcrOption, isContainChild |
485 |
, originalPoint, connectionPoint, baseSymbol, additionalSymbol,isExceptDetect) |
486 |
threadLock.release() |
487 |
|
488 |
## Rotate Symbol
|
489 |
symGray = cv2.rotate(symGray, cv2.ROTATE_90_COUNTERCLOCKWISE) |
490 |
symbolRotatedAngle = symbolRotatedAngle + 90
|
491 |
|
492 |
if additionalSymbol is not None: |
493 |
additionalSymbol = getRotatedChildInfo(additionalSymbol) |
494 |
|
495 |
splitCount = splitCount // 2
|
496 |
|
497 |
avgMpCount = 0
|
498 |
if foundSymbolCount != 0: |
499 |
avgMpCount = totalMpCount / foundSymbolCount |
500 |
|
501 |
|
502 |
threadLock.acquire() |
503 |
srcName = os.path.splitext(os.path.basename(mainRes))[0]
|
504 |
listWidget.addItem('Finish Symbol : ' + os.path.basename(symbolPath.replace('.png', '')) + ' - (' + str(foundSymbolCount) + ')') |
505 |
threadLock.release() |
506 |
|
507 |
'''
|
508 |
@history 2018.05.17 Jeongwoo Bitwise_not target changed (Original Image → Symbol Image)
|
509 |
'''
|
510 |
def removeDetectedSymbol(sym): |
511 |
global srcGray
|
512 |
global ocrCompletedSrc
|
513 |
global threadLock
|
514 |
|
515 |
path = sym.getPath() |
516 |
sp = sym.getSp() |
517 |
sw = sym.getWidth() |
518 |
sh = sym.getHeight() |
519 |
sAngle = sym.getRotatedAngle() |
520 |
symImg = cv2.imread(path) |
521 |
symImg = cvtGrayImage(symImg) |
522 |
|
523 |
for i in range(sAngle//90): |
524 |
symImg = cv2.rotate(symImg, cv2.ROTATE_90_COUNTERCLOCKWISE) |
525 |
|
526 |
threadLock.acquire() |
527 |
temp = [] |
528 |
temp = srcGray[sp[1]:sp[1]+sh, sp[0]:sp[0]+sw] |
529 |
symImgBin = cv2.bitwise_not(symImg) |
530 |
result = cv2.bitwise_xor(symImgBin, temp) |
531 |
kernel1 = np.ones((5, 5), np.uint8) |
532 |
result = cv2.dilate(result, kernel1) |
533 |
srcGray[sp[1]:sp[1]+sh, sp[0]:sp[0]+sw] = result |
534 |
ocrCompletedSrc[sp[1]:sp[1]+sh, sp[0]:sp[0]+sw] = result |
535 |
threadLock.release() |
536 |
|
537 |
def drawRectOnSrc(sym): |
538 |
global src
|
539 |
global srcGray
|
540 |
global ocrCompletedSrc
|
541 |
|
542 |
path = sym.getPath() |
543 |
sp = sym.getSp() |
544 |
sw = sym.getWidth() |
545 |
sh = sym.getHeight() |
546 |
sAngle = sym.getRotatedAngle() |
547 |
symImg = cv2.imread(path) |
548 |
symImg = cvtGrayImage(symImg) |
549 |
|
550 |
cv2.rectangle(src, sp, (sp[0]+sw, sp[1]+sh), (0, 0, 255), 2) |
551 |
|
552 |
'''
|
553 |
@history 2018.04.27 Jeongwoo Remove Tesseract Log on listWidget
|
554 |
2018.05.04 Jeongwoo Change method to OCR with tesseract_ocr_module.py
|
555 |
2018.05.09 Jeongwoo Add global variable textInfoList, Remove text in symbol and Add tesseract result text
|
556 |
2018.05.10 Jeongwoo Remove not used if-statement
|
557 |
'''
|
558 |
def drawFoundSymbols(symbol, listWidget): |
559 |
global src
|
560 |
global canvas
|
561 |
global WHITE_LIST_CHARS
|
562 |
global searchedSymbolList
|
563 |
global textInfoList
|
564 |
|
565 |
symbolId = symbol.getId() |
566 |
symbolPath = symbol.getPath() |
567 |
symbolSp = symbol.getSp() |
568 |
symbolWidth = symbol.getWidth() |
569 |
symbolHeight = symbol.getHeight() |
570 |
symbolRotatedAngle = symbol.getRotatedAngle() |
571 |
symbolOcrOption = symbol.getOcrOption() |
572 |
|
573 |
symImg = cv2.imread(symbolPath, 1)
|
574 |
for i in range(symbolRotatedAngle//90): |
575 |
symImg = cv2.rotate(symImg, cv2.ROTATE_90_COUNTERCLOCKWISE) |
576 |
|
577 |
chan, w, h = symImg.shape[::-1]
|
578 |
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) |
579 |
|
580 |
if symbolOcrOption == SymbolBase.OCR_OPTION_ALL_FIND or symbolOcrOption == SymbolBase.OCR_OPTION_HALF_AND_HALF: |
581 |
if symbolOcrOption == SymbolBase.OCR_OPTION_HALF_AND_HALF:
|
582 |
inSqWidth = int(((w//2) * math.cos(round(math.radians(45), 2)))) * 2 |
583 |
inSqHeight = int(((h//2) * math.sin(round(math.radians(45), 2)))) * 2 |
584 |
inSqX = w//2 - (inSqWidth//2) |
585 |
inSqY = h//2 - (inSqHeight//2) |
586 |
else:
|
587 |
inSqWidth = w |
588 |
inSqHeight = h |
589 |
inSqX = 0
|
590 |
inSqY = 0
|
591 |
|
592 |
roi = src[symbolSp[1]:symbolSp[1]+h, symbolSp[0]:symbolSp[0]+w] |
593 |
srcRoi = roi[inSqY:inSqY+inSqHeight, inSqX:inSqX+inSqWidth] |
594 |
symRev = cv2.bitwise_not(symImg[inSqY:inSqY+inSqHeight, inSqX:inSqX+inSqWidth]) |
595 |
bitImg = cv2.bitwise_or(srcRoi, symRev) |
596 |
|
597 |
kernel1 = np.ones((2, 2), np.uint8) |
598 |
bitImg = cv2.dilate(bitImg, kernel1) |
599 |
#kernel2 = np.ones((1, 1), np.uint8)
|
600 |
#bitImg = cv2.erode(bitImg, kernel2)
|
601 |
|
602 |
try:
|
603 |
threadLock.acquire() |
604 |
im = Image.fromarray(bitImg) |
605 |
sp = (0, 0) |
606 |
if symbolOcrOption == SymbolBase.OCR_OPTION_HALF_AND_HALF:
|
607 |
sp = (symbolSp[0]+inSqX, symbolSp[1]+ inSqY) |
608 |
else:
|
609 |
sp = (symbolSp[0]+inSqX, symbolSp[1] + inSqY) |
610 |
tList = TOCR.getTextInfoInSymbol(bitImg, sp) |
611 |
|
612 |
resultText = ''
|
613 |
if tList is not None: |
614 |
for index in range(len(tList)): |
615 |
textInfo = tList[index] |
616 |
if index != 0: |
617 |
resultText = resultText + ","
|
618 |
resultText = resultText + textInfo.getText() |
619 |
cv2.putText(canvas, textInfo.getText(), (textInfo.getX(), textInfo.getY()), 2, 1.0, (0, 0, 0)) # cv2.FONT_HERSHEY_SIMPLEX |
620 |
|
621 |
# Sub List textInfo in symbol
|
622 |
tempList = [] |
623 |
for textInfo in textInfoList: |
624 |
tx = textInfo.getX() |
625 |
ty = textInfo.getY() |
626 |
if (tx >= symbolSp[0] and tx <= symbolSp[0] + symbolWidth) and (ty >= symbolSp[1] and ty <= symbolSp[1] + symbolHeight): |
627 |
tempList.append(textInfo) |
628 |
|
629 |
# Remove textInfo
|
630 |
for textInfo in tempList: |
631 |
textInfoList.remove(textInfo) |
632 |
|
633 |
# Append Tesseract result
|
634 |
textInfoList.extend(tList) |
635 |
|
636 |
# text value in symbol object update
|
637 |
index = [i for i, item in enumerate(searchedSymbolList) if item.getSp() == symbolSp] |
638 |
if len(index) > 0: |
639 |
searchedSymbolList[index[0]].setText(resultText)
|
640 |
except Exception as ex: |
641 |
print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)) |
642 |
finally:
|
643 |
threadLock.release() |
644 |
|
645 |
|
646 |
def drawFoundSymbolsOnCanvas(mainRes, width, height, listWidget): |
647 |
global src
|
648 |
global srcGray
|
649 |
global ocrCompletedSrc
|
650 |
global searchedSymbolList
|
651 |
global canvas
|
652 |
global textInfoList
|
653 |
|
654 |
canvas = np.zeros((height, width, 3), np.uint8)
|
655 |
canvas[::] = (255, 255, 255) |
656 |
|
657 |
try:
|
658 |
#pool = futures.ThreadPoolExecutor(max_workers = THREAD_MAX_WORKER)
|
659 |
for symbol in searchedSymbolList: |
660 |
#pool.submit(drawFoundSymbols, symbol)
|
661 |
drawFoundSymbols(symbol, listWidget) |
662 |
#pool.shutdown(wait = True)
|
663 |
|
664 |
for text in textInfoList: |
665 |
if not checkTextInSymbol((text.getX(), text.getY())): |
666 |
cv2.putText(canvas, text.getText(), (text.getX(), text.getY()+text.getH()), 2, 1.0, (0, 0, 0)) |
667 |
|
668 |
srcName = os.path.splitext(os.path.basename(mainRes))[0]
|
669 |
cv2.imwrite(os.path.dirname(os.path.realpath(__file__)) + '\\res\\Result\\result_moved_'+srcName+'_600dpi.png', canvas) |
670 |
|
671 |
except Exception as ex: |
672 |
print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)) |
673 |
|
674 |
#Generate target symbol data list
|
675 |
'''
|
676 |
@history 2018.04.24 Jeongwoo Add isExceptDetect Field
|
677 |
2018.05.09 Jeongwoo Add targetSymbolList clear
|
678 |
'''
|
679 |
def initTargetSymbolDataList(): |
680 |
############ region SQLite
|
681 |
global targetSymbolList
|
682 |
targetSymbolList.clear() |
683 |
dict = {} |
684 |
tempTargetList = AppDocData.instance().getTargetSymbolList() |
685 |
|
686 |
## Init Symbol Data from SQLite
|
687 |
for target in tempTargetList: |
688 |
symId = target.getId() // 100 # symId |
689 |
if symId in dict: |
690 |
dict[symId].append(target)
|
691 |
else:
|
692 |
symGroup = [] |
693 |
symGroup.append(target) |
694 |
dict[symId] = symGroup
|
695 |
|
696 |
## Sort each symbol list by symbol id
|
697 |
for k, v in dict.items(): |
698 |
dict[k] = sorted(v, key=lambda symbol:symbol.id) |
699 |
|
700 |
## Insert each symbol list into targetSymbolList
|
701 |
for sym in list(dict.values()): |
702 |
targetSymbolList.append(sym) |
703 |
############ endregion SQLite
|
704 |
|
705 |
return targetSymbolList
|
706 |
|
707 |
'''
|
708 |
@brief read image drawing and then remove text
|
709 |
@author jwkim
|
710 |
@date
|
711 |
@history humkyung 2018.04.06 check if file exists
|
712 |
Jeongwoo 2018.05.09 Use Tesseract OCR after Azure OCR (Azure OCR : Getting text area)
|
713 |
Jeongwoo 2018.05.25 Add condition on if-statemen
|
714 |
'''
|
715 |
def initMainSrc(mainRes): |
716 |
global src
|
717 |
global srcGray
|
718 |
global ocrCompletedSrc
|
719 |
global textInfoList
|
720 |
global noteTextInfoList
|
721 |
|
722 |
try:
|
723 |
if os.path.isfile(mainRes):
|
724 |
#load original src & symbol
|
725 |
src = cv2.imread(mainRes, 1)
|
726 |
|
727 |
#gray scale
|
728 |
if len(src.shape) == 3: |
729 |
srcGray = cvtGrayImage(src) |
730 |
else:
|
731 |
srcGray = src.copy() |
732 |
srcGray = cv2.threshold(srcGray, 127, 255, cv2.THRESH_BINARY)[1] |
733 |
|
734 |
ocrCompletedSrc = srcGray.copy() |
735 |
|
736 |
area = AppDocData.instance().getArea('Drawing')
|
737 |
if area is not None: |
738 |
#TODO: 영역을 설정한 값과 이미지 좌표계를 차이를 보정
|
739 |
area.img = srcGray[round(area.y+1):round(area.y+area.height), round(area.x+1):round(area.x+area.width)] |
740 |
|
741 |
#(_tempOcrSrc, textInfoList) = OCR.removeTextFromNpArray(area.img if area is not None else srcGray, area.x if area is not None else 0, area.y if area is not None else 0)
|
742 |
(_tempOcrSrc, tInfoList) = OCR.removeTextFromNpArray(area.img if area is not None else srcGray, area.x if area is not None else 0, area.y if area is not None else 0) |
743 |
|
744 |
global MIN_TEXT_SIZE
|
745 |
for tInfo in tInfoList: |
746 |
if tInfo.getW() >= MIN_TEXT_SIZE or tInfo.getH() >= MIN_TEXT_SIZE: |
747 |
resultTextInfo = TOCR.getTextInfo(ocrCompletedSrc[tInfo.getY():tInfo.getY()+tInfo.getH(),tInfo.getX():tInfo.getX()+tInfo.getW()], (tInfo.getX(), tInfo.getY())) |
748 |
if resultTextInfo is not None and len(resultTextInfo) > 0: |
749 |
textInfoList.extend(resultTextInfo) |
750 |
ocrCompletedSrc = removeText(ocrCompletedSrc, resultTextInfo[0].getText(), resultTextInfo[0].getX(), resultTextInfo[0].getY(), resultTextInfo[0].getW(), resultTextInfo[0].getH()) |
751 |
else:
|
752 |
print(tInfo.getText()) |
753 |
#global MIN_TEXT_SIZE
|
754 |
#for textInfo in textInfoList:
|
755 |
# if textInfo.getW() >= MIN_TEXT_SIZE or textInfo.getH() >= MIN_TEXT_SIZE:
|
756 |
# ocrCompletedSrc = removeText(ocrCompletedSrc, textInfo.getText(), textInfo.getX(), textInfo.getY(), textInfo.getW(), textInfo.getH())
|
757 |
|
758 |
noteArea = AppDocData.instance().getArea('Note')
|
759 |
if noteArea is not None: |
760 |
noteArea.img = srcGray[round(noteArea.y-1):round(noteArea.y+noteArea.height-1), round(noteArea.x-1):round(noteArea.x+noteArea.width-1)] |
761 |
#(_tempNoteOcrSrc, noteTextInfoList) = OCR.removeTextFromNpArray(noteArea.img, noteArea.x, noteArea.y)
|
762 |
noteTextInfoList = TOCR.getTextInfo(noteArea.img, (noteArea.x, noteArea.y)) |
763 |
except Exception as ex: |
764 |
print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)) |
765 |
|
766 |
'''
|
767 |
@brief Main function
|
768 |
@author jwkim
|
769 |
@date
|
770 |
@history humkyung 2018.04.06 change error display from message box to print
|
771 |
Jeongwoo 2018.04.25 Remove 'Current Symbol : ' QListItem
|
772 |
Jeongwoo 2018.05.09 Make Comments OCR.removeTextFromNpArray block
|
773 |
Jeongwoo 2018.05.25 Remove imgLineList variable and parameter on writeXml()
|
774 |
'''
|
775 |
def executeRecognition(path, listWidget): |
776 |
global src
|
777 |
global srcGray
|
778 |
global ocrCompletedSrc
|
779 |
global searchedSymbolList
|
780 |
global threadLock
|
781 |
global textInfoList
|
782 |
global noteTextInfoList
|
783 |
|
784 |
res = [] |
785 |
try:
|
786 |
docData = AppDocData.instance() |
787 |
project = docData.getCurrentProject() |
788 |
|
789 |
srcList = [] |
790 |
srcList.append(path) |
791 |
|
792 |
initTargetSymbolDataList() |
793 |
|
794 |
for mainRes in srcList: |
795 |
#Init src
|
796 |
src = [] |
797 |
srcGray = [] |
798 |
ocrCompletedSrc = [] |
799 |
searchedSymbolList = [] |
800 |
textInfoList = [] |
801 |
|
802 |
#start = timeit.default_timer()
|
803 |
|
804 |
initMainSrc(mainRes) |
805 |
|
806 |
listWidget.addItem("Start recognition : " + mainRes)
|
807 |
|
808 |
#threadLock = threading.Lock()
|
809 |
pool = futures.ThreadPoolExecutor(max_workers = THREAD_MAX_WORKER) |
810 |
for targetItem in targetSymbolList: |
811 |
if type(targetItem).__name__ == 'list': |
812 |
#detectSymbolsOnPid(mainRes, target)
|
813 |
pool.submit(detectSymbolsOnPid, mainRes, targetItem, listWidget) |
814 |
else:
|
815 |
#detectSymbolOnPid(mainRes, target)
|
816 |
pool.submit(detectSymbolOnPid, mainRes, targetItem, listWidget) |
817 |
|
818 |
pool.shutdown(wait = True)
|
819 |
|
820 |
chan, docData.imgWidth, docData.imgHeight = src.shape[::-1]
|
821 |
drawFoundSymbolsOnCanvas(mainRes, docData.imgWidth, docData.imgHeight, listWidget) |
822 |
|
823 |
docData.imgName = os.path.splitext(os.path.basename(mainRes))[0]
|
824 |
|
825 |
pool = futures.ThreadPoolExecutor(max_workers = THREAD_MAX_WORKER) |
826 |
for sym in searchedSymbolList: |
827 |
#threadLock.acquire()
|
828 |
pool.submit(removeDetectedSymbol, sym) |
829 |
pool.submit(drawRectOnSrc, sym) |
830 |
#drawRectOnSrc()
|
831 |
#threadLock.release()
|
832 |
pool.shutdown(wait = True)
|
833 |
|
834 |
####area = AppDocData.instance().getArea('Drawing')
|
835 |
####(_tempOcrSrc, tInfoList) = OCR.removeTextFromNpArray(area.img if area is not None else srcGray, area.x if area is not None else 0, area.y if area is not None else 0)
|
836 |
#####(srcGray, tInfoList) = OCR.removeTextFromNpArray(area.img if area is not None else srcGray)
|
837 |
####if area is not None:
|
838 |
#### srcGray[int(area.y):int(area.y+area.height), int(area.x):int(area.x+area.width)] = _tempOcrSrc
|
839 |
####else:
|
840 |
#### srcGray = _tempOcrSrc
|
841 |
####srcGray = TOCR.removeTextFromNpArray(srcGray, TOCR.FLAG_IMAGE_TO_DATA)
|
842 |
global MIN_TEXT_SIZE
|
843 |
for textInfo in textInfoList: |
844 |
#if not checkTextInSymbol((textInfo.getX(), textInfo.getY())):
|
845 |
if textInfo.getW() >= MIN_TEXT_SIZE or textInfo.getH() >= MIN_TEXT_SIZE: |
846 |
removeText(srcGray, textInfo.getText(), textInfo.getX(), textInfo.getY(), textInfo.getW(), textInfo.getH()) |
847 |
|
848 |
## Remove Noise
|
849 |
kernel1 = np.ones((2, 2), np.uint8) |
850 |
srcGray = cv2.dilate(srcGray, kernel1) |
851 |
#kernel2 = np.ones((4, 4), np.uint8)
|
852 |
srcGray = cv2.erode(srcGray, kernel1) |
853 |
|
854 |
removedSymbolImgPath = os.path.join(project.getTempPath(), os.path.basename(path)) |
855 |
cv2.imwrite(removedSymbolImgPath, srcGray) |
856 |
area = AppDocData.instance().getArea('Drawing')
|
857 |
if area is not None: |
858 |
area.img = srcGray[round(area.y+1):round(area.y+area.height), round(area.x+1):round(area.x+area.width)] |
859 |
cv2.imwrite(os.path.join(project.getTempPath(), "rect_" + os.path.basename(path)), src)
|
860 |
|
861 |
xmlPath = xg.writeXml(docData.imgName, docData.imgWidth, docData.imgHeight, searchedSymbolList, textInfoList, noteTextInfoList) |
862 |
res.append(xmlPath) |
863 |
|
864 |
listWidget.addItem("Searched symbol count : " + str(len(searchedSymbolList))) |
865 |
|
866 |
#stop = timeit.default_timer()
|
867 |
#seconds = stop - start
|
868 |
|
869 |
#listWidget.addItem("\nRunning Time : " + str(seconds / 60) + "min\n")
|
870 |
except Exception as ex: |
871 |
print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)) |
872 |
|
873 |
return res
|
874 |
|
875 |
if __name__ == '__main__': |
876 |
import DTI_PID_UI |
877 |
from ProjectDialog import Ui_Dialog |
878 |
|
879 |
app = QApplication(sys.argv) |
880 |
|
881 |
try:
|
882 |
dlg = Ui_Dialog() |
883 |
selectedProject = dlg.showDialog() |
884 |
if selectedProject is not None: |
885 |
form = ExampleApp() |
886 |
form.show() |
887 |
except Exception as ex: |
888 |
print('에러가 발생했습니다.\n', ex)
|
889 |
|
890 |
sys.exit(app.exec_()) |