Разработка системы определения эмоций по лицу (Facial Emotion Recognition)
Распознавание эмоций по мимике — задача классификации выражения лица на базовые состояния. Классическая модель Экмана выделяет 7 универсальных эмоций: радость, грусть, гнев, страх, удивление, отвращение, нейтральное. Применения: анализ вовлечённости в онлайн-обучении, мониторинг удовлетворённости клиентов в колл-центре, исследования UX, контроль состояния водителя.
Архитектура модели
Пайплайн: детекция лица → выравнивание → классификация эмоций.
import torch
import torch.nn as nn
import timm
import cv2
import numpy as np
from insightface.app import FaceAnalysis
class EmotionRecognizer:
def __init__(self, model_path: str):
# Детекция и выравнивание лица
self.detector = FaceAnalysis(allowed_modules=['detection'])
self.detector.prepare(ctx_id=0, det_size=(640, 640))
# Классификатор эмоций
backbone = timm.create_model('efficientnet_b0', pretrained=False)
backbone.classifier = nn.Sequential(
nn.Dropout(0.3),
nn.Linear(backbone.num_features, 7)
)
backbone.load_state_dict(torch.load(model_path))
backbone.eval()
self.model = backbone
self.emotions = ['angry', 'disgust', 'fear', 'happy',
'neutral', 'sad', 'surprise']
self.transform = get_inference_transform()
@torch.no_grad()
def predict(self, image: np.ndarray) -> list[dict]:
faces = self.detector.get(image)
results = []
for face in faces:
x1, y1, x2, y2 = face.bbox.astype(int)
face_crop = image[y1:y2, x1:x2]
face_crop = cv2.resize(face_crop, (48, 48))
tensor = self.transform(face_crop).unsqueeze(0)
logits = self.model(tensor)
probs = torch.softmax(logits, dim=1).squeeze()
emotion_scores = {
self.emotions[i]: float(probs[i])
for i in range(7)
}
dominant = max(emotion_scores, key=emotion_scores.get)
results.append({
'bbox': [x1, y1, x2, y2],
'emotion': dominant,
'confidence': emotion_scores[dominant],
'all_scores': emotion_scores
})
return results
Датасеты и качество моделей
| Датасет | Размер | Условия | Классы |
|---|---|---|---|
| FER-2013 | 35k фото | Дикая природа | 7 |
| AffectNet | 1M фото | Дикая природа | 8 (+ contempt) |
| RAF-DB | 30k фото | Реальные | 7 + compound |
| CK+ | 593 видео | Лабораторные | 7 |
| SFEW | 1766 кадров | Кинофильмы | 7 |
Точность на FER-2013:
- EfficientNet-B0 fine-tuned: 73.1%
- Vision Transformer (ViT-B/16): 74.8%
- EfficientFace: 73.3%
Главная сложность: метки в публичных датасетах субъективны, люди не соглашаются в 30–40% случаев. Accuracy 75% — это предел для FER-2013 из-за человеческого несогласия.
Временная аналитика на видео
Покадровая классификация нестабильна — эмоция «мигает» между кадрами. Решения:
- Temporal smoothing: скользящее среднее по 10–30 кадрам
- RNN/LSTM поверх frame-level классификатора: учитывает временную динамику
- Aggregation по интервалу: средняя эмоция за N-секундный интервал для аналитики
from collections import deque
class TemporalEmotionTracker:
def __init__(self, window_size: int = 30):
self.window = deque(maxlen=window_size)
def update(self, emotion_scores: dict) -> dict:
self.window.append(emotion_scores)
# Усредняем по окну
averaged = {}
for emotion in emotion_scores:
averaged[emotion] = sum(
frame[emotion] for frame in self.window
) / len(self.window)
return averaged
Ограничения и этические аспекты
Важно понимать ограничения технологии:
- Культурные различия в выражении эмоций (мимика варьируется между культурами)
- Нейтральное лицо ≠ нейтральное состояние человека
- Актёрская мимика отличается от искренней
Технология не должна использоваться для скрытого мониторинга сотрудников без их ведома. В production всегда требуется юридическое согласие.
| Задача | Срок |
|---|---|
| SDK для мобильного/веб приложения | 2–3 недели |
| Аналитика вовлечённости на видео | 3–5 недель |
| Кастомная модель на корпоративном датасете | 5–8 недель |







