5 changed files with 442 additions and 0 deletions
@ -0,0 +1,100 @@
@@ -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 @@
@@ -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 @@
@@ -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