├── license
├── ru.pdf
└── en_us.pdf
├── images
└── demo.gif
├── requirements.txt
├── examples
├── f17a6060-6ced-4bd1-9886-8578cfbb864f.mp4
├── demo_video_torch.ipynb
└── demo_video_onnx.ipynb
├── config_example.yaml
├── pyproject.toml
├── .pre-commit-config.yaml
├── .gitignore
├── README.md
├── demo.py
└── constants.py
/license/ru.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hukenovs/slovo/HEAD/license/ru.pdf
--------------------------------------------------------------------------------
/images/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hukenovs/slovo/HEAD/images/demo.gif
--------------------------------------------------------------------------------
/license/en_us.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hukenovs/slovo/HEAD/license/en_us.pdf
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | loguru==0.7.0
2 | omegaconf==2.3.0
3 | onnxruntime==1.14.1
4 | opencv-contrib-python==4.7.0.72
5 | torch>=1.8
6 |
--------------------------------------------------------------------------------
/examples/f17a6060-6ced-4bd1-9886-8578cfbb864f.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hukenovs/slovo/HEAD/examples/f17a6060-6ced-4bd1-9886-8578cfbb864f.mp4
--------------------------------------------------------------------------------
/config_example.yaml:
--------------------------------------------------------------------------------
1 | model_path: mvit32-2.onnx
2 | frame_interval: 2 # set frame interval
3 | mean: [123.675, 116.28, 103.53]
4 | std: [58.395, 57.12, 57.375]
5 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | # [project]
2 | # dynamic = ["dependencies"]
3 | #
4 | # [tool.setuptools.dynamic]
5 | # dependencies = requirements.txt
6 |
7 | # TODO 2020/06/02: Check Flake8
8 | # [tool.flake8]
9 | # ignore = ['E203', 'E266', 'E501', 'W503', 'F403', 'F401']
10 | # max-line-length = 120
11 | # max-complexity = 18
12 | # select = ['B','C','E','F','W','T4','B9']
13 |
14 | [tool.isort]
15 | # known_third_party = ["cv2", "numpy", "pandas", "qimage2ndarray", "scipy", "sklearn", "tensorflow", "torch"]
16 | multi_line_output = 3
17 | include_trailing_comma = true
18 | force_grid_wrap = 0
19 | use_parentheses = true
20 | line_length = 120
21 |
22 | [tool.black]
23 | line-length = 120
24 | target-version = ['py39']
25 | exclude = '''
26 | /(
27 | \.eggs
28 | | \.git
29 | | \.hg
30 | | \.mypy_cache
31 | | \.tox
32 | | \.venv
33 | | _build
34 | | buck-out
35 | | build
36 | | dist
37 | # The following are specific to Black, you probably don't want those.
38 | | blib2to3
39 | | tests/data
40 | | profiling
41 | )/
42 | '''
43 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | exclude: '^$'
2 | fail_fast: false
3 | default_language_version:
4 | python: python3.9
5 | repos:
6 | - repo: https://github.com/psf/black
7 | rev: 22.6.0
8 | hooks:
9 | - id: black
10 | language_version: python3
11 | args:
12 | - "--line-length=120"
13 | - repo: https://github.com/PyCQA/flake8
14 | rev: 4.0.1
15 | hooks:
16 | - id: flake8
17 | language_version: python3
18 | args:
19 | - "--max-line-length=120"
20 | - "--ignore=E203,E265,E309,E501,E265,W503,E402"
21 | - repo: https://github.com/pre-commit/pre-commit-hooks
22 | rev: v3.2.0
23 | hooks:
24 | - id: check-docstring-first
25 | - id: check-merge-conflict
26 | - id: check-yaml
27 | - id: trailing-whitespace
28 | - id: end-of-file-fixer
29 | - id: requirements-txt-fixer
30 |
31 | - repo: https://github.com/PyCQA/autoflake
32 | rev: v1.4
33 | hooks:
34 | - id: autoflake
35 | args: ['-r', '--in-place',
36 | '--remove-all-unused-imports',
37 | '--ignore-init-module-imports',
38 | '--remove-unused-variables',
39 | '--remove-duplicate-keys'
40 | ]
41 |
42 | - repo: https://github.com/PyCQA/isort
43 | rev: 5.10.1
44 | hooks:
45 | - id: isort
46 | args:
47 | - "--profile=black"
48 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *.pyc
5 | *$py.class
6 |
7 | # C extensions
8 | *.so
9 |
10 | # Distribution / packaging
11 | .Python
12 | env/
13 | build/
14 | develop-eggs/
15 | dist/
16 | downloads/
17 | eggs/
18 | .eggs/
19 | lib64/
20 | /parts/
21 | sdist/
22 | var/
23 | wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | .DS_Store
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .coverage
43 | .coverage.*
44 | .cache
45 | nosetests.xml
46 | coverage.xml
47 | *,cover
48 | .hypothesis/
49 |
50 | # Translations
51 | *.mo
52 | *.pot
53 |
54 | # Django stuff:
55 | *.log
56 | local_settings.py
57 |
58 | # Flask stuff:
59 | instance/
60 | .webassets-cache
61 |
62 | # Scrapy stuff:
63 | .scrapy
64 |
65 | # Sphinx documentation
66 | docs/_build/
67 |
68 | # PyBuilder
69 | target/
70 |
71 | # Jupyter Notebook
72 | .ipynb_checkpoints
73 |
74 | # IntelliJ IDEA
75 | .idea/
76 |
77 | #VS Code
78 | .vscode/
79 |
80 | # pyenv
81 | .python-version
82 |
83 | # celery beat schedule file
84 | celerybeat-schedule
85 |
86 | # SageMath parsed files
87 | *.sage.py
88 |
89 | # dotenv
90 | .env
91 |
92 | # virtualenv
93 | .venv
94 | venv/
95 | ENV/
96 |
97 | # Spyder project settings
98 | .spyderproject
99 | .spyproject
100 |
101 | # Rope project settings
102 | .ropeproject
103 |
104 | # User added:
105 | /templates
106 | *.pth
107 | *.pt
108 | *.onnx
109 |
--------------------------------------------------------------------------------
/examples/demo_video_torch.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 1,
6 | "id": "775d1720-0fa3-40bf-a9e5-a8cc44744dca",
7 | "metadata": {},
8 | "outputs": [],
9 | "source": [
10 | "from IPython import display\n",
11 | "import sys\n",
12 | "sys.path.append(\"../\")\n",
13 | "\n",
14 | "import torch\n",
15 | "import numpy as np\n",
16 | "import cv2\n",
17 | "from PIL import Image\n",
18 | "\n",
19 | "from constants import classes"
20 | ]
21 | },
22 | {
23 | "cell_type": "code",
24 | "execution_count": 2,
25 | "id": "76ed828a-a15a-489c-bfd8-dcf24d3db703",
26 | "metadata": {},
27 | "outputs": [],
28 | "source": [
29 | "path_to_model = \"../mvit16-1.pt\"\n",
30 | "path_to_input_video = \"f17a6060-6ced-4bd1-9886-8578cfbb864f.mp4\"\n",
31 | "path_to_output_video = \"output_torch.mp4\""
32 | ]
33 | },
34 | {
35 | "cell_type": "code",
36 | "execution_count": 3,
37 | "id": "4a36baad-bd49-4126-b98a-4f20b7919caf",
38 | "metadata": {},
39 | "outputs": [],
40 | "source": [
41 | "model = torch.jit.load(path_to_model)\n",
42 | "model.eval()\n",
43 | "window_size = 16 # from model name\n",
44 | "threshold = 0.5\n",
45 | "frame_interval = 1\n",
46 | "mean = [123.675, 116.28, 103.53]\n",
47 | "std = [58.395, 57.12, 57.375]"
48 | ]
49 | },
50 | {
51 | "cell_type": "code",
52 | "execution_count": 4,
53 | "id": "d72fb23e-3946-4b76-ac62-cfcc325ff657",
54 | "metadata": {},
55 | "outputs": [],
56 | "source": [
57 | "def resize(im, new_shape=(224, 224)):\n",
58 | " \"\"\"\n",
59 | " Resize and pad image while preserving aspect ratio.\n",
60 | "\n",
61 | " Parameters\n",
62 | " ----------\n",
63 | " im : np.ndarray\n",
64 | " Image to be resized.\n",
65 | " new_shape : Tuple[int]\n",
66 | " Size of the new image.\n",
67 | "\n",
68 | " Returns\n",
69 | " -------\n",
70 | " np.ndarray\n",
71 | " Resized image.\n",
72 | " \"\"\"\n",
73 | " shape = im.shape[:2] # current shape [height, width]\n",
74 | " if isinstance(new_shape, int):\n",
75 | " new_shape = (new_shape, new_shape)\n",
76 | "\n",
77 | " # Scale ratio (new / old)\n",
78 | " r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])\n",
79 | "\n",
80 | " # Compute padding\n",
81 | " new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))\n",
82 | " dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] # wh padding\n",
83 | "\n",
84 | " dw /= 2\n",
85 | " dh /= 2\n",
86 | "\n",
87 | " if shape[::-1] != new_unpad: # resize\n",
88 | " im = cv2.resize(im, new_unpad, interpolation=cv2.INTER_LINEAR)\n",
89 | " top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))\n",
90 | " left, right = int(round(dw - 0.1)), int(round(dw + 0.1))\n",
91 | " im = cv2.copyMakeBorder(im, top, bottom, left, right, cv2.BORDER_CONSTANT, value=(114, 114, 114)) # add border\n",
92 | " return im"
93 | ]
94 | },
95 | {
96 | "cell_type": "code",
97 | "execution_count": 5,
98 | "id": "184ed911-6b9b-4250-a30b-c347e3be2ed1",
99 | "metadata": {},
100 | "outputs": [],
101 | "source": [
102 | "cap = cv2.VideoCapture(path_to_input_video)\n",
103 | "_,frame = cap.read()\n",
104 | "shape = frame.shape\n",
105 | "fourcc = cv2.VideoWriter_fourcc(*'H264')\n",
106 | "writer = cv2.VideoWriter(path_to_output_video, fourcc, 30, (frame.shape[1], frame.shape[0]+50))\n",
107 | "\n",
108 | "tensors_list = []\n",
109 | "prediction_list = []\n",
110 | "prediction_list.append(\"---\")\n",
111 | "\n",
112 | "frame_counter = 0\n",
113 | "while True:\n",
114 | " _, frame = cap.read()\n",
115 | " if frame is None:\n",
116 | " break\n",
117 | " frame_counter += 1\n",
118 | " if frame_counter == frame_interval:\n",
119 | " image = cv2.cvtColor(frame.copy(), cv2.COLOR_BGR2RGB)\n",
120 | " image = resize(image, (224, 224))\n",
121 | " image = (image - mean) / std\n",
122 | " image = np.transpose(image, [2, 0, 1])\n",
123 | " tensors_list.append(image)\n",
124 | " if len(tensors_list) == window_size:\n",
125 | " input_tensor = np.stack(tensors_list[: window_size], axis=1)[None][None]\n",
126 | " input_tensor = input_tensor.astype(np.float32)\n",
127 | " input_tensor = torch.from_numpy(input_tensor)\n",
128 | " with torch.no_grad():\n",
129 | " outputs = model(input_tensor)[0]\n",
130 | " gloss = str(classes[outputs.argmax().item()])\n",
131 | " if outputs.max() > threshold:\n",
132 | " if gloss != prediction_list[-1] and len(prediction_list):\n",
133 | " if gloss != \"---\":\n",
134 | " prediction_list.append(gloss)\n",
135 | " tensors_list.clear()\n",
136 | " frame_counter = 0\n",
137 | "\n",
138 | " text = \" \".join(prediction_list)\n",
139 | " text_div = np.zeros((50, frame.shape[1], 3), dtype=np.uint8)\n",
140 | " cv2.putText(text_div, text, (10, 30), cv2.FONT_HERSHEY_COMPLEX, 0.7, (255, 255, 255), 2)\n",
141 | "\n",
142 | " frame = np.concatenate((frame, text_div), axis=0)\n",
143 | " writer.write(frame)\n",
144 | "writer.release()\n",
145 | "cap.release()"
146 | ]
147 | },
148 | {
149 | "cell_type": "code",
150 | "execution_count": 6,
151 | "id": "3c512a02-1d2b-4603-b3cd-9801216c3bdf",
152 | "metadata": {},
153 | "outputs": [],
154 | "source": [
155 | "from IPython.display import display, HTML"
156 | ]
157 | },
158 | {
159 | "cell_type": "code",
160 | "execution_count": 7,
161 | "id": "53a41c5c-dcff-439b-a17a-07b9530525f8",
162 | "metadata": {},
163 | "outputs": [
164 | {
165 | "data": {
166 | "text/html": [
167 | ""
168 | ],
169 | "text/plain": [
170 | ""
171 | ]
172 | },
173 | "metadata": {},
174 | "output_type": "display_data"
175 | }
176 | ],
177 | "source": [
178 | "video_tag = f''\n",
179 | "display(HTML(video_tag))"
180 | ]
181 | },
182 | {
183 | "cell_type": "code",
184 | "execution_count": null,
185 | "id": "226bd051-c0f4-48eb-9555-5c24526cccf5",
186 | "metadata": {},
187 | "outputs": [],
188 | "source": []
189 | }
190 | ],
191 | "metadata": {
192 | "kernelspec": {
193 | "display_name": "Python 3 (ipykernel)",
194 | "language": "python",
195 | "name": "python3"
196 | },
197 | "language_info": {
198 | "codemirror_mode": {
199 | "name": "ipython",
200 | "version": 3
201 | },
202 | "file_extension": ".py",
203 | "mimetype": "text/x-python",
204 | "name": "python",
205 | "nbconvert_exporter": "python",
206 | "pygments_lexer": "ipython3",
207 | "version": "3.9.13"
208 | }
209 | },
210 | "nbformat": 4,
211 | "nbformat_minor": 5
212 | }
213 |
--------------------------------------------------------------------------------
/examples/demo_video_onnx.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 1,
6 | "id": "775d1720-0fa3-40bf-a9e5-a8cc44744dca",
7 | "metadata": {},
8 | "outputs": [],
9 | "source": [
10 | "from IPython import display\n",
11 | "import sys\n",
12 | "sys.path.append(\"../\")\n",
13 | "\n",
14 | "import onnxruntime as ort\n",
15 | "import numpy as np\n",
16 | "import cv2\n",
17 | "from PIL import Image\n",
18 | "\n",
19 | "from constants import classes"
20 | ]
21 | },
22 | {
23 | "cell_type": "code",
24 | "execution_count": 2,
25 | "id": "76ed828a-a15a-489c-bfd8-dcf24d3db703",
26 | "metadata": {},
27 | "outputs": [],
28 | "source": [
29 | "path_to_model = \"../mvit16-1.onnx\"\n",
30 | "path_to_input_video = \"f17a6060-6ced-4bd1-9886-8578cfbb864f.mp4\"\n",
31 | "path_to_output_video = \"output_onnx.mp4\""
32 | ]
33 | },
34 | {
35 | "cell_type": "code",
36 | "execution_count": 3,
37 | "id": "785fd614-df61-4bcc-90c3-2016da92fa46",
38 | "metadata": {},
39 | "outputs": [],
40 | "source": [
41 | "session = ort.InferenceSession(path_to_model)\n",
42 | "input_name = session.get_inputs()[0].name\n",
43 | "input_shape = session.get_inputs()[0].shape\n",
44 | "window_size = input_shape[3]\n",
45 | "output_names = [output.name for output in session.get_outputs()]\n",
46 | "\n",
47 | "threshold = 0.5\n",
48 | "frame_interval = 1\n",
49 | "mean = [123.675, 116.28, 103.53]\n",
50 | "std = [58.395, 57.12, 57.375]"
51 | ]
52 | },
53 | {
54 | "cell_type": "code",
55 | "execution_count": 4,
56 | "id": "d72fb23e-3946-4b76-ac62-cfcc325ff657",
57 | "metadata": {},
58 | "outputs": [],
59 | "source": [
60 | "def resize(im, new_shape=(224, 224)):\n",
61 | " \"\"\"\n",
62 | " Resize and pad image while preserving aspect ratio.\n",
63 | "\n",
64 | " Parameters\n",
65 | " ----------\n",
66 | " im : np.ndarray\n",
67 | " Image to be resized.\n",
68 | " new_shape : Tuple[int]\n",
69 | " Size of the new image.\n",
70 | "\n",
71 | " Returns\n",
72 | " -------\n",
73 | " np.ndarray\n",
74 | " Resized image.\n",
75 | " \"\"\"\n",
76 | " shape = im.shape[:2] # current shape [height, width]\n",
77 | " if isinstance(new_shape, int):\n",
78 | " new_shape = (new_shape, new_shape)\n",
79 | "\n",
80 | " # Scale ratio (new / old)\n",
81 | " r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])\n",
82 | "\n",
83 | " # Compute padding\n",
84 | " new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))\n",
85 | " dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] # wh padding\n",
86 | "\n",
87 | " dw /= 2\n",
88 | " dh /= 2\n",
89 | "\n",
90 | " if shape[::-1] != new_unpad: # resize\n",
91 | " im = cv2.resize(im, new_unpad, interpolation=cv2.INTER_LINEAR)\n",
92 | " top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))\n",
93 | " left, right = int(round(dw - 0.1)), int(round(dw + 0.1))\n",
94 | " im = cv2.copyMakeBorder(im, top, bottom, left, right, cv2.BORDER_CONSTANT, value=(114, 114, 114)) # add border\n",
95 | " return im"
96 | ]
97 | },
98 | {
99 | "cell_type": "code",
100 | "execution_count": 5,
101 | "id": "184ed911-6b9b-4250-a30b-c347e3be2ed1",
102 | "metadata": {},
103 | "outputs": [],
104 | "source": [
105 | "cap = cv2.VideoCapture(path_to_input_video)\n",
106 | "_,frame = cap.read()\n",
107 | "shape = frame.shape\n",
108 | "fourcc = cv2.VideoWriter_fourcc(*'H264')\n",
109 | "writer = cv2.VideoWriter(path_to_output_video, fourcc, 30, (frame.shape[1], frame.shape[0]+50))\n",
110 | "\n",
111 | "tensors_list = []\n",
112 | "prediction_list = []\n",
113 | "prediction_list.append(\"---\")\n",
114 | "\n",
115 | "frame_counter = 0\n",
116 | "while True:\n",
117 | " _, frame = cap.read()\n",
118 | " if frame is None:\n",
119 | " break\n",
120 | " frame_counter += 1\n",
121 | " if frame_counter == frame_interval:\n",
122 | " image = cv2.cvtColor(frame.copy(), cv2.COLOR_BGR2RGB)\n",
123 | " image = resize(image, (224, 224))\n",
124 | " image = (image - mean) / std\n",
125 | " image = np.transpose(image, [2, 0, 1])\n",
126 | " tensors_list.append(image)\n",
127 | " if len(tensors_list) == window_size:\n",
128 | " input_tensor = np.stack(tensors_list[: window_size], axis=1)[None][None]\n",
129 | " outputs = session.run(output_names, {input_name: input_tensor.astype(np.float32)})[0]\n",
130 | " gloss = str(classes[outputs.argmax()])\n",
131 | " if outputs.max() > threshold:\n",
132 | " if gloss != prediction_list[-1] and len(prediction_list):\n",
133 | " if gloss != \"---\":\n",
134 | " prediction_list.append(gloss)\n",
135 | " tensors_list.clear()\n",
136 | " frame_counter = 0\n",
137 | "\n",
138 | " text = \" \".join(prediction_list)\n",
139 | " text_div = np.zeros((50, frame.shape[1], 3), dtype=np.uint8)\n",
140 | " cv2.putText(text_div, text, (10, 30), cv2.FONT_HERSHEY_COMPLEX, 0.7, (255, 255, 255), 2)\n",
141 | "\n",
142 | " frame = np.concatenate((frame, text_div), axis=0)\n",
143 | " writer.write(frame)\n",
144 | "writer.release()\n",
145 | "cap.release()"
146 | ]
147 | },
148 | {
149 | "cell_type": "code",
150 | "execution_count": 6,
151 | "id": "3c512a02-1d2b-4603-b3cd-9801216c3bdf",
152 | "metadata": {},
153 | "outputs": [],
154 | "source": [
155 | "from IPython.display import display, HTML"
156 | ]
157 | },
158 | {
159 | "cell_type": "code",
160 | "execution_count": 7,
161 | "id": "53a41c5c-dcff-439b-a17a-07b9530525f8",
162 | "metadata": {},
163 | "outputs": [
164 | {
165 | "data": {
166 | "text/html": [
167 | ""
168 | ],
169 | "text/plain": [
170 | ""
171 | ]
172 | },
173 | "metadata": {},
174 | "output_type": "display_data"
175 | }
176 | ],
177 | "source": [
178 | "video_tag = f''\n",
179 | "display(HTML(video_tag))"
180 | ]
181 | },
182 | {
183 | "cell_type": "code",
184 | "execution_count": null,
185 | "id": "226bd051-c0f4-48eb-9555-5c24526cccf5",
186 | "metadata": {},
187 | "outputs": [],
188 | "source": []
189 | }
190 | ],
191 | "metadata": {
192 | "kernelspec": {
193 | "display_name": "Python 3 (ipykernel)",
194 | "language": "python",
195 | "name": "python3"
196 | },
197 | "language_info": {
198 | "codemirror_mode": {
199 | "name": "ipython",
200 | "version": 3
201 | },
202 | "file_extension": ".py",
203 | "mimetype": "text/x-python",
204 | "name": "python",
205 | "nbconvert_exporter": "python",
206 | "pygments_lexer": "ipython3",
207 | "version": "3.9.13"
208 | }
209 | },
210 | "nbformat": 4,
211 | "nbformat_minor": 5
212 | }
213 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Slovo - Russian Sign Language Dataset
2 |
3 | We introduce a large-scale video dataset **Slovo** for Russian Sign Language task. Slovo dataset size is about **16 GB**, and it contains **20400** RGB videos for **1000** sign language gestures from 194 singers. Each class has 20 samples. The dataset is divided into training set and test set by subject `user_id`. The training set includes 15300 videos, and the test set includes 5100 videos. The total video recording time is ~9.2 hours. About 35% of the videos are recorded in HD format, and 65% of the videos are in FullHD resolution. The average video length with gesture is 50 frames.
4 |
5 | For more information see our paper - [arXiv](https://arxiv.org/abs/2305.14527).
6 |
7 | ## Downloads
8 | ### [Main download link](https://rndml-team-cv.obs.ru-moscow-1.hc.sbercloud.ru/datasets/slovo/slovo.zip)
9 |
10 | | Downloads | Size (GB) | Comment |
11 | |--------------------------------------------------------------------------------------------------------:|:----------|:---------------------------------------------------------------------|
12 | | [Slovo](https://rndml-team-cv.obs.ru-moscow-1.hc.sbercloud.ru/datasets/slovo/slovo.zip) | ~16 | Trimmed HD+ videos by `(start, end)` annotations |
13 | | [Origin](https://rndml-team-cv.obs.ru-moscow-1.hc.sbercloud.ru/datasets/slovo/slovo_full.zip) | ~105 | Original HD+ videos from mining stage |
14 | | [360p](https://rndml-team-cv.obs.ru-moscow-1.hc.sbercloud.ru/datasets/slovo/slovo_full360.zip) | ~13 | Resized original videos by `min_side = 360` |
15 | | [Landmarks](https://rndml-team-cv.obs.ru-moscow-1.hc.sbercloud.ru/datasets/slovo/slovo_mediapipe.json) | ~1.2 | Mediapipe hand landmark annotations for each frame of trimmed videos |
16 |
17 | Also, you can download **Slovo** from [Kaggle](https://www.kaggle.com/datasets/kapitanov/slovo).
18 |
19 | Annotation file is easy to use and contains some useful columns, see `annotations.csv` file:
20 |
21 | | | attachment_id | user_id | width | height | length | text | train | begin | end |
22 | |---:|:--------------|:--------|------:|-------:|-------:|-------:|:--------|:------|:----|
23 | | 0 | de81cc1c-... | 1b... | 1440 | 1920 | 14 | привет | True | 30 | 45 |
24 | | 1 | 3c0cec5a-... | 64... | 1440 | 1920 | 32 | утро | False | 43 | 66 |
25 | | 2 | d17ca986-... | cf... | 1920 | 1080 | 44 | улица | False | 12 | 31 |
26 |
27 | where:
28 | - `attachment_id` - video file name
29 | - `user_id` - unique anonymized user ID
30 | - `width` - video width
31 | - `height` - video height
32 | - `length` - video length
33 | - `text` - gesture class in Russian Langauge
34 | - `train` - train or test boolean flag
35 | - `begin` - start of the gesture (for original dataset)
36 | - `end` - end of the gesture (for original dataset)
37 |
38 | For convenience, we have also prepared a compressed version of the dataset, in which all videos are processed by the minimum side `min_side = 360`. Download link - **[slovo360p](https://rndml-team-cv.obs.ru-moscow-1.hc.sbercloud.ru/datasets/slovo/slovo_full360.zip)**.
39 | Also, we annotate trimmed videos by using **MediaPipe** and provide hand keypoints in [this annotation file](https://rndml-team-cv.obs.ru-moscow-1.hc.sbercloud.ru/datasets/slovo/slovo_mediapipe.json).
40 |
41 | ## Models
42 | We provide some pre-trained models as the baseline for Russian sign language recognition.
43 | We tested models with frames number from [16, 32, 48], and **the best for each are below**.
44 | The first number in the model name is frames number and the second is frame interval.
45 |
46 | | Model Name | Model Size (MB) | Metric | ONNX | TorchScript |
47 | |-------------------|-----------------|--------|-----------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------|
48 | | MViTv2-small-16-4 | 140.51 | 58.35 | [weights](https://rndml-team-cv.obs.ru-moscow-1.hc.sbercloud.ru/datasets/slovo/models/mvit/onnx/mvit16-4.onnx) | [weights](https://rndml-team-cv.obs.ru-moscow-1.hc.sbercloud.ru/datasets/slovo/models/mvit/pt/mvit16-4.pt) |
49 | | MViTv2-small-32-2 | 140.79 | 64.09 | [weights](https://rndml-team-cv.obs.ru-moscow-1.hc.sbercloud.ru/datasets/slovo/models/mvit/onnx/mvit32-2.onnx) | [weights](https://rndml-team-cv.obs.ru-moscow-1.hc.sbercloud.ru/datasets/slovo/models/mvit/pt/mvit32-2.pt) |
50 | | MViTv2-small-48-2 | 141.05 | 62.18 | [weights](https://rndml-team-cv.obs.ru-moscow-1.hc.sbercloud.ru/datasets/slovo/models/mvit/onnx/mvit48-2.onnx) | [weights](https://rndml-team-cv.obs.ru-moscow-1.hc.sbercloud.ru/datasets/slovo/models/mvit/pt/mvit48-2.pt) |
51 | | Swin-large-16-3 | 821.65 | 48.04 | [weights](https://rndml-team-cv.obs.ru-moscow-1.hc.sbercloud.ru/datasets/slovo/models/swin/onnx/swin16-3.onnx) | [weights](https://rndml-team-cv.obs.ru-moscow-1.hc.sbercloud.ru/datasets/slovo/models/swin/pt/swin16-3.pt) |
52 | | Swin-large-32-2 | 821.74 | 54.84 | [weights](https://rndml-team-cv.obs.ru-moscow-1.hc.sbercloud.ru/datasets/slovo/models/swin/onnx/swin32-2.onnx) | [weights](https://rndml-team-cv.obs.ru-moscow-1.hc.sbercloud.ru/datasets/slovo/models/swin/pt/swin32-2.pt) |
53 | | Swin-large-48-1 | 821.78 | 55.66 | [weights](https://rndml-team-cv.obs.ru-moscow-1.hc.sbercloud.ru/datasets/slovo/models/swin/onnx/swin48-1.onnx) | [weights](https://rndml-team-cv.obs.ru-moscow-1.hc.sbercloud.ru/datasets/slovo/models/swin/pt/swin48-1.pt) |
54 | | ResNet-i3d-16-3 | 146.43 | 32.86 | [weights](https://rndml-team-cv.obs.ru-moscow-1.hc.sbercloud.ru/datasets/slovo/models/resnet/onnx/resnet16-3.onnx) | [weights](https://rndml-team-cv.obs.ru-moscow-1.hc.sbercloud.ru/datasets/slovo/models/resnet/pt/resnet16-3.pt) |
55 | | ResNet-i3d-32-2 | 146.43 | 38.38 | [weights](https://rndml-team-cv.obs.ru-moscow-1.hc.sbercloud.ru/datasets/slovo/models/resnet/onnx/resnet32-2.onnx) | [weights](https://rndml-team-cv.obs.ru-moscow-1.hc.sbercloud.ru/datasets/slovo/models/resnet/pt/resnet32-2.pt) |
56 | | ResNet-i3d-48-1 | 146.43 | 43.91 | [weights](https://rndml-team-cv.obs.ru-moscow-1.hc.sbercloud.ru/datasets/slovo/models/resnet/onnx/resnet48-1.onnx) | [weights](https://rndml-team-cv.obs.ru-moscow-1.hc.sbercloud.ru/datasets/slovo/models/resnet/pt/resnet48-1.pt) |
57 |
58 | ## SignFlow models
59 |
60 | | Model Name | Desc | ONNX | Params |
61 | |------------|---------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------|--------|
62 | | SignFlow-A | **63.3 Top-1** Acc on [WLASL-2000](https://paperswithcode.com/sota/sign-language-recognition-on-wlasl-2000) (SOTA) | [weights](https://rndml-team-cv.obs.ru-moscow-1.hc.sbercloud.ru/datasets/slovo/models/SignFlow-A.onnx) | 36M |
63 | | SignFlow-R | Pre-trained on **~50000** samples, has **267** classes, tested with GigaChat (as-is and context-based modes) | [weights](https://rndml-team-cv.obs.ru-moscow-1.hc.sbercloud.ru/datasets/slovo/models/SignFlow-R.onnx) | 37M |
64 |
65 |
66 | ## Demo
67 | ```console
68 | usage: demo.py [-h] -p CONFIG [--mp] [-v] [-l LENGTH]
69 |
70 | optional arguments:
71 | -h, --help show this help message and exit
72 | -p CONFIG, --config CONFIG
73 | Path to config
74 | --mp Enable multiprocessing
75 | -v, --verbose Enable logging
76 | -l LENGTH, --length LENGTH
77 | Deque length for predictions
78 |
79 |
80 | python demo.py -p
81 | ```
82 |
83 | 
84 |
85 | ## Authors and Credits
86 | - [Kapitanov Alexander](https://www.linkedin.com/in/hukenovs)
87 | - [Kvanchiani Karina](https://www.linkedin.com/in/kvanchiani)
88 | - [Nagaev Alexander](https://www.linkedin.com/in/nagadit/)
89 | - [Petrova Elizaveta](https://www.linkedin.com/in/elizaveta-petrova-248135263/)
90 |
91 | ## Citation
92 | You can cite the paper using the following BibTeX entry:
93 |
94 | @inproceedings{kapitanov2023slovo,
95 | title={Slovo: Russian Sign Language Dataset},
96 | author={Kapitanov, Alexander and Karina, Kvanchiani and Nagaev, Alexander and Elizaveta, Petrova},
97 | booktitle={International Conference on Computer Vision Systems},
98 | pages={63--73},
99 | year={2023},
100 | organization={Springer}
101 | }
102 |
103 | ## Links
104 | - [arXiv](https://arxiv.org/abs/2305.14527)
105 | - [Kaggle](https://www.kaggle.com/datasets/kapitanov/slovo)
106 | - [Habr](https://habr.com/ru/companies/sberdevices/articles/737018/)
107 | - [Medium](https://medium.com/@nagadit/slovo-russian-sign-language-dataset-a8a8bd6fa17d)
108 | - [Github](https://github.com/hukenovs/slovo)
109 | - [Gitlab](https://gitlab.aicloud.sbercloud.ru/rndcv/slovo)
110 | - [Paperswithcode](https://paperswithcode.com/paper/slovo-russian-sign-language-dataset)
111 |
112 | ## License
113 | 
This work is licensed under a variant of Creative Commons Attribution-ShareAlike 4.0 International License.
114 |
115 | Please see the specific [license](https://github.com/hukenovs/slovo/blob/master/license/en_us.pdf).
116 |
--------------------------------------------------------------------------------
/demo.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import sys
3 | import time
4 | from collections import deque
5 | from multiprocessing import Manager, Process, Value
6 | from typing import Optional, Tuple
7 |
8 | import onnxruntime as ort
9 | from loguru import logger
10 |
11 | ort.set_default_logger_severity(4) # NOQA
12 | logger.add(sys.stdout, format="{level} | {message}") # NOQA
13 | logger.remove(0) # NOQA
14 | import cv2
15 | import numpy as np
16 | from omegaconf import OmegaConf
17 |
18 | from constants import classes
19 |
20 |
21 | class BaseRecognition:
22 | def __init__(self, model_path: str, tensors_list, prediction_list, verbose):
23 | self.verbose = verbose
24 | self.started = None
25 | self.output_names = None
26 | self.input_shape = None
27 | self.input_name = None
28 | self.session = None
29 | self.model_path = model_path
30 | self.window_size = None
31 | self.tensors_list = tensors_list
32 | self.prediction_list = prediction_list
33 |
34 | def clear_tensors(self):
35 | """
36 | Clear the list of tensors.
37 | """
38 | for _ in range(self.window_size):
39 | self.tensors_list.pop(0)
40 |
41 | def run(self):
42 | """
43 | Run the recognition model.
44 | """
45 | if self.session is None:
46 | self.session = ort.InferenceSession(self.model_path)
47 | self.input_name = self.session.get_inputs()[0].name
48 | self.input_shape = self.session.get_inputs()[0].shape
49 | self.window_size = self.input_shape[3]
50 | self.output_names = [output.name for output in self.session.get_outputs()]
51 |
52 | if len(self.tensors_list) >= self.input_shape[3]:
53 | input_tensor = np.stack(self.tensors_list[: self.window_size], axis=1)[None][None]
54 | st = time.time()
55 | outputs = self.session.run(self.output_names, {self.input_name: input_tensor.astype(np.float32)})[0]
56 | et = round(time.time() - st, 3)
57 | gloss = str(classes[outputs.argmax()])
58 | if gloss != self.prediction_list[-1] and len(self.prediction_list):
59 | if gloss != "---":
60 | self.prediction_list.append(gloss)
61 | self.clear_tensors()
62 | if self.verbose:
63 | logger.info(f"- Prediction time {et}, new gloss: {gloss}")
64 | logger.info(f" --- {len(self.tensors_list)} frames in queue")
65 |
66 | def kill(self):
67 | pass
68 |
69 |
70 | class Recognition(BaseRecognition):
71 | def __init__(self, model_path: str, tensors_list: list, prediction_list: list, verbose: bool):
72 | """
73 | Initialize recognition model.
74 |
75 | Parameters
76 | ----------
77 | model_path : str
78 | Path to the model.
79 | tensors_list : List
80 | List of tensors to be used for prediction.
81 | prediction_list : List
82 | List of predictions.
83 |
84 | Notes
85 | -----
86 | The recognition model is run in a separate process.
87 | """
88 | super().__init__(
89 | model_path=model_path, tensors_list=tensors_list, prediction_list=prediction_list, verbose=verbose
90 | )
91 | self.started = True
92 |
93 | def start(self):
94 | self.run()
95 |
96 |
97 | class RecognitionMP(Process, BaseRecognition):
98 | def __init__(self, model_path: str, tensors_list, prediction_list, verbose):
99 | """
100 | Initialize recognition model.
101 |
102 | Parameters
103 | ----------
104 | model_path : str
105 | Path to the model.
106 | tensors_list : Manager.list
107 | List of tensors to be used for prediction.
108 | prediction_list : Manager.list
109 | List of predictions.
110 |
111 | Notes
112 | -----
113 | The recognition model is run in a separate process.
114 | """
115 | super().__init__()
116 | BaseRecognition.__init__(
117 | self, model_path=model_path, tensors_list=tensors_list, prediction_list=prediction_list, verbose=verbose
118 | )
119 | self.started = Value("i", False)
120 |
121 | def run(self):
122 | while True:
123 | BaseRecognition.run(self)
124 | self.started = True
125 |
126 |
127 | class Runner:
128 | STACK_SIZE = 6
129 |
130 | def __init__(
131 | self,
132 | model_path: str,
133 | config: OmegaConf = None,
134 | mp: bool = False,
135 | verbose: bool = False,
136 | length: int = STACK_SIZE,
137 | ) -> None:
138 | """
139 | Initialize runner.
140 |
141 | Parameters
142 | ----------
143 | model_path : str
144 | Path to the model.
145 | config : OmegaConf
146 | Configuration file.
147 | length : int
148 | Deque length for predictions
149 |
150 | Notes
151 | -----
152 | The runner uses multiprocessing to run the recognition model in a separate process.
153 |
154 | """
155 | self.multiprocess = mp
156 | self.cap = cv2.VideoCapture(0)
157 | self.manager = Manager() if self.multiprocess else None
158 | self.tensors_list = self.manager.list() if self.multiprocess else []
159 | self.prediction_list = self.manager.list() if self.multiprocess else []
160 | self.prediction_list.append("---")
161 | self.frame_counter = 0
162 | self.frame_interval = config.frame_interval
163 | self.length = length
164 | self.prediction_classes = deque(maxlen=length)
165 | self.mean = config.mean
166 | self.std = config.std
167 | if self.multiprocess:
168 | self.recognizer = RecognitionMP(model_path, self.tensors_list, self.prediction_list, verbose)
169 | else:
170 | self.recognizer = Recognition(model_path, self.tensors_list, self.prediction_list, verbose)
171 |
172 | def add_frame(self, image):
173 | """
174 | Add frame to queue.
175 |
176 | Parameters
177 | ----------
178 | image : np.ndarray
179 | Frame to be added.
180 | """
181 | self.frame_counter += 1
182 | if self.frame_counter == self.frame_interval:
183 | image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
184 | image = self.resize(image, (224, 224))
185 | image = (image - self.mean) / self.std
186 | image = np.transpose(image, [2, 0, 1])
187 | self.tensors_list.append(image)
188 | self.frame_counter = 0
189 |
190 | @staticmethod
191 | def resize(im, new_shape=(224, 224)):
192 | """
193 | Resize and pad image while preserving aspect ratio.
194 |
195 | Parameters
196 | ----------
197 | im : np.ndarray
198 | Image to be resized.
199 | new_shape : Tuple[int]
200 | Size of the new image.
201 |
202 | Returns
203 | -------
204 | np.ndarray
205 | Resized image.
206 | """
207 | shape = im.shape[:2] # current shape [height, width]
208 | if isinstance(new_shape, int):
209 | new_shape = (new_shape, new_shape)
210 |
211 | # Scale ratio (new / old)
212 | r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
213 |
214 | # Compute padding
215 | new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
216 | dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] # wh padding
217 |
218 | dw /= 2
219 | dh /= 2
220 |
221 | if shape[::-1] != new_unpad: # resize
222 | im = cv2.resize(im, new_unpad, interpolation=cv2.INTER_LINEAR)
223 | top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
224 | left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
225 | im = cv2.copyMakeBorder(im, top, bottom, left, right, cv2.BORDER_CONSTANT, value=(114, 114, 114)) # add border
226 | return im
227 |
228 | def run(self):
229 | """
230 | Run the runner.
231 |
232 | Notes
233 | -----
234 | The runner will run until the user presses 'q'.
235 | """
236 | if self.multiprocess:
237 | self.recognizer.start()
238 |
239 | while self.cap.isOpened():
240 | if self.recognizer.started:
241 | _, frame = self.cap.read()
242 | text_div = np.zeros((50, frame.shape[1], 3), dtype=np.uint8)
243 | self.add_frame(frame)
244 |
245 | if not self.multiprocess:
246 | self.recognizer.start()
247 |
248 | if self.prediction_list:
249 | text = " ".join(self.prediction_list)
250 | cv2.putText(text_div, text, (10, 30), cv2.FONT_HERSHEY_COMPLEX, 0.7, (255, 255, 255), 2)
251 |
252 | if len(self.prediction_list) > self.length:
253 | self.prediction_list.pop(0)
254 |
255 | frame = np.concatenate((frame, text_div), axis=0)
256 | cv2.imshow("frame", frame)
257 | condition = cv2.waitKey(10) & 0xFF
258 | if condition in {ord("q"), ord("Q"), 27}:
259 | if self.multiprocess:
260 | self.recognizer.kill()
261 | self.cap.release()
262 | cv2.destroyAllWindows()
263 | break
264 |
265 |
266 | def parse_arguments(params: Optional[Tuple] = None) -> argparse.Namespace:
267 | parser = argparse.ArgumentParser(description="Demo full frame classification...")
268 |
269 | parser.add_argument("-p", "--config", required=True, type=str, help="Path to config")
270 | parser.add_argument("--mp", required=False, action="store_true", help="Enable multiprocessing")
271 | parser.add_argument("-v", "--verbose", required=False, action="store_true", help="Enable logging")
272 | parser.add_argument("-l", "--length", required=False, type=int, default=4, help="Deque length for predictions")
273 |
274 | known_args, _ = parser.parse_known_args(params)
275 | return known_args
276 |
277 |
278 | if __name__ == "__main__":
279 | args = parse_arguments()
280 | conf = OmegaConf.load(args.config)
281 | runner = Runner(conf.model_path, conf, args.mp, args.verbose, args.length)
282 | runner.run()
283 |
--------------------------------------------------------------------------------
/constants.py:
--------------------------------------------------------------------------------
1 | classes = {
2 | 0: "Ё",
3 | 1: "А",
4 | 2: "Р",
5 | 3: "Е",
6 | 4: "Ч",
7 | 5: "Л",
8 | 6: "Ц",
9 | 7: "С",
10 | 8: "Й",
11 | 9: "З",
12 | 10: "Ь",
13 | 11: "Я",
14 | 12: "Б",
15 | 13: "Щ",
16 | 14: "У",
17 | 15: "Ы",
18 | 16: "Д",
19 | 17: "Ф",
20 | 18: "Т",
21 | 19: "Ъ",
22 | 20: "О",
23 | 21: "М",
24 | 22: "П",
25 | 23: "Х",
26 | 24: "Э",
27 | 25: "Н",
28 | 26: "В",
29 | 27: "Ю",
30 | 28: "Ш",
31 | 29: "Ж",
32 | 30: "Г",
33 | 31: "К",
34 | 32: "И",
35 | 33: "козел",
36 | 34: "гнездо",
37 | 35: "вой",
38 | 36: "креветка",
39 | 37: "бегемот",
40 | 38: "нашествие",
41 | 39: "лиса",
42 | 40: "динозавр",
43 | 41: "перо",
44 | 42: "крокодил",
45 | 43: "летучая мышь",
46 | 44: "дикое животное",
47 | 45: "попугай",
48 | 46: "ежик",
49 | 47: "волк",
50 | 48: "овца",
51 | 49: "петух",
52 | 50: "белка",
53 | 51: "пингвин",
54 | 52: "помесь",
55 | 53: "осел",
56 | 54: "дятел",
57 | 55: "воротник",
58 | 56: "аллигатор",
59 | 57: "кошка",
60 | 58: "рога",
61 | 59: "лошадь",
62 | 60: "жираф",
63 | 61: "вол",
64 | 62: "населять",
65 | 63: "верблюд",
66 | 64: "коза",
67 | 65: "ползанье",
68 | 66: "бабочка",
69 | 67: "клюв",
70 | 68: "собака",
71 | 69: "кролик",
72 | 70: "паук",
73 | 71: "водоплавающее",
74 | 72: "носорог",
75 | 73: "мышь",
76 | 74: "зверь",
77 | 75: "плавник",
78 | 76: "паучья сеть",
79 | 77: "животное",
80 | 78: "лев",
81 | 79: "тигр",
82 | 80: "миграция",
83 | 81: "копыто",
84 | 82: "зубы",
85 | 83: "коготь",
86 | 84: "рыба",
87 | 85: "лось",
88 | 86: "пчела",
89 | 87: "птица",
90 | 88: "комар",
91 | 89: "змея",
92 | 90: "гавканье",
93 | 91: "бык",
94 | 92: "зоопарк",
95 | 93: "клевать",
96 | 94: "акула",
97 | 95: "рычать",
98 | 96: "панда",
99 | 97: "крыса",
100 | 98: "олень",
101 | 99: "делать выводы",
102 | 100: "орел",
103 | 101: "лебедь",
104 | 102: "рычание",
105 | 103: "медведь",
106 | 104: "слон",
107 | 105: "дельфин",
108 | 106: "павлин",
109 | 107: "вилять",
110 | 108: "собачий домик",
111 | 109: "домашнее животное",
112 | 110: "курица",
113 | 111: "медуза",
114 | 112: "клетка",
115 | 113: "свинья",
116 | 114: "лягушка",
117 | 115: "голова",
118 | 116: "корм для домашних животных",
119 | 117: "корова",
120 | 118: "обезьяна",
121 | 119: "соты",
122 | 120: "крыло",
123 | 121: "заяц",
124 | 122: "прирученный",
125 | 123: "поверхностный",
126 | 124: "интеллектуальный",
127 | 125: "застенчивый",
128 | 126: "добрый",
129 | 127: "истерик",
130 | 128: "аниматор",
131 | 129: "кротость",
132 | 130: "адаптивный",
133 | 131: "амбициозный",
134 | 132: "проныра",
135 | 133: "скучный",
136 | 134: "гордый",
137 | 135: "одаренный",
138 | 136: "прагматичный",
139 | 137: "испорченный",
140 | 138: "нежный",
141 | 139: "любопытный",
142 | 140: "гений",
143 | 141: "рассеянный",
144 | 142: "непослушный",
145 | 143: "наивный",
146 | 144: "острый",
147 | 145: "вежливый",
148 | 146: "отличный",
149 | 147: "смелость",
150 | 148: "правша",
151 | 149: "разумный",
152 | 150: "ленивый",
153 | 151: "детский",
154 | 152: "ребяческий",
155 | 153: "стойкость",
156 | 154: "уверенный",
157 | 155: "обычный",
158 | 156: "миленький",
159 | 157: "остроумие",
160 | 158: "послушный",
161 | 159: "невиновный",
162 | 160: "опытный",
163 | 161: "инициатива",
164 | 162: "тщеславный",
165 | 163: "доминантный",
166 | 164: "наблюдательность",
167 | 165: "сильный",
168 | 166: "вредный; озорной",
169 | 167: "неловкий; неуклюжий",
170 | 168: "верный",
171 | 169: "особенный",
172 | 170: "мятежный",
173 | 171: "жестокий",
174 | 172: "зрелищный",
175 | 173: "строгий",
176 | 174: "злобный",
177 | 175: "нерадивый",
178 | 176: "логичный",
179 | 177: "торжественный",
180 | 178: "враждебный",
181 | 179: "получающий удовольствие",
182 | 180: "потрясающий",
183 | 181: "доминирующий",
184 | 182: "жадный",
185 | 183: "угрюмый",
186 | 184: "трус",
187 | 185: "жёсткий",
188 | 186: "подлинный",
189 | 187: "милосердный",
190 | 188: "модный",
191 | 189: "осторожный",
192 | 190: "неудобно",
193 | 191: "недобрый",
194 | 192: "красивый",
195 | 193: "жадность",
196 | 194: "высокомерный",
197 | 195: "приличный",
198 | 196: "искренний",
199 | 197: "аккуратный",
200 | 198: "лицемерный",
201 | 199: "положительный",
202 | 200: "грубый",
203 | 201: "ответственный",
204 | 202: "отвратительный",
205 | 203: "странный",
206 | 204: "глупый",
207 | 205: "адаптивное поведение",
208 | 206: "отрицательный",
209 | 207: "оживленный",
210 | 208: "адаптивность",
211 | 209: "объективный",
212 | 210: "агрессивный",
213 | 211: "предусмотрительный",
214 | 212: "недовольный",
215 | 213: "мудрый",
216 | 214: "причинять резкую боль",
217 | 215: "талант",
218 | 216: "мужественность",
219 | 217: "неприятный",
220 | 218: "смешной",
221 | 219: "запасливый",
222 | 220: "милый",
223 | 221: "трусливо",
224 | 222: "образованный",
225 | 223: "веселый",
226 | 224: "дружелюбный",
227 | 225: "нахальный",
228 | 226: "личные качества",
229 | 227: "беспокоящий",
230 | 228: "наглость",
231 | 229: "зло",
232 | 230: "продуманный",
233 | 231: "активный",
234 | 232: "брюзжать",
235 | 233: "скромный",
236 | 234: "оранжевый",
237 | 235: "основной цвет",
238 | 236: "бледный",
239 | 237: "тёмный",
240 | 238: "серебро",
241 | 239: "нераскрашенный",
242 | 240: "розовый",
243 | 241: "цветовой оттенок",
244 | 242: "прозрачный",
245 | 243: "оттенок",
246 | 244: "коричневый",
247 | 245: "бежевый",
248 | 246: "непрозрачность",
249 | 247: "цвет",
250 | 248: "красноватый",
251 | 249: "фиолетовый",
252 | 250: "желтый",
253 | 251: "темнота",
254 | 252: "красный",
255 | 253: "золотой",
256 | 254: "черный",
257 | 255: "розовато-лиловый",
258 | 256: "серый",
259 | 257: "синий",
260 | 258: "зелёный",
261 | 259: "белый",
262 | 260: "грустный",
263 | 261: "подавленный",
264 | 262: "ошеломить",
265 | 263: "нервный",
266 | 264: "напряжение",
267 | 265: "преданный",
268 | 266: "мурашки",
269 | 267: "облегчение",
270 | 268: "ожидать",
271 | 269: "зависть",
272 | 270: "жажда",
273 | 271: "слабый",
274 | 272: "отношение",
275 | 273: "отчаяться",
276 | 274: "печаль",
277 | 275: "озабоченный",
278 | 276: "страх",
279 | 277: "испуганный",
280 | 278: "напуганный",
281 | 279: "разрыдаться",
282 | 280: "злость",
283 | 281: "отвращение",
284 | 282: "сочувствие",
285 | 283: "неудобство",
286 | 284: "самопроверка",
287 | 285: "быть потрясенным",
288 | 286: "доверять",
289 | 287: "скорбеть",
290 | 288: "смущенный",
291 | 289: "желать",
292 | 290: "вскружить",
293 | 291: "самоанализ",
294 | 292: "желание",
295 | 293: "импульс",
296 | 294: "презрение",
297 | 295: "разъяренный",
298 | 296: "самовосприятие",
299 | 297: "беспокоиться",
300 | 298: "возрадоваться",
301 | 299: "подозревать",
302 | 300: "терпеть",
303 | 301: "влюбиться",
304 | 302: "восхищаться",
305 | 303: "нравиться",
306 | 304: "злой",
307 | 305: "расстроенный",
308 | 306: "расслабление",
309 | 307: "жуткий",
310 | 308: "не выносить",
311 | 309: "спокойный",
312 | 310: "гнев",
313 | 311: "сарказм",
314 | 312: "отважиться",
315 | 313: "сожалеть",
316 | 314: "ленность",
317 | 315: "расслабиться",
318 | 316: "интересный",
319 | 317: "самоосознание",
320 | 318: "весенняя",
321 | 319: "ненавидеть",
322 | 320: "доброта",
323 | 321: "отчаянный",
324 | 322: "сомнение",
325 | 323: "страшный",
326 | 324: "покаяние",
327 | 325: "испытывать",
328 | 326: "счастливый",
329 | 327: "испуг",
330 | 328: "предпочитать",
331 | 329: "дистресс",
332 | 330: "возбуждение",
333 | 331: "несчастье",
334 | 332: "довольный",
335 | 333: "благодарность",
336 | 334: "голод",
337 | 335: "изношенный",
338 | 336: "счастье",
339 | 337: "радость",
340 | 338: "страдать",
341 | 339: "мотивировать",
342 | 340: "вдохновлять",
343 | 341: "непреклонный",
344 | 342: "рассердиться",
345 | 343: "жалость",
346 | 344: "безумно",
347 | 345: "безразличие",
348 | 346: "доставлять удовольствие",
349 | 347: "изможденный",
350 | 348: "добропорядочность",
351 | 349: "любовный",
352 | 350: "позор",
353 | 351: "забота",
354 | 352: "горе",
355 | 353: "влюбленный",
356 | 354: "рыдать",
357 | 355: "вздрагивать",
358 | 356: "самоконтроль",
359 | 357: "ошарашенный",
360 | 358: "неверие",
361 | 359: "одержимый",
362 | 360: "вдохновение",
363 | 361: "самообман",
364 | 362: "наслаждайся",
365 | 363: "возбужденный",
366 | 364: "любить",
367 | 365: "терпение",
368 | 366: "обнимать",
369 | 367: "горький",
370 | 368: "презирать",
371 | 369: "беспокойство",
372 | 370: "расстройство",
373 | 371: "надоело",
374 | 372: "слабость",
375 | 373: "покраснеть",
376 | 374: "голодный",
377 | 375: "разгневанный",
378 | 376: "ревнивый",
379 | 377: "раздражать",
380 | 378: "смириться",
381 | 379: "обманчивость",
382 | 380: "самомнение",
383 | 381: "жаждущий",
384 | 382: "возбуждать",
385 | 383: "надеяться",
386 | 384: "беспокойный",
387 | 385: "мотивация",
388 | 386: "пренебрежение",
389 | 387: "благоговение",
390 | 388: "суматоха",
391 | 389: "горевать",
392 | 390: "неуважение",
393 | 391: "сомневаться",
394 | 392: "нравственность",
395 | 393: "горюющий",
396 | 394: "надежда",
397 | 395: "одинокий",
398 | 396: "довольство",
399 | 397: "агрессия",
400 | 398: "страдание",
401 | 399: "благополучие",
402 | 400: "любовь",
403 | 401: "боль",
404 | 402: "скучающий",
405 | 403: "домогаться",
406 | 404: "разочарованный",
407 | 405: "дрожать",
408 | 406: "так себе",
409 | 407: "стыдно",
410 | 408: "вина",
411 | 409: "весёлый",
412 | 410: "страсть",
413 | 411: "радостный",
414 | 412: "объятие",
415 | 413: "сожаление",
416 | 414: "обеспокоенный",
417 | 415: "рвеность",
418 | 416: "гордость",
419 | 417: "внутреннее",
420 | 418: "одновременность",
421 | 419: "восхищение",
422 | 420: "поднос",
423 | 421: "кафе",
424 | 422: "лить",
425 | 423: "откупоривать",
426 | 424: "есть",
427 | 425: "закуска перед едой",
428 | 426: "кормить",
429 | 427: "окунать",
430 | 428: "бутылка",
431 | 429: "жевать",
432 | 430: "вкусный",
433 | 431: "жадно есть",
434 | 432: "переваривать",
435 | 433: "MakDonalds",
436 | 434: "предложить тост",
437 | 435: "аллергия на еду",
438 | 436: "откусывание",
439 | 437: "ланч",
440 | 438: "помощник повара",
441 | 439: "кубок",
442 | 440: "плата за вход",
443 | 441: "резервирование; бронирование",
444 | 442: "аппетит",
445 | 443: "кафетерий",
446 | 444: "нежный, мягкий",
447 | 445: "пир",
448 | 446: "прием пищи",
449 | 447: "обедать",
450 | 448: "кусок; тонкими слоями",
451 | 449: "пить",
452 | 450: "кружка",
453 | 451: "кусать",
454 | 452: "вилка",
455 | 453: "делать мелкий глоток",
456 | 454: "ароматный",
457 | 455: "банкет",
458 | 456: "заварочный чайник",
459 | 457: "глотать",
460 | 458: "кувшин",
461 | 459: "ресторан",
462 | 460: "переваривание",
463 | 461: "глотать жадно",
464 | 462: "официант",
465 | 463: "заказывать",
466 | 464: "женский туалет",
467 | 465: "повар",
468 | 466: "мужской туалет",
469 | 467: "накрывать на стол",
470 | 468: "вкус",
471 | 469: "место для пикника",
472 | 470: "диета",
473 | 471: "питьевая вода",
474 | 472: "Прощание",
475 | 473: "С днем рождения",
476 | 474: "Пока",
477 | 475: "Позвонить на сервис",
478 | 476: "Привет!",
479 | 477: "Добро пожаловать!",
480 | 478: "слово",
481 | 479: "пока (что)",
482 | 480: "верить",
483 | 481: "иначе",
484 | 482: "искать",
485 | 483: "чемодан",
486 | 484: "слышать",
487 | 485: "вред",
488 | 486: "десять",
489 | 487: "почва",
490 | 488: "сто",
491 | 489: "достаточно",
492 | 490: "пустой",
493 | 491: "охотно",
494 | 492: "значить",
495 | 493: "собирать",
496 | 494: "такой",
497 | 495: "между",
498 | 496: "карандаш",
499 | 497: "маленький",
500 | 498: "готовый",
501 | 499: "восемь",
502 | 500: "сказать",
503 | 501: "восток",
504 | 502: "иностранный",
505 | 503: "платить",
506 | 504: "дочь",
507 | 505: "неделя",
508 | 506: "семнадцать",
509 | 507: "не",
510 | 508: "двенадцатый",
511 | 509: "писать",
512 | 510: "целый",
513 | 511: "часы",
514 | 512: "конечно",
515 | 513: "звук",
516 | 514: "использовать",
517 | 515: "быстрый",
518 | 516: "семья",
519 | 517: "четверг",
520 | 518: "мужчина",
521 | 519: "потом",
522 | 520: "длинный",
523 | 521: "отец",
524 | 522: "ручка",
525 | 523: "оба",
526 | 524: "там",
527 | 525: "тоже",
528 | 526: "часть",
529 | 527: "десятый",
530 | 528: "дом",
531 | 529: "новости",
532 | 530: "лежать",
533 | 531: "еще",
534 | 532: "медленный",
535 | 533: "семь",
536 | 534: "возможный",
537 | 535: "женщина",
538 | 536: "рот",
539 | 537: "муж",
540 | 538: "жёлтый",
541 | 539: "среда",
542 | 540: "хорошо",
543 | 541: "налог",
544 | 542: "смеяться",
545 | 543: "магазин",
546 | 544: "другой",
547 | 545: "путешествовать",
548 | 546: "причина",
549 | 547: "дешёвый",
550 | 548: "книга",
551 | 549: "еда",
552 | 550: "шесть",
553 | 551: "направление",
554 | 552: "идти",
555 | 553: "комната",
556 | 554: "вчера",
557 | 555: "один",
558 | 556: "срочный",
559 | 557: "известный",
560 | 558: "двенадцать",
561 | 559: "низкий",
562 | 560: "пятый",
563 | 561: "везде",
564 | 562: "мы",
565 | 563: "двадцать",
566 | 564: "ноль",
567 | 565: "должен",
568 | 566: "почему",
569 | 567: "девочка",
570 | 568: "мир",
571 | 569: "буду",
572 | 570: "пятьдесят",
573 | 571: "смотреть",
574 | 572: "месяц",
575 | 573: "короткий",
576 | 574: "необходимый",
577 | 575: "с",
578 | 576: "если",
579 | 577: "полный",
580 | 578: "важный",
581 | 579: "без",
582 | 580: "человек",
583 | 581: "немного",
584 | 582: "завтра",
585 | 583: "большой",
586 | 584: "всегда",
587 | 585: "молодой",
588 | 586: "вниз",
589 | 587: "стоять",
590 | 588: "сестра",
591 | 589: "ночь",
592 | 590: "билет",
593 | 591: "плакать",
594 | 592: "имя",
595 | 593: "или",
596 | 594: "свободный",
597 | 595: "мальчик",
598 | 596: "говорить",
599 | 597: "утро",
600 | 598: "лето",
601 | 599: "погода",
602 | 600: "путешествие",
603 | 601: "обучать",
604 | 602: "пять",
605 | 603: "сумка",
606 | 604: "ребёнок",
607 | 605: "север",
608 | 606: "час",
609 | 607: "очень",
610 | 608: "место",
611 | 609: "все",
612 | 610: "ужасный",
613 | 611: "держать",
614 | 612: "одежда",
615 | 613: "точно",
616 | 614: "опасный",
617 | 615: "седьмой",
618 | 616: "много",
619 | 617: "второй",
620 | 618: "различный",
621 | 619: "тридцать",
622 | 620: "трудный",
623 | 621: "два",
624 | 622: "лучший",
625 | 623: "одиннадцатый",
626 | 624: "девятый",
627 | 625: "быстро",
628 | 626: "люди",
629 | 627: "чувствовать",
630 | 628: "письмо",
631 | 629: "мочь",
632 | 630: "друг",
633 | 631: "назад",
634 | 632: "шестьдесят",
635 | 633: "улица",
636 | 634: "уже",
637 | 635: "правый",
638 | 636: "сердце",
639 | 637: "дорога",
640 | 638: "высокий",
641 | 639: "слушать",
642 | 640: "кроме",
643 | 641: "цена",
644 | 642: "восьмой",
645 | 643: "народ",
646 | 644: "твердый",
647 | 645: "мне",
648 | 646: "тебе",
649 | 647: "мертвый",
650 | 648: "твой",
651 | 649: "сын",
652 | 650: "девяносто",
653 | 651: "хлеб",
654 | 652: "четвертый",
655 | 653: "сегодня",
656 | 654: "брат",
657 | 655: "наконец",
658 | 656: "постепенно",
659 | 657: "рука",
660 | 658: "весна",
661 | 659: "хороший",
662 | 660: "тот",
663 | 661: "шум",
664 | 662: "вода",
665 | 663: "следующий",
666 | 664: "шестнадцать",
667 | 665: "безопасный",
668 | 666: "несколько",
669 | 667: "тысяча",
670 | 668: "нас",
671 | 669: "только",
672 | 670: "воздух",
673 | 671: "одиннадцать",
674 | 672: "восемьдесят",
675 | 673: "последний",
676 | 674: "картина",
677 | 675: "газета",
678 | 676: "сторона",
679 | 677: "способ",
680 | 678: "переводить",
681 | 679: "ждать",
682 | 680: "особенно",
683 | 681: "довольно",
684 | 682: "бумага",
685 | 683: "вероятно",
686 | 684: "шестой",
687 | 685: "обычно",
688 | 686: "пятница",
689 | 687: "под",
690 | 688: "тот же самый (одинаковый)",
691 | 689: "посылать",
692 | 690: "четырнадцать",
693 | 691: "с тех пор как",
694 | 692: "вещь",
695 | 693: "понять",
696 | 694: "девятнадцать",
697 | 695: "читать",
698 | 696: "невозможный",
699 | 697: "юг",
700 | 698: "делать",
701 | 699: "перед",
702 | 700: "возможно",
703 | 701: "понедельник",
704 | 702: "жизнь",
705 | 703: "голос",
706 | 704: "грязный",
707 | 705: "опять",
708 | 706: "чистый",
709 | 707: "три",
710 | 708: "вопрос",
711 | 709: "ничего",
712 | 710: "город",
713 | 711: "ты",
714 | 712: "часто",
715 | 713: "против",
716 | 714: "их",
717 | 715: "вечер",
718 | 716: "ошибка",
719 | 717: "прибывать",
720 | 718: "я",
721 | 719: "это",
722 | 720: "осень",
723 | 721: "наш",
724 | 722: "школа",
725 | 723: "вторник",
726 | 724: "здесь",
727 | 725: "веселиться",
728 | 726: "глаз",
729 | 727: "ключ",
730 | 728: "сзади",
731 | 729: "старый",
732 | 730: "первый",
733 | 731: "дорогой",
734 | 732: "солнце",
735 | 733: "что",
736 | 734: "когда",
737 | 735: "мой",
738 | 736: "плохой",
739 | 737: "четыре",
740 | 738: "язык",
741 | 739: "левый",
742 | 740: "третий",
743 | 741: "лучше",
744 | 742: "видеть",
745 | 743: "скоро",
746 | 744: "деньги",
747 | 745: "жена",
748 | 746: "сверху",
749 | 747: "худший",
750 | 748: "суббота",
751 | 749: "никогда",
752 | 750: "воскресенье",
753 | 751: "год",
754 | 752: "новый",
755 | 753: "зима",
756 | 754: "никто",
757 | 755: "время",
758 | 756: "работать",
759 | 757: "восемнадцать",
760 | 758: "иногда",
761 | 759: "однажды",
762 | 760: "кто?",
763 | 761: "до",
764 | 762: "пятнадцать",
765 | 763: "после",
766 | 764: "семьдесят",
767 | 765: "минута",
768 | 766: "Плохо",
769 | 767: "Даже",
770 | 768: "Забрать",
771 | 769: "Лицо",
772 | 770: "Память",
773 | 771: "Лопата",
774 | 772: "Смех",
775 | 773: "Футбол",
776 | 774: "Выручить",
777 | 775: "Соль",
778 | 776: "Чашка",
779 | 777: "Подробно",
780 | 778: "Корона",
781 | 779: "Рубль",
782 | 780: "Крест",
783 | 781: "Цемент",
784 | 782: "Выигрыш",
785 | 783: "Золото",
786 | 784: "Жарить",
787 | 785: "Пылесос",
788 | 786: "Почки",
789 | 787: "Свисток",
790 | 788: "Чужой",
791 | 789: "Религия",
792 | 790: "Сразу",
793 | 791: "Обладать",
794 | 792: "Борода",
795 | 793: "Витамин",
796 | 794: "Раб",
797 | 795: "остановить",
798 | 796: "из-за",
799 | 797: "чудесный",
800 | 798: "боюсь",
801 | 799: "закончить",
802 | 800: "им",
803 | 801: "эти",
804 | 802: "ваш",
805 | 803: "который",
806 | 804: "немедленно",
807 | 805: "нога",
808 | 806: "ранний",
809 | 807: "следовало",
810 | 808: "остановиться",
811 | 809: "поздний",
812 | 810: "этот",
813 | 811: "значит",
814 | 812: "верх",
815 | 813: "одолжить",
816 | 814: "получить",
817 | 815: "проверить",
818 | 816: "замужем",
819 | 817: "нуждаться",
820 | 818: "открыть",
821 | 819: "подруга",
822 | 820: "остаться",
823 | 821: "ясный",
824 | 822: "минимум",
825 | 823: "тогда",
826 | 824: "над",
827 | 825: "ей",
828 | 826: "близко",
829 | 827: "сюрприз",
830 | 828: "изучить",
831 | 829: "предложить",
832 | 830: "терять",
833 | 831: "теперь",
834 | 832: "прекрасный",
835 | 833: "около",
836 | 834: "что-нибудь",
837 | 835: "взять",
838 | 836: "нам",
839 | 837: "дать",
840 | 838: "положить",
841 | 839: "оставить",
842 | 840: "сколько",
843 | 841: "вас",
844 | 842: "жаркий",
845 | 843: "уронить",
846 | 844: "слишком",
847 | 845: "вы",
848 | 846: "позволить",
849 | 847: "привезти",
850 | 848: "петь",
851 | 849: "вам",
852 | 850: "наверху",
853 | 851: "наружу",
854 | 852: "ехать",
855 | 853: "те",
856 | 854: "от",
857 | 855: "прийти",
858 | 856: "закрыть",
859 | 857: "её",
860 | 858: "всё",
861 | 859: "женат",
862 | 860: "бы",
863 | 861: "показать",
864 | 862: "весь",
865 | 863: "сорок",
866 | 864: "восемьсот",
867 | 865: "пять тысяч",
868 | 866: "три тысячи",
869 | 867: "пятьсот",
870 | 868: "двести",
871 | 869: "четыреста",
872 | 870: "семьсот",
873 | 871: "тринадцать",
874 | 872: "девять",
875 | 873: "девятьсот",
876 | 874: "четыре тысячи",
877 | 875: "две тысячи",
878 | 876: "триста",
879 | 877: "миллион",
880 | 878: "время от 0 ночи до 12 дня",
881 | 879: "вспомнить",
882 | 880: "бояться",
883 | 881: "мыть",
884 | 882: "свет",
885 | 883: "найти",
886 | 884: "обеспокоен",
887 | 885: "запад",
888 | 886: "сердитый",
889 | 887: "жить",
890 | 888: "шутить",
891 | 889: "думать",
892 | 890: "великий",
893 | 891: "сухой",
894 | 892: "спросить",
895 | 893: "спать",
896 | 894: "сначала",
897 | 895: "хотеть",
898 | 896: "знать",
899 | 897: "цвета",
900 | 898: "продолжить",
901 | 899: "холодный",
902 | 900: "да",
903 | 901: "мать",
904 | 902: "доволен",
905 | 903: "легкий",
906 | 904: "привык",
907 | 905: "быть",
908 | 906: "удивлён",
909 | 907: "внизу",
910 | 908: "рад",
911 | 909: "болит",
912 | 910: "согласно",
913 | 911: "купить",
914 | 912: "играть",
915 | 913: "изменить",
916 | 914: "ответить",
917 | 915: "начать",
918 | 916: "сквозь",
919 | 917: "больше",
920 | 918: "упасть",
921 | 919: "вверх",
922 | 920: "приятный",
923 | 921: "тёплый",
924 | 922: "уверен",
925 | 923: "лечь",
926 | 924: "забыть",
927 | 925: "меня",
928 | 926: "принести",
929 | 927: "тебя",
930 | 928: "встретить",
931 | 929: "тяжёлый",
932 | 930: "обещать",
933 | 931: "день",
934 | 932: "кусок",
935 | 933: "помочь",
936 | 934: "продать",
937 | 935: "иметь",
938 | 936: "назначенное время",
939 | 937: "октябрь",
940 | 938: "редко",
941 | 939: "постоянный",
942 | 940: "задерживать",
943 | 941: "канун",
944 | 942: "затем; потом",
945 | 943: "ожидающий",
946 | 944: "запоздалый",
947 | 945: "6 часов",
948 | 946: "поздно",
949 | 947: "недавний",
950 | 948: "в спешке",
951 | 949: "распределять",
952 | 950: "продлевать",
953 | 951: "расписание",
954 | 952: "откладывать",
955 | 953: "немедленный",
956 | 954: "время по Гринвичу",
957 | 955: "день и ночь",
958 | 956: "между тем",
959 | 957: "ноябрь",
960 | 958: "ранее",
961 | 959: "продолжительность",
962 | 960: "ежедневный",
963 | 961: "будущее",
964 | 962: "днем",
965 | 963: "по часам",
966 | 964: "две недели",
967 | 965: "начинать",
968 | 966: "вечный",
969 | 967: "затягивать",
970 | 968: "выходные",
971 | 969: "бывший",
972 | 970: "наконец-то",
973 | 971: "временный",
974 | 972: "восход солнца",
975 | 973: "время для измерений",
976 | 974: "в настоящее время",
977 | 975: "в этом году",
978 | 976: "краткий",
979 | 977: "декабрь",
980 | 978: "календарь",
981 | 979: "7:45",
982 | 980: "еще нет",
983 | 981: "рано",
984 | 982: "август",
985 | 983: "в прошлом году",
986 | 984: "отложить",
987 | 985: "2 часа",
988 | 986: "вечность",
989 | 987: "дважды",
990 | 988: "занятый",
991 | 989: "начало",
992 | 990: "отложенный",
993 | 991: "мгновение",
994 | 992: "неожиданный",
995 | 993: "предыдущий",
996 | 994: "в восемь пятнадцать",
997 | 995: "продление",
998 | 996: "башенные часы",
999 | 997: "в то время как",
1000 | 998: "время 11 часов",
1001 | 999: "навсегда",
1002 | 1000: "---",
1003 | }
1004 |
--------------------------------------------------------------------------------