opex792 commited on
Commit
f318822
·
verified ·
1 Parent(s): 5985dd0

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +161 -64
app.py CHANGED
@@ -25,75 +25,173 @@ def order_points(pts):
25
  """
26
  rect = np.zeros((4, 2), dtype="float32")
27
  s = pts.sum(axis=1)
28
- rect[0] = pts[np.argmin(s)]
29
- rect[2] = pts[np.argmax(s)]
30
  diff = np.diff(pts, axis=1)
31
- rect[1] = pts[np.argmin(diff)]
32
- rect[3] = pts[np.argmax(diff)]
33
  return rect
34
 
35
- def correct_perspective(image):
36
  """
37
- Находит текстовый блок и исправляет искажение перспективы.
38
  """
39
- logging.info("Запуск коррекции перспективы...")
40
- # Создаем копию, чтобы не изменять оригинал
41
- img_for_transform = image.copy()
 
 
 
 
 
 
 
42
 
43
- # Инвертируем изображение для поиска контуров (белый текст на черном фоне)
44
- inverted = cv2.bitwise_not(img_for_transform)
 
45
 
46
- # Находим контуры. RETR_EXTERNAL находит только внешние контуры.
47
- contours, _ = cv2.findContours(inverted, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
48
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  if not contours:
50
- logging.warning("Контуры не найдены. Пропуск коррекции перспективы.")
51
  return image
52
-
53
- # Находим самый большой контур по площади
54
- largest_contour = max(contours, key=cv2.contourArea)
55
 
56
- # Находим минимальный ограничивающий прямоугольник (может быть повернут)
57
- rect = cv2.minAreaRect(largest_contour)
58
- box = cv2.boxPoints(rect)
59
 
60
- # Проверка на адекватность: если найденный бокс слишком мал, это шум
61
- if cv2.contourArea(box) < 500: # Пороговое значение, можно подбирать
62
- logging.warning("Найденный контур слишком мал. Пропуск коррекции.")
63
  return image
64
-
65
- # Упорядочиваем 4 угла
66
- ordered_box = order_points(box)
67
- (tl, tr, br, bl) = ordered_box
68
-
69
- # Вычисляем ширину и высоту целевого прямоугольника
70
- widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
71
- widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
72
- maxWidth = max(int(widthA), int(widthB))
73
-
74
- heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
75
- heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
76
- maxHeight = max(int(heightA), int(heightB))
77
 
78
- # Проверка на адекватность соотношения сторон
79
- if maxHeight == 0 or maxWidth / maxHeight < 1.5:
80
- logging.warning(f"Неадекватное соотношение сторон ({maxWidth}/{maxHeight}). Пропуск коррекции.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  return image
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
 
83
- # Задаем точки назначения (идеальный прямоугольник)
84
- dst = np.array([
85
- [0, 0],
86
- [maxWidth - 1, 0],
87
- [maxWidth - 1, maxHeight - 1],
88
- [0, maxHeight - 1]], dtype="float32")
89
-
90
- # Вычисляем матрицу преобразования перспективы и применяем ее
91
- M = cv2.getPerspectiveTransform(ordered_box, dst)
92
- warped = cv2.warpPerspective(img_for_transform, M, (maxWidth, maxHeight), flags=cv2.INTER_LINEAR, borderValue=(255,255,255))
 
 
 
 
 
 
 
 
 
93
 
94
- logging.info("Коррекция перспективы успешно применена.")
95
- return warped
 
 
 
 
96
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
  def fetch_and_solve_captcha():
99
  try:
@@ -109,35 +207,36 @@ def fetch_and_solve_captcha():
109
  nparr = np.frombuffer(image_bytes, np.uint8)
110
  original_image = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
111
 
112
- scale_factor = 2
 
113
  width = int(original_image.shape[1] * scale_factor)
114
  height = int(original_image.shape[0] * scale_factor)
115
  upscaled_image = cv2.resize(original_image, (width, height), interpolation=cv2.INTER_CUBIC)
116
 
 
117
  hsv = cv2.cvtColor(upscaled_image, cv2.COLOR_BGR2HSV)
118
  lower_blue = np.array([90, 50, 50])
119
  upper_blue = np.array([130, 255, 255])
120
  mask = cv2.inRange(hsv, lower_blue, upper_blue)
121
 
 
122
  kernel = np.ones((2, 2), np.uint8)
123
- cleaned_mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel, iterations=2)
124
-
125
- inverted_for_processing = cv2.bitwise_not(cleaned_mask)
126
-
127
- # ПРИМЕНЯЕМ НОВЫЙ АЛГОРИТМ КОРРЕКЦИИ
128
- corrected_image = correct_perspective(inverted_for_processing)
129
 
130
- processed_image = corrected_image
 
131
 
132
- tesseract_config = r'--oem 3 --psm 7 -c tessedit_char_whitelist=0123456789'
133
- text = pytesseract.image_to_string(processed_image, config=tesseract_config)
 
134
  recognized_text = re.sub(r'\s+', '', text).strip() or "Не распознано"
135
  logging.info(f"Распознано: {recognized_text}")
136
 
 
137
  _, buffer_orig = cv2.imencode('.png', original_image)
138
  original_b64 = base64.b64encode(buffer_orig).decode('utf-8')
139
 
140
- _, buffer_proc = cv2.imencode('.png', processed_image)
141
  processed_b64 = base64.b64encode(buffer_proc).decode('utf-8')
142
 
143
  return {
@@ -170,5 +269,3 @@ if __name__ == '__main__':
170
  solved_captchas.append(initial_captcha)
171
 
172
  app.run(host='0.0.0.0', port=7860, debug=False)
173
-
174
-
 
25
  """
26
  rect = np.zeros((4, 2), dtype="float32")
27
  s = pts.sum(axis=1)
28
+ rect[0] = pts[np.argmin(s)] # верх-лево
29
+ rect[2] = pts[np.argmax(s)] # низ-право
30
  diff = np.diff(pts, axis=1)
31
+ rect[1] = pts[np.argmin(diff)] # верх-право
32
+ rect[3] = pts[np.argmax(diff)] # низ-лево
33
  return rect
34
 
35
+ def find_text_region_contour(image):
36
  """
37
+ Находит контур текстовой области более надежным способом.
38
  """
39
+ # Создаем несколько версий для поиска контуров
40
+
41
+ # Метод 1: Морфологические операции для объединения символов
42
+ kernel_horizontal = cv2.getStructuringElement(cv2.MORPH_RECT, (25, 1))
43
+ kernel_vertical = cv2.getStructuringElement(cv2.MORPH_RECT, (1, 10))
44
+
45
+ # Расширяем по горизонтали чтобы соединить буквы
46
+ dilated_h = cv2.dilate(image, kernel_horizontal, iterations=2)
47
+ # Небольшое расширение по вертикали
48
+ dilated = cv2.dilate(dilated_h, kernel_vertical, iterations=1)
49
 
50
+ # Закрываем промежутки
51
+ kernel_close = cv2.getStructuringElement(cv2.MORPH_RECT, (15, 8))
52
+ closed = cv2.morphologyEx(dilated, cv2.MORPH_CLOSE, kernel_close, iterations=1)
53
 
54
+ return closed
 
55
 
56
+ def correct_perspective_improved(image):
57
+ """
58
+ Улучшенная коррекция перспективы с более надежным поиском текстовой области.
59
+ """
60
+ logging.info("Запуск улучшенной коррекции перспективы...")
61
+
62
+ if len(image.shape) == 3:
63
+ gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
64
+ else:
65
+ gray = image.copy()
66
+
67
+ # Бинаризация если еще не сделана
68
+ if len(np.unique(gray)) > 2:
69
+ _, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
70
+ else:
71
+ binary = gray.copy()
72
+
73
+ # Инвертируем если нужно (текст должен быть белым)
74
+ if np.sum(binary == 255) < np.sum(binary == 0):
75
+ binary = cv2.bitwise_not(binary)
76
+
77
+ # Находим область текста
78
+ text_region = find_text_region_contour(binary)
79
+
80
+ # Находим контуры объединенной текстовой области
81
+ contours, _ = cv2.findContours(text_region, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
82
+
83
  if not contours:
84
+ logging.warning("Контуры текстовой области не найдены.")
85
  return image
 
 
 
86
 
87
+ # Берем самый большой контур
88
+ largest_contour = max(contours, key=cv2.contourArea)
 
89
 
90
+ # Проверяем минимальную площадь
91
+ if cv2.contourArea(largest_contour) < 1000:
92
+ logging.warning("Найденная текстовая область слишком мала.")
93
  return image
 
 
 
 
 
 
 
 
 
 
 
 
 
94
 
95
+ # Аппроксимируем контур до прямоугольника
96
+ epsilon = 0.02 * cv2.arcLength(largest_contour, True)
97
+ approx = cv2.approxPolyDP(largest_contour, epsilon, True)
98
+
99
+ # Если не получили 4 точки, используем минимальный ограничивающий прямоугольник
100
+ if len(approx) != 4:
101
+ rect = cv2.minAreaRect(largest_contour)
102
+ box = cv2.boxPoints(rect)
103
+ approx = np.int0(box)
104
+
105
+ # Преобразуем к нужному формату
106
+ if approx.shape[1] == 1:
107
+ approx = approx.squeeze(1)
108
+
109
+ # Упорядочиваем точки
110
+ ordered_points = order_points(approx.astype("float32"))
111
+
112
+ # Вычисляем размеры выходного изображения
113
+ (tl, tr, br, bl) = ordered_points
114
+
115
+ # Ширина: максимум из верхней и нижней стороны
116
+ width_top = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
117
+ width_bottom = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
118
+ max_width = max(int(width_top), int(width_bottom))
119
+
120
+ # Высота: максимум из левой и правой стороны
121
+ height_left = np.sqrt(((bl[0] - tl[0]) ** 2) + ((bl[1] - tl[1]) ** 2))
122
+ height_right = np.sqrt(((br[0] - tr[0]) ** 2) + ((br[1] - tr[1]) ** 2))
123
+ max_height = max(int(height_left), int(height_right))
124
+
125
+ # Проверяем адекватность размеров
126
+ if max_width < 50 or max_height < 20:
127
+ logging.warning(f"Неадекватные размеры области: {max_width}x{max_height}")
128
  return image
129
+
130
+ # Добавляем небольшие отступы для лучшего распознавания
131
+ padding = 10
132
+ max_width += padding * 2
133
+ max_height += padding * 2
134
+
135
+ # Целевые точки (прямоугольник)
136
+ dst_points = np.array([
137
+ [padding, padding], # верх-лево
138
+ [max_width - padding, padding], # верх-право
139
+ [max_width - padding, max_height - padding], # низ-право
140
+ [padding, max_height - padding] # низ-лево
141
+ ], dtype="float32")
142
+
143
+ # Вычисляем матрицу трансформации и применяем
144
+ transform_matrix = cv2.getPerspectiveTransform(ordered_points, dst_points)
145
+ corrected = cv2.warpPerspective(
146
+ image,
147
+ transform_matrix,
148
+ (max_width, max_height),
149
+ flags=cv2.INTER_LINEAR,
150
+ borderMode=cv2.BORDER_CONSTANT,
151
+ borderValue=(255, 255, 255)
152
+ )
153
+
154
+ logging.info(f"Коррекция перспективы применена. Размер: {max_width}x{max_height}")
155
+ return corrected
156
 
157
+ def enhance_for_ocr(image):
158
+ """
159
+ Дополнительная обработка для улучшения OCR.
160
+ """
161
+ # Преобразуем в градации серого если нужно
162
+ if len(image.shape) == 3:
163
+ gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
164
+ else:
165
+ gray = image.copy()
166
+
167
+ # Увеличиваем контраст
168
+ clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
169
+ enhanced = clahe.apply(gray)
170
+
171
+ # Бинаризация с адаптивным порогом
172
+ binary = cv2.adaptiveThreshold(
173
+ enhanced, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
174
+ cv2.THRESH_BINARY, 11, 2
175
+ )
176
 
177
+ # Морфологическая очистка
178
+ kernel = np.ones((2,2), np.uint8)
179
+ cleaned = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel, iterations=1)
180
+ cleaned = cv2.morphologyEx(cleaned, cv2.MORPH_OPEN, kernel, iterations=1)
181
+
182
+ return cleaned
183
 
184
+ def correct_perspective(image):
185
+ """
186
+ Основная функция коррекции перспективы.
187
+ """
188
+ # Применяем улучшенную коррекцию перспективы
189
+ corrected = correct_perspective_improved(image)
190
+
191
+ # Дополнительная обработка для OCR
192
+ enhanced = enhance_for_ocr(corrected)
193
+
194
+ return enhanced
195
 
196
  def fetch_and_solve_captcha():
197
  try:
 
207
  nparr = np.frombuffer(image_bytes, np.uint8)
208
  original_image = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
209
 
210
+ # Увеличиваем масштаб для лучшего качества
211
+ scale_factor = 3 # Увеличили с 2 до 3
212
  width = int(original_image.shape[1] * scale_factor)
213
  height = int(original_image.shape[0] * scale_factor)
214
  upscaled_image = cv2.resize(original_image, (width, height), interpolation=cv2.INTER_CUBIC)
215
 
216
+ # Выделение синего текста
217
  hsv = cv2.cvtColor(upscaled_image, cv2.COLOR_BGR2HSV)
218
  lower_blue = np.array([90, 50, 50])
219
  upper_blue = np.array([130, 255, 255])
220
  mask = cv2.inRange(hsv, lower_blue, upper_blue)
221
 
222
+ # Очистка маски
223
  kernel = np.ones((2, 2), np.uint8)
224
+ cleaned_mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel, iterations=1)
 
 
 
 
 
225
 
226
+ # ПРИМЕНЯЕМ УЛУЧШЕННУЮ КОРРЕКЦИЮ ПЕРСПЕКТИВЫ
227
+ corrected_image = correct_perspective(cleaned_mask)
228
 
229
+ # Настройки для Tesseract - более точные для цифр
230
+ tesseract_config = r'--oem 3 --psm 8 -c tessedit_char_whitelist=0123456789'
231
+ text = pytesseract.image_to_string(corrected_image, config=tesseract_config)
232
  recognized_text = re.sub(r'\s+', '', text).strip() or "Не распознано"
233
  logging.info(f"Распознано: {recognized_text}")
234
 
235
+ # Кодируем изображения для отображения
236
  _, buffer_orig = cv2.imencode('.png', original_image)
237
  original_b64 = base64.b64encode(buffer_orig).decode('utf-8')
238
 
239
+ _, buffer_proc = cv2.imencode('.png', corrected_image)
240
  processed_b64 = base64.b64encode(buffer_proc).decode('utf-8')
241
 
242
  return {
 
269
  solved_captchas.append(initial_captcha)
270
 
271
  app.run(host='0.0.0.0', port=7860, debug=False)