Files changed (1) hide show
  1. main.py +179 -0
main.py ADDED
@@ -0,0 +1,179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import tkinter as tk
2
+ from tkinter import messagebox
3
+ from PIL import Image, ImageDraw, ImageOps
4
+ import os
5
+ import torch
6
+ import pickle
7
+ import numpy as np
8
+ import torchvision.transforms as transforms
9
+ from model import PosFormer, Vocab, PosVocab, ScaleToLimitRange
10
+
11
+ class FormulaApp:
12
+ def __init__(self, root):
13
+ self.root = root
14
+ self.root.title("Распознавание математических формул")
15
+
16
+ # Увеличим размер холста для лучшего распознавания
17
+ self.canvas_width = 800
18
+ self.canvas_height = 400
19
+ self.pen_color = "black"
20
+
21
+ # Загрузка модели и словаря
22
+ self.model = None
23
+ self.main_vocab = None
24
+ self.load_model()
25
+
26
+ # Создание интерфейса
27
+ self.canvas = tk.Canvas(root, width=self.canvas_width, height=self.canvas_height, bg='white')
28
+ self.canvas.pack(pady=10)
29
+
30
+ self.image = Image.new("RGB", (self.canvas_width, self.canvas_height), "white")
31
+ self.draw = ImageDraw.Draw(self.image)
32
+
33
+ self.canvas.bind("<Button-1>", self.start_draw)
34
+ self.canvas.bind("<B1-Motion>", self.draw_on_canvas)
35
+
36
+ btn_frame = tk.Frame(root)
37
+ btn_frame.pack(pady=5)
38
+
39
+ tk.Button(btn_frame, text="Распознать", command=self.recognize).pack(side=tk.LEFT, padx=5)
40
+ tk.Button(btn_frame, text="Очистить", command=self.clear_canvas).pack(side=tk.LEFT, padx=5)
41
+ tk.Button(btn_frame, text="Сохранить", command=self.save_all).pack(side=tk.LEFT, padx=5)
42
+
43
+ self.eraser_button = tk.Button(btn_frame, text="Ластик", command=self.toggle_eraser)
44
+ self.eraser_button.pack(side=tk.LEFT, padx=5)
45
+
46
+ self.result_label = tk.Label(root, text="Результат: ", font=("Arial", 12))
47
+ self.result_label.pack(pady=10)
48
+
49
+ self.last_x = None
50
+ self.last_y = None
51
+ self.is_erasing = False
52
+
53
+ # Папки для хранения
54
+ self.draw_folder = "рисунки"
55
+ self.text_folder = "формулы"
56
+ os.makedirs(self.draw_folder, exist_ok=True)
57
+ os.makedirs(self.text_folder, exist_ok=True)
58
+
59
+ # Инициализация препроцессинга
60
+ self.size_controller = ScaleToLimitRange(h_lo=32, h_hi=256, w_lo=32, w_hi=512)
61
+ self.to_tensor = transforms.ToTensor()
62
+ self.normalize = transforms.Normalize([0.5], [0.5])
63
+ self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
64
+
65
+ def load_model(self):
66
+ try:
67
+ print("Загрузка словаря...")
68
+ with open("crohme_vocab.pkl", "rb") as f:
69
+ self.main_vocab = pickle.load(f)
70
+ pos_vocab = PosVocab()
71
+ print("Словарь загружен.")
72
+
73
+ print("Создание модели...")
74
+ self.model = PosFormer(self.main_vocab, pos_vocab).to(self.device)
75
+ print("Модель создана.")
76
+
77
+ print("Загрузка весов из 43%.pth...")
78
+ checkpoint = torch.load("43%.pth", map_location=self.device)
79
+ self.model.load_state_dict(checkpoint['model_state_dict'])
80
+ self.model.eval()
81
+ print(" Модель успешно загружена и готова к работе.")
82
+
83
+ except Exception as e:
84
+ print(f" ОШИБКА: Не удалось загрузить модель или словарь: {e}")
85
+ messagebox.showerror("Ошибка", f"Не удалось загрузить модель: {e}")
86
+
87
+ def preprocess_image(self, pil_image):
88
+ """Подготовка изображения для модели"""
89
+ # Конвертируем в grayscale и инвертируем цвета
90
+ img_gray = pil_image.convert('L')
91
+ img_gray = ImageOps.invert(img_gray)
92
+
93
+ # Конвертируем в numpy для обработки
94
+ img_np = np.array(img_gray)
95
+
96
+ # Масштабируем изображение
97
+ img_np = self.size_controller(img_np)
98
+
99
+ # Конвертируем обратно в PIL и применяем трансформации
100
+ final_img_pil = Image.fromarray(img_np)
101
+ tensor = self.normalize(self.to_tensor(final_img_pil))
102
+
103
+ # Добавляем batch dimension
104
+ return tensor.unsqueeze(0).to(self.device)
105
+
106
+ def recognize(self):
107
+ if self.model is None:
108
+ messagebox.showerror("Ошибка", "Модель не загружена")
109
+ return
110
+
111
+ try:
112
+ # Препроцессинг изображения
113
+ image_tensor = self.preprocess_image(self.image)
114
+
115
+ # Распознавание с beam search
116
+ predicted_ids = self.model.generate_beam_search(image_tensor, beam_size=10, max_gen_len=200)
117
+
118
+ # Декодирование результата
119
+ latex_string = self.main_vocab.decode(predicted_ids[0].cpu().tolist())
120
+ self.result_label.config(text=f"Результат: $${latex_string}$$")
121
+
122
+ except Exception as e:
123
+ messagebox.showerror("Ошибка", f"Ошибка при распознавании: {e}")
124
+ self.result_label.config(text="Результат: ошибка распознавания")
125
+
126
+ def start_draw(self, event):
127
+ self.last_x = event.x
128
+ self.last_y = event.y
129
+
130
+ def draw_on_canvas(self, event):
131
+ x, y = event.x, event.y
132
+ color = "white" if self.is_erasing else self.pen_color
133
+ width = 8 if self.is_erasing else 4
134
+
135
+ self.canvas.create_line(self.last_x, self.last_y, x, y, width=width,
136
+ fill=color, capstyle=tk.ROUND, smooth=True)
137
+ self.draw.line([self.last_x, self.last_y, x, y], fill=color, width=width)
138
+
139
+ self.last_x = x
140
+ self.last_y = y
141
+
142
+ def toggle_eraser(self):
143
+ self.is_erasing = not self.is_erasing
144
+ self.eraser_button.config(text="Карандаш" if self.is_erasing else "Ластик")
145
+
146
+ def clear_canvas(self):
147
+ self.canvas.delete("all")
148
+ self.image = Image.new("RGB", (self.canvas_width, self.canvas_height), "white")
149
+ self.draw = ImageDraw.Draw(self.image)
150
+ self.result_label.config(text="Результат: ")
151
+ self.is_erasing = False
152
+ self.eraser_button.config(text="Ластик")
153
+
154
+ def get_next_filename(self, folder, prefix, ext):
155
+ i = 1
156
+ while True:
157
+ filename = f"{prefix}_{i}.{ext}"
158
+ full_path = os.path.join(folder, filename)
159
+ if not os.path.exists(full_path):
160
+ return full_path
161
+ i += 1
162
+
163
+ def save_all(self):
164
+ # Сохранить рисунок
165
+ image_path = self.get_next_filename(self.draw_folder, "рисунок", "png")
166
+ self.image.save(image_path)
167
+
168
+ # Сохранить распознанную формулу
169
+ text = self.result_label.cget("text").replace("Результат: ", "")
170
+ formula_path = self.get_next_filename(self.text_folder, "формула", "txt")
171
+ with open(formula_path, "w", encoding="utf-8") as f:
172
+ f.write(text)
173
+
174
+ messagebox.showinfo("Сохранение", f"Изображение и формула сохранены:\n{image_path}\n{formula_path}")
175
+
176
+ if __name__ == "__main__":
177
+ root = tk.Tk()
178
+ app = FormulaApp(root)
179
+ root.mainloop()