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