프로젝트

일반

사용자정보

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

hytos / DTI_PID / DTI_PID / TextDetector.py @ 0a40c9a5

이력 | 보기 | 이력해설 | 다운로드 (15.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
class TextDetector:
22
    '''
23
        @brief  constructor
24
        @author humkyung
25
        @date   2018.07.11
26
    '''
27
    def __init__(self):
28
        self.textInfoList = []
29
        self.otherTextInfoList = []
30
        self.titleBlockTextInfoList = []
31

    
32
    '''
33
        @brief  detect text areas
34
        @author humkyung
35
        @date   2018.06.16
36
    '''
37
    def detectTextAreas(self, img, offset):
38
        tInfoList = []
39
        try:
40
            tInfoList = self.getTextAreaInfo(img, offset[0], offset[1])
41
        except Exception as ex:
42
            print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
43

    
44
        return tInfoList
45

    
46
    '''
47
        @brief      Get Text Area info by contour
48
        @author     Jeongwoo
49
        @date       2018.06.05
50
        @history    2018.06.08  Jeongwoo    Add angle
51
                    humkyung 2018.06.18 fixed logic to detect text area
52
    '''
53
    def getTextAreaInfo(self, imgGray, offsetX, offsetY):
54
        from AppDocData import AppDocData
55

    
56
        appDocData = AppDocData.instance()
57
        project = appDocData.getCurrentProject()
58

    
59
        configs = appDocData.getConfigs('Text Size', 'Max Text Size')
60
        maxTextSize = int(configs[0].value) if 1 == len(configs) else 100
61

    
62
        contourImg = np.ones(imgGray.shape, np.uint8) * 255
63
        binaryImg,mask = cv2.threshold(imgGray, 127, 255, cv2.THRESH_BINARY)
64
        
65
        image, contours, hierarchy = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
66
        for contour in contours:
67
            # remove too big one
68
            [x, y, w, h] = cv2.boundingRect(contour)
69
            if (w > maxTextSize and h > maxTextSize): continue
70

    
71
            area = cv2.contourArea(contour, True)
72
            if area >= 0:
73
                cv2.drawContours(contourImg, [contour], -1, (0,0,0), -1)
74
                cv2.drawContours(contourImg, [contour], -1, (255,255,255), 1)
75
            else:
76
                cv2.drawContours(contourImg, [contour], -1, (255,255,255), -1)
77
                
78
        path = os.path.join(project.getTempPath(), 'OCR_{}.png'.format(appDocData.imgName))
79
        cv2.imwrite(path, contourImg)
80

    
81
        rects = []
82
        configs = appDocData.getConfigs('Text Recognition', 'Expand Size')
83
        expandSize = int(configs[0].value) if 1 == len(configs) else 10
84
        configs = appDocData.getConfigs('Text Recognition', 'Shrink Size')
85
        shrinkSize = int(configs[0].value) if 1 == len(configs) else 0
86

    
87
        eroded = cv2.erode(contourImg, np.ones((expandSize,expandSize), np.uint8))
88
        
89
        path = os.path.join(project.getTempPath(), 'ERODED_OCR_{}.png'.format(appDocData.imgName))
90
        cv2.imwrite(path, eroded)
91

    
92
        eroded = cv2.bitwise_not(eroded)
93
        
94
        image, contours, hierarchy = cv2.findContours(eroded, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
95
        for contour in contours:
96
            area = cv2.contourArea(contour, True)
97
            if area < 0:
98
                [x, y, w, h] = cv2.boundingRect(contour)
99
                
100
                img = contourImg[y:y+h, x:x+w]
101
                img = cv2.bitwise_not(img)
102

    
103
                horizontal,max_width = 0,0
104
                vertical,max_height = 0,0
105
                _, _contours, _ = cv2.findContours(img, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
106
                for xx in _contours:
107
                    [_x, _y, _w, _h] = cv2.boundingRect(xx)
108
                    cv2.rectangle(img, (_x, _y), (_x+_w, _y+_h), 255, 1)
109

    
110
                    max_width = _x if _x > max_width else max_width
111
                    max_height = _y if _y > max_height else max_height
112

    
113
                    if (_w < _h) or (_w > maxTextSize and _h < maxTextSize): # width is greater than height
114
                        horizontal += 1 + (_w*_h)/(w*h)
115
                    else:
116
                        vertical += 1 + (_w*_h)/(w*h)
117

    
118
                if (w < 10 and h < 10) or (max_width > maxTextSize and max_height > maxTextSize): continue; # skip too small or big one
119
                
120
                """
121
                if w > maxTextSize:
122
                    horizontal = 1
123
                elif h > maxTextSize:
124
                    vertical = 1
125
                else:
126
                    if shrinkSize > 0:
127
                        img = cv2.erode(img, np.ones((shrinkSize,shrinkSize), np.uint8))
128

129
                    _, _contours, _ = cv2.findContours(img, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
130
                    for xx in _contours:
131
                        [_x, _y, _w, _h] = cv2.boundingRect(xx)
132
                        cv2.rectangle(img, (_x, _y), (_x+_w, _y+_h), 255, 1)
133

134
                        if (_w < _h) or (_w > maxTextSize and _h < maxTextSize): # width is greater than height
135
                            horizontal += 1 + (_w*_h)/(w*h)
136
                        else:
137
                            vertical += 1 + (_w*_h)/(w*h)
138
                """
139

    
140
                if horizontal > vertical:
141
                    filePath = os.path.join(project.getTempPath(), "Tile", "H-{}-{}-{}-{}.png".format(x,y,w,h))
142
                else:
143
                    filePath = os.path.join(project.getTempPath(), "Tile", "V-{}-{}-{}-{}.png".format(x,y,w,h))
144
                cv2.imwrite(filePath, img)
145

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

    
148
        configs = appDocData.getConfigs('Text Recognition', 'Merge Size')
149
        mergeSize = int(configs[0].value) if 1 == len(configs) else 10
150
        # merge rectangles
151
        intersected = True
152
        while intersected:
153
            intersected = False
154
            for rect in rects[:]:
155
                if 0 == rect[0]:
156
                    rectExpand = rect[1].adjusted(-mergeSize, 0, mergeSize, 0)
157
                else:
158
                    rectExpand = rect[1].adjusted(0, -mergeSize, 0, mergeSize)
159

    
160
                matches = [x for x in rects if (x[0] == rect[0]) and rectExpand.intersects(x[1])]
161
                if len(matches) > 1:
162
                    united = matches[0]
163
                    for _rect in matches:
164
                        united[1] = united[1].united(_rect[1])
165
                        if _rect in rects: rects.remove(_rect)
166
                    rects.append(united)
167
                    intersected = True
168
                    break
169

    
170
        list = []
171
        for rect in rects:
172
            angle = rect[0]
173
            list.append(ti.TextInfo('', round(offsetX) + rect[1].x(), round(offsetY) + rect[1].y(), rect[1].width(), rect[1].height(), angle))
174

    
175
        return list
176

    
177
    '''
178
        @brief      recognize text of given text info
179
        @author     humkyung
180
        @date       2018.07.24
181
        @history    change parameter updateProgressSignal to worker
182
                    2018.11.08 euisung     add white char list check process on db
183
    '''
184
    @staticmethod
185
    def recognizeTextFromImage(tInfo, imgOCR, offset, searchedSymbolList, worker, listWidget, maxProgressValue):
186
        import re
187
        res = []
188

    
189
        appDocData = AppDocData.instance()
190

    
191
        try:
192
            x = tInfo.getX() - round(offset[0])
193
            y = tInfo.getY() - round(offset[1])
194
            img = imgOCR[y:y+tInfo.getH(), x:x+tInfo.getW()]
195

    
196
            # set angle 0 if symbol contains the text area is instrumentation
197
            category = None
198
            contains = [symbol for symbol in searchedSymbolList if symbol.contains(tInfo)]
199
            if contains:
200
                _type = contains[0].getType()
201
                category = appDocData.getSymbolCategoryByType(_type)
202
                if 'Instrumentation' == category: tInfo.setAngle(0)
203
            # up to here
204

    
205
            whiteCharList = appDocData.getConfigs('Text Recognition', 'White Character List')
206
            if len(whiteCharList) is 0:
207
                resultTextInfo = TOCR.getTextInfo(img, (x, y), tInfo.getAngle(), language=appDocData.OCRData)
208
            else:
209
                resultTextInfo = TOCR.getTextInfo(img, (x, y), tInfo.getAngle(), language=appDocData.OCRData, conf = whiteCharList[0].value)
210

    
211
            if resultTextInfo is not None and len(resultTextInfo) > 0:
212
                for result in resultTextInfo:
213
                    result.setX(result.getX() + round(offset[0]))
214
                    result.setY(result.getY() + round(offset[1]))
215
                    if 'Instrumentation' == category:
216
                        text = re.sub('[^a-zA-Z0-9]+', '', result.getText())
217
                        result.setText(text)
218
                res.extend(resultTextInfo)
219
                
220
                if listWidget is not None:
221
                    item = QListWidgetItem('{},{},{} is recognized'.format(resultTextInfo[0].getX(), resultTextInfo[0].getY(), resultTextInfo[0].getText()))
222
                    listWidget.addItem(item)
223
            else:
224
                pass
225

    
226
            if worker is not None: worker.updateProgress.emit(maxProgressValue, resultTextInfo[0].getText() if resultTextInfo is not None and 1 == len(resultTextInfo) else None)
227
        except Exception as ex:
228
            message = 'error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
229
            worker.displayLog.emit(MessageType.Error, message)
230

    
231
        return res
232

    
233
    '''
234
        @brief      read image drawing and then remove text
235
        @author     jwkim
236
        @date       
237
        @history    humkyung 2018.04.06 check if file exists
238
                    Jeongwoo 2018.05.09 Use Tesseract OCR after Azure OCR (Azure OCR : Getting text area)
239
                    Jeongwoo 2018.05.25 Add condition on if-statement
240
                    Jeongwoo 2018.06.05 Get text area data list by config.type
241
                    Jeongwoo 2018.06.08 Add angle Parameter on TOCR.getTextInfo
242
                    humkyung 2018.06.16 update proessbar while recognizing text
243
                    humkyung 2018.07.03 remove white space and replace given oldStr with newStr
244
                    humkyung 2018.07.07 change method name to recognizeText
245
                    euisung  2018.11.08 add white char list check process on db
246
                    euisung  2018.11.12 add title block properties
247
    '''
248
    def recognizeText(self, imgSrc, offset, tInfoList, searchedSymbolList, worker, listWidget, maxProgressValue, onlyTextArea = False):
249
        import concurrent.futures as futures
250
        from Area import Area
251

    
252
        try:
253
            appDocData = AppDocData.instance()
254
            project = appDocData.getCurrentProject()
255

    
256
            path = os.path.join(project.getTempPath(), 'OCR_{}.png'.format(appDocData.imgName))
257
            if os.path.isfile(path):
258
                imgOCR = cv2.imread(path, 1)
259
                imgOCR = cv2.threshold(cv2.cvtColor(imgOCR, cv2.COLOR_BGR2GRAY), 127, 255, cv2.THRESH_BINARY)[1]
260

    
261
                pool = futures.ThreadPoolExecutor(max_workers = THREAD_MAX_WORKER)
262
                for tInfo in tInfoList:
263
                    future = pool.submit(TextDetector.recognizeTextFromImage, tInfo, imgOCR, offset, searchedSymbolList, worker, listWidget, maxProgressValue)
264
                    data = future.result()
265
                    if data: self.textInfoList.extend(data)
266
                pool.shutdown(wait = True)
267

    
268
            if onlyTextArea:
269
                return
270
            # parse texts in area except Drawing area
271
            whiteCharList = appDocData.getConfigs('Text Recognition', 'White Character List')
272
            for area in appDocData.getAreaList():
273
                if area.name == 'Drawing': continue
274

    
275
                if area.name == 'Unit':
276
                    img = imgSrc[round(area.y):round(area.y+area.height), round(area.x):round(area.x+area.width)]
277
                    if len(whiteCharList) is 0:
278
                        texts = TOCR.getTextInfo(img, (area.x, area.y), 0, language=appDocData.OCRData)
279
                    else:
280
                        texts = TOCR.getTextInfo(img, (area.x, area.y), 0, language=appDocData.OCRData, conf = whiteCharList[0].value)
281
                    if texts is not None and len(texts) > 0:
282
                        appDocData.activeDrawing.setAttr('Unit', texts[0].getText())
283
                        self.otherTextInfoList.append([area.name, texts])
284
                else:
285
                    if area is not None and hasattr(area, 'img') and area.img is not None:
286
                        if len(whiteCharList) is 0:
287
                            texts = TOCR.getTextInfo(area.img, (area.x, area.y), language=appDocData.OCRData)
288
                        else:
289
                            texts = TOCR.getTextInfo(area.img, (area.x, area.y), language=appDocData.OCRData, conf=whiteCharList[0].value)
290
                        self.otherTextInfoList.append([area.name, texts])
291

    
292
            titleBlockProps = appDocData.getTitleBlockProperties()
293
            for titleBlockProp in titleBlockProps:
294
                area = Area(titleBlockProp[0])
295
                area.parse(titleBlockProp[2])
296
                img = imgSrc[round(area.y):round(area.y+area.height), round(area.x):round(area.x+area.width)]
297
                if len(whiteCharList) is 0:
298
                    texts = TOCR.getTextInfo(img, (area.x, area.y), 0, language=appDocData.OCRData)
299
                else:
300
                    texts = TOCR.getTextInfo(img, (area.x, area.y), 0, language=appDocData.OCRData, conf=whiteCharList[0].value)
301
                self.titleBlockTextInfoList.append([area.name, texts])
302

    
303
            if worker is not None: worker.updateProgress.emit(maxProgressValue, None)
304
        except Exception as ex:
305
            message = 'error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
306
            worker.displayLog.emit(MessageType.Error, message)
307

    
308
    '''
309
        @brief      remove text from image
310
        @author     humkyung
311
        @date       2018.07.24
312
    '''
313
    def removeTextFromImage(self, imgSrc, offset):
314
        appDocData = AppDocData.instance()
315
        project = appDocData.getCurrentProject()
316

    
317
        path = os.path.join(project.getTempPath(), 'OCR_{}.png'.format(appDocData.imgName))
318
        if os.path.isfile(path):
319
            imgOCR = cv2.imread(path, 1)
320
            imgOCR = cv2.threshold(cv2.cvtColor(imgOCR, cv2.COLOR_BGR2GRAY), 127, 255, cv2.THRESH_BINARY)[1]
321

    
322
            # remove recognized text from image
323
            for text in self.textInfoList:
324
                x = round(text.getX() - offset[0])
325
                y = round(text.getY() - offset[1])
326
                width = round(text.getW())
327
                height = round(text.getH())
328
                self.removeText(imgSrc, (round(text.getX()), round(text.getY())), imgOCR[y:y+height, x:x+width])
329
            # up to here
330

    
331
    '''
332
        @brief  remove text from image by using ocr image
333
        @author
334
    '''
335
    def removeText(self, img, pt, imgOCR):
336
        try:
337
            x = round(pt[0])
338
            y = round(pt[1])
339
            width, height = imgOCR.shape[::-1]
340
            imgOCR = cv2.dilate(imgOCR, np.ones((1,1), np.uint8))
341
            imgXOR = cv2.bitwise_xor(img[y:y+height, x:x+width], cv2.bitwise_not(imgOCR))
342
            imgXOR = cv2.dilate(imgXOR, np.ones((2,2), np.uint8))
343
            img[y:y+height, x:x+width] = imgXOR
344
        except Exception as ex:
345
            print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
346

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