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
|