프로젝트

일반

사용자정보

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

hytos / DTI_PID / DTI_PID / TextDetector.py @ 94302d85

이력 | 보기 | 이력해설 | 다운로드 (16.6 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
        minSize = 5
62

    
63
        contourImg = np.ones(imgGray.shape, np.uint8) * 255
64
        binaryImg,mask = cv2.threshold(imgGray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
65

    
66
        image, contours, hierarchy = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
67
        for contour in contours:
68
            # remove too big one or horizontal/vertical line
69
            [x, y, w, h] = cv2.boundingRect(contour)
70
            area = cv2.contourArea(contour, True)
71

    
72
            # skip one which size is greater than max size or less then minimum size
73
            if area >= 0:
74
                if (w > maxTextSize or h > maxTextSize) or (w <= minSize and h <= minSize): continue
75

    
76
            if area >= 0:
77
                cv2.drawContours(contourImg, [contour], -1, (0,0,0), -1)
78
                cv2.drawContours(contourImg, [contour], -1, (255,255,255), 1)
79
            else:
80
                cv2.drawContours(contourImg, [contour], -1, (255,255,255), -1)
81
                
82
        path = os.path.join(project.getTempPath(), 'OCR_{}.png'.format(appDocData.imgName))
83
        cv2.imwrite(path, contourImg)
84

    
85
        rects = []
86
        configs = appDocData.getConfigs('Text Recognition', 'Expand Size')
87
        expandSize = int(configs[0].value) if 1 == len(configs) else 10
88
        configs = appDocData.getConfigs('Text Recognition', 'Shrink Size')
89
        shrinkSize = int(configs[0].value) if 1 == len(configs) else 0
90

    
91
        eroded = cv2.erode(contourImg, np.ones((expandSize,expandSize), np.uint8))
92
        #path = os.path.join(project.getTempPath(), 'ERODED_OCR_{}.png'.format(appDocData.imgName))
93
        #cv2.imwrite(path, eroded)
94

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

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

    
113
                    max_width = _x if _x > max_width else max_width
114
                    max_height = _y if _y > max_height else max_height
115

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

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

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

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

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

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

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

    
165
                matches = [x for x in rects if (x[0] == rect[0]) and rectExpand.intersects(x[1])]
166
                if len(matches) > 1:
167
                    united = matches[0]
168
                    for _rect in matches:
169
                        united[1] = united[1].united(_rect[1])
170
                        if _rect in rects: rects.remove(_rect)
171
                    rects.append(united)
172
                    intersected = True
173
                    break
174

    
175
        list = []
176
        for rect in rects:
177
            angle = rect[0]
178
            list.append(ti.TextInfo('', round(offsetX) + rect[1].x(), round(offsetY) + rect[1].y(), rect[1].width(), rect[1].height(), angle))
179

    
180
            x = rect[1].x()
181
            y = rect[1].y()
182
            w = rect[1].width()
183
            h = rect[1].height()
184
            img = contourImg[y:y+h, x:x+w]
185
            ## DEBUG
186
            #if angle == 0:
187
            #    filePath = os.path.join(project.getTempPath(), "Tile", "H-{}-{}-{}-{}.png".format(x,y,w,h))
188
            #else:
189
            #    filePath = os.path.join(project.getTempPath(), "Tile", "V-{}-{}-{}-{}.png".format(x,y,w,h))
190
            #cv2.imwrite(filePath, img)
191
            ## up to here
192

    
193
        return list
194

    
195
    '''
196
        @brief      recognize text of given text info
197
        @author     humkyung
198
        @date       2018.07.24
199
        @history    change parameter updateProgressSignal to worker
200
                    2018.11.08 euisung     add white char list check process on db
201
    '''
202
    @staticmethod
203
    def recognizeTextFromImage(tInfo, imgOCR, offset, searchedSymbolList, worker, listWidget, maxProgressValue):
204
        import re
205
        res = []
206

    
207
        appDocData = AppDocData.instance()
208

    
209
        try:
210
            x = tInfo.getX() - round(offset[0])
211
            y = tInfo.getY() - round(offset[1])
212
            img = imgOCR[y:y+tInfo.getH(), x:x+tInfo.getW()]
213

    
214
            # set angle 0 if symbol contains the text area is instrumentation
215
            category = None
216
            contains = [symbol for symbol in searchedSymbolList if symbol.contains(tInfo)]
217
            if contains:
218
                _type = contains[0].getType()
219
                category = appDocData.getSymbolCategoryByType(_type)
220
                if 'Instrumentation' == category: tInfo.setAngle(0)
221
            # up to here
222

    
223
            whiteCharList = appDocData.getConfigs('Text Recognition', 'White Character List')
224
            if len(whiteCharList) is 0:
225
                resultTextInfo = TOCR.getTextInfo(img, (x, y), tInfo.getAngle(), language=appDocData.OCRData)
226
            else:
227
                resultTextInfo = TOCR.getTextInfo(img, (x, y), tInfo.getAngle(), language=appDocData.OCRData, conf = whiteCharList[0].value)
228

    
229
            if resultTextInfo is not None and len(resultTextInfo) > 0:
230
                for result in resultTextInfo:
231
                    result.setX(result.getX() + round(offset[0]))
232
                    result.setY(result.getY() + round(offset[1]))
233
                    if 'Instrumentation' == category:
234
                        text = re.sub('[^a-zA-Z0-9]+', '', result.getText())
235
                        result.setText(text)
236
                res.extend(resultTextInfo)
237
                
238
                if listWidget is not None:
239
                    item = QListWidgetItem('{},{},{} is recognized'.format(resultTextInfo[0].getX(), resultTextInfo[0].getY(), resultTextInfo[0].getText()))
240
                    listWidget.addItem(item)
241
            else:
242
                pass
243

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

    
249
        return res
250

    
251
    '''
252
        @brief      read image drawing and then remove text
253
        @author     jwkim
254
        @date       
255
        @history    humkyung 2018.04.06 check if file exists
256
                    Jeongwoo 2018.05.09 Use Tesseract OCR after Azure OCR (Azure OCR : Getting text area)
257
                    Jeongwoo 2018.05.25 Add condition on if-statement
258
                    Jeongwoo 2018.06.05 Get text area data list by config.type
259
                    Jeongwoo 2018.06.08 Add angle Parameter on TOCR.getTextInfo
260
                    humkyung 2018.06.16 update proessbar while recognizing text
261
                    humkyung 2018.07.03 remove white space and replace given oldStr with newStr
262
                    humkyung 2018.07.07 change method name to recognizeText
263
                    euisung  2018.11.08 add white char list check process on db
264
                    euisung  2018.11.12 add title block properties
265
    '''
266
    def recognizeText(self, imgSrc, offset, tInfoList, searchedSymbolList, worker, listWidget, maxProgressValue, onlyTextArea = False):
267
        import concurrent.futures as futures
268
        from Area import Area
269

    
270
        try:
271
            self.otherTextInfoList = []
272
            self.titleBlockTextInfoList = []
273
            self.textInfoList = []
274
            
275
            appDocData = AppDocData.instance()
276
            project = appDocData.getCurrentProject()
277

    
278
            path = os.path.join(project.getTempPath(), 'OCR_{}.png'.format(appDocData.imgName))
279
            if os.path.isfile(path):
280
                imgOCR = cv2.imread(path, 1)
281
                imgOCR = cv2.threshold(cv2.cvtColor(imgOCR, cv2.COLOR_BGR2GRAY), 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]
282

    
283
                pool = futures.ThreadPoolExecutor(max_workers = THREAD_MAX_WORKER)
284
                for tInfo in tInfoList:
285
                    future = pool.submit(TextDetector.recognizeTextFromImage, tInfo, imgOCR, offset, searchedSymbolList, worker, listWidget, maxProgressValue)
286
                    data = future.result()
287
                    if data: self.textInfoList.extend(data)
288
                pool.shutdown(wait = True)
289

    
290
                for index in range(len(self.textInfoList) - 1, -1, -1):
291
                    if len(self.textInfoList[index].getText()) is 1:
292
                        self.textInfoList.pop(index)
293

    
294
                if onlyTextArea:
295
                    return
296
                # parse texts in area except Drawing area
297
                whiteCharList = appDocData.getConfigs('Text Recognition', 'White Character List')
298
                for area in appDocData.getAreaList():
299
                    if area.name == 'Drawing': continue
300

    
301
                    if area.name == 'Unit':
302
                        img = imgSrc[round(area.y):round(area.y+area.height), round(area.x):round(area.x+area.width)]
303
                        if len(whiteCharList) is 0:
304
                            texts = TOCR.getTextInfo(img, (area.x, area.y), 0, language='eng')
305
                        else:
306
                            texts = TOCR.getTextInfo(img, (area.x, area.y), 0, language='eng', conf = whiteCharList[0].value)
307
                        if texts is not None and len(texts) > 0:
308
                            appDocData.activeDrawing.setAttr('Unit', texts[0].getText())
309
                            self.otherTextInfoList.append([area.name, texts])
310
                    else:
311
                        if area is not None and hasattr(area, 'img') and area.img is not None:
312
                            if len(whiteCharList) is 0:
313
                                texts = TOCR.getTextInfo(area.img, (area.x, area.y), 0, language='eng')
314
                            else:
315
                                texts = TOCR.getTextInfo(area.img, (area.x, area.y), 0, language='eng', conf=whiteCharList[0].value)
316
                            self.otherTextInfoList.append([area.name, texts])
317

    
318
                titleBlockProps = appDocData.getTitleBlockProperties()
319
                for titleBlockProp in titleBlockProps:
320
                    area = Area(titleBlockProp[0])
321
                    area.parse(titleBlockProp[2])
322
                    img = imgSrc[round(area.y):round(area.y+area.height), round(area.x):round(area.x+area.width)]
323
                    if len(whiteCharList) is 0:
324
                        texts = TOCR.getTextInfo(img, (area.x, area.y), 0, language=appDocData.OCRData)
325
                    else:
326
                        texts = TOCR.getTextInfo(img, (area.x, area.y), 0, language='eng', conf=whiteCharList[0].value)
327
                    self.titleBlockTextInfoList.append([area.name, texts])
328

    
329
                if worker is not None: worker.updateProgress.emit(maxProgressValue, None)
330
        except Exception as ex:
331
            message = 'error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
332
            worker.displayLog.emit(MessageType.Error, message)
333

    
334
    '''
335
        @brief      remove text from image
336
        @author     humkyung
337
        @date       2018.07.24
338
    '''
339
    def removeTextFromImage(self, imgSrc, offset):
340
        appDocData = AppDocData.instance()
341
        project = appDocData.getCurrentProject()
342

    
343
        path = os.path.join(project.getTempPath(), 'OCR_{}.png'.format(appDocData.imgName))
344
        if os.path.isfile(path):
345
            imgOCR = cv2.imread(path)
346
            imgOCR = cv2.threshold(cv2.cvtColor(imgOCR, cv2.COLOR_BGR2GRAY), 127, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]
347

    
348
            # remove recognized text from image
349
            for text in self.textInfoList:
350
                x = round(text.getX() - offset[0])
351
                y = round(text.getY() - offset[1])
352
                width = round(text.getW())
353
                height = round(text.getH())
354
                self.removeText(imgSrc, (round(text.getX()), round(text.getY())), imgOCR[y:y+height, x:x+width])
355
            # up to here
356

    
357
    '''
358
        @brief  remove text from image by using ocr image
359
        @author
360
    '''
361
    def removeText(self, img, pt, imgOCR):
362
        try:
363
            x = round(pt[0])
364
            y = round(pt[1])
365
            width, height = imgOCR.shape[::-1]
366
            
367
            temp = img[y:y+height, x:x+width]
368
            imgOCR = cv2.erode(imgOCR, np.ones((3,3), np.uint8))
369
            mask = cv2.bitwise_or(temp, imgOCR)
370
            imgXOR = cv2.bitwise_xor(temp, mask)
371
            img[y:y+height, x:x+width] = cv2.bitwise_not(imgXOR)
372

    
373
        except Exception as ex:
374
            print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
375

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