hytos / DTI_PID / DTI_PID / TextDetector.py @ d559e1d7
이력 | 보기 | 이력해설 | 다운로드 (32.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 |
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 |
def decode_predictions(self, scores, geometry): |
50 |
# grab the number of rows and columns from the scores volume, then
|
51 |
# initialize our set of bounding box rectangles and corresponding
|
52 |
# confidence scores
|
53 |
(numRows, numCols) = scores.shape[2:4] |
54 |
rects = [] |
55 |
confidences = [] |
56 | |
57 |
# loop over the number of rows
|
58 |
for y in range(0, numRows): |
59 |
# extract the scores (probabilities), followed by the
|
60 |
# geometrical data used to derive potential bounding box
|
61 |
# coordinates that surround text
|
62 |
scoresData = scores[0, 0, y] |
63 |
xData0 = geometry[0, 0, y] |
64 |
xData1 = geometry[0, 1, y] |
65 |
xData2 = geometry[0, 2, y] |
66 |
xData3 = geometry[0, 3, y] |
67 |
anglesData = geometry[0, 4, y] |
68 | |
69 |
# loop over the number of columns
|
70 |
for x in range(0, numCols): |
71 |
# if our score does not have sufficient probability,
|
72 |
# ignore it
|
73 |
if scoresData[x] < 0.5: # args["min_confidence"]: |
74 |
continue
|
75 | |
76 |
# compute the offset factor as our resulting feature
|
77 |
# maps will be 4x smaller than the input image
|
78 |
(offsetX, offsetY) = (x * 4.0, y * 4.0) |
79 | |
80 |
# extract the rotation angle for the prediction and
|
81 |
# then compute the sin and cosine
|
82 |
angle = anglesData[x] |
83 |
cos = np.cos(angle) |
84 |
sin = np.sin(angle) |
85 | |
86 |
# use the geometry volume to derive the width and height
|
87 |
# of the bounding box
|
88 |
h = xData0[x] + xData2[x] |
89 |
w = xData1[x] + xData3[x] |
90 | |
91 |
# compute both the starting and ending (x, y)-coordinates
|
92 |
# for the text prediction bounding box
|
93 |
endX = int(offsetX + (cos * xData1[x]) + (sin * xData2[x]))
|
94 |
endY = int(offsetY - (sin * xData1[x]) + (cos * xData2[x]))
|
95 |
startX = int(endX - w)
|
96 |
startY = int(endY - h)
|
97 | |
98 |
# add the bounding box coordinates and probability score
|
99 |
# to our respective lists
|
100 |
rects.append((startX, startY, endX, endY)) |
101 |
confidences.append(scoresData[x]) |
102 | |
103 |
# return a tuple of the bounding boxes and associated confidences
|
104 |
return (rects, confidences)
|
105 | |
106 |
'''
|
107 |
@brief Get Text Area info by contour
|
108 |
@author Jeongwoo
|
109 |
@date 2018.06.05
|
110 |
@history 2018.06.08 Jeongwoo Add angle
|
111 |
humkyung 2018.06.18 fixed logic to detect text area
|
112 |
'''
|
113 |
def getTextAreaInfo(self, imgGray, offset_x, offset_y): |
114 |
#from imutils.object_detection import non_max_suppression
|
115 |
from AppDocData import AppDocData |
116 | |
117 |
res_list = [] |
118 |
ocr_image = None
|
119 |
try:
|
120 |
app_doc_data = AppDocData.instance() |
121 |
project = app_doc_data.getCurrentProject() |
122 | |
123 |
configs = app_doc_data.getConfigs('Text Size', 'Max Text Size') |
124 |
maxTextSize = int(configs[0].value) if 1 == len(configs) else 100 |
125 |
configs = app_doc_data.getConfigs('Text Size', 'Min Text Size') |
126 |
minSize = int(configs[0].value) if 1 == len(configs) else 15 |
127 | |
128 |
ocr_image = imgGray.copy() # np.ones(imgGray.shape, np.uint8) * 255
|
129 | |
130 |
configs = app_doc_data.getConfigs('Engine', 'Text Area') |
131 |
if configs and int(configs[0].value) is 1: |
132 |
# get text box original way
|
133 |
not_containing_bbox, binary_image = self.getTextBox(ocr_image, imgGray, maxTextSize, minSize)
|
134 |
else:
|
135 |
# using craft
|
136 |
return self.getTextBox_craft(ocr_image, maxTextSize, minSize, offset_x, offset_y, web=True) |
137 | |
138 |
rects = [] |
139 | |
140 |
for bbox in not_containing_bbox: |
141 |
x, y = bbox.left(), bbox.top() |
142 |
w, h = bbox.width(), bbox.height() |
143 |
img = binary_image[bbox.top():bbox.bottom(), bbox.left():bbox.right()] |
144 |
img = cv2.dilate(img, np.ones((2, 2), np.uint8)) |
145 |
img = cv2.bitwise_not(img) |
146 | |
147 |
horizontal, max_width = 0, 0 |
148 |
vertical, max_height = 0, 0 |
149 |
_contours, _ = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) |
150 |
for xx in _contours: |
151 |
[_x, _y, _w, _h] = cv2.boundingRect(xx) |
152 | |
153 |
if min(_w, _h) / max(_w, _h) < 0.3: |
154 |
continue
|
155 | |
156 |
max_width = _x if _x > max_width else max_width |
157 |
max_height = _y if _y > max_height else max_height |
158 | |
159 |
if (_w < _h) or (_w > maxTextSize > _h): # count character that looks like horizontal |
160 |
horizontal += 1# + (_w * _h) / (w * h) |
161 |
else:
|
162 |
vertical += 1# + (_w * _h) / (w * h) |
163 | |
164 |
if (w < minSize and h < minSize) or (max_width > maxTextSize and max_height > maxTextSize): |
165 |
continue # skip too small or big one |
166 | |
167 |
rects.append([0 if horizontal >= vertical else 90, QRect(x, y, w, h)]) |
168 | |
169 |
configs = app_doc_data.getConfigs('Text Recognition', 'Merge Size') |
170 |
mergeSize = int(configs[0].value) if 1 == len(configs) else 10 |
171 |
# merge rectangles
|
172 |
interestings = [] |
173 |
while rects:
|
174 |
rect = rects.pop() |
175 | |
176 |
if 0 == rect[0]: # x-direction text |
177 |
rectExpand = rect[1].adjusted(-mergeSize, 0, mergeSize, 0) |
178 |
matches = [x for x in rects if (x[0] == rect[0]) and |
179 |
abs(x[1].height() - rect[1].height()) < (x[1].height() + rect[1].height())*0.5 and |
180 |
abs(x[1].center().y() - rect[1].center().y()) < rect[1].height()*0.25 and |
181 |
rectExpand.intersects(x[1])]
|
182 |
else: # y -direction text |
183 |
rectExpand = rect[1].adjusted(0, -mergeSize, 0, mergeSize) |
184 |
matches = [x for x in rects if (x[0] == rect[0]) and |
185 |
abs(x[1].width() - rect[1].width()) < (x[1].width() + rect[1].width())*0.5 and |
186 |
abs(x[1].center().x() - rect[1].center().x()) < rect[1].width()*0.25 and |
187 |
rectExpand.intersects(x[1])]
|
188 | |
189 |
if matches:
|
190 |
for _rect in matches: |
191 |
rect[1] = rect[1].united(_rect[1]) |
192 |
if _rect in rects: |
193 |
rects.remove(_rect) |
194 |
rects.append(rect) |
195 |
else:
|
196 |
interestings.append(rect) |
197 | |
198 |
for rect in interestings: |
199 |
matches = [_rect for _rect in interestings if rect != _rect and _rect[1].contains(rect[1])] |
200 |
# if there is no boxes which contains
|
201 |
if not matches: |
202 |
angle = rect[0]
|
203 |
res_list.append(ti.TextInfo('', round(offset_x) + rect[1].x(), round(offset_y) + rect[1].y(), rect[1].width(), |
204 |
rect[1].height(), angle))
|
205 |
except Exception as ex: |
206 |
message = 'error occurred({}) in {}:{}'.format(repr(ex), sys.exc_info()[-1].tb_frame.f_code.co_filename, |
207 |
sys.exc_info()[-1].tb_lineno)
|
208 |
print(message) |
209 | |
210 |
return res_list, ocr_image
|
211 | |
212 |
def getTextBox_craft(self, ocr_image, maxTextSize, minSize, offset_x, offset_y, web=False): |
213 |
""" get text box by using craft """
|
214 | |
215 |
from AppWebService import AppWebService |
216 |
from AppDocData import AppDocData |
217 | |
218 |
app_doc_data = AppDocData.instance() |
219 |
project = app_doc_data.getCurrentProject() |
220 | |
221 |
binary_image = cv2.threshold(ocr_image, 200, 255, cv2.THRESH_BINARY)[1] |
222 |
|
223 |
score_path = os.path.join(project.getTempPath(), 'OCR_CRAFT_SCORE_{}.png'.format(app_doc_data.imgName))
|
224 |
img_path = os.path.join(project.getTempPath(), 'OCR_CRAFT_{}.png'.format(app_doc_data.imgName))
|
225 | |
226 |
if not web: |
227 |
sys.path.insert(0, os.path.dirname(os.path.realpath('./'))+ '\\WebServer\\CRAFT_pytorch_master') |
228 |
import text_craft |
229 | |
230 |
boxes = text_craft.get_text_box(ocr_image, img_path, score_path, os.path.dirname(os.path.realpath('./')) + '\\WebServer\\CRAFT_pytorch_master\\weights\\craft_mlt_25k.pth') |
231 |
else:
|
232 |
app_web_service = AppWebService() |
233 |
boxes = app_web_service.request_text_box(ocr_image, img_path, score_path) |
234 | |
235 |
rects = [] |
236 | |
237 |
for box in boxes: |
238 |
rects.append(QRect(box[0], box[1], box[4] - box[0], box[5] - box[1])) |
239 | |
240 |
configs = app_doc_data.getConfigs('Text Recognition', 'Merge Size') |
241 |
mergeSize = int(configs[0].value) if 1 == len(configs) else 10 |
242 |
#gap_size = mergeSize / 2
|
243 |
gap_size = 3
|
244 | |
245 |
verticals = [] |
246 |
horizontals = [] |
247 |
for rect in rects: |
248 |
if rect.width() < minSize and rect.height() < maxTextSize: |
249 |
rect._vertical = False
|
250 |
horizontals.append(rect) |
251 |
elif rect.height() < minSize and rect.width() < maxTextSize: |
252 |
rect._vertical = True
|
253 |
verticals.append(rect) |
254 |
elif rect.width() < minSize or rect.height() < minSize: |
255 |
continue
|
256 |
elif rect.height() > rect.width():
|
257 |
rect._vertical = True
|
258 |
verticals.append(rect) |
259 |
else:
|
260 |
rect._vertical = False
|
261 |
horizontals.append(rect) |
262 | |
263 |
v_merges = [] |
264 |
for vertical1 in verticals: |
265 |
for vertical2 in verticals: |
266 |
if vertical1 is vertical2: |
267 |
continue
|
268 |
if abs(vertical1.center().x() - vertical2.center().x()) < gap_size: |
269 |
t1, t2 = vertical1.top() - mergeSize, vertical2.top() - mergeSize |
270 |
b1, b2 = vertical1.bottom() + mergeSize, vertical2.bottom() + mergeSize |
271 |
l_x_y, s_x_y = [t1, b1], [t2, b2] |
272 |
if not (max(l_x_y) < min(s_x_y) or max(s_x_y) < min(l_x_y)): |
273 |
inserted = False
|
274 |
for merge in v_merges: |
275 |
if vertical1 in merge and vertical2 in merge: |
276 |
inserted = True
|
277 |
break
|
278 |
elif vertical1 in merge and vertical2 not in merge: |
279 |
merge.append(vertical2) |
280 |
inserted = True
|
281 |
break
|
282 |
elif vertical2 in merge and vertical1 not in merge: |
283 |
merge.append(vertical1) |
284 |
inserted = True
|
285 |
break
|
286 |
if not inserted: |
287 |
v_merges.append([vertical1, vertical2]) |
288 | |
289 |
h_merges = [] |
290 |
for horizontal1 in horizontals: |
291 |
for horizontal2 in horizontals: |
292 |
if horizontal1 is horizontal2: |
293 |
continue
|
294 |
if abs(horizontal1.center().y() - horizontal2.center().y()) < gap_size: |
295 |
l1, l2 = horizontal1.left() - mergeSize, horizontal2.left() - mergeSize |
296 |
r1, r2 = horizontal1.right() + mergeSize, horizontal2.right() + mergeSize |
297 |
l_x_y, s_x_y = [l1, r1], [l2, r2] |
298 |
if not (max(l_x_y) < min(s_x_y) or max(s_x_y) < min(l_x_y)): |
299 |
inserted = False
|
300 |
for merge in h_merges: |
301 |
if horizontal1 in merge and horizontal2 in merge: |
302 |
inserted = True
|
303 |
break
|
304 |
elif horizontal1 in merge and horizontal2 not in merge: |
305 |
merge.append(horizontal2) |
306 |
inserted = True
|
307 |
break
|
308 |
elif horizontal2 in merge and horizontal1 not in merge: |
309 |
merge.append(horizontal1) |
310 |
inserted = True
|
311 |
break
|
312 |
if not inserted: |
313 |
h_merges.append([horizontal1, horizontal2]) |
314 | |
315 |
for merge in v_merges + h_merges: |
316 |
for rect in merge: |
317 |
if rect in rects: |
318 |
rects.remove(rect) |
319 |
else:
|
320 |
print(str(rect))
|
321 | |
322 |
for merge in v_merges: |
323 |
max_x, max_y, min_x, min_y = 0, 0, sys.maxsize, sys.maxsize |
324 |
for rect in merge: |
325 |
if rect.left() < min_x:
|
326 |
min_x = rect.left() |
327 |
if rect.right() > max_x:
|
328 |
max_x = rect.right() |
329 |
if rect.top() < min_y:
|
330 |
min_y = rect.top() |
331 |
if rect.bottom() > max_y:
|
332 |
max_y = rect.bottom() |
333 | |
334 |
rect = QRect(min_x, min_y, max_x - min_x, max_y - min_y) |
335 |
rect._vertical = True
|
336 |
rects.append(rect) |
337 |
|
338 |
for merge in h_merges: |
339 |
max_x, max_y, min_x, min_y = 0, 0, sys.maxsize, sys.maxsize |
340 |
for rect in merge: |
341 |
if rect.left() < min_x:
|
342 |
min_x = rect.left() |
343 |
if rect.right() > max_x:
|
344 |
max_x = rect.right() |
345 |
if rect.top() < min_y:
|
346 |
min_y = rect.top() |
347 |
if rect.bottom() > max_y:
|
348 |
max_y = rect.bottom() |
349 | |
350 |
rect = QRect(min_x, min_y, max_x - min_x, max_y - min_y) |
351 |
rect._vertical = False
|
352 |
rects.append(rect) |
353 | |
354 |
res_rects = [] |
355 |
for rect in rects: |
356 |
res_rects.append(ti.TextInfo('', round(offset_x) + rect.x(), round(offset_y) + rect.y(), rect.width(), |
357 |
rect.height(), 90 if rect._vertical else 0)) |
358 | |
359 |
return res_rects, binary_image
|
360 | |
361 |
def getTextBox(self, ocr_image, imgGray, maxTextSize, minSize): |
362 |
""" get text box """
|
363 |
from AppDocData import AppDocData |
364 | |
365 |
app_doc_data = AppDocData.instance() |
366 |
project = app_doc_data.getCurrentProject() |
367 | |
368 |
cv2.rectangle(ocr_image, (0, 0), ocr_image.shape[::-1], (255, 255, 255), -1) |
369 | |
370 |
mask = cv2.threshold(imgGray, 200, 255, cv2.THRESH_BINARY)[1] |
371 | |
372 |
contours, hierarchy = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE) |
373 |
for contour in contours: |
374 |
# remove too big one or horizontal/vertical line
|
375 |
[x, y, w, h] = cv2.boundingRect(contour) |
376 |
area = cv2.contourArea(contour, True)
|
377 | |
378 |
# skip one which size is greater than max size or less then minimum size
|
379 |
if (w > maxTextSize or h > maxTextSize) or (w <= minSize and h <= minSize): |
380 |
#cv2.drawContours(ocr_image, [contour], -1, (255, 255, 255), -1)
|
381 |
continue
|
382 | |
383 |
if area >= 0: |
384 |
cv2.drawContours(ocr_image, [contour], -1, (0, 0, 0), -1) |
385 |
#cv2.drawContours(ocr_image, [contour], -1, (255, 255, 255), 1)
|
386 |
#else:
|
387 |
# cv2.drawContours(ocr_image, [contour], -1, (255, 255, 255), -1)
|
388 | |
389 |
path = os.path.join(project.getTempPath(), 'OCR_{}.png'.format(app_doc_data.imgName))
|
390 |
cv2.imwrite(path, ocr_image) |
391 | |
392 |
"""
|
393 |
east = False
|
394 |
if east:
|
395 |
# define the two output layer names for the EAST detector model that
|
396 |
# we are interested -- the first is the output probabilities and the
|
397 |
# second can be used to derive the bounding box coordinates of text
|
398 |
layerNames = [
|
399 |
"feature_fusion/Conv_7/Sigmoid",
|
400 |
"feature_fusion/concat_3"]
|
401 |
|
402 |
# load the pre-trained EAST text detector
|
403 |
net = cv2.dnn.readNet("C:\\ProgramData\\Digital PID\\frozen_east_text_detection.pb")
|
404 |
|
405 |
(H, W) = ocr_image.shape[:2]
|
406 |
# construct a blob from the image and then perform a forward pass of
|
407 |
# the model to obtain the two output layer sets
|
408 |
blob = cv2.dnn.blobFromImage(ocr_image, 1.0, (W, H), (123.68, 116.78, 103.94), swapRB=True, crop=False)
|
409 |
net.setInput(blob)
|
410 |
(scores, geometry) = net.forward(layerNames)
|
411 |
|
412 |
# decode the predictions, then apply non-maxima suppression to
|
413 |
# suppress weak, overlapping bounding boxes
|
414 |
(rects, confidences) = self.decode_predictions(scores, geometry)
|
415 |
boxes = non_max_suppression(np.array(rects), probs=confidences)
|
416 |
# loop over the bounding boxes
|
417 |
for (startX, startY, endX, endY) in boxes:
|
418 |
pass
|
419 |
else:
|
420 |
"""
|
421 |
configs = app_doc_data.getConfigs('Text Recognition', 'Expand Size') |
422 |
expand_size = int(configs[0].value) if 1 == len(configs) else 10 |
423 |
configs = app_doc_data.getConfigs('Text Recognition', 'Shrink Size') |
424 |
shrinkSize = int(configs[0].value) if 1 == len(configs) else 0 |
425 | |
426 |
binary_image = cv2.threshold(ocr_image, 200, 255, cv2.THRESH_BINARY)[1] |
427 |
eroded = cv2.erode(binary_image, np.ones((expand_size, expand_size), np.uint8)) |
428 |
eroded = cv2.bitwise_not(eroded) |
429 | |
430 |
bboxes = [] |
431 |
contours, hierarchy = cv2.findContours(eroded, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) |
432 |
for contour in contours: |
433 |
area = cv2.contourArea(contour, True)
|
434 |
if area < 0: |
435 |
[x, y, w, h] = cv2.boundingRect(contour) |
436 |
bboxes.append(QRect(x, y, w, h)) |
437 | |
438 |
# exclude bounding boxes contains child bounding box
|
439 |
not_containing_bbox = [] |
440 |
for bbox in bboxes: |
441 |
matches = [_bbox for _bbox in bboxes if bbox != _bbox and bbox.contains(_bbox)] |
442 |
if not matches: |
443 |
not_containing_bbox.append(bbox) |
444 |
# up to here
|
445 | |
446 |
return not_containing_bbox, binary_image
|
447 | |
448 | |
449 |
'''
|
450 |
@brief recognize text of given text info
|
451 |
@author humkyung
|
452 |
@date 2018.07.24
|
453 |
@history change parameter updateProgressSignal to worker
|
454 |
2018.11.08 euisung add white char list check process on db
|
455 |
'''
|
456 |
@staticmethod
|
457 |
def recognizeTextFromImage(tInfos, imgOCR, offset, searchedSymbolList, worker, listWidget, maxProgressValue): |
458 |
import re |
459 |
res = [] |
460 | |
461 |
app_doc_data = AppDocData.instance() |
462 | |
463 |
try:
|
464 |
for tInfo in tInfos: |
465 |
x = tInfo.getX() - round(offset[0]) |
466 |
y = tInfo.getY() - round(offset[1]) |
467 |
img = imgOCR[y:y + tInfo.getH(), x:x + tInfo.getW()] |
468 | |
469 |
# set angle 0 if symbol contains the text area is instrumentation
|
470 |
category = None
|
471 |
if searchedSymbolList:
|
472 |
contains = [symbol for symbol in searchedSymbolList if symbol.contains(tInfo)] |
473 |
if contains:
|
474 |
_type = contains[0].getType()
|
475 |
category = app_doc_data.getSymbolCategoryByType(_type) |
476 |
if 'Instrumentation' == category: |
477 |
tInfo.setAngle(0)
|
478 |
# up to here
|
479 | |
480 |
white_char_list = app_doc_data.getConfigs('Text Recognition', 'White Character List') |
481 |
resultTextInfo = TOCR.getTextInfo(img, (x, y), tInfo.getAngle(), language=app_doc_data.OCRData, |
482 |
conf=white_char_list[0].value if white_char_list else '') |
483 | |
484 |
if resultTextInfo and len(resultTextInfo) > 0: |
485 |
for result in resultTextInfo: |
486 |
result.setX(result.getX() + round(offset[0])) |
487 |
result.setY(result.getY() + round(offset[1])) |
488 |
if 'Instrumentation' == category: |
489 |
text = re.sub('[^a-zA-Z0-9]+', '', result.getText()) |
490 |
result.setText(text) |
491 | |
492 |
res.extend(resultTextInfo) |
493 | |
494 |
if listWidget is not None: |
495 |
item = QListWidgetItem( |
496 |
'{},{},{} is recognized'.format(resultTextInfo[0].getX(), resultTextInfo[0].getY(), |
497 |
resultTextInfo[0].getText()))
|
498 |
listWidget.addItem(item) |
499 |
else:
|
500 |
pass
|
501 | |
502 |
if worker is not None: |
503 |
worker.updateProgress.emit(maxProgressValue, |
504 |
resultTextInfo[0].getText() if resultTextInfo is not None and 1 == len( |
505 |
resultTextInfo) else None) |
506 |
except Exception as ex: |
507 |
message = 'error occurred({}) in {}:{}'.format(repr(ex), sys.exc_info()[-1].tb_frame.f_code.co_filename, |
508 |
sys.exc_info()[-1].tb_lineno)
|
509 |
if worker is not None: |
510 |
worker.displayLog.emit(MessageType.Error, message) |
511 | |
512 |
return res
|
513 | |
514 |
'''
|
515 |
@brief read image drawing and then remove text
|
516 |
@author jwkim
|
517 |
@date
|
518 |
@history humkyung 2018.04.06 check if file exists
|
519 |
Jeongwoo 2018.05.09 Use Tesseract OCR after Azure OCR (Azure OCR : Getting text area)
|
520 |
Jeongwoo 2018.05.25 Add condition on if-statement
|
521 |
Jeongwoo 2018.06.05 Get text area data list by config.type
|
522 |
Jeongwoo 2018.06.08 Add angle Parameter on TOCR.getTextInfo
|
523 |
humkyung 2018.06.16 update proessbar while recognizing text
|
524 |
humkyung 2018.07.03 remove white space and replace given oldStr with newStr
|
525 |
humkyung 2018.07.07 change method name to recognizeText
|
526 |
euisung 2018.11.08 add white char list check process on db
|
527 |
euisung 2018.11.12 add title block properties
|
528 |
'''
|
529 | |
530 |
def recognizeText(self, imgSrc, offset, tInfoList, searchedSymbolList, worker, listWidget, maxProgressValue, |
531 |
onlyTextArea=False):
|
532 |
import concurrent.futures as futures |
533 |
from App import App |
534 |
from Area import Area |
535 | |
536 |
try:
|
537 |
self.otherTextInfoList = []
|
538 |
self.titleBlockTextInfoList = []
|
539 |
self.textInfoList = []
|
540 | |
541 |
app_doc_data = AppDocData.instance() |
542 |
project = app_doc_data.getCurrentProject() |
543 | |
544 |
text_info_array = np.array_split(tInfoList, App.THREAD_MAX_WORKER |
545 |
if len(tInfoList) > App.THREAD_MAX_WORKER else len(tInfoList)) |
546 |
with futures.ThreadPoolExecutor(max_workers=App.THREAD_MAX_WORKER) as pool: |
547 |
future_text = {pool.submit(TextDetector.recognizeTextFromImage, tInfo, imgSrc, offset, |
548 |
searchedSymbolList, worker, listWidget, maxProgressValue): |
549 |
tInfo for tInfo in text_info_array} |
550 | |
551 |
for future in futures.as_completed(future_text): |
552 |
try:
|
553 |
data = future.result() |
554 |
if data:
|
555 |
self.textInfoList.extend(data)
|
556 |
except Exception as ex: |
557 |
message = 'error occurred({}) in {}:{}'.format(repr(ex), sys.exc_info()[-1].tb_frame.f_code.co_filename, |
558 |
sys.exc_info()[-1].tb_lineno)
|
559 |
if worker:
|
560 |
worker.displayLog.emit(MessageType.Error, message) |
561 | |
562 |
if onlyTextArea:
|
563 |
return
|
564 |
# parse texts in area except Drawing area
|
565 |
whiteCharList = app_doc_data.getConfigs('Text Recognition', 'White Character List') |
566 |
for area in app_doc_data.getAreaList(): |
567 |
if area.name == 'Drawing': continue |
568 | |
569 |
if area.name == 'Note': |
570 |
if area is not None and hasattr(area, 'img') and area.img is not None: |
571 |
if len(whiteCharList) is 0: |
572 |
texts = TOCR.getTextInfo(area.img, (area.x, area.y), 0, language='eng') |
573 |
else:
|
574 |
texts = TOCR.getTextInfo(area.img, (area.x, area.y), 0, language='eng', |
575 |
conf=whiteCharList[0].value)
|
576 |
self.otherTextInfoList.append([area.name, texts])
|
577 |
else:
|
578 |
img = app_doc_data.imgSrc[round(area.y):round(area.y + area.height), |
579 |
round(area.x):round(area.x + area.width)] |
580 |
if len(whiteCharList) is 0: |
581 |
texts = TOCR.getTextInfo(img, (area.x, area.y), 0, language='eng') |
582 |
else:
|
583 |
texts = TOCR.getTextInfo(img, (area.x, area.y), 0, language='eng', |
584 |
conf=whiteCharList[0].value)
|
585 |
if texts is not None and len(texts) > 0: |
586 |
if area.name == 'Unit': |
587 |
app_doc_data.activeDrawing.setAttr('Unit', texts[0].getText()) |
588 |
self.otherTextInfoList.append([area.name, texts])
|
589 | |
590 |
titleBlockProps = app_doc_data.getTitleBlockProperties() |
591 |
if titleBlockProps:
|
592 |
for titleBlockProp in titleBlockProps: |
593 |
area = Area(titleBlockProp[0])
|
594 |
area.parse(titleBlockProp[2])
|
595 |
if not (titleBlockProp[3] and titleBlockProp[3] != ''): |
596 |
img = app_doc_data.imgSrc[round(area.y):round(area.y + area.height), |
597 |
round(area.x):round(area.x + area.width)] |
598 |
if len(whiteCharList) is 0: |
599 |
texts = TOCR.getTextInfo(img, (area.x, area.y), 0, language=app_doc_data.OCRData)
|
600 |
else:
|
601 |
texts = TOCR.getTextInfo(img, (area.x, area.y), 0, language='eng', |
602 |
conf=whiteCharList[0].value)
|
603 |
texts = [ti.TextInfo('\n'.join([textInfo.getText() for textInfo in texts]), area.x, area.y, |
604 |
area.width, area.height, 0)]
|
605 |
else:
|
606 |
texts = [ti.TextInfo(titleBlockProp[3], area.x, area.y, area.width, area.height, 0)] |
607 |
self.titleBlockTextInfoList.append([area.name, texts])
|
608 | |
609 |
if worker is not None: worker.updateProgress.emit(maxProgressValue, None) |
610 | |
611 |
"""
|
612 |
for text_box in tInfoList:
|
613 |
x = text_box.getX()
|
614 |
y = text_box.getY()
|
615 |
cv2.rectangle(imgSrc, (x - offset[0], y - offset[1]),
|
616 |
(x - offset[0] + text_box.getW(), y - offset[1] + text_box.getH()), 1, 1)
|
617 |
cv2.imwrite('c:\\Temp\\text_box.png', imgSrc)
|
618 |
"""
|
619 |
except Exception as ex: |
620 |
message = 'error occurred({}) in {}:{}'.format(repr(ex), sys.exc_info()[-1].tb_frame.f_code.co_filename, |
621 |
sys.exc_info()[-1].tb_lineno)
|
622 |
if worker:
|
623 |
worker.displayLog.emit(MessageType.Error, message) |
624 | |
625 |
'''
|
626 |
@brief remove text from image
|
627 |
@author humkyung
|
628 |
@date 2018.07.24
|
629 |
'''
|
630 | |
631 |
def remove_text_from_image(self, imgSrc, offset): |
632 |
# remove recognized text from image
|
633 |
for text in self.textInfoList: |
634 |
x = round(text.getX() - offset[0]) |
635 |
y = round(text.getY() - offset[1]) |
636 |
width = round(text.getW())
|
637 |
height = round(text.getH())
|
638 |
cv2.rectangle(imgSrc, (x, y), (x + width, y + height), 255, -1) |
639 |
# up to here
|
640 | |
641 |
# DEBUG
|
642 |
#cv2.imwrite("c:\\temp\\remove_texts.png", imgSrc)
|
643 | |
644 | |
645 |
if __name__ == "__main__": |
646 |
image = cv2.imread('d:\\Projects\\DTIPID\\Projects\\IX3\\Temp\\OCR_Document_2_Page1.png')
|
647 |
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) |
648 |
output = gray.copy() |
649 |
gray = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY)[1] |
650 | |
651 |
expand_size = 5
|
652 |
eroded = cv2.erode(gray, np.ones((expand_size, expand_size), np.uint8)) |
653 |
eroded = cv2.bitwise_not(eroded) |
654 |
cv2.imwrite('c:\\temp\\eroded.png', eroded)
|
655 | |
656 |
bboxes = [] |
657 |
contours, hierarchy = cv2.findContours(eroded, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) |
658 |
for contour in contours: |
659 |
area = cv2.contourArea(contour, True)
|
660 |
if area < 0: |
661 |
[x, y, w, h] = cv2.boundingRect(contour) |
662 |
bboxes.append(QRect(x, y, w, h)) |
663 | |
664 |
# exclude bounding boxes contains child bounding box
|
665 |
not_containing_bbox = [] |
666 |
for bbox in bboxes: |
667 |
matches = [_bbox for _bbox in bboxes if bbox != _bbox and bbox.contains(_bbox)] |
668 |
if not matches: |
669 |
not_containing_bbox.append(bbox) |
670 |
# up to here
|
671 | |
672 |
rects = [] |
673 |
for bbox in not_containing_bbox: |
674 |
x, y = bbox.left(), bbox.top() |
675 |
w, h = bbox.width(), bbox.height() |
676 |
img = gray[bbox.top():bbox.bottom(), bbox.left():bbox.right()] |
677 |
img = cv2.bitwise_not(img) |
678 | |
679 |
horizontal, max_width = 0, 0 |
680 |
vertical, max_height = 0, 0 |
681 |
_contours, _ = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) |
682 |
for xx in _contours: |
683 |
[_x, _y, _w, _h] = cv2.boundingRect(xx) |
684 | |
685 |
max_width = _x if _x > max_width else max_width |
686 |
max_height = _y if _y > max_height else max_height |
687 | |
688 |
if (_w*0.9 < _h) or (_w > 80 > _h): # width is greater than height |
689 |
horizontal += 1 + (_w * _h) / (w * h)
|
690 |
else:
|
691 |
vertical += 1 + (_w * _h) / (w * h)
|
692 | |
693 |
if (w < 5 and h < 5) or (max_width > 80 and max_height > 80): |
694 |
continue # skip too small or big one |
695 | |
696 |
rects.append([0 if horizontal > vertical else 90, QRect(x, y, w, h)]) |
697 | |
698 |
merge_size = 10
|
699 |
# merge rectangles
|
700 |
interestings = [] |
701 |
while rects:
|
702 |
rect = rects.pop() |
703 | |
704 |
if 0 == rect[0]: # x-direction text |
705 |
rect_expand = rect[1].adjusted(-merge_size, 0, merge_size, 0) |
706 |
matches = [x for x in rects if (x[0] == rect[0]) and |
707 |
abs(x[1].height() - rect[1].height()) < (x[1].height() + rect[1].height()) * 0.5 and |
708 |
abs(x[1].center().y() - rect[1].center().y()) < rect[1].height() * 0.25 and |
709 |
rect_expand.intersects(x[1].adjusted(-merge_size, 0, merge_size, 0))] |
710 |
else: # y -direction text |
711 |
rect_expand = rect[1].adjusted(0, -merge_size, 0, merge_size) |
712 |
matches = [x for x in rects if (x[0] == rect[0]) and |
713 |
abs(x[1].width() - rect[1].width()) < (x[1].width() + rect[1].width()) * 0.5 and |
714 |
abs(x[1].center().x() - rect[1].center().x()) < rect[1].width() * 0.25 and |
715 |
rect_expand.intersects(x[1].adjusted(0, -merge_size, 0, merge_size))] |
716 | |
717 |
if matches:
|
718 |
for _rect in matches: |
719 |
rect[1] = rect[1].united(_rect[1]) |
720 |
if _rect in rects: |
721 |
rects.remove(_rect) |
722 |
rects.append(rect) |
723 |
else:
|
724 |
interestings.append(rect) |
725 | |
726 |
for orientation, bbox in interestings: |
727 |
cv2.rectangle(output, (bbox.x(), bbox.y()), (bbox.right(), bbox.bottom()), (0, 255, 0), 1) |
728 | |
729 |
"""
|
730 |
mser = cv2.MSER_create(_min_area=10)
|
731 |
regions, _ = mser.detectRegions(gray) # Get the text area
|
732 |
hulls = [cv2.convexHull(p.reshape(-1, 1, 2)) for p in regions] # Drawing text areas
|
733 |
# Processing irregular detection boxes into rectangular boxes
|
734 |
keep = []
|
735 |
for c in hulls:
|
736 |
x, y, w, h = cv2.boundingRect(c)
|
737 |
cv2.rectangle(output, (x, y), (x + w, y + h), (0, 255, 0), 1)
|
738 |
"""
|
739 |
#cv2.polylines(output, hulls, 1, (0, 255, 0))
|
740 |
cv2.imwrite('c:\\temp\\mser.png', output)
|
741 |