프로젝트

일반

사용자정보

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

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

    
클립보드 이미지 추가 (최대 크기: 500 MB)