프로젝트

일반

사용자정보

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

hytos / DTI_PID / DTI_PID / TextDetector.py @ ae3a61a6

이력 | 보기 | 이력해설 | 다운로드 (18.5 KB)

1
# coding: utf-8
2
"""
3
    This is text detector module
4
"""
5
import sys
6
import os
7
import cv2
8
import numpy as np
9
from PyQt5.QtCore import *
10
from PyQt5.QtGui import *
11
from PyQt5.QtWidgets import *
12
from PyQt5.QtSvg import *
13

    
14
from AppDocData import *
15
import TextInfo as ti
16
import tesseract_ocr_module as TOCR
17

    
18
MIN_TEXT_SIZE = 10
19
THREAD_MAX_WORKER = os.cpu_count()
20

    
21

    
22
class TextDetector:
23
    '''
24
        @brief  constructor
25
        @author humkyung
26
        @date   2018.07.11
27
    '''
28

    
29
    def __init__(self):
30
        self.textInfoList = []
31
        self.otherTextInfoList = []
32
        self.titleBlockTextInfoList = []
33

    
34
    '''
35
        @brief  detect text areas
36
        @author humkyung
37
        @date   2018.06.16
38
    '''
39

    
40
    def detectTextAreas(self, img, offset):
41
        tInfoList = []
42
        try:
43
            tInfoList = self.getTextAreaInfo(img, offset[0], offset[1])
44
        except Exception as ex:
45
            print('error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
46
                                                       sys.exc_info()[-1].tb_lineno))
47

    
48
        return tInfoList
49

    
50
    '''
51
        @brief      Get Text Area info by contour
52
        @author     Jeongwoo
53
        @date       2018.06.05
54
        @history    2018.06.08  Jeongwoo    Add angle
55
                    humkyung 2018.06.18 fixed logic to detect text area
56
    '''
57

    
58
    def getTextAreaInfo(self, imgGray, offsetX, offsetY):
59
        from AppDocData import AppDocData
60

    
61
        appDocData = AppDocData.instance()
62
        project = appDocData.getCurrentProject()
63

    
64
        configs = appDocData.getConfigs('Text Size', 'Max Text Size')
65
        maxTextSize = int(configs[0].value) if 1 == len(configs) else 100
66
        minSize = 5
67

    
68
        contourImg = np.ones(imgGray.shape, np.uint8) * 255
69
        binaryImg, mask = cv2.threshold(imgGray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
70

    
71
        contours, hierarchy = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
72
        for contour in contours:
73
            # remove too big one or horizontal/vertical line
74
            [x, y, w, h] = cv2.boundingRect(contour)
75
            area = cv2.contourArea(contour, True)
76

    
77
            # skip one which size is greater than max size or less then minimum size
78
            if area >= 0:
79
                if (w > maxTextSize or h > maxTextSize) or (w <= minSize and h <= minSize): continue
80

    
81
            if area >= 0:
82
                cv2.drawContours(contourImg, [contour], -1, (0, 0, 0), -1)
83
                cv2.drawContours(contourImg, [contour], -1, (255, 255, 255), 1)
84
            else:
85
                cv2.drawContours(contourImg, [contour], -1, (255, 255, 255), -1)
86

    
87
        path = os.path.join(project.getTempPath(), 'OCR_{}.png'.format(appDocData.imgName))
88
        cv2.imwrite(path, contourImg)
89

    
90
        rects = []
91
        configs = appDocData.getConfigs('Text Recognition', 'Expand Size')
92
        expandSize = int(configs[0].value) if 1 == len(configs) else 10
93
        configs = appDocData.getConfigs('Text Recognition', 'Shrink Size')
94
        shrinkSize = int(configs[0].value) if 1 == len(configs) else 0
95

    
96
        eroded = cv2.erode(contourImg, np.ones((expandSize, expandSize), np.uint8))
97
        # path = os.path.join(project.getTempPath(), 'ERODED_OCR_{}.png'.format(appDocData.imgName))
98
        # cv2.imwrite(path, eroded)
99

    
100
        eroded = cv2.bitwise_not(eroded)
101
        # path = os.path.join(project.getTempPath(), 'bitwise_not_{}.png'.format(appDocData.imgName))
102
        # cv2.imwrite(path, eroded)
103

    
104
        contours, hierarchy = cv2.findContours(eroded, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
105
        for contour in contours:
106
            area = cv2.contourArea(contour, True)
107
            if area < 0:
108
                [x, y, w, h] = cv2.boundingRect(contour)
109

    
110
                img = contourImg[y:y + h, x:x + w]
111
                img = cv2.bitwise_not(img)
112

    
113
                horizontal, max_width = 0, 0
114
                vertical, max_height = 0, 0
115
                _contours, _ = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
116
                for xx in _contours:
117
                    [_x, _y, _w, _h] = cv2.boundingRect(xx)
118
                    cv2.rectangle(img, (_x, _y), (_x + _w, _y + _h), 255, 1)
119

    
120
                    max_width = _x if _x > max_width else max_width
121
                    max_height = _y if _y > max_height else max_height
122

    
123
                    if (_w < _h) or (_w > maxTextSize and _h < maxTextSize):  # width is greater than height
124
                        horizontal += 1 + (_w * _h) / (w * h)
125
                    else:
126
                        vertical += 1 + (_w * _h) / (w * h)
127

    
128
                if (w < 10 and h < 10) or (
129
                        max_width > maxTextSize and max_height > maxTextSize): continue;  # skip too small or big one
130

    
131
                """
132
                if w > maxTextSize:
133
                    horizontal = 1
134
                elif h > maxTextSize:
135
                    vertical = 1
136
                else:
137
                    if shrinkSize > 0:
138
                        img = cv2.erode(img, np.ones((shrinkSize,shrinkSize), np.uint8))
139

140
                    _contours, _ = cv2.findContours(img, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
141
                    for xx in _contours:
142
                        [_x, _y, _w, _h] = cv2.boundingRect(xx)
143
                        cv2.rectangle(img, (_x, _y), (_x+_w, _y+_h), 255, 1)
144

145
                        if (_w < _h) or (_w > maxTextSize and _h < maxTextSize): # width is greater than height
146
                            horizontal += 1 + (_w*_h)/(w*h)
147
                        else:
148
                            vertical += 1 + (_w*_h)/(w*h)
149
                """
150

    
151
                """
152
                if horizontal > vertical:
153
                    filePath = os.path.join(project.getTempPath(), "Tile", "H-{}-{}-{}-{}.png".format(x,y,w,h))
154
                else:
155
                    filePath = os.path.join(project.getTempPath(), "Tile", "V-{}-{}-{}-{}.png".format(x,y,w,h))
156
                cv2.imwrite(filePath, img)
157
                """
158

    
159
                rects.append([0 if horizontal > vertical else 90, QRect(x, y, w, h)])
160

    
161
        configs = appDocData.getConfigs('Text Recognition', 'Merge Size')
162
        mergeSize = int(configs[0].value) if 1 == len(configs) else 10
163
        # merge rectangles
164
        intersected = True
165
        while intersected:
166
            intersected = False
167
            for rect in rects[:]:  # clone rects
168
                if 0 == rect[0]:
169
                    rectExpand = rect[1].adjusted(-mergeSize, 0, mergeSize, 0)
170
                else:
171
                    rectExpand = rect[1].adjusted(0, -mergeSize, 0, mergeSize)
172

    
173
                matches = [x for x in rects if (x[0] == rect[0]) and rectExpand.intersects(x[1])]
174
                if len(matches) > 1:
175
                    united = matches[0]
176
                    for _rect in matches:
177
                        united[1] = united[1].united(_rect[1])
178
                        if _rect in rects: rects.remove(_rect)
179
                    rects.append(united)
180
                    intersected = True
181
                    break
182

    
183
        list = []
184
        for rect in rects:
185
            angle = rect[0]
186
            list.append(ti.TextInfo('', round(offsetX) + rect[1].x(), round(offsetY) + rect[1].y(), rect[1].width(),
187
                                    rect[1].height(), angle))
188

    
189
            x = rect[1].x()
190
            y = rect[1].y()
191
            w = rect[1].width()
192
            h = rect[1].height()
193
            img = contourImg[y:y + h, x:x + w]
194
            ## DEBUG
195
            # if angle == 0:
196
            #    filePath = os.path.join(project.getTempPath(), "Tile", "H-{}-{}-{}-{}.png".format(x,y,w,h))
197
            # else:
198
            #    filePath = os.path.join(project.getTempPath(), "Tile", "V-{}-{}-{}-{}.png".format(x,y,w,h))
199
            # cv2.imwrite(filePath, img)
200
            ## up to here
201

    
202
        return list
203

    
204
    '''
205
        @brief      recognize text of given text info
206
        @author     humkyung
207
        @date       2018.07.24
208
        @history    change parameter updateProgressSignal to worker
209
                    2018.11.08 euisung     add white char list check process on db
210
    '''
211

    
212
    @staticmethod
213
    def recognizeTextFromImage(tInfos, imgOCR, offset, searchedSymbolList, worker, listWidget, maxProgressValue):
214
        import re
215
        res = []
216

    
217
        app_doc_data = AppDocData.instance()
218

    
219
        try:
220
            for tInfo in tInfos:
221
                x = tInfo.getX() - round(offset[0])
222
                y = tInfo.getY() - round(offset[1])
223
                img = imgOCR[y:y + tInfo.getH(), x:x + tInfo.getW()]
224

    
225
                # set angle 0 if symbol contains the text area is instrumentation
226
                category = None
227
                contains = [symbol for symbol in searchedSymbolList if symbol.contains(tInfo)]
228
                if contains:
229
                    _type = contains[0].getType()
230
                    category = app_doc_data.getSymbolCategoryByType(_type)
231
                    if 'Instrumentation' == category:
232
                        tInfo.setAngle(0)
233
                # up to here
234

    
235
                whiteCharList = app_doc_data.getConfigs('Text Recognition', 'White Character List')
236
                if len(whiteCharList) is 0:
237
                    resultTextInfo = TOCR.getTextInfo(img, (x, y), tInfo.getAngle(), language=app_doc_data.OCRData)
238
                else:
239
                    resultTextInfo = TOCR.getTextInfo(img, (x, y), tInfo.getAngle(), language=app_doc_data.OCRData,
240
                                                      conf=whiteCharList[0].value)
241

    
242
                if resultTextInfo is not None and len(resultTextInfo) > 0:
243
                    for result in resultTextInfo:
244
                        result.setX(result.getX() + round(offset[0]))
245
                        result.setY(result.getY() + round(offset[1]))
246
                        if 'Instrumentation' == category:
247
                            text = re.sub('[^a-zA-Z0-9]+', '', result.getText())
248
                            result.setText(text)
249

    
250
                    res.extend(resultTextInfo)
251

    
252
                    if listWidget is not None:
253
                        item = QListWidgetItem(
254
                            '{},{},{} is recognized'.format(resultTextInfo[0].getX(), resultTextInfo[0].getY(),
255
                                                            resultTextInfo[0].getText()))
256
                        listWidget.addItem(item)
257
                else:
258
                    pass
259

    
260
                if worker is not None:
261
                    worker.updateProgress.emit(maxProgressValue,
262
                                               resultTextInfo[0].getText() if resultTextInfo is not None and 1 == len(
263
                                                   resultTextInfo) else None)
264
        except Exception as ex:
265
            message = 'error occurred({}) in {}:{}'.format(repr(ex), sys.exc_info()[-1].tb_frame.f_code.co_filename,
266
                                                           sys.exc_info()[-1].tb_lineno)
267
            if worker is not None:
268
                worker.displayLog.emit(MessageType.Error, message)
269

    
270
        return res
271

    
272
    '''
273
        @brief      read image drawing and then remove text
274
        @author     jwkim
275
        @date       
276
        @history    humkyung 2018.04.06 check if file exists
277
                    Jeongwoo 2018.05.09 Use Tesseract OCR after Azure OCR (Azure OCR : Getting text area)
278
                    Jeongwoo 2018.05.25 Add condition on if-statement
279
                    Jeongwoo 2018.06.05 Get text area data list by config.type
280
                    Jeongwoo 2018.06.08 Add angle Parameter on TOCR.getTextInfo
281
                    humkyung 2018.06.16 update proessbar while recognizing text
282
                    humkyung 2018.07.03 remove white space and replace given oldStr with newStr
283
                    humkyung 2018.07.07 change method name to recognizeText
284
                    euisung  2018.11.08 add white char list check process on db
285
                    euisung  2018.11.12 add title block properties
286
    '''
287

    
288
    def recognizeText(self, imgSrc, offset, tInfoList, searchedSymbolList, worker, listWidget, maxProgressValue,
289
                      onlyTextArea=False):
290
        import concurrent.futures as futures
291
        from multiprocessing import Process, Queue
292
        from Area import Area
293

    
294
        try:
295
            self.otherTextInfoList = []
296
            self.titleBlockTextInfoList = []
297
            self.textInfoList = []
298

    
299
            appDocData = AppDocData.instance()
300
            project = appDocData.getCurrentProject()
301

    
302
            path = os.path.join(project.getTempPath(), 'OCR_{}.png'.format(appDocData.imgName))
303
            if os.path.isfile(path):
304
                imgOCR = cv2.imread(path, 1)
305
                imgOCR = \
306
                cv2.threshold(cv2.cvtColor(imgOCR, cv2.COLOR_BGR2GRAY), 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
307

    
308
                text_info_array = np.array_split(tInfoList, THREAD_MAX_WORKER)
309
                pool = futures.ThreadPoolExecutor(max_workers=THREAD_MAX_WORKER)
310
                for tInfo in text_info_array:
311
                    future = pool.submit(TextDetector.recognizeTextFromImage, tInfo, imgOCR, offset, searchedSymbolList,
312
                                         worker, listWidget, maxProgressValue)
313
                    data = future.result()
314
                    if data:
315
                        self.textInfoList.extend(data)
316
                pool.shutdown(wait=True)
317

    
318
                # remove text item has only 1 character
319
                # for index in range(len(self.textInfoList) - 1, -1, -1):
320
                #    if len(self.textInfoList[index].getText()) is 1:
321
                #        self.textInfoList.pop(index)
322

    
323
                if onlyTextArea:
324
                    return
325
                # parse texts in area except Drawing area
326
                whiteCharList = appDocData.getConfigs('Text Recognition', 'White Character List')
327
                for area in appDocData.getAreaList():
328
                    if area.name == 'Drawing': continue
329

    
330
                    if area.name == 'Note':
331
                        if area is not None and hasattr(area, 'img') and area.img is not None:
332
                            if len(whiteCharList) is 0:
333
                                texts = TOCR.getTextInfo(area.img, (area.x, area.y), 0, language='eng')
334
                            else:
335
                                texts = TOCR.getTextInfo(area.img, (area.x, area.y), 0, language='eng',
336
                                                         conf=whiteCharList[0].value)
337
                            self.otherTextInfoList.append([area.name, texts])
338
                    else:
339
                        img = imgSrc[round(area.y):round(area.y + area.height),
340
                              round(area.x):round(area.x + area.width)]
341
                        if len(whiteCharList) is 0:
342
                            texts = TOCR.getTextInfo(img, (area.x, area.y), 0, language='eng')
343
                        else:
344
                            texts = TOCR.getTextInfo(img, (area.x, area.y), 0, language='eng',
345
                                                     conf=whiteCharList[0].value)
346
                        if texts is not None and len(texts) > 0:
347
                            if area.name == 'Unit':
348
                                appDocData.activeDrawing.setAttr('Unit', texts[0].getText())
349
                            self.otherTextInfoList.append([area.name, texts])
350

    
351
                titleBlockProps = appDocData.getTitleBlockProperties()
352
                if titleBlockProps:
353
                    for titleBlockProp in titleBlockProps:
354
                        area = Area(titleBlockProp[0])
355
                        area.parse(titleBlockProp[2])
356
                        if not (titleBlockProp[3] and titleBlockProp[3] != ''):
357
                            img = imgSrc[round(area.y):round(area.y + area.height),
358
                                  round(area.x):round(area.x + area.width)]
359
                            if len(whiteCharList) is 0:
360
                                texts = TOCR.getTextInfo(img, (area.x, area.y), 0, language=appDocData.OCRData)
361
                            else:
362
                                texts = TOCR.getTextInfo(img, (area.x, area.y), 0, language='eng',
363
                                                         conf=whiteCharList[0].value)
364
                            texts = [ti.TextInfo('\n'.join([textInfo.getText() for textInfo in texts]), area.x, area.y,
365
                                                 area.width, area.height, 0)]
366
                        else:
367
                            texts = [ti.TextInfo(titleBlockProp[3], area.x, area.y, area.width, area.height, 0)]
368
                        self.titleBlockTextInfoList.append([area.name, texts])
369

    
370
                if worker is not None: worker.updateProgress.emit(maxProgressValue, None)
371
        except Exception as ex:
372
            message = 'error occurred({}) in {}:{}'.format(repr(ex), sys.exc_info()[-1].tb_frame.f_code.co_filename,
373
                                                           sys.exc_info()[-1].tb_lineno)
374
            worker.displayLog.emit(MessageType.Error, message)
375

    
376
    '''
377
        @brief      remove text from image
378
        @author     humkyung
379
        @date       2018.07.24
380
    '''
381

    
382
    def removeTextFromImage(self, imgSrc, offset):
383
        appDocData = AppDocData.instance()
384
        project = appDocData.getCurrentProject()
385

    
386
        path = os.path.join(project.getTempPath(), 'OCR_{}.png'.format(appDocData.imgName))
387
        if os.path.isfile(path):
388
            imgOCR = cv2.imread(path)
389
            imgOCR = \
390
            cv2.threshold(cv2.cvtColor(imgOCR, cv2.COLOR_BGR2GRAY), 127, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
391

    
392
            # remove recognized text from image
393
            for text in self.textInfoList:
394
                x = round(text.getX() - offset[0])
395
                y = round(text.getY() - offset[1])
396
                width = round(text.getW())
397
                height = round(text.getH())
398
                self.removeText(imgSrc, (round(text.getX()), round(text.getY())), imgOCR[y:y + height, x:x + width])
399
            # up to here
400

    
401
    '''
402
        @brief  remove text from image by using ocr image
403
        @author
404
    '''
405

    
406
    def removeText(self, img, pt, imgOCR):
407
        try:
408
            x = round(pt[0])
409
            y = round(pt[1])
410
            width, height = imgOCR.shape[::-1]
411

    
412
            temp = img[y:y + height, x:x + width]
413
            imgOCR = cv2.erode(imgOCR, np.ones((3, 3), np.uint8))
414
            mask = cv2.bitwise_or(temp, imgOCR)
415
            imgXOR = cv2.bitwise_xor(temp, mask)
416
            img[y:y + height, x:x + width] = cv2.bitwise_not(imgXOR)
417

    
418
        except Exception as ex:
419
            print('error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
420
                                                       sys.exc_info()[-1].tb_lineno))
421

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