Browse Source

Scan, prepare and main program

master
FedorSarafanov 3 months ago
parent
commit
46169db01c
  1. 100
      a4_prepare.py
  2. BIN
      a4keyboard_scan.jpg
  3. BIN
      background_image.png
  4. 1
      rectangles.json
  5. 341
      speckb.py

100
a4_prepare.py

@ -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)

BIN
a4keyboard_scan.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 937 KiB

BIN
background_image.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

1
rectangles.json

@ -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]]

341
speckb.py

@ -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…
Cancel
Save