프로젝트

일반

사용자정보

통계
| 브랜치(Branch): | 개정판:

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