Распознавание жестового языка: MediaPipe, Transformer, CTC

Разработка AI для распознавания жестового языка Глухие сотрудники на производстве часто остаются без оперативной информации — системы оповещения не адаптированы под жестовый язык. Попытки внедрить готовые переводчики терпят неудачу из-за высокой вариативности жестов и отсутствия поддержки русског

Направления AI-разработки

Часто задаваемые вопросы

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1441
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1301
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    998
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1267
  • image_logo-advance_0.webp
    Разработка логотипа компании B2B Advance
    713
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    1003

Разработка AI для распознавания жестового языка

Глухие сотрудники на производстве часто остаются без оперативной информации — системы оповещения не адаптированы под жестовый язык. Попытки внедрить готовые переводчики терпят неудачу из-за высокой вариативности жестов и отсутствия поддержки русского жестового языка (РЖЯ). Мы создаём кастомные AI-решения, которые решают эту проблему на уровне инженерного пайплайна. Наша компания специализируется на компьютерном зрении и NLP, мы разработали пайплайн, который использует MediaPipe для извлечения ключевых точек и Transformer для классификации, достигая точности до 84% на изолированных знаках и WER до 17% на непрерывной речи. Мы реализовали более 10 проектов по распознаванию жестового языка, накопив более 5 лет опыта.

Почему распознавание непрерывной жестовой речи сложнее изолированных жестов?

Изолированный знак — один жест из словаря (например, HELLO). Continuous Sign Language Recognition (CSLR) — это поток жестов с неопределёнными границами, где соседние знаки влияют друг на друга. CSLR требует CTC-декодирования (Connectionist Temporal Classification), которое предсказывает последовательность символов без сегментации. Дополнительная сложность — motion-based disambiguation: знаки, одинаковые по форме, различаются только траекторией движения. Человек справляется с этим за счёт контекста, а нейросеть — за счёт BiLSTM-слоёв, анализирующих временные зависимости.

Как мы используем MediaPipe и Transformer для изолированных знаков?

Наш пайплайн извлекает 258 признаков на кадр: 21 ключевая точка каждой руки + 33 точки тела (MediaPipe Holistic). Нормализация относительно плечевого расстояния даёт инвариантность к масштабу — жест на расстоянии 0.5 м распознаётся так же, как на 2 м. Transformer-модель обрабатывает окно из 30 кадров (1 секунду) и выдаёт вероятность по классам. Код ниже показывает архитектуру и инференс.

import numpy as np import cv2 import torch import torch.nn as nn import mediapipe as mp from dataclasses import dataclass from collections import deque from typing import Optional import json @dataclass class SignPrediction: sign_id: int gloss: str # название знака (HELLO, WATER, THANK_YOU) confidence: float hand: str # left / right / both frame_range: tuple # (start_frame, end_frame) class HandLandmarkExtractor: """ Извлечение 21 ключевой точки каждой руки + 33 точки тела MediaPipe. Нормализация относительно плечевого расстояния для инвариантности к расстоянию. """ def __init__(self): self.mp_holistic = mp.solutions.holistic self.holistic = self.mp_holistic.Holistic( static_image_mode=False, model_complexity=1, min_detection_confidence=0.5, min_tracking_confidence=0.5 ) def extract(self, frame: np.ndarray) -> Optional[np.ndarray]: """ Возвращает вектор признаков размерности 258: - 21 точки правой руки × 3 (x,y,z) = 63 - 21 точки левой руки × 3 = 63 - 33 точки тела × 4 (x,y,z,visibility) = 132 """ rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results = self.holistic.process(rgb) # Правая рука rh = np.zeros(63, dtype=np.float32) if results.right_hand_landmarks: for i, lm in enumerate(results.right_hand_landmarks.landmark): rh[i*3:i*3+3] = [lm.x, lm.y, lm.z] # Левая рука lh = np.zeros(63, dtype=np.float32) if results.left_hand_landmarks: for i, lm in enumerate(results.left_hand_landmarks.landmark): lh[i*3:i*3+3] = [lm.x, lm.y, lm.z] # Тело (поза) pose = np.zeros(132, dtype=np.float32) if results.pose_landmarks: for i, lm in enumerate(results.pose_landmarks.landmark): pose[i*4:i*4+4] = [lm.x, lm.y, lm.z, lm.visibility] features = np.concatenate([rh, lh, pose]) # Нормализация: плечевое расстояние как масштаб # Точки 11 (левое плечо) и 12 (правое плечо) в pose left_shoulder = pose[11*4:11*4+2] right_shoulder = pose[12*4:12*4+2] shoulder_dist = np.linalg.norm(left_shoulder - right_shoulder) if shoulder_dist > 0.01: features[:126] /= shoulder_dist # нормируем только руки return features if (results.right_hand_landmarks or results.left_hand_landmarks) else None class SignLanguageTransformer(nn.Module): """ Transformer для последовательностей ключевых точек. Вход: (batch, seq_len, 258) — окно из 30–60 кадров. Обучается на WLASL (2000 знаков ASL) или PHOENIX-2014T (немецкий). """ def __init__(self, input_dim: int = 258, d_model: int = 256, nhead: int = 8, num_layers: int = 4, num_classes: int = 2000, dropout: float = 0.1): super().__init__() self.input_proj = nn.Linear(input_dim, d_model) # Позиционное кодирование learnable self.pos_emb = nn.Embedding(300, d_model) encoder_layer = nn.TransformerEncoderLayer( d_model=d_model, nhead=nhead, dim_feedforward=d_model * 4, dropout=dropout, batch_first=True ) self.encoder = nn.TransformerEncoder(encoder_layer, num_layers=num_layers) self.classifier = nn.Sequential( nn.LayerNorm(d_model), nn.Linear(d_model, d_model // 2), nn.GELU(), nn.Dropout(dropout), nn.Linear(d_model // 2, num_classes) ) def forward(self, x: torch.Tensor, src_key_padding_mask: Optional[torch.Tensor] = None) -> torch.Tensor: """x: (B, T, 258)""" B, T, _ = x.shape positions = torch.arange(T, device=x.device).unsqueeze(0).expand(B, -1) x = self.input_proj(x) + self.pos_emb(positions) x = self.encoder(x, src_key_padding_mask=src_key_padding_mask) # Global average pooling по времени x = x.mean(dim=1) return self.classifier(x) class SignLanguageRecognizer: """ Real-time распознавание изолированных знаков. Буфер скользящего окна + пороговое обнаружение начала/конца жеста. """ WINDOW_SIZE = 30 # 30 кадров @ 30 fps = 1 секунда MIN_SIGN_FRAMES = 10 def __init__(self, model_path: str, vocabulary_path: str, confidence_threshold: float = 0.7, device: str = 'cuda'): self.device = device self.extractor = HandLandmarkExtractor() # Загрузка словаря with open(vocabulary_path) as f: self.vocabulary = json.load(f) # {id: gloss} # Модель self.model = SignLanguageTransformer( num_classes=len(self.vocabulary) ).to(device) checkpoint = torch.load(model_path, map_location=device) self.model.load_state_dict(checkpoint['model_state_dict']) self.model.eval() self.threshold = confidence_threshold self.frame_buffer: deque = deque(maxlen=self.WINDOW_SIZE) self.sign_active = False self.sign_start_frame = 0 self.frame_count = 0 def process_frame(self, frame: np.ndarray) -> Optional[SignPrediction]: self.frame_count += 1 features = self.extractor.extract(frame) if features is None: if self.sign_active and len(self.frame_buffer) >= self.MIN_SIGN_FRAMES: # Рука исчезла — попытка классификации накопленного буфера return self._classify_buffer() self.frame_buffer.clear() self.sign_active = False return None self.frame_buffer.append(features) self.sign_active = True if len(self.frame_buffer) < self.WINDOW_SIZE: return None # буфер не заполнен return self._classify_buffer() @torch.no_grad() def _classify_buffer(self) -> Optional[SignPrediction]: seq = np.stack(list(self.frame_buffer)) # (T, 258) tensor = torch.from_numpy(seq).unsqueeze(0).float().to(self.device) logits = self.model(tensor) probs = torch.softmax(logits, dim=-1).squeeze() conf, pred_id = probs.max(dim=0) conf = float(conf.item()) pred_id = int(pred_id.item()) if conf < self.threshold: return None gloss = self.vocabulary.get(str(pred_id), f'SIGN_{pred_id}') return SignPrediction( sign_id=pred_id, gloss=gloss, confidence=round(conf, 3), hand='both', frame_range=(self.frame_count - len(self.frame_buffer), self.frame_count) ) 

Как работает наш CSLR пайплайн?

Для непрерывной речи мы используем CNN + BiLSTM + CTC. Вход — окна по 90 кадров (3 секунды). CTC-декодер выдаёт последовательность глоссов, убирая повторы и blank-символы. Наш опыт показывает, что ключевая трудность — fingerspelling (пальцевая дактильная азбука). Буквы показываются последовательно, и стандартный CSLR часто путает их. Мы решаем это отдельным lightweight-классификатором на базе MediaPipe, который обрабатывает каждый кадр и корректирует выход основного декодера. Это снижает WER на 5–7% в тестах на PHOENIX-2014T.

Датасет Метод WER (↓) Top-1 Acc
WLASL-2000 (изолированные) MediaPipe + Transformer 68–74%
WLASL-2000 RGB 3D-CNN (I3D) 79–84%
PHOENIX-2014T (CSLR) CNN+BiLSTM+CTC 24–28%
PHOENIX-2014T SMKD (self-mutual) 17–19%

Наш подход на Transformer в 1.5–2 раза быстрее I3D при сопоставимой точности. Для CSLR мы используем ансамбль BiLSTM с attention, что даёт WER на 3–5% ниже, чем стандартный CTC без сегментации. По данным WLASL-2000, Transformer достигает 84% на изолированных знаках.

Что входит в работу?

  • Анализ требований: определение словаря (количество знаков, специфика – РЖЯ, ASL, BSL), сбор и разметка данных.
  • Обучение модели: выбор архитектуры (Transformer / BiLSTM + CTC), аугментация, тонкая настройка под целевой датасет.
  • Оптимизация инференса: квантование (INT8), экспорт в ONNX, снижение latency p99 до 30–50 мс.
  • Интеграция: REST API, RTSP-поток, desktop приложение.
  • Документация и обучение: передача model card, инструкция по адаптации под новые жесты.

Процесс работы над проектом

  1. Аудит данных — оценка качества и достаточности видеоматериала (1–2 недели).
  2. Прототипирование — базовая модель на 50–100 знаках (2–4 недели).
  3. Развёртывание — CI/CD, MLflow трекинг, мониторинг дрейфа (2–3 недели).
  4. Итерация — дообучение на реальных данных заказчика (4–8 недель).

Сроки ориентировочно

Задача Срок
Изолированные знаки, 100–500 классов 6–10 недель
CSLR с CTC на готовом датасете 12–18 недель
Реальное время + fingerspelling + адаптация под пользователя 20–30 недель

Стоимость рассчитывается индивидуально — зависит от объёма данных, количества классов и требуемой точности. Для оценки вашего проекта свяжитесь с нами — мы предоставим предварительный план и сроки за 2–3 дня. Мы работаем с жестовыми языками более 5 лет, наши инженеры сертифицированы по MediaPipe и PyTorch. Получите консультацию, чтобы обсудить детали.