프로젝트

일반

사용자정보

통계
| 개정판:

hytos / DTI_PID / DTI_PID / TextDetector.py @ 19d19912

이력 | 보기 | 이력해설 | 다운로드 (13.2 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

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

    
43
        return tInfoList
44

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

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

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

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

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

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

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

    
91
        eroded = cv2.bitwise_not(eroded)
92
        
93
        image, contours, hierarchy = cv2.findContours(eroded, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
94
        for contour in contours:
95
            area = cv2.contourArea(contour, True)
96
            if area < 0:
97
                [x, y, w, h] = cv2.boundingRect(contour)
98
                if (w < 10 and h < 10) or (w > maxTextSize and h > maxTextSize): continue; # skip too small or big one
99
                
100
                img = contourImg[y:y+h, x:x+w]
101
                img = cv2.bitwise_not(img)
102

    
103
                horizontal = 0
104
                vertical = 0
105
                if w > maxTextSize:
106
                    horizontal = 1
107
                elif h > maxTextSize:
108
                    vertical = 1
109
                else:
110
                    if shrinkSize > 0:
111
                        img = cv2.erode(img, np.ones((shrinkSize,shrinkSize), np.uint8))
112

    
113
                    _, _contours, _ = cv2.findContours(img, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
114
                    for xx in _contours:
115
                        [_x, _y, _w, _h] = cv2.boundingRect(xx)
116
                        cv2.rectangle(img, (_x, _y), (_x+_w, _y+_h), 255, 1)
117

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

    
123
                if horizontal > vertical:
124
                    filePath = os.path.join(project.getTempPath(), "Tile", "H-{}-{}-{}-{}.png".format(x,y,w,h))
125
                else:
126
                    filePath = os.path.join(project.getTempPath(), "Tile", "V-{}-{}-{}-{}.png".format(x,y,w,h))
127

    
128
                cv2.imwrite(filePath, img)
129

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

    
132
        configs = appDocData.getConfigs('Text Recognition', 'Merge Size')
133
        mergeSize = int(configs[0].value) if 1 == len(configs) else 10
134
        # merge rectangles
135
        intersected = True
136
        while intersected:
137
            intersected = False
138
            for rect in rects[:]:
139
                if 0 == rect[0]:
140
                    rectExpand = rect[1].adjusted(-mergeSize, 0, mergeSize, 0)
141
                else:
142
                    rectExpand = rect[1].adjusted(0, -mergeSize, 0, mergeSize)
143

    
144
                matches = [x for x in rects if (x[0] == rect[0]) and rectExpand.intersects(x[1])]
145
                if len(matches) > 1:
146
                    united = matches[0]
147
                    for _rect in matches:
148
                        united[1] = united[1].united(_rect[1])
149
                        if _rect in rects: rects.remove(_rect)
150
                    rects.append(united)
151
                    intersected = True
152
                    break
153

    
154
        list = []
155
        for rect in rects:
156
            angle = rect[0]
157
            list.append(ti.TextInfo('', round(offsetX) + rect[1].x(), round(offsetY) + rect[1].y(), rect[1].width(), rect[1].height(), angle))
158

    
159
        return list
160

    
161
    '''
162
        @brief      recognize text of given text info
163
        @author     humkyung
164
        @date       2018.07.24
165
        @history    change parameter updateProgressSignal to worker
166
    '''
167
    @staticmethod
168
    def recognizeTextFromImage(tInfo, imgOCR, offset, searchedSymbolList, worker, listWidget, maxProgressValue):
169
        import re
170
        res = []
171

    
172
        appDocData = AppDocData.instance()
173

    
174
        try:
175
            x = tInfo.getX() - round(offset[0])
176
            y = tInfo.getY() - round(offset[1])
177
            img = imgOCR[y:y+tInfo.getH(), x:x+tInfo.getW()]
178

    
179
            # set angle 0 if symbol contains the text area is instrumentation
180
            category = None
181
            contains = [symbol for symbol in searchedSymbolList if symbol.contains(tInfo)]
182
            if contains:
183
                _type = contains[0].getType()
184
                category = appDocData.getSymbolCategoryByType(_type)
185
                if 'Instrumentation' == category: tInfo.setAngle(0)
186
            # up to here
187

    
188
            resultTextInfo = TOCR.getTextInfo(img, (x, y), tInfo.getAngle())
189
            if resultTextInfo is not None and len(resultTextInfo) > 0:
190
                for result in resultTextInfo:
191
                    result.setX(result.getX() + round(offset[0]))
192
                    result.setY(result.getY() + round(offset[1]))
193
                    if 'Instrumentation' == category:
194
                        text = re.sub('[^a-zA-Z0-9]+', '', result.getText())
195
                        result.setText(text)
196
                res.extend(resultTextInfo)
197
                
198
                if listWidget is not None:
199
                    item = QListWidgetItem('{},{},{} is recognized'.format(resultTextInfo[0].getX(), resultTextInfo[0].getY(), resultTextInfo[0].getText()))
200
                    listWidget.addItem(item)
201
            else:
202
                pass
203

    
204
            if worker is not None: worker.updateProgress.emit(maxProgressValue, resultTextInfo[0].getText())
205
        except Exception as ex:
206
            message = 'error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
207
            worker.displayLog.emit(MessageType.Error, message)
208

    
209
        return res
210

    
211
    '''
212
        @brief      read image drawing and then remove text
213
        @author     jwkim
214
        @date       
215
        @history    humkyung 2018.04.06 check if file exists
216
                    Jeongwoo 2018.05.09 Use Tesseract OCR after Azure OCR (Azure OCR : Getting text area)
217
                    Jeongwoo 2018.05.25 Add condition on if-statement
218
                    Jeongwoo 2018.06.05 Get text area data list by config.type
219
                    Jeongwoo 2018.06.08 Add angle Parameter on TOCR.getTextInfo
220
                    humkyung 2018.06.16 update proessbar while recognizing text
221
                    humkyung 2018.07.03 remove white space and replace given oldStr with newStr
222
                    humkyung 2018.07.07 change method name to recognizeText 
223
    '''
224
    def recognizeText(self, imgSrc, offset, tInfoList, searchedSymbolList, worker, listWidget, maxProgressValue, onlyTextArea = False):
225
        import concurrent.futures as futures
226

    
227
        try:
228
            appDocData = AppDocData.instance()
229
            project = appDocData.getCurrentProject()
230

    
231
            path = os.path.join(project.getTempPath(), 'OCR_{}.png'.format(appDocData.imgName))
232
            if os.path.isfile(path):
233
                imgOCR = cv2.imread(path, 1)
234
                imgOCR = cv2.threshold(cv2.cvtColor(imgOCR, cv2.COLOR_BGR2GRAY), 127, 255, cv2.THRESH_BINARY)[1]
235

    
236
                pool = futures.ThreadPoolExecutor(max_workers = THREAD_MAX_WORKER)
237
                for tInfo in tInfoList:
238
                    future = pool.submit(TextDetector.recognizeTextFromImage, tInfo, imgOCR, offset, searchedSymbolList, worker, listWidget, maxProgressValue)
239
                    data = future.result()
240
                    if data: self.textInfoList.extend(data)
241
                pool.shutdown(wait = True)
242

    
243
            if onlyTextArea:
244
                return
245
            # parse texts in area except Drawing area
246
            for area in appDocData.getAreaList():
247
                if area.name == 'Drawing': continue
248

    
249
                if area.name == 'Unit':
250
                    img = imgSrc[round(area.y):round(area.y+area.height), round(area.x):round(area.x+area.width)]
251
                    texts = TOCR.getTextInfo(img, (area.x, area.y), 0)
252
                    if texts is not None and len(texts) > 0:
253
                        appDocData.activeDrawing.setAttr('Unit', texts[0].getText())
254
                        self.otherTextInfoList.append([area.name, texts])
255
                else:
256
                    if area is not None and hasattr(area, 'img') and area.img is not None:
257
                        texts = TOCR.getTextInfo(area.img, (area.x, area.y))
258
                        self.otherTextInfoList.append([area.name, texts])
259

    
260
            if worker is not None: worker.updateProgress.emit(maxProgressValue, None)
261
        except Exception as ex:
262
            message = 'error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
263
            worker.displayLog.emit(MessageType.Error, message)
264

    
265
    '''
266
        @brief      remove text from image
267
        @author     humkyung
268
        @date       2018.07.24
269
    '''
270
    def removeTextFromImage(self, imgSrc, offset):
271
        appDocData = AppDocData.instance()
272
        project = appDocData.getCurrentProject()
273

    
274
        path = os.path.join(project.getTempPath(), 'OCR_{}.png'.format(appDocData.imgName))
275
        if os.path.isfile(path):
276
            imgOCR = cv2.imread(path, 1)
277
            imgOCR = cv2.threshold(cv2.cvtColor(imgOCR, cv2.COLOR_BGR2GRAY), 127, 255, cv2.THRESH_BINARY)[1]
278

    
279
            # remove recognized text from image
280
            for text in self.textInfoList:
281
                x = round(text.getX() - offset[0])
282
                y = round(text.getY() - offset[1])
283
                width = round(text.getW())
284
                height = round(text.getH())
285
                self.removeText(imgSrc, (round(text.getX()), round(text.getY())), imgOCR[y:y+height, x:x+width])
286
            # up to here
287

    
288
    '''
289
        @brief  detect text in given area with givn angle
290
        @author humkyung
291
        @date   2018.07.11
292
    '''
293
    def recognizeTextInArea(self, imgSrc, area, angle=0):
294
        try:
295
            img = imgSrc[round(area[1]):round(area[1] + area[3]), round(area[0]):round(area[0] + area[2])]
296
            return TOCR.getTextInfo(img, (area[0], area[1]))
297
        except Exception as ex:
298
            print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
299

    
300
    '''
301
        @brief  remove text from image by using ocr image
302
        @author
303
    '''
304
    def removeText(self, img, pt, imgOCR):
305
        try:
306
            x = round(pt[0])
307
            y = round(pt[1])
308
            width, height = imgOCR.shape[::-1]
309
            imgOCR = cv2.dilate(imgOCR, np.ones((1,1), np.uint8))
310
            imgXOR = cv2.bitwise_xor(img[y:y+height, x:x+width], cv2.bitwise_not(imgOCR))
311
            imgXOR = cv2.dilate(imgXOR, np.ones((2,2), np.uint8))
312
            img[y:y+height, x:x+width] = imgXOR
313
        except Exception as ex:
314
            print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
315

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