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
|