5 changed files with 442 additions and 0 deletions
@ -0,0 +1,100 @@ |
|||||||
|
import cv2 |
||||||
|
import numpy as np |
||||||
|
import random |
||||||
|
import json |
||||||
|
|
||||||
|
# Загрузка изображения |
||||||
|
image = cv2.imread("a4keyboard_scan.jpg") |
||||||
|
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) |
||||||
|
|
||||||
|
# Применяем адаптивный порог |
||||||
|
thresh = cv2.adaptiveThreshold( |
||||||
|
gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2 |
||||||
|
) |
||||||
|
|
||||||
|
# Находим контуры и собираем размеры прямоугольников |
||||||
|
rectangles = [ |
||||||
|
cv2.boundingRect(contour) |
||||||
|
for contour in cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[ |
||||||
|
0 |
||||||
|
] |
||||||
|
if (cv2.boundingRect(contour)[2] > 20 and cv2.boundingRect(contour)[3] > 20) |
||||||
|
] |
||||||
|
|
||||||
|
# Вычисляем медианные размеры |
||||||
|
widths, heights = zip(*[(w, h) for _, _, w, h in rectangles]) |
||||||
|
median_width, median_height = np.median(widths), np.median(heights) |
||||||
|
|
||||||
|
# Фильтрация прямоугольников по размеру |
||||||
|
filtered_rectangles = [ |
||||||
|
rect |
||||||
|
for rect in rectangles |
||||||
|
if 0.5 * median_width <= rect[2] <= 10 * median_width |
||||||
|
and 0.5 * median_height <= rect[3] <= 10 * median_height |
||||||
|
] |
||||||
|
|
||||||
|
# Порог для тёмного фона |
||||||
|
darkness_threshold = 80 |
||||||
|
filtered_rectangles_dark = [ |
||||||
|
rect |
||||||
|
for rect in filtered_rectangles |
||||||
|
if cv2.mean(gray[rect[1] : rect[1] + rect[3], rect[0] : rect[0] + rect[2]])[0] |
||||||
|
< darkness_threshold |
||||||
|
] |
||||||
|
|
||||||
|
# Уменьшаем размеры и закрашиваем области |
||||||
|
padding = 8 |
||||||
|
for x, y, w, h in filtered_rectangles_dark: |
||||||
|
x_padded, y_padded = x + padding, y + padding |
||||||
|
w_padded, h_padded = max(w - 2 * padding, 1), max(h - 2 * padding, 1) |
||||||
|
roi = image[y_padded : y_padded + h_padded, x_padded : x_padded + w_padded] |
||||||
|
mean_color = cv2.mean(roi)[:3] |
||||||
|
|
||||||
|
# Генерация области с шумом |
||||||
|
noise = np.random.uniform(-10, 10, roi.shape).astype(np.int16) |
||||||
|
noisy_color = np.clip(np.array(mean_color, dtype=np.int16) + noise, 0, 255).astype( |
||||||
|
np.uint8 |
||||||
|
) |
||||||
|
image[y_padded : y_padded + h_padded, x_padded : x_padded + w_padded] = noisy_color |
||||||
|
|
||||||
|
# Масштабирование изображения и прямоугольников |
||||||
|
scale_factor = 0.65 |
||||||
|
# image = cv2.resize(image, (image.shape[1] // 2, image.shape[0] // 2)) |
||||||
|
image = cv2.resize( |
||||||
|
image, None, fx=scale_factor, fy=scale_factor, interpolation=cv2.INTER_LINEAR |
||||||
|
) |
||||||
|
scaled_rectangles = [ |
||||||
|
( |
||||||
|
int(x * scale_factor), |
||||||
|
int(y * scale_factor), |
||||||
|
int(w * scale_factor), |
||||||
|
int(h * scale_factor), |
||||||
|
) |
||||||
|
for x, y, w, h in filtered_rectangles_dark |
||||||
|
] |
||||||
|
|
||||||
|
# Сортируем прямоугольники построчно |
||||||
|
row_tolerance = 10 |
||||||
|
scaled_rectangles.sort(key=lambda rect: rect[1]) |
||||||
|
|
||||||
|
rows = [] |
||||||
|
current_row = [scaled_rectangles[0]] |
||||||
|
|
||||||
|
for rect in scaled_rectangles[1:]: |
||||||
|
if abs(rect[1] - current_row[-1][1]) <= row_tolerance: |
||||||
|
current_row.append(rect) |
||||||
|
else: |
||||||
|
rows.append(current_row) |
||||||
|
current_row = [rect] |
||||||
|
|
||||||
|
rows.append(current_row) |
||||||
|
|
||||||
|
sorted_rectangles = [ |
||||||
|
rect for row in rows for rect in sorted(row, key=lambda rect: rect[0]) |
||||||
|
] |
||||||
|
|
||||||
|
# Сохраняем изображение и данные прямоугольников |
||||||
|
cv2.imwrite("background_image.png", image) |
||||||
|
|
||||||
|
with open("rectangles.json", "w") as f: |
||||||
|
json.dump(sorted_rectangles, f) |
After Width: | Height: | Size: 937 KiB |
After Width: | Height: | Size: 1.3 MiB |
@ -0,0 +1 @@ |
|||||||
|
[[21, 24, 66, 50], [168, 24, 65, 50], [240, 24, 66, 50], [313, 24, 66, 50], [386, 24, 66, 50], [495, 24, 66, 50], [570, 24, 65, 50], [642, 25, 65, 50], [715, 26, 65, 49], [826, 26, 65, 49], [898, 26, 65, 50], [971, 26, 65, 50], [1045, 26, 65, 50], [1136, 26, 65, 50], [1209, 25, 65, 50], [1282, 25, 66, 51], [22, 96, 65, 65], [95, 96, 65, 65], [168, 96, 65, 65], [241, 96, 65, 65], [313, 96, 65, 65], [386, 96, 66, 65], [459, 96, 66, 65], [532, 96, 66, 65], [606, 96, 65, 65], [679, 97, 65, 64], [752, 97, 65, 65], [825, 97, 65, 65], [898, 98, 65, 64], [971, 98, 138, 65], [1136, 97, 65, 65], [1209, 97, 66, 65], [1283, 97, 65, 65], [1376, 97, 65, 65], [1449, 97, 65, 65], [1522, 97, 65, 65], [1595, 96, 65, 65], [20, 169, 102, 65], [131, 169, 65, 65], [204, 169, 65, 64], [277, 169, 65, 64], [350, 169, 65, 65], [423, 169, 65, 65], [495, 169, 66, 65], [569, 169, 65, 65], [642, 170, 66, 64], [715, 170, 65, 65], [787, 170, 66, 65], [861, 170, 66, 64], [934, 170, 66, 64], [1008, 171, 102, 64], [1136, 170, 65, 65], [1209, 170, 65, 65], [1283, 170, 65, 65], [1376, 170, 65, 65], [1449, 170, 65, 65], [1522, 170, 65, 65], [1595, 170, 66, 137], [21, 241, 120, 65], [149, 241, 65, 65], [222, 242, 65, 65], [295, 242, 65, 65], [368, 242, 65, 65], [440, 241, 66, 66], [514, 242, 66, 65], [587, 242, 65, 65], [660, 243, 66, 65], [733, 243, 65, 65], [806, 243, 65, 64], [880, 243, 65, 64], [952, 243, 157, 65], [1376, 243, 65, 65], [1449, 243, 65, 65], [1522, 243, 66, 65], [20, 314, 157, 65], [185, 314, 66, 66], [258, 315, 65, 65], [331, 315, 65, 65], [403, 315, 66, 65], [477, 315, 66, 65], [549, 315, 66, 65], [622, 315, 66, 65], [696, 316, 66, 64], [769, 316, 65, 64], [843, 316, 65, 64], [915, 316, 194, 65], [1209, 316, 65, 65], [1376, 316, 65, 65], [1449, 316, 65, 65], [1522, 315, 65, 65], [1595, 315, 65, 137], [20, 387, 102, 65], [130, 388, 65, 65], [202, 388, 102, 65], [312, 388, 432, 66], [750, 389, 85, 65], [842, 390, 83, 64], [934, 390, 83, 64], [1025, 390, 84, 64], [1135, 389, 66, 65], [1209, 388, 66, 65], [1282, 389, 65, 65], [1376, 389, 138, 65], [1522, 389, 65, 65]] |
@ -0,0 +1,341 @@ |
|||||||
|
from tkinter import Tk, Canvas, font |
||||||
|
from PIL import Image, ImageTk |
||||||
|
import json |
||||||
|
import subprocess |
||||||
|
import pyperclip |
||||||
|
|
||||||
|
# Загрузка данных |
||||||
|
with open("rectangles.json", "r") as f: |
||||||
|
rectangles = json.load(f) |
||||||
|
|
||||||
|
# Загрузка изображения |
||||||
|
pil_image = Image.open("background_image.png") |
||||||
|
|
||||||
|
# Создаём интерфейс Tkinter |
||||||
|
root = Tk() |
||||||
|
root.title("SFG Special Symbols Keyboard") |
||||||
|
|
||||||
|
# Запретить изменение размера окна |
||||||
|
root.resizable(False, False) |
||||||
|
|
||||||
|
# Создаём Canvas и устанавливаем фон |
||||||
|
canvas = Canvas(root, width=pil_image.width, height=pil_image.height) |
||||||
|
canvas.pack() |
||||||
|
tk_image = ImageTk.PhotoImage(pil_image) |
||||||
|
canvas.create_image(0, 0, anchor="nw", image=tk_image) |
||||||
|
|
||||||
|
key_map = { |
||||||
|
0: "Esc", |
||||||
|
1: "F1", |
||||||
|
2: "F2", |
||||||
|
3: "F3", |
||||||
|
4: "F4", |
||||||
|
5: "F5", |
||||||
|
6: "F6", |
||||||
|
7: "F7", |
||||||
|
8: "F8", |
||||||
|
9: "F9", |
||||||
|
10: "F10", |
||||||
|
11: "F11", |
||||||
|
12: "F12", |
||||||
|
13: "PrtSc", |
||||||
|
14: "ScrL", |
||||||
|
15: "Pause", |
||||||
|
16: "`", |
||||||
|
17: "1", |
||||||
|
18: "2", |
||||||
|
19: "3", |
||||||
|
20: "4", |
||||||
|
21: "5", |
||||||
|
22: "6", |
||||||
|
23: "7", |
||||||
|
24: "8", |
||||||
|
25: "9", |
||||||
|
26: "0", |
||||||
|
27: "-", |
||||||
|
28: "=", |
||||||
|
29: "Backspace", |
||||||
|
30: "Ins", |
||||||
|
31: "Home", |
||||||
|
32: "PgUp", |
||||||
|
33: "NumL", |
||||||
|
34: "/.", |
||||||
|
35: "*.", |
||||||
|
36: "-.", |
||||||
|
37: "Tab", |
||||||
|
38: "q", |
||||||
|
39: "w", |
||||||
|
40: "e", |
||||||
|
41: "r", |
||||||
|
42: "t", |
||||||
|
43: "y", |
||||||
|
44: "u", |
||||||
|
45: "i", |
||||||
|
46: "o", |
||||||
|
47: "p", |
||||||
|
48: "[", |
||||||
|
49: "]", |
||||||
|
50: "\\", |
||||||
|
51: "Del", |
||||||
|
52: "End", |
||||||
|
53: "PgDn", |
||||||
|
54: "7.", |
||||||
|
55: "8.", |
||||||
|
56: "9.", |
||||||
|
57: "+.", |
||||||
|
58: "Caps", |
||||||
|
59: "a", |
||||||
|
60: "s", |
||||||
|
61: "d", |
||||||
|
62: "f", |
||||||
|
63: "g", |
||||||
|
64: "h", |
||||||
|
65: "j", |
||||||
|
66: "k", |
||||||
|
67: "l", |
||||||
|
68: ";", |
||||||
|
69: "'", |
||||||
|
70: "Enter", |
||||||
|
71: "4.", |
||||||
|
72: "5.", |
||||||
|
73: "6.", |
||||||
|
74: "ShiftL", |
||||||
|
75: "z", |
||||||
|
76: "x", |
||||||
|
77: "c", |
||||||
|
78: "v", |
||||||
|
79: "b", |
||||||
|
80: "n", |
||||||
|
81: "m", |
||||||
|
82: ",", |
||||||
|
83: ".", |
||||||
|
84: "/", |
||||||
|
85: "ShiftR", |
||||||
|
86: "↑", |
||||||
|
87: "1.", |
||||||
|
88: "2.", |
||||||
|
89: "3.", |
||||||
|
90: "Ret.", |
||||||
|
91: "CtrlL", |
||||||
|
92: "Win", |
||||||
|
93: "Alt", |
||||||
|
94: "Space", |
||||||
|
95: "AltGr", |
||||||
|
96: "Fn", |
||||||
|
97: "Menu", |
||||||
|
98: "CtrlR", |
||||||
|
99: "←", |
||||||
|
100: "↓", |
||||||
|
101: "→", |
||||||
|
102: "Ins.", |
||||||
|
103: "Del.", |
||||||
|
} |
||||||
|
|
||||||
|
keycode_map = { |
||||||
|
9: "Esc", |
||||||
|
67: "F1", |
||||||
|
68: "F2", |
||||||
|
69: "F3", |
||||||
|
70: "F4", |
||||||
|
71: "F5", |
||||||
|
72: "F6", |
||||||
|
73: "F7", |
||||||
|
74: "F8", |
||||||
|
75: "F9", |
||||||
|
76: "F10", |
||||||
|
95: "F11", |
||||||
|
96: "F12", |
||||||
|
107: "PrtSc", |
||||||
|
78: "ScrL", |
||||||
|
127: "Pause", |
||||||
|
49: "`", |
||||||
|
10: "1", |
||||||
|
11: "2", |
||||||
|
12: "3", |
||||||
|
13: "4", |
||||||
|
14: "5", |
||||||
|
15: "6", |
||||||
|
16: "7", |
||||||
|
17: "8", |
||||||
|
18: "9", |
||||||
|
19: "0", |
||||||
|
20: "-", |
||||||
|
21: "=", |
||||||
|
22: "Backspace", |
||||||
|
118: "Ins", |
||||||
|
110: "Home", |
||||||
|
112: "PgUp", |
||||||
|
77: "NumL", |
||||||
|
106: "/.", |
||||||
|
63: "*.", |
||||||
|
82: "-.", |
||||||
|
23: "Tab", |
||||||
|
24: "q", |
||||||
|
25: "w", |
||||||
|
26: "e", |
||||||
|
27: "r", |
||||||
|
28: "t", |
||||||
|
29: "y", |
||||||
|
30: "u", |
||||||
|
31: "i", |
||||||
|
32: "o", |
||||||
|
33: "p", |
||||||
|
34: "[", |
||||||
|
35: "]", |
||||||
|
51: "\\", |
||||||
|
119: "Del", |
||||||
|
115: "End", |
||||||
|
117: "PgDn", |
||||||
|
79: "7.", |
||||||
|
80: "8.", |
||||||
|
81: "9.", |
||||||
|
86: "+.", |
||||||
|
66: "Caps", |
||||||
|
38: "a", |
||||||
|
39: "s", |
||||||
|
40: "d", |
||||||
|
41: "f", |
||||||
|
42: "g", |
||||||
|
43: "h", |
||||||
|
44: "j", |
||||||
|
45: "k", |
||||||
|
46: "l", |
||||||
|
47: ";", |
||||||
|
48: "'", |
||||||
|
36: "Enter", |
||||||
|
83: "4.", |
||||||
|
84: "5.", |
||||||
|
85: "6.", |
||||||
|
50: "ShiftL", |
||||||
|
52: "z", |
||||||
|
53: "x", |
||||||
|
54: "c", |
||||||
|
55: "v", |
||||||
|
56: "b", |
||||||
|
57: "n", |
||||||
|
58: "m", |
||||||
|
59: ",", |
||||||
|
60: ".", |
||||||
|
61: "/", |
||||||
|
62: "ShiftR", |
||||||
|
111: "↑", |
||||||
|
87: "1.", |
||||||
|
88: "2.", |
||||||
|
89: "3.", |
||||||
|
104: "Ret.", |
||||||
|
37: "CtrlL", |
||||||
|
133: "Win", |
||||||
|
64: "Alt", |
||||||
|
65: "Space", |
||||||
|
108: "AltGr", |
||||||
|
105: "CtrlR", |
||||||
|
113: "←", |
||||||
|
116: "↓", |
||||||
|
114: "→", |
||||||
|
118: "Ins", |
||||||
|
119: "Del", |
||||||
|
90: "Ins.", |
||||||
|
91: "Del.", |
||||||
|
135: "Menu", |
||||||
|
} |
||||||
|
|
||||||
|
# Словарь специальных символов для физики и математики |
||||||
|
special_symbols = { |
||||||
|
"q": "∞", # Бесконечность |
||||||
|
"w": "∑", # Сумма |
||||||
|
"e": "∫", # Интеграл |
||||||
|
"r": "√", # Корень |
||||||
|
"t": "∂", # Частная производная |
||||||
|
"y": "≈", # Приблизительно равно |
||||||
|
"u": "≥", # Больше или равно |
||||||
|
"i": "≤", # Меньше или равно |
||||||
|
"o": "⊕", # Прямое суммирование |
||||||
|
"p": "±", # Плюс-минус |
||||||
|
"[": "→", # Стрелка вправо |
||||||
|
"]": "←", # Стрелка влево |
||||||
|
"a": "⊗", # Тензорное произведение |
||||||
|
"s": "∝", # Пропорционально |
||||||
|
"d": "≠", # Не равно |
||||||
|
"f": "∃", # Существует |
||||||
|
"g": "∀", # Для всех |
||||||
|
"h": "∈", # Принадлежит |
||||||
|
"j": "∉", # Не принадлежит |
||||||
|
"k": "⊂", # Подмножество |
||||||
|
"l": "⊃", # Надмножество |
||||||
|
";": "∩", # Пересечение |
||||||
|
"'": "∪", # Объединение |
||||||
|
"z": "∧", # Логическое И |
||||||
|
"x": "∨", # Логическое ИЛИ |
||||||
|
"c": "¬", # Логическое НЕ |
||||||
|
"v": "∴", # Следовательно |
||||||
|
"b": "∠", # Угол |
||||||
|
"n": "°", # Градусы |
||||||
|
"m": "∥", # Параллельность |
||||||
|
",": "≅", # Конгруэнтность |
||||||
|
".": "∂", # Частная производная (дублируем для удобства) |
||||||
|
"/": "÷", # Деление |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
# Словарь для отслеживания подсветки |
||||||
|
highlighted_rects = {} |
||||||
|
|
||||||
|
# Создаём текстовые подписи для клавиш |
||||||
|
padding = 8 |
||||||
|
sym_font = font.Font(family="Arial", size=12, weight="bold", slant="roman") |
||||||
|
spec_font = font.Font(family="Arial", size=18, weight="bold", slant="roman") |
||||||
|
|
||||||
|
for i, (x, y, w, h) in enumerate(rectangles): |
||||||
|
key_label = key_map.get(i, f"Key {i}") |
||||||
|
special_symbol = special_symbols.get(key_label, "") |
||||||
|
|
||||||
|
rect_id = canvas.create_rectangle(x, y, x + w, y + h, outline="black", width=0) |
||||||
|
|
||||||
|
spec_id = canvas.create_text( |
||||||
|
x + padding + (w - 2 * padding) // 2, |
||||||
|
y + padding + (h - 2 * padding) // 2, |
||||||
|
text=special_symbol, |
||||||
|
font=spec_font, |
||||||
|
fill="white", |
||||||
|
) |
||||||
|
|
||||||
|
text_id = canvas.create_text( |
||||||
|
x + padding, # Смещение на 5 пикселей от левого края |
||||||
|
y + padding, # Смещение на 5 пикселей от верхнего края |
||||||
|
text=key_label, |
||||||
|
font=sym_font, |
||||||
|
fill="#777777", |
||||||
|
anchor="nw", # Выравнивание текста по верхнему левому углу |
||||||
|
) |
||||||
|
highlighted_rects[key_label] = rect_id |
||||||
|
|
||||||
|
|
||||||
|
# Обработчик нажатия клавиши |
||||||
|
def on_key_press(event): |
||||||
|
key = keycode_map.get(event.keycode) |
||||||
|
if key in highlighted_rects: |
||||||
|
# Подсветка клавиши |
||||||
|
canvas.itemconfig(highlighted_rects[key], outline="red", width=4) |
||||||
|
|
||||||
|
|
||||||
|
# Обработчик отпускания клавиши |
||||||
|
def on_key_release(event): |
||||||
|
print(event.keycode) |
||||||
|
key = keycode_map.get(event.keycode) |
||||||
|
special_symbol = special_symbols.get(key) |
||||||
|
if key in highlighted_rects: |
||||||
|
# Убираем подсветку клавиши |
||||||
|
canvas.itemconfig(highlighted_rects[key], outline="white", width=2) |
||||||
|
|
||||||
|
if special_symbol: |
||||||
|
pyperclip.copy(special_symbol) # Копируем символ в буфер обмена |
||||||
|
root.after(100, root.destroy) # Закрываем программу через 100 мс |
||||||
|
subprocess.Popen(["bash", "-c", "sleep 0.12 ; xdotool key Ctrl+v"]) |
||||||
|
|
||||||
|
|
||||||
|
# Привязываем обработчики к окну |
||||||
|
root.bind("<KeyPress>", on_key_press) |
||||||
|
root.bind("<KeyRelease>", on_key_release) |
||||||
|
|
||||||
|
# Запускаем интерфейс |
||||||
|
root.mainloop() |
Loading…
Reference in new issue