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