hytos / DTI_PID / DTI_PID / TextDetector.py @ ae3a61a6
이력 | 보기 | 이력해설 | 다운로드 (18.5 KB)
1 |
# coding: utf-8
|
---|---|
2 |
"""
|
3 |
This is text detector module
|
4 |
"""
|
5 |
import sys |
6 |
import os |
7 |
import cv2 |
8 |
import numpy as np |
9 |
from PyQt5.QtCore import * |
10 |
from PyQt5.QtGui import * |
11 |
from PyQt5.QtWidgets import * |
12 |
from PyQt5.QtSvg import * |
13 |
|
14 |
from AppDocData import * |
15 |
import TextInfo as ti |
16 |
import tesseract_ocr_module as TOCR |
17 |
|
18 |
MIN_TEXT_SIZE = 10
|
19 |
THREAD_MAX_WORKER = os.cpu_count() |
20 |
|
21 |
|
22 |
class TextDetector: |
23 |
'''
|
24 |
@brief constructor
|
25 |
@author humkyung
|
26 |
@date 2018.07.11
|
27 |
'''
|
28 |
|
29 |
def __init__(self): |
30 |
self.textInfoList = []
|
31 |
self.otherTextInfoList = []
|
32 |
self.titleBlockTextInfoList = []
|
33 |
|
34 |
'''
|
35 |
@brief detect text areas
|
36 |
@author humkyung
|
37 |
@date 2018.06.16
|
38 |
'''
|
39 |
|
40 |
def detectTextAreas(self, img, offset): |
41 |
tInfoList = [] |
42 |
try:
|
43 |
tInfoList = self.getTextAreaInfo(img, offset[0], offset[1]) |
44 |
except Exception as ex: |
45 |
print('error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
46 |
sys.exc_info()[-1].tb_lineno))
|
47 |
|
48 |
return tInfoList
|
49 |
|
50 |
'''
|
51 |
@brief Get Text Area info by contour
|
52 |
@author Jeongwoo
|
53 |
@date 2018.06.05
|
54 |
@history 2018.06.08 Jeongwoo Add angle
|
55 |
humkyung 2018.06.18 fixed logic to detect text area
|
56 |
'''
|
57 |
|
58 |
def getTextAreaInfo(self, imgGray, offsetX, offsetY): |
59 |
from AppDocData import AppDocData |
60 |
|
61 |
appDocData = AppDocData.instance() |
62 |
project = appDocData.getCurrentProject() |
63 |
|
64 |
configs = appDocData.getConfigs('Text Size', 'Max Text Size') |
65 |
maxTextSize = int(configs[0].value) if 1 == len(configs) else 100 |
66 |
minSize = 5
|
67 |
|
68 |
contourImg = np.ones(imgGray.shape, np.uint8) * 255
|
69 |
binaryImg, mask = cv2.threshold(imgGray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) |
70 |
|
71 |
contours, hierarchy = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE) |
72 |
for contour in contours: |
73 |
# remove too big one or horizontal/vertical line
|
74 |
[x, y, w, h] = cv2.boundingRect(contour) |
75 |
area = cv2.contourArea(contour, True)
|
76 |
|
77 |
# skip one which size is greater than max size or less then minimum size
|
78 |
if area >= 0: |
79 |
if (w > maxTextSize or h > maxTextSize) or (w <= minSize and h <= minSize): continue |
80 |
|
81 |
if area >= 0: |
82 |
cv2.drawContours(contourImg, [contour], -1, (0, 0, 0), -1) |
83 |
cv2.drawContours(contourImg, [contour], -1, (255, 255, 255), 1) |
84 |
else:
|
85 |
cv2.drawContours(contourImg, [contour], -1, (255, 255, 255), -1) |
86 |
|
87 |
path = os.path.join(project.getTempPath(), 'OCR_{}.png'.format(appDocData.imgName))
|
88 |
cv2.imwrite(path, contourImg) |
89 |
|
90 |
rects = [] |
91 |
configs = appDocData.getConfigs('Text Recognition', 'Expand Size') |
92 |
expandSize = int(configs[0].value) if 1 == len(configs) else 10 |
93 |
configs = appDocData.getConfigs('Text Recognition', 'Shrink Size') |
94 |
shrinkSize = int(configs[0].value) if 1 == len(configs) else 0 |
95 |
|
96 |
eroded = cv2.erode(contourImg, np.ones((expandSize, expandSize), np.uint8)) |
97 |
# path = os.path.join(project.getTempPath(), 'ERODED_OCR_{}.png'.format(appDocData.imgName))
|
98 |
# cv2.imwrite(path, eroded)
|
99 |
|
100 |
eroded = cv2.bitwise_not(eroded) |
101 |
# path = os.path.join(project.getTempPath(), 'bitwise_not_{}.png'.format(appDocData.imgName))
|
102 |
# cv2.imwrite(path, eroded)
|
103 |
|
104 |
contours, hierarchy = cv2.findContours(eroded, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) |
105 |
for contour in contours: |
106 |
area = cv2.contourArea(contour, True)
|
107 |
if area < 0: |
108 |
[x, y, w, h] = cv2.boundingRect(contour) |
109 |
|
110 |
img = contourImg[y:y + h, x:x + w] |
111 |
img = cv2.bitwise_not(img) |
112 |
|
113 |
horizontal, max_width = 0, 0 |
114 |
vertical, max_height = 0, 0 |
115 |
_contours, _ = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) |
116 |
for xx in _contours: |
117 |
[_x, _y, _w, _h] = cv2.boundingRect(xx) |
118 |
cv2.rectangle(img, (_x, _y), (_x + _w, _y + _h), 255, 1) |
119 |
|
120 |
max_width = _x if _x > max_width else max_width |
121 |
max_height = _y if _y > max_height else max_height |
122 |
|
123 |
if (_w < _h) or (_w > maxTextSize and _h < maxTextSize): # width is greater than height |
124 |
horizontal += 1 + (_w * _h) / (w * h)
|
125 |
else:
|
126 |
vertical += 1 + (_w * _h) / (w * h)
|
127 |
|
128 |
if (w < 10 and h < 10) or ( |
129 |
max_width > maxTextSize and max_height > maxTextSize): continue; # skip too small or big one |
130 |
|
131 |
"""
|
132 |
if w > maxTextSize:
|
133 |
horizontal = 1
|
134 |
elif h > maxTextSize:
|
135 |
vertical = 1
|
136 |
else:
|
137 |
if shrinkSize > 0:
|
138 |
img = cv2.erode(img, np.ones((shrinkSize,shrinkSize), np.uint8))
|
139 |
|
140 |
_contours, _ = cv2.findContours(img, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
|
141 |
for xx in _contours:
|
142 |
[_x, _y, _w, _h] = cv2.boundingRect(xx)
|
143 |
cv2.rectangle(img, (_x, _y), (_x+_w, _y+_h), 255, 1)
|
144 |
|
145 |
if (_w < _h) or (_w > maxTextSize and _h < maxTextSize): # width is greater than height
|
146 |
horizontal += 1 + (_w*_h)/(w*h)
|
147 |
else:
|
148 |
vertical += 1 + (_w*_h)/(w*h)
|
149 |
"""
|
150 |
|
151 |
"""
|
152 |
if horizontal > vertical:
|
153 |
filePath = os.path.join(project.getTempPath(), "Tile", "H-{}-{}-{}-{}.png".format(x,y,w,h))
|
154 |
else:
|
155 |
filePath = os.path.join(project.getTempPath(), "Tile", "V-{}-{}-{}-{}.png".format(x,y,w,h))
|
156 |
cv2.imwrite(filePath, img)
|
157 |
"""
|
158 |
|
159 |
rects.append([0 if horizontal > vertical else 90, QRect(x, y, w, h)]) |
160 |
|
161 |
configs = appDocData.getConfigs('Text Recognition', 'Merge Size') |
162 |
mergeSize = int(configs[0].value) if 1 == len(configs) else 10 |
163 |
# merge rectangles
|
164 |
intersected = True
|
165 |
while intersected:
|
166 |
intersected = False
|
167 |
for rect in rects[:]: # clone rects |
168 |
if 0 == rect[0]: |
169 |
rectExpand = rect[1].adjusted(-mergeSize, 0, mergeSize, 0) |
170 |
else:
|
171 |
rectExpand = rect[1].adjusted(0, -mergeSize, 0, mergeSize) |
172 |
|
173 |
matches = [x for x in rects if (x[0] == rect[0]) and rectExpand.intersects(x[1])] |
174 |
if len(matches) > 1: |
175 |
united = matches[0]
|
176 |
for _rect in matches: |
177 |
united[1] = united[1].united(_rect[1]) |
178 |
if _rect in rects: rects.remove(_rect) |
179 |
rects.append(united) |
180 |
intersected = True
|
181 |
break
|
182 |
|
183 |
list = [] |
184 |
for rect in rects: |
185 |
angle = rect[0]
|
186 |
list.append(ti.TextInfo('', round(offsetX) + rect[1].x(), round(offsetY) + rect[1].y(), rect[1].width(), |
187 |
rect[1].height(), angle))
|
188 |
|
189 |
x = rect[1].x()
|
190 |
y = rect[1].y()
|
191 |
w = rect[1].width()
|
192 |
h = rect[1].height()
|
193 |
img = contourImg[y:y + h, x:x + w] |
194 |
## DEBUG
|
195 |
# if angle == 0:
|
196 |
# filePath = os.path.join(project.getTempPath(), "Tile", "H-{}-{}-{}-{}.png".format(x,y,w,h))
|
197 |
# else:
|
198 |
# filePath = os.path.join(project.getTempPath(), "Tile", "V-{}-{}-{}-{}.png".format(x,y,w,h))
|
199 |
# cv2.imwrite(filePath, img)
|
200 |
## up to here
|
201 |
|
202 |
return list |
203 |
|
204 |
'''
|
205 |
@brief recognize text of given text info
|
206 |
@author humkyung
|
207 |
@date 2018.07.24
|
208 |
@history change parameter updateProgressSignal to worker
|
209 |
2018.11.08 euisung add white char list check process on db
|
210 |
'''
|
211 |
|
212 |
@staticmethod
|
213 |
def recognizeTextFromImage(tInfos, imgOCR, offset, searchedSymbolList, worker, listWidget, maxProgressValue): |
214 |
import re |
215 |
res = [] |
216 |
|
217 |
app_doc_data = AppDocData.instance() |
218 |
|
219 |
try:
|
220 |
for tInfo in tInfos: |
221 |
x = tInfo.getX() - round(offset[0]) |
222 |
y = tInfo.getY() - round(offset[1]) |
223 |
img = imgOCR[y:y + tInfo.getH(), x:x + tInfo.getW()] |
224 |
|
225 |
# set angle 0 if symbol contains the text area is instrumentation
|
226 |
category = None
|
227 |
contains = [symbol for symbol in searchedSymbolList if symbol.contains(tInfo)] |
228 |
if contains:
|
229 |
_type = contains[0].getType()
|
230 |
category = app_doc_data.getSymbolCategoryByType(_type) |
231 |
if 'Instrumentation' == category: |
232 |
tInfo.setAngle(0)
|
233 |
# up to here
|
234 |
|
235 |
whiteCharList = app_doc_data.getConfigs('Text Recognition', 'White Character List') |
236 |
if len(whiteCharList) is 0: |
237 |
resultTextInfo = TOCR.getTextInfo(img, (x, y), tInfo.getAngle(), language=app_doc_data.OCRData) |
238 |
else:
|
239 |
resultTextInfo = TOCR.getTextInfo(img, (x, y), tInfo.getAngle(), language=app_doc_data.OCRData, |
240 |
conf=whiteCharList[0].value)
|
241 |
|
242 |
if resultTextInfo is not None and len(resultTextInfo) > 0: |
243 |
for result in resultTextInfo: |
244 |
result.setX(result.getX() + round(offset[0])) |
245 |
result.setY(result.getY() + round(offset[1])) |
246 |
if 'Instrumentation' == category: |
247 |
text = re.sub('[^a-zA-Z0-9]+', '', result.getText()) |
248 |
result.setText(text) |
249 |
|
250 |
res.extend(resultTextInfo) |
251 |
|
252 |
if listWidget is not None: |
253 |
item = QListWidgetItem( |
254 |
'{},{},{} is recognized'.format(resultTextInfo[0].getX(), resultTextInfo[0].getY(), |
255 |
resultTextInfo[0].getText()))
|
256 |
listWidget.addItem(item) |
257 |
else:
|
258 |
pass
|
259 |
|
260 |
if worker is not None: |
261 |
worker.updateProgress.emit(maxProgressValue, |
262 |
resultTextInfo[0].getText() if resultTextInfo is not None and 1 == len( |
263 |
resultTextInfo) else None) |
264 |
except Exception as ex: |
265 |
message = 'error occurred({}) in {}:{}'.format(repr(ex), sys.exc_info()[-1].tb_frame.f_code.co_filename, |
266 |
sys.exc_info()[-1].tb_lineno)
|
267 |
if worker is not None: |
268 |
worker.displayLog.emit(MessageType.Error, message) |
269 |
|
270 |
return res
|
271 |
|
272 |
'''
|
273 |
@brief read image drawing and then remove text
|
274 |
@author jwkim
|
275 |
@date
|
276 |
@history humkyung 2018.04.06 check if file exists
|
277 |
Jeongwoo 2018.05.09 Use Tesseract OCR after Azure OCR (Azure OCR : Getting text area)
|
278 |
Jeongwoo 2018.05.25 Add condition on if-statement
|
279 |
Jeongwoo 2018.06.05 Get text area data list by config.type
|
280 |
Jeongwoo 2018.06.08 Add angle Parameter on TOCR.getTextInfo
|
281 |
humkyung 2018.06.16 update proessbar while recognizing text
|
282 |
humkyung 2018.07.03 remove white space and replace given oldStr with newStr
|
283 |
humkyung 2018.07.07 change method name to recognizeText
|
284 |
euisung 2018.11.08 add white char list check process on db
|
285 |
euisung 2018.11.12 add title block properties
|
286 |
'''
|
287 |
|
288 |
def recognizeText(self, imgSrc, offset, tInfoList, searchedSymbolList, worker, listWidget, maxProgressValue, |
289 |
onlyTextArea=False):
|
290 |
import concurrent.futures as futures |
291 |
from multiprocessing import Process, Queue |
292 |
from Area import Area |
293 |
|
294 |
try:
|
295 |
self.otherTextInfoList = []
|
296 |
self.titleBlockTextInfoList = []
|
297 |
self.textInfoList = []
|
298 |
|
299 |
appDocData = AppDocData.instance() |
300 |
project = appDocData.getCurrentProject() |
301 |
|
302 |
path = os.path.join(project.getTempPath(), 'OCR_{}.png'.format(appDocData.imgName))
|
303 |
if os.path.isfile(path):
|
304 |
imgOCR = cv2.imread(path, 1)
|
305 |
imgOCR = \ |
306 |
cv2.threshold(cv2.cvtColor(imgOCR, cv2.COLOR_BGR2GRAY), 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1] |
307 |
|
308 |
text_info_array = np.array_split(tInfoList, THREAD_MAX_WORKER) |
309 |
pool = futures.ThreadPoolExecutor(max_workers=THREAD_MAX_WORKER) |
310 |
for tInfo in text_info_array: |
311 |
future = pool.submit(TextDetector.recognizeTextFromImage, tInfo, imgOCR, offset, searchedSymbolList, |
312 |
worker, listWidget, maxProgressValue) |
313 |
data = future.result() |
314 |
if data:
|
315 |
self.textInfoList.extend(data)
|
316 |
pool.shutdown(wait=True)
|
317 |
|
318 |
# remove text item has only 1 character
|
319 |
# for index in range(len(self.textInfoList) - 1, -1, -1):
|
320 |
# if len(self.textInfoList[index].getText()) is 1:
|
321 |
# self.textInfoList.pop(index)
|
322 |
|
323 |
if onlyTextArea:
|
324 |
return
|
325 |
# parse texts in area except Drawing area
|
326 |
whiteCharList = appDocData.getConfigs('Text Recognition', 'White Character List') |
327 |
for area in appDocData.getAreaList(): |
328 |
if area.name == 'Drawing': continue |
329 |
|
330 |
if area.name == 'Note': |
331 |
if area is not None and hasattr(area, 'img') and area.img is not None: |
332 |
if len(whiteCharList) is 0: |
333 |
texts = TOCR.getTextInfo(area.img, (area.x, area.y), 0, language='eng') |
334 |
else:
|
335 |
texts = TOCR.getTextInfo(area.img, (area.x, area.y), 0, language='eng', |
336 |
conf=whiteCharList[0].value)
|
337 |
self.otherTextInfoList.append([area.name, texts])
|
338 |
else:
|
339 |
img = imgSrc[round(area.y):round(area.y + area.height), |
340 |
round(area.x):round(area.x + area.width)] |
341 |
if len(whiteCharList) is 0: |
342 |
texts = TOCR.getTextInfo(img, (area.x, area.y), 0, language='eng') |
343 |
else:
|
344 |
texts = TOCR.getTextInfo(img, (area.x, area.y), 0, language='eng', |
345 |
conf=whiteCharList[0].value)
|
346 |
if texts is not None and len(texts) > 0: |
347 |
if area.name == 'Unit': |
348 |
appDocData.activeDrawing.setAttr('Unit', texts[0].getText()) |
349 |
self.otherTextInfoList.append([area.name, texts])
|
350 |
|
351 |
titleBlockProps = appDocData.getTitleBlockProperties() |
352 |
if titleBlockProps:
|
353 |
for titleBlockProp in titleBlockProps: |
354 |
area = Area(titleBlockProp[0])
|
355 |
area.parse(titleBlockProp[2])
|
356 |
if not (titleBlockProp[3] and titleBlockProp[3] != ''): |
357 |
img = imgSrc[round(area.y):round(area.y + area.height), |
358 |
round(area.x):round(area.x + area.width)] |
359 |
if len(whiteCharList) is 0: |
360 |
texts = TOCR.getTextInfo(img, (area.x, area.y), 0, language=appDocData.OCRData)
|
361 |
else:
|
362 |
texts = TOCR.getTextInfo(img, (area.x, area.y), 0, language='eng', |
363 |
conf=whiteCharList[0].value)
|
364 |
texts = [ti.TextInfo('\n'.join([textInfo.getText() for textInfo in texts]), area.x, area.y, |
365 |
area.width, area.height, 0)]
|
366 |
else:
|
367 |
texts = [ti.TextInfo(titleBlockProp[3], area.x, area.y, area.width, area.height, 0)] |
368 |
self.titleBlockTextInfoList.append([area.name, texts])
|
369 |
|
370 |
if worker is not None: worker.updateProgress.emit(maxProgressValue, None) |
371 |
except Exception as ex: |
372 |
message = 'error occurred({}) in {}:{}'.format(repr(ex), sys.exc_info()[-1].tb_frame.f_code.co_filename, |
373 |
sys.exc_info()[-1].tb_lineno)
|
374 |
worker.displayLog.emit(MessageType.Error, message) |
375 |
|
376 |
'''
|
377 |
@brief remove text from image
|
378 |
@author humkyung
|
379 |
@date 2018.07.24
|
380 |
'''
|
381 |
|
382 |
def removeTextFromImage(self, imgSrc, offset): |
383 |
appDocData = AppDocData.instance() |
384 |
project = appDocData.getCurrentProject() |
385 |
|
386 |
path = os.path.join(project.getTempPath(), 'OCR_{}.png'.format(appDocData.imgName))
|
387 |
if os.path.isfile(path):
|
388 |
imgOCR = cv2.imread(path) |
389 |
imgOCR = \ |
390 |
cv2.threshold(cv2.cvtColor(imgOCR, cv2.COLOR_BGR2GRAY), 127, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1] |
391 |
|
392 |
# remove recognized text from image
|
393 |
for text in self.textInfoList: |
394 |
x = round(text.getX() - offset[0]) |
395 |
y = round(text.getY() - offset[1]) |
396 |
width = round(text.getW())
|
397 |
height = round(text.getH())
|
398 |
self.removeText(imgSrc, (round(text.getX()), round(text.getY())), imgOCR[y:y + height, x:x + width]) |
399 |
# up to here
|
400 |
|
401 |
'''
|
402 |
@brief remove text from image by using ocr image
|
403 |
@author
|
404 |
'''
|
405 |
|
406 |
def removeText(self, img, pt, imgOCR): |
407 |
try:
|
408 |
x = round(pt[0]) |
409 |
y = round(pt[1]) |
410 |
width, height = imgOCR.shape[::-1]
|
411 |
|
412 |
temp = img[y:y + height, x:x + width] |
413 |
imgOCR = cv2.erode(imgOCR, np.ones((3, 3), np.uint8)) |
414 |
mask = cv2.bitwise_or(temp, imgOCR) |
415 |
imgXOR = cv2.bitwise_xor(temp, mask) |
416 |
img[y:y + height, x:x + width] = cv2.bitwise_not(imgXOR) |
417 |
|
418 |
except Exception as ex: |
419 |
print('error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
420 |
sys.exc_info()[-1].tb_lineno))
|
421 |
|
422 |
return img
|